Compare commits

..

4 Commits

Author SHA1 Message Date
dependabot[bot] b43fc0c6ea Bump the npm-dependencies group across 1 directory with 19 updates
Bumps the npm-dependencies group with 19 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) | `3.1121.0` | `3.1127.0` |
| [@simplewebauthn/browser](https://github.com/MasterKale/SimpleWebAuthn/tree/HEAD/packages/browser) | `13.3.0` | `14.0.0` |
| [@simplewebauthn/server](https://github.com/MasterKale/SimpleWebAuthn/tree/HEAD/packages/server) | `13.3.3` | `14.0.0` |
| [@tanstack/react-table](https://github.com/TanStack/table/tree/HEAD/packages/react-table) | `8.21.3` | `9.2.4` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `1.38.0` | `1.41.0` |
| [next](https://github.com/vercel/next.js) | `16.3.3` | `16.3.4` |
| [next-intl](https://github.com/amannn/next-intl) | `4.14.1` | `4.14.2` |
| [nodemailer](https://github.com/nodemailer/nodemailer) | `9.1.0` | `10.0.0` |
| [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node) | `5.51.4` | `5.51.6` |
| [react-day-picker](https://github.com/gpbl/react-day-picker/tree/HEAD/packages/react-day-picker) | `9.14.0` | `10.0.1` |
| [stripe](https://github.com/stripe/stripe-node) | `22.6.0` | `22.6.1` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `26.4.0` | `26.4.1` |
| [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) | `19.2.5` | `19.2.7` |
| [eslint](https://github.com/eslint/eslint) | `10.9.1` | `10.10.0` |
| [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) | `16.3.3` | `16.3.4` |
| [postcss](https://github.com/postcss/postcss) | `8.5.26` | `8.5.28` |
| [tsc-alias](https://github.com/justkey007/tsc-alias) | `1.9.2` | `1.9.4` |
| [typescript](https://github.com/microsoft/TypeScript) | `6.0.3` | `7.0.2` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.68.0` | `8.69.0` |



Updates `@aws-sdk/client-s3` from 3.1121.0 to 3.1127.0
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-s3/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1127.0/clients/client-s3)

Updates `@simplewebauthn/browser` from 13.3.0 to 14.0.0
- [Release notes](https://github.com/MasterKale/SimpleWebAuthn/releases)
- [Changelog](https://github.com/MasterKale/SimpleWebAuthn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/MasterKale/SimpleWebAuthn/commits/v14.0.0/packages/browser)

Updates `@simplewebauthn/server` from 13.3.3 to 14.0.0
- [Release notes](https://github.com/MasterKale/SimpleWebAuthn/releases)
- [Changelog](https://github.com/MasterKale/SimpleWebAuthn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/MasterKale/SimpleWebAuthn/commits/v14.0.0/packages/server)

Updates `@tanstack/react-table` from 8.21.3 to 9.2.4
- [Release notes](https://github.com/TanStack/table/releases)
- [Changelog](https://github.com/TanStack/table/blob/main/packages/react-table/CHANGELOG.md)
- [Commits](https://github.com/TanStack/table/commits/@tanstack/react-table@9.2.4/packages/react-table)

Updates `lucide-react` from 1.38.0 to 1.41.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.41.0/packages/lucide-react)

Updates `next` from 16.3.3 to 16.3.4
- [Release notes](https://github.com/vercel/next.js/releases)
- [Commits](https://github.com/vercel/next.js/compare/v16.3.3...v16.3.4)

Updates `next-intl` from 4.14.1 to 4.14.2
- [Release notes](https://github.com/amannn/next-intl/releases)
- [Changelog](https://github.com/amannn/next-intl/blob/main/CHANGELOG.md)
- [Commits](https://github.com/amannn/next-intl/compare/v4.14.1...v4.14.2)

Updates `nodemailer` from 9.1.0 to 10.0.0
- [Release notes](https://github.com/nodemailer/nodemailer/releases)
- [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodemailer/nodemailer/compare/v9.1.0...v10.0.0)

Updates `posthog-node` from 5.51.4 to 5.51.6
- [Release notes](https://github.com/PostHog/posthog-js/releases)
- [Changelog](https://github.com/PostHog/posthog-js/blob/main/packages/node/CHANGELOG.md)
- [Commits](https://github.com/PostHog/posthog-js/commits/posthog-node@5.51.6/packages/node)

Updates `react-day-picker` from 9.14.0 to 10.0.1
- [Release notes](https://github.com/gpbl/react-day-picker/releases)
- [Changelog](https://github.com/gpbl/react-day-picker/blob/main/packages/react-day-picker/CHANGELOG.md)
- [Commits](https://github.com/gpbl/react-day-picker/commits/v10.0.1/packages/react-day-picker)

Updates `stripe` from 22.6.0 to 22.6.1
- [Release notes](https://github.com/stripe/stripe-node/releases)
- [Changelog](https://github.com/stripe/stripe-node/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stripe/stripe-node/compare/v22.6.0...v22.6.1)

Updates `@types/node` from 26.4.0 to 26.4.1
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `@types/react-dom` from 19.2.5 to 19.2.7
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

Updates `eslint` from 10.9.1 to 10.10.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.9.1...v10.10.0)

Updates `eslint-config-next` from 16.3.3 to 16.3.4
- [Release notes](https://github.com/vercel/next.js/releases)
- [Commits](https://github.com/vercel/next.js/commits/v16.3.4/packages/eslint-config-next)

Updates `postcss` from 8.5.26 to 8.5.28
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.26...8.5.28)

Updates `tsc-alias` from 1.9.2 to 1.9.4
- [Release notes](https://github.com/justkey007/tsc-alias/releases)
- [Commits](https://github.com/justkey007/tsc-alias/compare/v1.9.2...v1.9.4)

Updates `typescript` from 6.0.3 to 7.0.2
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Commits](https://github.com/microsoft/TypeScript/compare/v6.0.3...v7.0.2)

Updates `typescript-eslint` from 8.68.0 to 8.69.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.69.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@aws-sdk/client-s3"
  dependency-version: 3.1127.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@simplewebauthn/browser"
  dependency-version: 14.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: "@simplewebauthn/server"
  dependency-version: 14.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: "@tanstack/react-table"
  dependency-version: 9.2.4
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: lucide-react
  dependency-version: 1.41.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: next
  dependency-version: 16.3.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: next-intl
  dependency-version: 4.14.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: nodemailer
  dependency-version: 10.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: posthog-node
  dependency-version: 5.51.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: react-day-picker
  dependency-version: 10.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: stripe
  dependency-version: 22.6.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@types/node"
  dependency-version: 26.4.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@types/react-dom"
  dependency-version: 19.2.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: eslint
  dependency-version: 10.10.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: eslint-config-next
  dependency-version: 16.3.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: postcss
  dependency-version: 8.5.28
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: tsc-alias
  dependency-version: 1.9.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: typescript
  dependency-version: 7.0.2
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: typescript-eslint
  dependency-version: 8.69.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-08 01:39:04 +00:00
Owen Schwartz 17375348b0 Merge pull request #3702 from fosrl/dev
1.22.2
2026-09-04 17:18:42 -04:00
Owen Schwartz e7fdbf9e85 Merge pull request #3699 from fosrl/dev
1.22.1-s.1
2026-09-04 10:38:39 -04:00
Milo Schwartz cddb5ecc3d Merge pull request #3691 from fosrl/dev
update readme
2026-09-03 16:19:47 -04:00
15 changed files with 1057 additions and 739 deletions
+1 -1
View File
@@ -23,7 +23,7 @@ export const clearExitNodes: CommandModule<
// Delete all exit nodes // Delete all exit nodes
const deletedCount = await db const deletedCount = await db
.delete(exitNodes) .delete(exitNodes)
.where(eq(exitNodes.exitNodeId, exitNodes.exitNodeId)).returning();; // delete all .where(eq(exitNodes.exitNodeId, exitNodes.exitNodeId)) .returning();; // delete all
console.log(`Deleted ${deletedCount.length} exit node(s) from the database`); console.log(`Deleted ${deletedCount.length} exit node(s) from the database`);
+3 -5
View File
@@ -152,7 +152,7 @@
"siteResourcesHowToAccess": "How to access", "siteResourcesHowToAccess": "How to access",
"siteResourcesTargetsOnSite": "Targets on this site", "siteResourcesTargetsOnSite": "Targets on this site",
"siteSetting": "{siteName} Settings", "siteSetting": "{siteName} Settings",
"siteNewtTunnel": "Pangolin Site (Recommended)", "siteNewtTunnel": "Newt Site (Recommended)",
"siteNewtTunnelDescription": "Easiest way to create an entrypoint into any network. No extra setup.", "siteNewtTunnelDescription": "Easiest way to create an entrypoint into any network. No extra setup.",
"siteWg": "Basic WireGuard", "siteWg": "Basic WireGuard",
"siteWgDescription": "Use any WireGuard client to establish a tunnel. Manual NAT setup required.", "siteWgDescription": "Use any WireGuard client to establish a tunnel. Manual NAT setup required.",
@@ -2898,7 +2898,7 @@
"editInternalResourceDialogAlias": "Alias", "editInternalResourceDialogAlias": "Alias",
"editInternalResourceDialogAliasDescription": "An optional internal DNS alias for this resource.", "editInternalResourceDialogAliasDescription": "An optional internal DNS alias for this resource.",
"createInternalResourceDialogNoSitesAvailable": "No Sites Available", "createInternalResourceDialogNoSitesAvailable": "No Sites Available",
"createInternalResourceDialogNoSitesAvailableDescription": "You need to have at least one site with a subnet configured to create private resources.", "createInternalResourceDialogNoSitesAvailableDescription": "You need to have at least one Newt site with a subnet configured to create private resources.",
"createInternalResourceDialogClose": "Close", "createInternalResourceDialogClose": "Close",
"createInternalResourceDialogCreateClientResource": "Create Private Resource", "createInternalResourceDialogCreateClientResource": "Create Private Resource",
"createInternalResourceDialogCreateClientResourceDescription": "Create a new resource that will only be accessible to clients connected to the organization", "createInternalResourceDialogCreateClientResourceDescription": "Create a new resource that will only be accessible to clients connected to the organization",
@@ -3767,7 +3767,6 @@
"noData": "No Data", "noData": "No Data",
"machineClients": "Machine Clients", "machineClients": "Machine Clients",
"install": "Install", "install": "Install",
"downloadInstaller": "Download Installer",
"run": "Run", "run": "Run",
"envFile": "Environment File", "envFile": "Environment File",
"serviceFile": "Service File", "serviceFile": "Service File",
@@ -4364,6 +4363,5 @@
"rdpUnicodeKeyboardMode": "Unicode keyboard mode", "rdpUnicodeKeyboardMode": "Unicode keyboard mode",
"sessionToolbarShow": "Show toolbar", "sessionToolbarShow": "Show toolbar",
"sessionToolbarHide": "Hide toolbar", "sessionToolbarHide": "Hide toolbar",
"actionUpdateSiteApprovals": "Update Site Approvals", "actionUpdateSiteApprovals": "Update Site Approvals"
"check": "Check"
} }
+893 -415
View File
File diff suppressed because it is too large Load Diff
+20 -20
View File
@@ -33,7 +33,7 @@
}, },
"dependencies": { "dependencies": {
"@asteasolutions/zod-to-openapi": "9.1.0", "@asteasolutions/zod-to-openapi": "9.1.0",
"@aws-sdk/client-s3": "3.1121.0", "@aws-sdk/client-s3": "3.1127.0",
"@devolutions/iron-remote-desktop": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-0.0.0.tgz", "@devolutions/iron-remote-desktop": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-0.0.0.tgz",
"@devolutions/iron-remote-desktop-rdp": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-rdp-0.0.1.tgz", "@devolutions/iron-remote-desktop-rdp": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-rdp-0.0.1.tgz",
"@headlessui/react": "2.2.10", "@headlessui/react": "2.2.10",
@@ -65,11 +65,11 @@
"@react-email/components": "1.0.12", "@react-email/components": "1.0.12",
"@react-email/render": "2.1.0", "@react-email/render": "2.1.0",
"@react-email/tailwind": "2.0.7", "@react-email/tailwind": "2.0.7",
"@simplewebauthn/browser": "13.3.0", "@simplewebauthn/browser": "14.0.0",
"@simplewebauthn/server": "13.3.3", "@simplewebauthn/server": "14.0.0",
"@tailwindcss/forms": "0.5.11", "@tailwindcss/forms": "0.5.11",
"@tanstack/react-query": "5.102.8", "@tanstack/react-query": "5.102.8",
"@tanstack/react-table": "8.21.3", "@tanstack/react-table": "9.2.4",
"@xterm/addon-fit": "^0.11.0", "@xterm/addon-fit": "^0.11.0",
"@xterm/addon-web-links": "^0.12.0", "@xterm/addon-web-links": "^0.12.0",
"@xterm/xterm": "^6.0.0", "@xterm/xterm": "^6.0.0",
@@ -96,21 +96,21 @@
"jmespath": "0.16.0", "jmespath": "0.16.0",
"js-yaml": "5.4.1", "js-yaml": "5.4.1",
"jsonwebtoken": "9.0.3", "jsonwebtoken": "9.0.3",
"lucide-react": "1.38.0", "lucide-react": "1.41.0",
"maxmind": "5.0.7", "maxmind": "5.0.7",
"moment": "2.30.1", "moment": "2.30.1",
"next": "16.3.3", "next": "16.3.4",
"next-intl": "4.14.1", "next-intl": "4.14.2",
"next-themes": "0.4.6", "next-themes": "0.4.6",
"nextjs-toploader": "3.9.17", "nextjs-toploader": "3.9.17",
"node-cache": "5.1.2", "node-cache": "5.1.2",
"nodemailer": "9.1.0", "nodemailer": "10.0.0",
"oslo": "1.2.1", "oslo": "1.2.1",
"pg": "8.23.0", "pg": "8.23.0",
"posthog-node": "5.51.4", "posthog-node": "5.51.6",
"qrcode.react": "4.2.0", "qrcode.react": "4.2.0",
"react": "19.2.8", "react": "19.2.8",
"react-day-picker": "9.14.0", "react-day-picker": "10.0.1",
"react-dom": "19.2.8", "react-dom": "19.2.8",
"react-easy-sort": "1.8.0", "react-easy-sort": "1.8.0",
"react-hook-form": "7.87.0", "react-hook-form": "7.87.0",
@@ -119,7 +119,7 @@
"reodotdev": "1.1.0", "reodotdev": "1.1.0",
"semver": "7.8.5", "semver": "7.8.5",
"sshpk": "1.18.0", "sshpk": "1.18.0",
"stripe": "22.6.0", "stripe": "22.6.1",
"swagger-ui-express": "5.0.1", "swagger-ui-express": "5.0.1",
"tailwind-merge": "3.6.0", "tailwind-merge": "3.6.0",
"topojson-client": "3.1.0", "topojson-client": "3.1.0",
@@ -152,12 +152,12 @@
"@types/jmespath": "0.15.2", "@types/jmespath": "0.15.2",
"@types/js-yaml": "4.0.9", "@types/js-yaml": "4.0.9",
"@types/jsonwebtoken": "9.0.10", "@types/jsonwebtoken": "9.0.10",
"@types/node": "26.4.0", "@types/node": "26.4.1",
"@types/nodemailer": "8.0.1", "@types/nodemailer": "8.0.1",
"@types/nprogress": "0.2.3", "@types/nprogress": "0.2.3",
"@types/pg": "8.23.1", "@types/pg": "8.23.1",
"@types/react": "19.2.18", "@types/react": "19.2.18",
"@types/react-dom": "19.2.5", "@types/react-dom": "19.2.7",
"@types/semver": "7.8.0", "@types/semver": "7.8.0",
"@types/sshpk": "1.17.5", "@types/sshpk": "1.17.5",
"@types/swagger-ui-express": "4.1.8", "@types/swagger-ui-express": "4.1.8",
@@ -168,20 +168,20 @@
"drizzle-kit": "0.31.10", "drizzle-kit": "0.31.10",
"esbuild": "0.28.2", "esbuild": "0.28.2",
"esbuild-node-externals": "2.0.0", "esbuild-node-externals": "2.0.0",
"eslint": "10.9.1", "eslint": "10.10.0",
"eslint-config-next": "16.3.3", "eslint-config-next": "16.3.4",
"postcss": "8.5.26", "postcss": "8.5.28",
"prettier": "3.9.6", "prettier": "3.9.6",
"react-email": "6.9.3", "react-email": "6.9.3",
"tailwindcss": "4.3.3", "tailwindcss": "4.3.3",
"tsc-alias": "1.9.2", "tsc-alias": "1.9.4",
"tsx": "4.23.13", "tsx": "4.23.13",
"typescript": "6.0.3", "typescript": "7.0.2",
"typescript-eslint": "8.68.0" "typescript-eslint": "8.69.0"
}, },
"overrides": { "overrides": {
"esbuild": "0.28.2", "esbuild": "0.28.2",
"dompurify": "3.4.0", "dompurify": "3.4.0",
"postcss": "8.5.26" "postcss": "8.5.28"
} }
} }
+1 -1
View File
@@ -71,7 +71,7 @@ export async function withRetry<T>(
const jitter = Math.random() * baseDelay; const jitter = Math.random() * baseDelay;
const delay = baseDelay + jitter; const delay = baseDelay + jitter;
logger.warn( logger.warn(
`Transient DB issue in ${context}, retrying attempt ${attempt}/${maxRetries} after ${delay.toFixed(0)}ms`, `Transient DB error in ${context}, retrying attempt ${attempt}/${maxRetries} after ${delay.toFixed(0)}ms`,
{ code: error?.code ?? error?.cause?.code } { code: error?.code ?? error?.cause?.code }
); );
await new Promise((resolve) => setTimeout(resolve, delay)); await new Promise((resolve) => setTimeout(resolve, delay));
+2 -2
View File
@@ -348,8 +348,8 @@ export const configSchema = z
.optional() .optional()
.pipe(z.string()) .pipe(z.string())
.transform((url) => url.toLowerCase()), .transform((url) => url.toLowerCase()),
subnet_group: z.string().optional().default("100.89.137.0/18"), subnet_group: z.string().optional().default("100.89.137.0/20"),
block_size: z.number().positive().gt(0).optional().default(22), block_size: z.number().positive().gt(0).optional().default(24),
site_block_size: z site_block_size: z
.number() .number()
.positive() .positive()
-5
View File
@@ -2478,12 +2478,7 @@ hybridRouter.post(
destinations: destinations destinations: destinations
}); });
} catch (error) { } catch (error) {
if (!(
error instanceof Error &&
error.message === "Exit node not allowed"
)) {
logger.error(error); logger.error(error);
}
return next( return next(
createHttpError( createHttpError(
HttpCode.INTERNAL_SERVER_ERROR, HttpCode.INTERNAL_SERVER_ERROR,
@@ -1,23 +0,0 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { EventEmitter } from "events";
export interface ExitNodeOnlineEvent {
exitNodeId: number;
endpoint: string;
}
export const EXIT_NODE_ONLINE_EVENT = "exit-node-online";
export const exitNodeEvents = new EventEmitter();
@@ -12,27 +12,11 @@
*/ */
import axios from "axios"; import axios from "axios";
import { db, newts, sites } from "@server/db"; import { db, exitNodes, newts, sites } from "@server/db";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import logger from "@server/logger"; import logger from "@server/logger";
import redisManager from "#private/lib/redis"; import redisManager from "#private/lib/redis";
import { sendToClient } from "../ws"; // import { sendToClient } from "#private/routers/ws";
import {
exitNodeEvents,
EXIT_NODE_ONLINE_EVENT,
ExitNodeOnlineEvent
} from "./exitNodeEvents";
exitNodeEvents.on(
EXIT_NODE_ONLINE_EVENT,
({ exitNodeId, endpoint }: ExitNodeOnlineEvent) => {
scheduleExitNodeReconnect(exitNodeId, endpoint).catch((error) => {
logger.error("Failed to schedule exit node reconnect", {
error
});
});
}
);
const INITIAL_DELAY_MS = 15 * 1000; // 15 seconds before first check const INITIAL_DELAY_MS = 15 * 1000; // 15 seconds before first check
const CHECK_INTERVAL_MS = 10 * 1000; // Check every 10 seconds const CHECK_INTERVAL_MS = 10 * 1000; // Check every 10 seconds
@@ -42,7 +26,7 @@ const REDIS_HASH_PREFIX = "exit-node-reconnect:";
interface PendingReconnect { interface PendingReconnect {
startTime: number; startTime: number;
endpoint: string; reachableAt: string;
} }
// In-memory tracking for this node // In-memory tracking for this node
@@ -56,15 +40,15 @@ let schedulerInterval: NodeJS.Timeout | null = null;
*/ */
export async function scheduleExitNodeReconnect( export async function scheduleExitNodeReconnect(
exitNodeId: number, exitNodeId: number,
endpoint: string reachableAt: string
): Promise<void> { ): Promise<void> {
logger.info( logger.info(
`Scheduling newt reconnect for exit node ${exitNodeId} (endpoint: ${endpoint})` `Scheduling newt reconnect for exit node ${exitNodeId} (reachableAt: ${reachableAt})`
); );
const entry: PendingReconnect = { const entry: PendingReconnect = {
startTime: Date.now(), startTime: Date.now(),
endpoint reachableAt
}; };
pendingReconnects.set(exitNodeId, entry); pendingReconnects.set(exitNodeId, entry);
@@ -79,8 +63,8 @@ export async function scheduleExitNodeReconnect(
); );
await redisManager.hset( await redisManager.hset(
`${REDIS_HASH_PREFIX}${exitNodeId}`, `${REDIS_HASH_PREFIX}${exitNodeId}`,
"endpoint", "reachableAt",
endpoint reachableAt
); );
} }
} }
@@ -117,14 +101,14 @@ async function processPendingReconnects(): Promise<void> {
`${REDIS_HASH_PREFIX}${id}`, `${REDIS_HASH_PREFIX}${id}`,
"startTime" "startTime"
); );
const endpoint = await redisManager.hget( const reachableAt = await redisManager.hget(
`${REDIS_HASH_PREFIX}${id}`, `${REDIS_HASH_PREFIX}${id}`,
"endpoint" "reachableAt"
); );
if (startTimeStr && endpoint) { if (startTimeStr && reachableAt) {
toProcess.set(id, { toProcess.set(id, {
startTime: parseInt(startTimeStr, 10), startTime: parseInt(startTimeStr, 10),
endpoint reachableAt
}); });
} }
} }
@@ -151,7 +135,7 @@ async function processPendingReconnects(): Promise<void> {
} }
// Check if the exit node HTTP endpoint is reachable // Check if the exit node HTTP endpoint is reachable
const pingUrl = `http://${entry.endpoint}/ping`; const pingUrl = `${entry.reachableAt}/ping`;
try { try {
await axios.get(pingUrl, { timeout: 5000 }); await axios.get(pingUrl, { timeout: 5000 });
} catch { } catch {
@@ -166,47 +150,47 @@ async function processPendingReconnects(): Promise<void> {
`Exit node ${exitNodeId} is reachable. Sending newt/wg/reconnect to connected newts.` `Exit node ${exitNodeId} is reachable. Sending newt/wg/reconnect to connected newts.`
); );
await sendReconnectToNewts(exitNodeId); // await sendReconnectToNewts(exitNodeId);
await removePending(exitNodeId); await removePending(exitNodeId);
} }
} }
async function sendReconnectToNewts(exitNodeId: number): Promise<void> { // async function sendReconnectToNewts(exitNodeId: number): Promise<void> {
try { // try {
const connectedNewts = await db // const connectedNewts = await db
.select({ newtId: newts.newtId }) // .select({ newtId: newts.newtId })
.from(newts) // .from(newts)
.innerJoin(sites, eq(newts.siteId, sites.siteId)) // .innerJoin(sites, eq(newts.siteId, sites.siteId))
.where(eq(sites.exitNodeId, exitNodeId)); // .where(eq(sites.exitNodeId, exitNodeId));
if (connectedNewts.length === 0) { // if (connectedNewts.length === 0) {
logger.debug( // logger.debug(
`No newts found for exit node ${exitNodeId}, nothing to reconnect` // `No newts found for exit node ${exitNodeId}, nothing to reconnect`
); // );
return; // return;
} // }
logger.info( // logger.info(
`Sending newt/wg/reconnect to ${connectedNewts.length} newt(s) for exit node ${exitNodeId}` // `Sending newt/wg/reconnect to ${connectedNewts.length} newt(s) for exit node ${exitNodeId}`
); // );
const reconnectMessage = { // const reconnectMessage = {
type: "newt/wg/reconnect", // type: "newt/wg/reconnect",
data: {} // data: {}
}; // };
await Promise.allSettled( // await Promise.allSettled(
connectedNewts.map(({ newtId }) => // connectedNewts.map(({ newtId }) =>
sendToClient(newtId, reconnectMessage) // sendToClient(newtId, reconnectMessage)
) // )
); // );
} catch (error) { // } catch (error) {
logger.error( // logger.error(
`Failed to send reconnect messages for exit node ${exitNodeId}`, // `Failed to send reconnect messages for exit node ${exitNodeId}`,
{ error } // { error }
); // );
} // }
} // }
async function removePending(exitNodeId: number): Promise<void> { async function removePending(exitNodeId: number): Promise<void> {
pendingReconnects.delete(exitNodeId); pendingReconnects.delete(exitNodeId);
@@ -16,7 +16,7 @@ import { MessageHandler } from "@server/routers/ws";
import { RemoteExitNode } from "@server/db"; import { RemoteExitNode } from "@server/db";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import logger from "@server/logger"; import logger from "@server/logger";
import { exitNodeEvents, EXIT_NODE_ONLINE_EVENT } from "./exitNodeEvents"; import { scheduleExitNodeReconnect } from "./exitNodeReconnectScheduler";
/** /**
* Handles ping messages from clients and responds with pong * Handles ping messages from clients and responds with pong
@@ -40,7 +40,7 @@ export const handleRemoteExitNodePingMessage: MessageHandler = async (
try { try {
// Fetch the current state before updating so we can detect the offline→online transition // Fetch the current state before updating so we can detect the offline→online transition
const [currentExitNode] = await db const [currentExitNode] = await db
.select({ online: exitNodes.online, endpoint: exitNodes.endpoint }) .select({ online: exitNodes.online, reachableAt: exitNodes.reachableAt })
.from(exitNodes) .from(exitNodes)
.where(eq(exitNodes.exitNodeId, remoteExitNode.exitNodeId)) .where(eq(exitNodes.exitNodeId, remoteExitNode.exitNodeId))
.limit(1); .limit(1);
@@ -55,14 +55,12 @@ export const handleRemoteExitNodePingMessage: MessageHandler = async (
.where(eq(exitNodes.exitNodeId, remoteExitNode.exitNodeId)); .where(eq(exitNodes.exitNodeId, remoteExitNode.exitNodeId));
// If the exit node was offline and is now coming online, schedule newt reconnects // If the exit node was offline and is now coming online, schedule newt reconnects
if ( if (currentExitNode && !currentExitNode.online && currentExitNode.reachableAt) {
currentExitNode && scheduleExitNodeReconnect(
!currentExitNode.online && remoteExitNode.exitNodeId,
currentExitNode.endpoint currentExitNode.reachableAt
) { ).catch((error) => {
exitNodeEvents.emit(EXIT_NODE_ONLINE_EVENT, { logger.error("Failed to schedule exit node reconnect", { error });
exitNodeId: remoteExitNode.exitNodeId,
endpoint: currentExitNode.endpoint
}); });
} }
} catch (error) { } catch (error) {
@@ -85,7 +85,7 @@ export const handleOlmServerInitAddPeerHandshake: MessageHandler = async (
); );
if (!resources || resources.length === 0) { if (!resources || resources.length === 0) {
logger.warn( logger.error(
`handleOlmServerInitAddPeerHandshake: Resource not found` `handleOlmServerInitAddPeerHandshake: Resource not found`
); );
await sendCancel(); await sendCancel();
@@ -94,7 +94,7 @@ export const handleOlmServerInitAddPeerHandshake: MessageHandler = async (
if (resources.length > 1) { if (resources.length > 1) {
// error but this should not happen because the nice id cant contain a dot and the alias has to have a dot and both have to be unique within the org so there should never be multiple matches // error but this should not happen because the nice id cant contain a dot and the alias has to have a dot and both have to be unique within the org so there should never be multiple matches
logger.warn( logger.error(
`handleOlmServerInitAddPeerHandshake: Multiple resources found matching the criteria` `handleOlmServerInitAddPeerHandshake: Multiple resources found matching the criteria`
); );
return; return;
@@ -119,7 +119,7 @@ export const handleOlmServerInitAddPeerHandshake: MessageHandler = async (
); );
if (currentResourceAssociationCaches.length === 0) { if (currentResourceAssociationCaches.length === 0) {
logger.warn( logger.error(
`handleOlmServerInitAddPeerHandshake: Client ${client.clientId} does not have access to resource ${resource.siteResourceId}` `handleOlmServerInitAddPeerHandshake: Client ${client.clientId} does not have access to resource ${resource.siteResourceId}`
); );
await sendCancel(); await sendCancel();
@@ -127,7 +127,7 @@ export const handleOlmServerInitAddPeerHandshake: MessageHandler = async (
} }
if (!resource.networkId) { if (!resource.networkId) {
logger.warn( logger.error(
`handleOlmServerInitAddPeerHandshake: Resource ${resource.siteResourceId} has no network` `handleOlmServerInitAddPeerHandshake: Resource ${resource.siteResourceId} has no network`
); );
await sendCancel(); await sendCancel();
@@ -141,7 +141,7 @@ export const handleOlmServerInitAddPeerHandshake: MessageHandler = async (
.where(eq(siteNetworks.networkId, resource.networkId)); .where(eq(siteNetworks.networkId, resource.networkId));
if (!siteRows || siteRows.length === 0) { if (!siteRows || siteRows.length === 0) {
logger.warn( logger.error(
`handleOlmServerInitAddPeerHandshake: No sites found for resource ${resource.siteResourceId}` `handleOlmServerInitAddPeerHandshake: No sites found for resource ${resource.siteResourceId}`
); );
await sendCancel(); await sendCancel();
@@ -164,7 +164,9 @@ export const handleOlmServerInitAddPeerHandshake: MessageHandler = async (
} }
if (sitesToProcess.length === 0) { if (sitesToProcess.length === 0) {
logger.warn(`handleOlmServerInitAddPeerHandshake: No sites to process`); logger.error(
`handleOlmServerInitAddPeerHandshake: No sites to process`
);
await sendCancel(); await sendCancel();
return; return;
} }
@@ -191,7 +193,7 @@ export const handleOlmServerInitAddPeerHandshake: MessageHandler = async (
} }
if (!site.exitNodeId) { if (!site.exitNodeId) {
logger.warn( logger.error(
`handleOlmServerInitAddPeerHandshake: Site ${site.siteId} has no exit node, skipping` `handleOlmServerInitAddPeerHandshake: Site ${site.siteId} has no exit node, skipping`
); );
continue; continue;
@@ -203,7 +205,7 @@ export const handleOlmServerInitAddPeerHandshake: MessageHandler = async (
.where(eq(exitNodes.exitNodeId, site.exitNodeId)); .where(eq(exitNodes.exitNodeId, site.exitNodeId));
if (!exitNode) { if (!exitNode) {
logger.warn( logger.error(
`handleOlmServerInitAddPeerHandshake: Exit node not found for site ${site.siteId}, skipping` `handleOlmServerInitAddPeerHandshake: Exit node not found for site ${site.siteId}, skipping`
); );
continue; continue;
@@ -227,7 +229,7 @@ export const handleOlmServerInitAddPeerHandshake: MessageHandler = async (
} }
if (!handshakeInitiated) { if (!handshakeInitiated) {
logger.warn( logger.error(
`handleOlmServerInitAddPeerHandshake: No accessible sites with valid exit nodes found, cancelling chain` `handleOlmServerInitAddPeerHandshake: No accessible sites with valid exit nodes found, cancelling chain`
); );
await sendCancel(); await sendCancel();
+1 -1
View File
@@ -104,7 +104,7 @@ export default async function OrgLayout(props: {
subscriptionStatus = subRes.data.data; subscriptionStatus = subRes.data.data;
} catch (error) { } catch (error) {
// If subscription fetch fails, keep subscriptionStatus as null // If subscription fetch fails, keep subscriptionStatus as null
// console.error("Failed to fetch subscription status:", error); console.error("Failed to fetch subscription status:", error);
} }
} }
+44 -109
View File
@@ -8,7 +8,6 @@ import {
SettingsSectionTitle SettingsSectionTitle
} from "./Settings"; } from "./Settings";
import { CheckboxWithLabel } from "./ui/checkbox"; import { CheckboxWithLabel } from "./ui/checkbox";
import { Button } from "./ui/button";
import { OptionSelect, type OptionSelectOption } from "./OptionSelect"; import { OptionSelect, type OptionSelectOption } from "./OptionSelect";
import { useState } from "react"; import { useState } from "react";
import { import {
@@ -19,14 +18,11 @@ import {
FaLinux, FaLinux,
FaWindows FaWindows
} from "react-icons/fa"; } from "react-icons/fa";
import { Download, ExternalLink } from "lucide-react"; import { ExternalLink } from "lucide-react";
import { SiKubernetes, SiNixos } from "react-icons/si"; import { SiKubernetes, SiNixos } from "react-icons/si";
import { useEnvContext } from "@app/hooks/useEnvContext"; import { useEnvContext } from "@app/hooks/useEnvContext";
export type CommandItem = export type CommandItem = string | { title: string; command: string };
| string
| { title: string; command: string }
| { title: string; link: string };
const PLATFORMS = [ const PLATFORMS = [
"linux", "linux",
@@ -45,12 +41,14 @@ export type NewtSiteInstallCommandsProps = {
id: string; id: string;
secret: string; secret: string;
endpoint: string; endpoint: string;
version?: string;
}; };
export function NewtSiteInstallCommands({ export function NewtSiteInstallCommands({
id, id,
secret, secret,
endpoint endpoint,
version = "latest"
}: NewtSiteInstallCommandsProps) { }: NewtSiteInstallCommandsProps) {
const t = useTranslations(); const t = useTranslations();
const { env } = useEnvContext(); const { env } = useEnvContext();
@@ -90,39 +88,25 @@ export function NewtSiteInstallCommands({
Run: [ Run: [
{ {
title: t("install"), title: t("install"),
command: `curl -fsSL https://static.pangolin.net/get-cli.sh | bash` command: `curl -fsSL https://static.pangolin.net/get-newt.sh | bash`
}, },
{ {
title: t("run"), title: t("run"),
command: `${runAsRootPrefix}pangolin up site --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}${disableSshFlag}` command: `${runAsRootPrefix}newt --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}${disableSshFlag}`
} }
], ],
"Auto Systemd Service": [ "Systemd Service": [
{ {
title: t("install"), title: t("install"),
command: `curl -fsSL https://static.pangolin.net/get-cli.sh | bash` command: `curl -fsSL https://static.pangolin.net/get-newt.sh | bash`
},
{
title: t("run"),
command: `sudo pangolin service install site --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}${disableSshFlag}`
},
{
title: t("check"),
command: `sudo pangolin service status site`
}
],
"Manual Systemd Service": [
{
title: t("install"),
command: `curl -fsSL https://static.pangolin.net/get-cli.sh | bash`
}, },
{ {
title: t("envFile"), title: t("envFile"),
command: `# Create the directory and environment file command: `# Create the directory and environment file
sudo install -d -m 0755 /etc/pangolin sudo install -d -m 0755 /etc/newt
sudo tee /etc/pangolin/pangolin-site.env > /dev/null << 'EOF' sudo tee /etc/newt/newt.env > /dev/null << 'EOF'
SITE_ID=${id} NEWT_ID=${id}
SITE_SECRET=${secret} NEWT_SECRET=${secret}
PANGOLIN_ENDPOINT=${endpoint}${ PANGOLIN_ENDPOINT=${endpoint}${
!acceptClients !acceptClients
? ` ? `
@@ -135,11 +119,11 @@ DISABLE_SSH=true`
: "" : ""
} }
EOF EOF
sudo chmod 600 /etc/pangolin/pangolin-site.env` sudo chmod 600 /etc/newt/newt.env`
}, },
{ {
title: t("serviceFile"), title: t("serviceFile"),
command: `sudo tee /etc/systemd/system/pangolin-site.service > /dev/null << 'EOF' command: `sudo tee /etc/systemd/system/newt.service > /dev/null << 'EOF'
[Unit] [Unit]
Description=Newt Description=Newt
Wants=network-online.target Wants=network-online.target
@@ -149,8 +133,8 @@ After=network-online.target
Type=simple Type=simple
User=root User=root
Group=root Group=root
EnvironmentFile=/etc/pangolin/pangolin-site.env EnvironmentFile=/etc/newt/newt.env
ExecStart=/home/owen/fossorial/cli/bin/pangolin up site ExecStart=/usr/local/bin/newt
Restart=always Restart=always
RestartSec=2 RestartSec=2
UMask=0077 UMask=0077
@@ -164,7 +148,7 @@ EOF`
{ {
title: t("enableAndStart"), title: t("enableAndStart"),
command: `sudo systemctl daemon-reload command: `sudo systemctl daemon-reload
sudo systemctl enable --now pangolin-site` sudo systemctl enable --now newt`
} }
] ]
}, },
@@ -172,51 +156,23 @@ sudo systemctl enable --now pangolin-site`
Run: [ Run: [
{ {
title: t("install"), title: t("install"),
command: `curl -fsSL https://static.pangolin.net/get-cli.sh | bash` command: `curl -fsSL https://static.pangolin.net/get-newt.sh | bash`
}, },
{ {
title: t("run"), title: t("run"),
command: `pangolin up site --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}` command: `newt --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}`
}
],
Service: [
{
title: t("install"),
command: `curl -fsSL https://static.pangolin.net/get-cli.sh | bash`
},
{
title: t("run"),
command: `sudo pangolin service install site --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}${disableSshFlag}`
},
{
title: t("check"),
command: `sudo pangolin service status site`
} }
] ]
}, },
windows: { windows: {
Run: [ x64: [
{ {
title: t("install"), title: t("install"),
link: `https://github.com/fosrl/cli/releases/latest/download/pangolin-cli_windows_installer.msi` command: `curl -o newt.exe -L "https://github.com/fosrl/newt/releases/download/${version}/newt_windows_amd64.exe"`
}, },
{ {
title: t("run"), title: t("run"),
command: `pangolin up site --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}` command: `newt.exe --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}`
}
],
Service: [
{
title: t("install"),
link: `https://github.com/fosrl/cli/releases/latest/download/pangolin-cli_windows_installer.msi`
},
{
title: t("run"),
command: `pangolin service install site --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}${disableSshFlag}`
},
{
title: t("check"),
command: `pangolin service status site`
} }
] ]
}, },
@@ -224,20 +180,19 @@ sudo systemctl enable --now pangolin-site`
"Docker Compose": [ "Docker Compose": [
`services: `services:
newt: newt:
image: fosrl/pangolin-cli image: fosrl/newt
container_name: newt container_name: newt
restart: unless-stopped restart: unless-stopped
environment: environment:
- PANGOLIN_ENDPOINT=${endpoint} - PANGOLIN_ENDPOINT=${endpoint}
- SITE_ID=${id} - NEWT_ID=${id}
- SITE_SECRET=${secret}${acceptClientsEnv}` - NEWT_SECRET=${secret}${acceptClientsEnv}`
], ],
"Docker Run": [ "Docker Run": [
`docker run -dit --network host fosrl/pangolin-cli up site --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}` `docker run -dit --network host fosrl/newt --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}`
] ]
}, },
kubernetes: { kubernetes: {
// we are leaving this using newt until we change it to use the cli
"Helm Chart": [ "Helm Chart": [
`helm repo add fossorial https://charts.fossorial.io`, `helm repo add fossorial https://charts.fossorial.io`,
`helm repo update fossorial`, `helm repo update fossorial`,
@@ -261,15 +216,15 @@ sudo systemctl enable --now pangolin-site`
podman: { podman: {
"Podman Quadlet": [ "Podman Quadlet": [
`[Unit] `[Unit]
Description=Pangolin Site Container Description=Newt container
[Container] [Container]
ContainerName=pangolin-site ContainerName=newt
Image=docker.io/fosrl/pangolin-cli Image=docker.io/fosrl/newt
Environment=PANGOLIN_ENDPOINT=${endpoint} Environment=PANGOLIN_ENDPOINT=${endpoint}
Environment=SITE_ID=${id} Environment=NEWT_ID=${id}
Environment=SITE_SECRET=${secret}${!acceptClients ? "\nEnvironment=DISABLE_CLIENTS=true" : ""} Environment=NEWT_SECRET=${secret}${!acceptClients ? "\nEnvironment=DISABLE_CLIENTS=true" : ""}
# Secret=pangolin-secret,type=env,target=SITE_SECRET # Secret=newt-secret,type=env,target=NEWT_SECRET
[Service] [Service]
Restart=always Restart=always
@@ -278,12 +233,12 @@ Restart=always
WantedBy=default.target` WantedBy=default.target`
], ],
"Podman Run": [ "Podman Run": [
`podman run -dit docker.io/fosrl/pangolin-cli up site --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}` `podman run -dit docker.io/fosrl/newt --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}`
] ]
}, },
nixos: { nixos: {
Flake: [ Flake: [
`${runAsRootPrefix}nix run 'nixpkgs#pangolin-cli' -- up site --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}${disableSshFlag}` `${runAsRootPrefix}nix run 'nixpkgs#fosrl-newt' -- --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}${disableSshFlag}`
] ]
} }
}; };
@@ -322,7 +277,9 @@ WantedBy=default.target`
/> />
<OptionSelect<string> <OptionSelect<string>
label={t("method")} label={
platform === "windows" ? t("architecture") : t("method")
}
options={getArchitectures(platform).map((arch) => ({ options={getArchitectures(platform).map((arch) => ({
value: arch, value: arch,
label: arch label: arch
@@ -420,23 +377,14 @@ WantedBy=default.target`
)} )}
<div className="mt-2 space-y-3"> <div className="mt-2 space-y-3">
{commands.map((item, index) => { {commands.map((item, index) => {
const isLink =
typeof item !== "string" && "link" in item;
const commandText = const commandText =
typeof item === "string" typeof item === "string" ? item : item.command;
? item
: isLink
? undefined
: item.command;
const linkHref = isLink
? (item as { link: string }).link
: undefined;
const title = const title =
typeof item === "string" typeof item === "string"
? undefined ? undefined
: item.title; : item.title;
const key = `${title ?? ""}::${commandText ?? linkHref}`; const key = `${title ?? ""}::${commandText}`;
return ( return (
<div key={key}> <div key={key}>
@@ -445,23 +393,10 @@ WantedBy=default.target`
{title} {title}
</p> </p>
)} )}
{isLink ? (
<Button
asChild
variant="outline"
className="w-full"
>
<a href={linkHref}>
<Download className="h-4 w-4 mr-2" />
{t("downloadInstaller")}
</a>
</Button>
) : (
<CopyTextBox <CopyTextBox
text={commandText!} text={commandText}
outline={true} outline={true}
/> />
)}
</div> </div>
); );
})} })}
@@ -521,11 +456,11 @@ function getPlatformName(platformName: Platform) {
function getArchitectures(platform: Platform) { function getArchitectures(platform: Platform) {
switch (platform) { switch (platform) {
case "linux": case "linux":
return ["Run", "Auto Systemd Service", "Manual Systemd Service"]; return ["Run", "Systemd Service"];
case "macos": case "macos":
return ["Run", "Service"]; return ["Run"];
case "windows": case "windows":
return ["Run", "Service"]; return ["x64"];
case "docker": case "docker":
return ["Docker Compose", "Docker Run"]; return ["Docker Compose", "Docker Run"];
case "kubernetes": case "kubernetes":
+19 -68
View File
@@ -1,4 +1,4 @@
import { Download, Terminal } from "lucide-react"; import { Terminal } from "lucide-react";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { useState } from "react"; import { useState } from "react";
import { FaDocker, FaWindows } from "react-icons/fa"; import { FaDocker, FaWindows } from "react-icons/fa";
@@ -10,13 +10,9 @@ import {
SettingsSectionHeader, SettingsSectionHeader,
SettingsSectionTitle SettingsSectionTitle
} from "./Settings"; } from "./Settings";
import { Button } from "./ui/button";
import { OptionSelect, type OptionSelectOption } from "./OptionSelect"; import { OptionSelect, type OptionSelectOption } from "./OptionSelect";
export type CommandItem = export type CommandItem = string | { title: string; command: string };
| string
| { title: string; command: string }
| { title: string; link: string };
const PLATFORMS = ["unix", "docker", "windows"] as const; const PLATFORMS = ["unix", "docker", "windows"] as const;
@@ -26,12 +22,14 @@ export type OlmInstallCommandsProps = {
id: string; id: string;
secret: string; secret: string;
endpoint: string; endpoint: string;
version?: string;
}; };
export function OlmInstallCommands({ export function OlmInstallCommands({
id, id,
secret, secret,
endpoint endpoint,
version = "latest"
}: OlmInstallCommandsProps) { }: OlmInstallCommandsProps) {
const t = useTranslations(); const t = useTranslations();
@@ -42,28 +40,14 @@ export function OlmInstallCommands({
const commandList: Record<Platform, Record<string, CommandItem[]>> = { const commandList: Record<Platform, Record<string, CommandItem[]>> = {
unix: { unix: {
Run: [ All: [
{ {
title: t("install"), title: t("install"),
command: `curl -fsSL https://static.pangolin.net/get-cli.sh | sudo bash` command: `curl -fsSL https://static.pangolin.net/get-cli.sh | sudo bash`
}, },
{ {
title: t("run"), title: t("run"),
command: `sudo pangolin up client --id ${id} --secret ${secret} --endpoint ${endpoint} --attach` command: `sudo pangolin up --id ${id} --secret ${secret} --endpoint ${endpoint} --attach`
}
],
Service: [
{
title: t("install"),
command: `curl -fsSL https://static.pangolin.net/get-cli.sh | bash`
},
{
title: t("run"),
command: `sudo pangolin service install client --id ${id} --secret ${secret} --endpoint ${endpoint}`
},
{
title: t("check"),
command: `sudo pangolin service status client`
} }
] ]
}, },
@@ -89,28 +73,15 @@ export function OlmInstallCommands({
] ]
}, },
windows: { windows: {
Run: [ x64: [
{ {
title: t("install"), title: t("install"),
link: `https://github.com/fosrl/cli/releases/latest/download/pangolin-cli_windows_installer.msi` command: `# Download and run the installer to install Olm first\n
curl -o olm.exe -L "https://github.com/fosrl/olm/releases/download/${version}/olm_windows_installer.exe"`
}, },
{ {
title: t("run"), title: t("run"),
command: `pangolin up client --id ${id} --secret ${secret} --endpoint ${endpoint}` command: `olm.exe --id ${id} --secret ${secret} --endpoint ${endpoint}`
}
],
Service: [
{
title: t("install"),
link: `https://github.com/fosrl/cli/releases/latest/download/pangolin-cli_windows_installer.msi`
},
{
title: t("run"),
command: `pangolin service install client --id ${id} --secret ${secret} --endpoint ${endpoint}`
},
{
title: t("check"),
command: `pangolin service status client`
} }
] ]
} }
@@ -150,7 +121,9 @@ export function OlmInstallCommands({
/> />
<OptionSelect<string> <OptionSelect<string>
label={t("method")} label={
platform === "docker" ? t("method") : t("architecture")
}
options={getArchitectures(platform).map((arch) => ({ options={getArchitectures(platform).map((arch) => ({
value: arch, value: arch,
label: arch label: arch
@@ -165,17 +138,8 @@ export function OlmInstallCommands({
<p className="font-semibold mb-3">{t("commands")}</p> <p className="font-semibold mb-3">{t("commands")}</p>
<div className="mt-2 space-y-3"> <div className="mt-2 space-y-3">
{commands.map((item, index) => { {commands.map((item, index) => {
const isLink =
typeof item !== "string" && "link" in item;
const commandText = const commandText =
typeof item === "string" typeof item === "string" ? item : item.command;
? item
: isLink
? undefined
: item.command;
const linkHref = isLink
? (item as { link: string }).link
: undefined;
const title = const title =
typeof item === "string" typeof item === "string"
? undefined ? undefined
@@ -188,23 +152,10 @@ export function OlmInstallCommands({
{title} {title}
</p> </p>
)} )}
{isLink ? (
<Button
asChild
variant="outline"
className="w-full"
>
<a href={linkHref}>
<Download className="h-4 w-4 mr-2" />
{t("downloadInstaller")}
</a>
</Button>
) : (
<CopyTextBox <CopyTextBox
text={commandText!} text={commandText}
outline={true} outline={true}
/> />
)}
</div> </div>
); );
})} })}
@@ -218,13 +169,13 @@ export function OlmInstallCommands({
function getArchitectures(platform: Platform) { function getArchitectures(platform: Platform) {
switch (platform) { switch (platform) {
case "unix": case "unix":
return ["Run", "Service"]; return ["All"];
case "windows": case "windows":
return ["Run", "Service"]; return ["x64"];
case "docker": case "docker":
return ["Docker Compose", "Docker Run"]; return ["Docker Compose", "Docker Run"];
default: default:
return ["Run"]; return ["x64"];
} }
} }
+1 -1
View File
@@ -14,7 +14,7 @@
"moduleResolution": "bundler", "moduleResolution": "bundler",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"jsx": "react-jsx", "jsx": "preserve",
"incremental": true, "incremental": true,
"paths": { "paths": {
"@server/*": [ "@server/*": [