From 69ecc223184c80a28fd0ae37acc9af9b9cf71176 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 01:39:38 -0800 Subject: [PATCH 01/43] New translations en-us.json (German) --- messages/de-DE.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/messages/de-DE.json b/messages/de-DE.json index a38e9ac4..ee3d65ea 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -1154,7 +1154,7 @@ "actionDeleteClient": "Client löschen", "actionArchiveClient": "Client archivieren", "actionUnarchiveClient": "Client dearchivieren", - "actionBlockClient": "Klient sperren", + "actionBlockClient": "Client sperren", "actionUnblockClient": "Client entsperren", "actionUpdateClient": "Client aktualisieren", "actionListClients": "Clients auflisten", @@ -2532,10 +2532,10 @@ "archiveClientQuestion": "Sind Sie sicher, dass Sie diesen Client archivieren möchten?", "archiveClientMessage": "Der Client wird archiviert und aus der Liste Ihrer aktiven Clients entfernt.", "archiveClientConfirm": "Client archivieren", - "blockClient": "Klient sperren", + "blockClient": "Client sperren", "blockClientQuestion": "Sind Sie sicher, dass Sie diesen Client blockieren möchten?", "blockClientMessage": "Das Gerät wird gezwungen, die Verbindung zu trennen, wenn es gerade verbunden ist. Sie können das Gerät später entsperren.", - "blockClientConfirm": "Klient sperren", + "blockClientConfirm": "Client sperren", "active": "Aktiv", "usernameOrEmail": "Benutzername oder E-Mail", "selectYourOrganization": "Wählen Sie Ihre Organisation", From 52484c774e96d7f3a306042ac789829364967d9d Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 12 Feb 2026 12:05:15 -0800 Subject: [PATCH 02/43] Setting up drizzle and fix site not showing in private resource --- .gitignore | 3 +- drizzle.config.ts | 7 +- server/setup/.gitignore | 1 + server/setup/migrations.ts | 150 ++++++++++++------ .../CreateInternalResourceDialog.tsx | 2 +- src/components/EditInternalResourceDialog.tsx | 2 +- 6 files changed, 107 insertions(+), 58 deletions(-) create mode 100644 server/setup/.gitignore diff --git a/.gitignore b/.gitignore index df9179a4..1067dc2c 100644 --- a/.gitignore +++ b/.gitignore @@ -51,4 +51,5 @@ dynamic/ scratch/ tsconfig.json hydrateSaas.ts -CLAUDE.md \ No newline at end of file +CLAUDE.md +drizzle.config.ts diff --git a/drizzle.config.ts b/drizzle.config.ts index ba4ca8fe..d8344f94 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,14 +1,15 @@ +import { APP_PATH } from "@server/lib/consts"; import { defineConfig } from "drizzle-kit"; import path from "path"; -const schema = [path.join("server", "db", "pg", "schema")]; +const schema = [path.join("server", "db", "sqlite", "schema")]; export default defineConfig({ - dialect: "postgresql", + dialect: "sqlite", schema: schema, out: path.join("server", "migrations"), verbose: true, dbCredentials: { - url: process.env.DATABASE_URL as string + url: path.join(APP_PATH, "db", "db.sqlite") } }); diff --git a/server/setup/.gitignore b/server/setup/.gitignore new file mode 100644 index 00000000..a61cfd64 --- /dev/null +++ b/server/setup/.gitignore @@ -0,0 +1 @@ +migrations.ts diff --git a/server/setup/migrations.ts b/server/setup/migrations.ts index 7ae21836..170cf93d 100644 --- a/server/setup/migrations.ts +++ b/server/setup/migrations.ts @@ -1,45 +1,78 @@ #! /usr/bin/env node -import { migrate } from "drizzle-orm/node-postgres/migrator"; -import { db } from "../db/pg"; -import semver from "semver"; -import { versionMigrations } from "../db/pg"; -import { __DIRNAME, APP_VERSION } from "@server/lib/consts"; +import { migrate } from "drizzle-orm/better-sqlite3/migrator"; +import { db, exists } from "../db/sqlite"; import path from "path"; -import m1 from "./scriptsPg/1.6.0"; -import m2 from "./scriptsPg/1.7.0"; -import m3 from "./scriptsPg/1.8.0"; -import m4 from "./scriptsPg/1.9.0"; -import m5 from "./scriptsPg/1.10.0"; -import m6 from "./scriptsPg/1.10.2"; -import m7 from "./scriptsPg/1.11.0"; -import m8 from "./scriptsPg/1.11.1"; -import m9 from "./scriptsPg/1.12.0"; -import m10 from "./scriptsPg/1.13.0"; -import m11 from "./scriptsPg/1.14.0"; -import m12 from "./scriptsPg/1.15.0"; +import semver from "semver"; +import { versionMigrations } from "../db/sqlite"; +import { __DIRNAME, APP_PATH, APP_VERSION } from "@server/lib/consts"; +import { SqliteError } from "better-sqlite3"; +import fs from "fs"; +import m1 from "./scriptsSqlite/1.0.0-beta1"; +import m2 from "./scriptsSqlite/1.0.0-beta2"; +import m3 from "./scriptsSqlite/1.0.0-beta3"; +import m4 from "./scriptsSqlite/1.0.0-beta5"; +import m5 from "./scriptsSqlite/1.0.0-beta6"; +import m6 from "./scriptsSqlite/1.0.0-beta9"; +import m7 from "./scriptsSqlite/1.0.0-beta10"; +import m8 from "./scriptsSqlite/1.0.0-beta12"; +import m13 from "./scriptsSqlite/1.0.0-beta13"; +import m15 from "./scriptsSqlite/1.0.0-beta15"; +import m16 from "./scriptsSqlite/1.0.0"; +import m17 from "./scriptsSqlite/1.1.0"; +import m18 from "./scriptsSqlite/1.2.0"; +import m19 from "./scriptsSqlite/1.3.0"; +import m20 from "./scriptsSqlite/1.5.0"; +import m21 from "./scriptsSqlite/1.6.0"; +import m22 from "./scriptsSqlite/1.7.0"; +import m23 from "./scriptsSqlite/1.8.0"; +import m24 from "./scriptsSqlite/1.9.0"; +import m25 from "./scriptsSqlite/1.10.0"; +import m26 from "./scriptsSqlite/1.10.1"; +import m27 from "./scriptsSqlite/1.10.2"; +import m28 from "./scriptsSqlite/1.11.0"; +import m29 from "./scriptsSqlite/1.11.1"; +import m30 from "./scriptsSqlite/1.12.0"; +import m31 from "./scriptsSqlite/1.13.0"; +import m32 from "./scriptsSqlite/1.14.0"; +import m33 from "./scriptsSqlite/1.15.0"; +import m34 from "./scriptsSqlite/1.15.3"; // THIS CANNOT IMPORT ANYTHING FROM THE SERVER // EXCEPT FOR THE DATABASE AND THE SCHEMA // Define the migration list with versions and their corresponding functions const migrations = [ - { version: "1.6.0", run: m1 }, - { version: "1.7.0", run: m2 }, - { version: "1.8.0", run: m3 }, - { version: "1.9.0", run: m4 }, - { version: "1.10.0", run: m5 }, - { version: "1.10.2", run: m6 }, - { version: "1.11.0", run: m7 }, - { version: "1.11.1", run: m8 }, - { version: "1.12.0", run: m9 }, - { version: "1.13.0", run: m10 }, - { version: "1.14.0", run: m11 }, - { version: "1.15.0", run: m12 } + { version: "1.0.0-beta.1", run: m1 }, + { version: "1.0.0-beta.2", run: m2 }, + { version: "1.0.0-beta.3", run: m3 }, + { version: "1.0.0-beta.5", run: m4 }, + { version: "1.0.0-beta.6", run: m5 }, + { version: "1.0.0-beta.9", run: m6 }, + { version: "1.0.0-beta.10", run: m7 }, + { version: "1.0.0-beta.12", run: m8 }, + { version: "1.0.0-beta.13", run: m13 }, + { version: "1.0.0-beta.15", run: m15 }, + { version: "1.0.0", run: m16 }, + { version: "1.1.0", run: m17 }, + { version: "1.2.0", run: m18 }, + { version: "1.3.0", run: m19 }, + { version: "1.5.0", run: m20 }, + { version: "1.6.0", run: m21 }, + { version: "1.7.0", run: m22 }, + { version: "1.8.0", run: m23 }, + { version: "1.9.0", run: m24 }, + { version: "1.10.0", run: m25 }, + { version: "1.10.1", run: m26 }, + { version: "1.10.2", run: m27 }, + { version: "1.11.0", run: m28 }, + { version: "1.11.1", run: m29 }, + { version: "1.12.0", run: m30 }, + { version: "1.13.0", run: m31 }, + { version: "1.14.0", run: m32 }, + { version: "1.15.0", run: m33 }, + { version: "1.15.3", run: m34 } // Add new migrations here as they are created -] as { - version: string; - run: () => Promise; -}[]; +] as const; await run(); @@ -48,6 +81,27 @@ async function run() { await runMigrations(); } +function backupDb() { + // make dir config/db/backups + const appPath = APP_PATH; + const dbDir = path.join(appPath, "db"); + + const backupsDir = path.join(dbDir, "backups"); + + // check if the backups directory exists and create it if it doesn't + if (!fs.existsSync(backupsDir)) { + fs.mkdirSync(backupsDir, { recursive: true }); + } + + // copy the db.sqlite file to backups + // add the date to the filename + const date = new Date(); + const dateString = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}_${date.getHours()}-${date.getMinutes()}-${date.getSeconds()}`; + const dbPath = path.join(dbDir, "db.sqlite"); + const backupPath = path.join(backupsDir, `db_${dateString}.sqlite`); + fs.copyFileSync(dbPath, backupPath); +} + export async function runMigrations() { if (process.env.DISABLE_MIGRATIONS) { console.log("Migrations are disabled. Skipping..."); @@ -56,23 +110,12 @@ export async function runMigrations() { try { const appVersion = APP_VERSION; - // determine if the migrations table exists - const exists = await db - .select() - .from(versionMigrations) - .limit(1) - .execute() - .then((res) => res.length > 0) - .catch(() => false); - if (exists) { - console.log("Migrations table exists, running scripts..."); await executeScripts(); } else { - console.log("Migrations table does not exist, creating it..."); console.log("Running migrations..."); try { - await migrate(db, { + migrate(db, { migrationsFolder: path.join(__DIRNAME, "init") // put here during the docker build }); console.log("Migrations completed successfully."); @@ -105,7 +148,7 @@ async function executeScripts() { const pendingMigrations = lastExecuted .map((m) => m) .sort((a, b) => semver.compare(b.version, a.version)); - const startVersion = pendingMigrations[0]?.version ?? "0.0.0"; + const startVersion = pendingMigrations[0]?.version ?? APP_VERSION; console.log(`Starting migrations from version ${startVersion}`); const migrationsToRun = migrations.filter((migration) => @@ -122,6 +165,11 @@ async function executeScripts() { console.log(`Running migration ${migration.version}`); try { + if (!process.env.DISABLE_BACKUP_ON_MIGRATION) { + // Backup the database before running the migration + backupDb(); + } + await migration.run(); // Update version in database @@ -138,19 +186,17 @@ async function executeScripts() { ); } catch (e) { if ( - e instanceof Error && - typeof (e as any).code === "string" && - (e as any).code === "23505" + e instanceof SqliteError && + e.code === "SQLITE_CONSTRAINT_UNIQUE" ) { console.error("Migration has already run! Skipping..."); - continue; // or return, depending on context + continue; } - console.error( `Failed to run migration ${migration.version}:`, e ); - throw e; + throw e; // Re-throw to stop migration process } } diff --git a/src/components/CreateInternalResourceDialog.tsx b/src/components/CreateInternalResourceDialog.tsx index 4c8d6325..34e8f55e 100644 --- a/src/components/CreateInternalResourceDialog.tsx +++ b/src/components/CreateInternalResourceDialog.tsx @@ -303,7 +303,7 @@ export default function CreateInternalResourceDialog({ const [udpCustomPorts, setUdpCustomPorts] = useState(""); const availableSites = sites.filter( - (site) => site.type === "newt" && site.subnet + (site) => site.type === "newt" ); const form = useForm({ diff --git a/src/components/EditInternalResourceDialog.tsx b/src/components/EditInternalResourceDialog.tsx index 4b5030be..4c1176e5 100644 --- a/src/components/EditInternalResourceDialog.tsx +++ b/src/components/EditInternalResourceDialog.tsx @@ -397,7 +397,7 @@ export default function EditInternalResourceDialog({ ); const availableSites = sites.filter( - (site) => site.type === "newt" && site.subnet + (site) => site.type === "newt" ); const form = useForm({ From bc7bc8da66451d2ea994f4fa2a71ba31b896b473 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 12 Feb 2026 12:07:57 -0800 Subject: [PATCH 03/43] Stop tracking files that should be ignored --- drizzle.config.ts | 15 --- server/setup/migrations.ts | 208 ------------------------------------- 2 files changed, 223 deletions(-) delete mode 100644 drizzle.config.ts delete mode 100644 server/setup/migrations.ts diff --git a/drizzle.config.ts b/drizzle.config.ts deleted file mode 100644 index d8344f94..00000000 --- a/drizzle.config.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { APP_PATH } from "@server/lib/consts"; -import { defineConfig } from "drizzle-kit"; -import path from "path"; - -const schema = [path.join("server", "db", "sqlite", "schema")]; - -export default defineConfig({ - dialect: "sqlite", - schema: schema, - out: path.join("server", "migrations"), - verbose: true, - dbCredentials: { - url: path.join(APP_PATH, "db", "db.sqlite") - } -}); diff --git a/server/setup/migrations.ts b/server/setup/migrations.ts deleted file mode 100644 index 170cf93d..00000000 --- a/server/setup/migrations.ts +++ /dev/null @@ -1,208 +0,0 @@ -#! /usr/bin/env node -import { migrate } from "drizzle-orm/better-sqlite3/migrator"; -import { db, exists } from "../db/sqlite"; -import path from "path"; -import semver from "semver"; -import { versionMigrations } from "../db/sqlite"; -import { __DIRNAME, APP_PATH, APP_VERSION } from "@server/lib/consts"; -import { SqliteError } from "better-sqlite3"; -import fs from "fs"; -import m1 from "./scriptsSqlite/1.0.0-beta1"; -import m2 from "./scriptsSqlite/1.0.0-beta2"; -import m3 from "./scriptsSqlite/1.0.0-beta3"; -import m4 from "./scriptsSqlite/1.0.0-beta5"; -import m5 from "./scriptsSqlite/1.0.0-beta6"; -import m6 from "./scriptsSqlite/1.0.0-beta9"; -import m7 from "./scriptsSqlite/1.0.0-beta10"; -import m8 from "./scriptsSqlite/1.0.0-beta12"; -import m13 from "./scriptsSqlite/1.0.0-beta13"; -import m15 from "./scriptsSqlite/1.0.0-beta15"; -import m16 from "./scriptsSqlite/1.0.0"; -import m17 from "./scriptsSqlite/1.1.0"; -import m18 from "./scriptsSqlite/1.2.0"; -import m19 from "./scriptsSqlite/1.3.0"; -import m20 from "./scriptsSqlite/1.5.0"; -import m21 from "./scriptsSqlite/1.6.0"; -import m22 from "./scriptsSqlite/1.7.0"; -import m23 from "./scriptsSqlite/1.8.0"; -import m24 from "./scriptsSqlite/1.9.0"; -import m25 from "./scriptsSqlite/1.10.0"; -import m26 from "./scriptsSqlite/1.10.1"; -import m27 from "./scriptsSqlite/1.10.2"; -import m28 from "./scriptsSqlite/1.11.0"; -import m29 from "./scriptsSqlite/1.11.1"; -import m30 from "./scriptsSqlite/1.12.0"; -import m31 from "./scriptsSqlite/1.13.0"; -import m32 from "./scriptsSqlite/1.14.0"; -import m33 from "./scriptsSqlite/1.15.0"; -import m34 from "./scriptsSqlite/1.15.3"; - -// THIS CANNOT IMPORT ANYTHING FROM THE SERVER -// EXCEPT FOR THE DATABASE AND THE SCHEMA - -// Define the migration list with versions and their corresponding functions -const migrations = [ - { version: "1.0.0-beta.1", run: m1 }, - { version: "1.0.0-beta.2", run: m2 }, - { version: "1.0.0-beta.3", run: m3 }, - { version: "1.0.0-beta.5", run: m4 }, - { version: "1.0.0-beta.6", run: m5 }, - { version: "1.0.0-beta.9", run: m6 }, - { version: "1.0.0-beta.10", run: m7 }, - { version: "1.0.0-beta.12", run: m8 }, - { version: "1.0.0-beta.13", run: m13 }, - { version: "1.0.0-beta.15", run: m15 }, - { version: "1.0.0", run: m16 }, - { version: "1.1.0", run: m17 }, - { version: "1.2.0", run: m18 }, - { version: "1.3.0", run: m19 }, - { version: "1.5.0", run: m20 }, - { version: "1.6.0", run: m21 }, - { version: "1.7.0", run: m22 }, - { version: "1.8.0", run: m23 }, - { version: "1.9.0", run: m24 }, - { version: "1.10.0", run: m25 }, - { version: "1.10.1", run: m26 }, - { version: "1.10.2", run: m27 }, - { version: "1.11.0", run: m28 }, - { version: "1.11.1", run: m29 }, - { version: "1.12.0", run: m30 }, - { version: "1.13.0", run: m31 }, - { version: "1.14.0", run: m32 }, - { version: "1.15.0", run: m33 }, - { version: "1.15.3", run: m34 } - // Add new migrations here as they are created -] as const; - -await run(); - -async function run() { - // run the migrations - await runMigrations(); -} - -function backupDb() { - // make dir config/db/backups - const appPath = APP_PATH; - const dbDir = path.join(appPath, "db"); - - const backupsDir = path.join(dbDir, "backups"); - - // check if the backups directory exists and create it if it doesn't - if (!fs.existsSync(backupsDir)) { - fs.mkdirSync(backupsDir, { recursive: true }); - } - - // copy the db.sqlite file to backups - // add the date to the filename - const date = new Date(); - const dateString = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}_${date.getHours()}-${date.getMinutes()}-${date.getSeconds()}`; - const dbPath = path.join(dbDir, "db.sqlite"); - const backupPath = path.join(backupsDir, `db_${dateString}.sqlite`); - fs.copyFileSync(dbPath, backupPath); -} - -export async function runMigrations() { - if (process.env.DISABLE_MIGRATIONS) { - console.log("Migrations are disabled. Skipping..."); - return; - } - try { - const appVersion = APP_VERSION; - - if (exists) { - await executeScripts(); - } else { - console.log("Running migrations..."); - try { - migrate(db, { - migrationsFolder: path.join(__DIRNAME, "init") // put here during the docker build - }); - console.log("Migrations completed successfully."); - } catch (error) { - console.error("Error running migrations:", error); - } - - await db - .insert(versionMigrations) - .values({ - version: appVersion, - executedAt: Date.now() - }) - .execute(); - } - } catch (e) { - console.error("Error running migrations:", e); - await new Promise((resolve) => - setTimeout(resolve, 1000 * 60 * 60 * 24 * 1) - ); - } -} - -async function executeScripts() { - try { - // Get the last executed version from the database - const lastExecuted = await db.select().from(versionMigrations); - - // Filter and sort migrations - const pendingMigrations = lastExecuted - .map((m) => m) - .sort((a, b) => semver.compare(b.version, a.version)); - const startVersion = pendingMigrations[0]?.version ?? APP_VERSION; - console.log(`Starting migrations from version ${startVersion}`); - - const migrationsToRun = migrations.filter((migration) => - semver.gt(migration.version, startVersion) - ); - - console.log( - "Migrations to run:", - migrationsToRun.map((m) => m.version).join(", ") - ); - - // Run migrations in order - for (const migration of migrationsToRun) { - console.log(`Running migration ${migration.version}`); - - try { - if (!process.env.DISABLE_BACKUP_ON_MIGRATION) { - // Backup the database before running the migration - backupDb(); - } - - await migration.run(); - - // Update version in database - await db - .insert(versionMigrations) - .values({ - version: migration.version, - executedAt: Date.now() - }) - .execute(); - - console.log( - `Successfully completed migration ${migration.version}` - ); - } catch (e) { - if ( - e instanceof SqliteError && - e.code === "SQLITE_CONSTRAINT_UNIQUE" - ) { - console.error("Migration has already run! Skipping..."); - continue; - } - console.error( - `Failed to run migration ${migration.version}:`, - e - ); - throw e; // Re-throw to stop migration process - } - } - - console.log("All migrations completed successfully"); - } catch (error) { - console.error("Migration process failed:", error); - throw error; - } -} From a409ec269b01586d87f80a65c3991e7530ec486e Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 12 Feb 2026 12:13:13 -0800 Subject: [PATCH 04/43] Change back to lokowitz db method --- .github/workflows/test.yml | 4 ++-- Dockerfile | 2 +- package.json | 9 +++------ server/db/README.md | 8 ++++---- server/db/migrate.ts | 3 +++ server/db/pg/index.ts | 1 + server/db/pg/migrate.ts | 4 +--- server/db/sqlite/index.ts | 1 + server/db/sqlite/migrate.ts | 4 +--- 9 files changed, 17 insertions(+), 19 deletions(-) create mode 100644 server/db/migrate.ts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e6af8d79..eec4ff33 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,10 +34,10 @@ jobs: run: npm run set:oss - name: Generate database migrations - run: npm run db:sqlite:generate + run: npm run db:generate - name: Apply database migrations - run: npm run db:sqlite:push + run: npm run db:push - name: Test with tsc run: npx tsc --noEmit diff --git a/Dockerfile b/Dockerfile index 197a1032..f82719a6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ COPY . . RUN if [ "$BUILD" = "oss" ]; then rm -rf server/private; fi && \ npm run set:$DATABASE && \ npm run set:$BUILD && \ - npm run db:$DATABASE:generate && \ + npm run db:generate && \ npm run build && \ npm run build:cli diff --git a/package.json b/package.json index 93e3c171..61dc023e 100644 --- a/package.json +++ b/package.json @@ -14,12 +14,9 @@ "dev": "NODE_ENV=development ENVIRONMENT=dev tsx watch server/index.ts", "dev:check": "npx tsc --noEmit && npm run format:check", "dev:setup": "cp config/config.example.yml config/config.yml && npm run set:oss && npm run set:sqlite && npm run db:sqlite:generate && npm run db:sqlite:push", - "db:pg:generate": "drizzle-kit generate --config=./drizzle.pg.config.ts", - "db:sqlite:generate": "drizzle-kit generate --config=./drizzle.sqlite.config.ts", - "db:pg:push": "npx tsx server/db/pg/migrate.ts", - "db:sqlite:push": "npx tsx server/db/sqlite/migrate.ts", - "db:pg:studio": "drizzle-kit studio --config=./drizzle.pg.config.ts", - "db:sqlite:studio": "drizzle-kit studio --config=./drizzle.sqlite.config.ts", + "db:generate": "drizzle-kit generate --config=./drizzle.config.ts", + "db:push": "npx tsx server/db/pg/migrate.ts", + "db:studio": "drizzle-kit studio --config=./drizzle.config.ts", "db:clear-migrations": "rm -rf server/migrations", "set:oss": "echo 'export const build = \"oss\" as \"saas\" | \"enterprise\" | \"oss\";' > server/build.ts && cp tsconfig.oss.json tsconfig.json", "set:saas": "echo 'export const build = \"saas\" as \"saas\" | \"enterprise\" | \"oss\";' > server/build.ts && cp tsconfig.saas.json tsconfig.json", diff --git a/server/db/README.md b/server/db/README.md index 36c3730b..1f7d57d1 100644 --- a/server/db/README.md +++ b/server/db/README.md @@ -56,15 +56,15 @@ Ensure drizzle-kit is installed. You must have a connection string in your config file, as shown above. ```bash -npm run db:pg:generate -npm run db:pg:push +npm run db:generate +npm run db:push ``` ### SQLite ```bash -npm run db:sqlite:generate -npm run db:sqlite:push +npm run db:generate +npm run db:push ``` ## Build Time diff --git a/server/db/migrate.ts b/server/db/migrate.ts new file mode 100644 index 00000000..67ff15ec --- /dev/null +++ b/server/db/migrate.ts @@ -0,0 +1,3 @@ +import { runMigrations } from "./"; + +await runMigrations(); diff --git a/server/db/pg/index.ts b/server/db/pg/index.ts index 6e2c79f5..43e2650f 100644 --- a/server/db/pg/index.ts +++ b/server/db/pg/index.ts @@ -1,3 +1,4 @@ export * from "./driver"; export * from "./schema/schema"; export * from "./schema/privateSchema"; +export * from "./migrate"; diff --git a/server/db/pg/migrate.ts b/server/db/pg/migrate.ts index 2d2abca3..d84cc010 100644 --- a/server/db/pg/migrate.ts +++ b/server/db/pg/migrate.ts @@ -4,7 +4,7 @@ import path from "path"; const migrationsFolder = path.join("server/migrations"); -const runMigrations = async () => { +export const runMigrations = async () => { console.log("Running migrations..."); try { await migrate(db as any, { @@ -17,5 +17,3 @@ const runMigrations = async () => { process.exit(1); } }; - -runMigrations(); diff --git a/server/db/sqlite/index.ts b/server/db/sqlite/index.ts index 6e2c79f5..43e2650f 100644 --- a/server/db/sqlite/index.ts +++ b/server/db/sqlite/index.ts @@ -1,3 +1,4 @@ export * from "./driver"; export * from "./schema/schema"; export * from "./schema/privateSchema"; +export * from "./migrate"; diff --git a/server/db/sqlite/migrate.ts b/server/db/sqlite/migrate.ts index 7c337ae2..79a3d8c7 100644 --- a/server/db/sqlite/migrate.ts +++ b/server/db/sqlite/migrate.ts @@ -4,7 +4,7 @@ import path from "path"; const migrationsFolder = path.join("server/migrations"); -const runMigrations = async () => { +export const runMigrations = async () => { console.log("Running migrations..."); try { migrate(db as any, { @@ -16,5 +16,3 @@ const runMigrations = async () => { process.exit(1); } }; - -runMigrations(); From 6496763aae52cf00f1d9a046c0dd2771c496eb89 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 12 Feb 2026 12:18:42 -0800 Subject: [PATCH 05/43] Cap retention days --- .github/workflows/cicd.yml | 2 +- .../routers/billing/featureLifecycle.ts | 113 ++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 2ebb4663..1d066d84 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -1,4 +1,4 @@ -name: Public Pipeline +name: Public CICD Pipeline # CI/CD workflow for building, publishing, mirroring, signing container images and building release binaries. # Actions are pinned to specific SHAs to reduce supply-chain risk. This workflow triggers on tag push events. diff --git a/server/private/routers/billing/featureLifecycle.ts b/server/private/routers/billing/featureLifecycle.ts index 46337fed..35345444 100644 --- a/server/private/routers/billing/featureLifecycle.ts +++ b/server/private/routers/billing/featureLifecycle.ts @@ -18,6 +18,113 @@ import logger from "@server/logger"; import { db, idp, idpOrg, loginPage, loginPageBranding, loginPageBrandingOrg, loginPageOrg, orgs, resources, roles } from "@server/db"; import { eq } from "drizzle-orm"; +/** + * Get the maximum allowed retention days for a given tier + * Returns null for enterprise tier (unlimited) + */ +function getMaxRetentionDaysForTier(tier: Tier | null): number | null { + if (!tier) { + return 3; // Free tier + } + + switch (tier) { + case "tier1": + return 7; + case "tier2": + return 30; + case "tier3": + return 90; + case "enterprise": + return null; // No limit + default: + return 3; // Default to free tier limit + } +} + +/** + * Cap retention days to the maximum allowed for the given tier + */ +async function capRetentionDays( + orgId: string, + tier: Tier | null +): Promise { + const maxRetentionDays = getMaxRetentionDaysForTier(tier); + + // If there's no limit (enterprise tier), no capping needed + if (maxRetentionDays === null) { + logger.debug( + `No retention day limit for org ${orgId} on tier ${tier || "free"}` + ); + return; + } + + // Get current org settings + const [org] = await db + .select() + .from(orgs) + .where(eq(orgs.orgId, orgId)); + + if (!org) { + logger.warn(`Org ${orgId} not found when capping retention days`); + return; + } + + const updates: Partial = {}; + let needsUpdate = false; + + // Cap request log retention if it exceeds the limit + if ( + org.settingsLogRetentionDaysRequest !== null && + org.settingsLogRetentionDaysRequest > maxRetentionDays + ) { + updates.settingsLogRetentionDaysRequest = maxRetentionDays; + needsUpdate = true; + logger.info( + `Capping request log retention from ${org.settingsLogRetentionDaysRequest} to ${maxRetentionDays} days for org ${orgId}` + ); + } + + // Cap access log retention if it exceeds the limit + if ( + org.settingsLogRetentionDaysAccess !== null && + org.settingsLogRetentionDaysAccess > maxRetentionDays + ) { + updates.settingsLogRetentionDaysAccess = maxRetentionDays; + needsUpdate = true; + logger.info( + `Capping access log retention from ${org.settingsLogRetentionDaysAccess} to ${maxRetentionDays} days for org ${orgId}` + ); + } + + // Cap action log retention if it exceeds the limit + if ( + org.settingsLogRetentionDaysAction !== null && + org.settingsLogRetentionDaysAction > maxRetentionDays + ) { + updates.settingsLogRetentionDaysAction = maxRetentionDays; + needsUpdate = true; + logger.info( + `Capping action log retention from ${org.settingsLogRetentionDaysAction} to ${maxRetentionDays} days for org ${orgId}` + ); + } + + // Apply updates if needed + if (needsUpdate) { + await db + .update(orgs) + .set(updates) + .where(eq(orgs.orgId, orgId)); + + logger.info( + `Successfully capped retention days for org ${orgId} to max ${maxRetentionDays} days` + ); + } else { + logger.debug( + `No retention day capping needed for org ${orgId}` + ); + } +} + export async function handleTierChange( orgId: string, newTier: SubscriptionType | null, @@ -40,6 +147,9 @@ export async function handleTierChange( logger.info( `Org ${orgId} is reverting to free tier, disabling all paid features` ); + // Cap retention days to free tier limits + await capRetentionDays(orgId, null); + // Disable all features in the tier matrix for (const [featureKey] of Object.entries(tierMatrix)) { const feature = featureKey as TierFeature; @@ -57,6 +167,9 @@ export async function handleTierChange( // Get the tier (cast as Tier since we've ruled out "license" and null) const tier = newTier as Tier; + // Cap retention days to the new tier's limits + await capRetentionDays(orgId, tier); + // Check each feature in the tier matrix for (const [featureKey, allowedTiers] of Object.entries(tierMatrix)) { const feature = featureKey as TierFeature; From 94e70219cfdff06513cf3769d276857aea921b76 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 12 Feb 2026 14:12:45 -0800 Subject: [PATCH 06/43] Make install sudo because run is sudo --- src/components/olm-install-commands.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/olm-install-commands.tsx b/src/components/olm-install-commands.tsx index 1728f528..b8ad8e7d 100644 --- a/src/components/olm-install-commands.tsx +++ b/src/components/olm-install-commands.tsx @@ -43,7 +43,7 @@ export function OlmInstallCommands({ All: [ { title: t("install"), - command: `curl -fsSL https://static.pangolin.net/get-cli.sh | bash` + command: `curl -fsSL https://static.pangolin.net/get-cli.sh | sudo bash` }, { title: t("run"), From f527c3092370ca01cdd367bf7af3d8cf50a41a16 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 12 Feb 2026 14:21:43 -0800 Subject: [PATCH 07/43] add post auth url --- package.json | 2 +- server/db/pg/schema/schema.ts | 3 ++- server/db/sqlite/schema/schema.ts | 3 ++- server/lib/normalizePostAuthPath.ts | 18 ++++++++++++++++++ server/routers/resource/authWithAccessToken.ts | 9 ++++++++- server/routers/resource/createResource.ts | 8 +++++--- server/routers/resource/getResourceAuthInfo.ts | 4 +++- server/routers/resource/updateResource.ts | 3 ++- src/app/auth/resource/[resourceGuid]/page.tsx | 6 ++++++ 9 files changed, 47 insertions(+), 9 deletions(-) create mode 100644 server/lib/normalizePostAuthPath.ts diff --git a/package.json b/package.json index 61dc023e..5cb205ce 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "dev:check": "npx tsc --noEmit && npm run format:check", "dev:setup": "cp config/config.example.yml config/config.yml && npm run set:oss && npm run set:sqlite && npm run db:sqlite:generate && npm run db:sqlite:push", "db:generate": "drizzle-kit generate --config=./drizzle.config.ts", - "db:push": "npx tsx server/db/pg/migrate.ts", + "db:push": "npx tsx server/db/migrate.ts", "db:studio": "drizzle-kit studio --config=./drizzle.config.ts", "db:clear-migrations": "rm -rf server/migrations", "set:oss": "echo 'export const build = \"oss\" as \"saas\" | \"enterprise\" | \"oss\";' > server/build.ts && cp tsconfig.oss.json tsconfig.json", diff --git a/server/db/pg/schema/schema.ts b/server/db/pg/schema/schema.ts index 3c957470..dc6f3758 100644 --- a/server/db/pg/schema/schema.ts +++ b/server/db/pg/schema/schema.ts @@ -142,7 +142,8 @@ export const resources = pgTable("resources", { }).default("forced"), // "forced" = always show, "automatic" = only when down maintenanceTitle: text("maintenanceTitle"), maintenanceMessage: text("maintenanceMessage"), - maintenanceEstimatedTime: text("maintenanceEstimatedTime") + maintenanceEstimatedTime: text("maintenanceEstimatedTime"), + postAuthPath: text("postAuthPath") }); export const targets = pgTable("targets", { diff --git a/server/db/sqlite/schema/schema.ts b/server/db/sqlite/schema/schema.ts index 4137db3c..42b2309b 100644 --- a/server/db/sqlite/schema/schema.ts +++ b/server/db/sqlite/schema/schema.ts @@ -162,7 +162,8 @@ export const resources = sqliteTable("resources", { }).default("forced"), // "forced" = always show, "automatic" = only when down maintenanceTitle: text("maintenanceTitle"), maintenanceMessage: text("maintenanceMessage"), - maintenanceEstimatedTime: text("maintenanceEstimatedTime") + maintenanceEstimatedTime: text("maintenanceEstimatedTime"), + postAuthPath: text("postAuthPath") }); export const targets = sqliteTable("targets", { diff --git a/server/lib/normalizePostAuthPath.ts b/server/lib/normalizePostAuthPath.ts new file mode 100644 index 00000000..7291f184 --- /dev/null +++ b/server/lib/normalizePostAuthPath.ts @@ -0,0 +1,18 @@ +/** + * Normalizes a post-authentication path for safe use when building redirect URLs. + * Returns a path that starts with / and does not allow open redirects (no //, no :). + */ +export function normalizePostAuthPath(path: string | null | undefined): string | null { + if (path == null || typeof path !== "string") { + return null; + } + const trimmed = path.trim(); + if (trimmed === "") { + return null; + } + // Reject protocol-relative (//) or scheme (:) to avoid open redirect + if (trimmed.includes("//") || trimmed.includes(":")) { + return null; + } + return trimmed.startsWith("/") ? trimmed : `/${trimmed}`; +} diff --git a/server/routers/resource/authWithAccessToken.ts b/server/routers/resource/authWithAccessToken.ts index 53f72cb2..a580ee40 100644 --- a/server/routers/resource/authWithAccessToken.ts +++ b/server/routers/resource/authWithAccessToken.ts @@ -14,6 +14,7 @@ import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToke import config from "@server/lib/config"; import stoi from "@server/lib/stoi"; import { logAccessAudit } from "#dynamic/lib/logAccessAudit"; +import { normalizePostAuthPath } from "@server/lib/normalizePostAuthPath"; const authWithAccessTokenBodySchema = z.strictObject({ accessToken: z.string(), @@ -164,10 +165,16 @@ export async function authWithAccessToken( requestIp: req.ip }); + let redirectUrl = `${resource.ssl ? "https" : "http"}://${resource.fullDomain}`; + const postAuthPath = normalizePostAuthPath(resource.postAuthPath); + if (postAuthPath) { + redirectUrl = redirectUrl + postAuthPath; + } + return response(res, { data: { session: token, - redirectUrl: `${resource.ssl ? "https" : "http"}://${resource.fullDomain}` + redirectUrl }, success: true, error: false, diff --git a/server/routers/resource/createResource.ts b/server/routers/resource/createResource.ts index ba1fdba2..232cea26 100644 --- a/server/routers/resource/createResource.ts +++ b/server/routers/resource/createResource.ts @@ -36,7 +36,8 @@ const createHttpResourceSchema = z http: z.boolean(), protocol: z.enum(["tcp", "udp"]), domainId: z.string(), - stickySession: z.boolean().optional() + stickySession: z.boolean().optional(), + postAuthPath: z.string().nullable().optional() }) .refine( (data) => { @@ -188,7 +189,7 @@ async function createHttpResource( ); } - const { name, domainId } = parsedBody.data; + const { name, domainId, postAuthPath } = parsedBody.data; const subdomain = parsedBody.data.subdomain; const stickySession = parsedBody.data.stickySession; @@ -255,7 +256,8 @@ async function createHttpResource( http: true, protocol: "tcp", ssl: true, - stickySession: stickySession + stickySession: stickySession, + postAuthPath: postAuthPath }) .returning(); diff --git a/server/routers/resource/getResourceAuthInfo.ts b/server/routers/resource/getResourceAuthInfo.ts index 7959bff5..7def75d5 100644 --- a/server/routers/resource/getResourceAuthInfo.ts +++ b/server/routers/resource/getResourceAuthInfo.ts @@ -35,6 +35,7 @@ export type GetResourceAuthInfoResponse = { whitelist: boolean; skipToIdpId: number | null; orgId: string; + postAuthPath: string | null; }; export async function getResourceAuthInfo( @@ -147,7 +148,8 @@ export async function getResourceAuthInfo( url, whitelist: resource.emailWhitelistEnabled, skipToIdpId: resource.skipToIdpId, - orgId: resource.orgId + orgId: resource.orgId, + postAuthPath: resource.postAuthPath ?? null }, success: true, error: false, diff --git a/server/routers/resource/updateResource.ts b/server/routers/resource/updateResource.ts index 79b59a2a..84b4f538 100644 --- a/server/routers/resource/updateResource.ts +++ b/server/routers/resource/updateResource.ts @@ -55,7 +55,8 @@ const updateHttpResourceBodySchema = z maintenanceModeType: z.enum(["forced", "automatic"]).optional(), maintenanceTitle: z.string().max(255).nullable().optional(), maintenanceMessage: z.string().max(2000).nullable().optional(), - maintenanceEstimatedTime: z.string().max(100).nullable().optional() + maintenanceEstimatedTime: z.string().max(100).nullable().optional(), + postAuthPath: z.string().nullable().optional() }) .refine((data) => Object.keys(data).length > 0, { error: "At least one field must be provided for update" diff --git a/src/app/auth/resource/[resourceGuid]/page.tsx b/src/app/auth/resource/[resourceGuid]/page.tsx index 5bb431a8..919dfbd8 100644 --- a/src/app/auth/resource/[resourceGuid]/page.tsx +++ b/src/app/auth/resource/[resourceGuid]/page.tsx @@ -26,6 +26,7 @@ import type { import { CheckOrgUserAccessResponse } from "@server/routers/org"; import OrgPolicyRequired from "@app/components/OrgPolicyRequired"; import { isOrgSubscribed } from "@app/lib/api/isOrgSubscribed"; +import { normalizePostAuthPath } from "@server/lib/normalizePostAuthPath"; export const dynamic = "force-dynamic"; @@ -108,6 +109,11 @@ export default async function ResourceAuthPage(props: { } catch (e) {} } + const normalizedPostAuthPath = normalizePostAuthPath(authInfo.postAuthPath); + if (normalizedPostAuthPath) { + redirectUrl = new URL(authInfo.url).origin + normalizedPostAuthPath; + } + const hasAuth = authInfo.password || authInfo.pincode || From a35586f762cc346d95134c5585d7979e02abd986 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 12 Feb 2026 14:47:32 -0800 Subject: [PATCH 08/43] Add sudo --- src/components/olm-install-commands.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/olm-install-commands.tsx b/src/components/olm-install-commands.tsx index b8ad8e7d..5418c8b9 100644 --- a/src/components/olm-install-commands.tsx +++ b/src/components/olm-install-commands.tsx @@ -55,7 +55,8 @@ export function OlmInstallCommands({ x64: [ { title: t("install"), - command: `curl -o olm.exe -L "https://github.com/fosrl/olm/releases/download/${version}/olm_windows_installer.exe"` + 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"), From e9d0ad6e371a5ecf8730d319ad6b1f950df744a5 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 12 Feb 2026 14:51:11 -0800 Subject: [PATCH 09/43] use pangolin cli for container --- messages/en-US.json | 2 +- src/components/newt-install-commands.tsx | 4 +-- src/components/olm-install-commands.tsx | 44 ++++++++++++------------ 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index aa9cb2b0..68f9640b 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Machine clients are for servers and automated systems that are not associated with a specific user. They authenticate with an ID and secret, and can run with Pangolin CLI, Olm CLI, or Olm as a container.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Olm Container", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "User", "clientsTableMachineClients": "Machine", "licenseTableValidUntil": "Valid Until", diff --git a/src/components/newt-install-commands.tsx b/src/components/newt-install-commands.tsx index b814fdae..3561cd6b 100644 --- a/src/components/newt-install-commands.tsx +++ b/src/components/newt-install-commands.tsx @@ -18,11 +18,11 @@ export type CommandItem = string | { title: string; command: string }; const PLATFORMS = [ "unix", - "windows", "docker", "kubernetes", "podman", - "nixos" + "nixos", + "windows" ] as const; type Platform = (typeof PLATFORMS)[number]; diff --git a/src/components/olm-install-commands.tsx b/src/components/olm-install-commands.tsx index 5418c8b9..9ff9970f 100644 --- a/src/components/olm-install-commands.tsx +++ b/src/components/olm-install-commands.tsx @@ -14,7 +14,7 @@ import { Button } from "./ui/button"; export type CommandItem = string | { title: string; command: string }; -const PLATFORMS = ["unix", "windows", "docker"] as const; +const PLATFORMS = ["unix", "docker", "windows"] as const; type Platform = (typeof PLATFORMS)[number]; @@ -51,6 +51,27 @@ export function OlmInstallCommands({ } ] }, + docker: { + "Docker Compose": [ + `services: + pangolin-cli: + image: fosrl/pangolin-cli + container_name: pangolin-cli + restart: unless-stopped + network_mode: host + cap_add: + - NET_ADMIN + devices: + - /dev/net/tun:/dev/net/tun + environment: + - PANGOLIN_ENDPOINT=${endpoint} + - CLIENT_ID=${id} + - CLIENT_SECRET=${secret}` + ], + "Docker Run": [ + `docker run -dit --network host --cap-add NET_ADMIN --device /dev/net/tun:/dev/net/tun fosrl/pangolin-cli up client --id ${id} --secret ${secret} --endpoint ${endpoint} --attach` + ] + }, windows: { x64: [ { @@ -63,27 +84,6 @@ export function OlmInstallCommands({ command: `olm.exe --id ${id} --secret ${secret} --endpoint ${endpoint}` } ] - }, - docker: { - "Docker Compose": [ - `services: - olm: - image: fosrl/olm - container_name: olm - restart: unless-stopped - network_mode: host - cap_add: - - NET_ADMIN - devices: - - /dev/net/tun:/dev/net/tun - environment: - - PANGOLIN_ENDPOINT=${endpoint} - - OLM_ID=${id} - - OLM_SECRET=${secret}` - ], - "Docker Run": [ - `docker run -dit --network host --cap-add NET_ADMIN --device /dev/net/tun:/dev/net/tun fosrl/olm --id ${id} --secret ${secret} --endpoint ${endpoint}` - ] } }; From c73d70933b408690b45fc8e9e0559483afa4492f Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 12 Feb 2026 14:52:29 -0800 Subject: [PATCH 10/43] bump version --- server/lib/consts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/lib/consts.ts b/server/lib/consts.ts index 018e017e..4f7e4d62 100644 --- a/server/lib/consts.ts +++ b/server/lib/consts.ts @@ -2,7 +2,7 @@ import path from "path"; import { fileURLToPath } from "url"; // This is a placeholder value replaced by the build process -export const APP_VERSION = "1.15.3"; +export const APP_VERSION = "1.15.4"; export const __FILENAME = fileURLToPath(import.meta.url); export const __DIRNAME = path.dirname(__FILENAME); From fdce016921b41b6355971b19133e9e1ac01df739 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 12 Feb 2026 15:00:12 -0800 Subject: [PATCH 11/43] add 1.15.4 migration --- server/setup/migrationsPg.ts | 4 +++- server/setup/migrationsSqlite.ts | 4 +++- server/setup/scriptsPg/1.15.4.ts | 30 ++++++++++++++++++++++++++++ server/setup/scriptsSqlite/1.15.4.ts | 27 +++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 server/setup/scriptsPg/1.15.4.ts create mode 100644 server/setup/scriptsSqlite/1.15.4.ts diff --git a/server/setup/migrationsPg.ts b/server/setup/migrationsPg.ts index c1ebfa09..fd28644c 100644 --- a/server/setup/migrationsPg.ts +++ b/server/setup/migrationsPg.ts @@ -18,6 +18,7 @@ import m10 from "./scriptsPg/1.13.0"; import m11 from "./scriptsPg/1.14.0"; import m12 from "./scriptsPg/1.15.0"; import m13 from "./scriptsPg/1.15.3"; +import m14 from "./scriptsPg/1.15.4"; // THIS CANNOT IMPORT ANYTHING FROM THE SERVER // EXCEPT FOR THE DATABASE AND THE SCHEMA @@ -36,7 +37,8 @@ const migrations = [ { version: "1.13.0", run: m10 }, { version: "1.14.0", run: m11 }, { version: "1.15.0", run: m12 }, - { version: "1.15.3", run: m13 } + { version: "1.15.3", run: m13 }, + { version: "1.15.4", run: m14 } // Add new migrations here as they are created ] as { version: string; diff --git a/server/setup/migrationsSqlite.ts b/server/setup/migrationsSqlite.ts index 170cf93d..39c133bf 100644 --- a/server/setup/migrationsSqlite.ts +++ b/server/setup/migrationsSqlite.ts @@ -36,6 +36,7 @@ import m31 from "./scriptsSqlite/1.13.0"; import m32 from "./scriptsSqlite/1.14.0"; import m33 from "./scriptsSqlite/1.15.0"; import m34 from "./scriptsSqlite/1.15.3"; +import m35 from "./scriptsSqlite/1.15.4"; // THIS CANNOT IMPORT ANYTHING FROM THE SERVER // EXCEPT FOR THE DATABASE AND THE SCHEMA @@ -70,7 +71,8 @@ const migrations = [ { version: "1.13.0", run: m31 }, { version: "1.14.0", run: m32 }, { version: "1.15.0", run: m33 }, - { version: "1.15.3", run: m34 } + { version: "1.15.3", run: m34 }, + { version: "1.15.4", run: m35 } // Add new migrations here as they are created ] as const; diff --git a/server/setup/scriptsPg/1.15.4.ts b/server/setup/scriptsPg/1.15.4.ts new file mode 100644 index 00000000..8c6cfe89 --- /dev/null +++ b/server/setup/scriptsPg/1.15.4.ts @@ -0,0 +1,30 @@ +import { db } from "@server/db/pg/driver"; +import { sql } from "drizzle-orm"; +import { __DIRNAME } from "@server/lib/consts"; + +const version = "1.15.4"; + +export default async function migration() { + console.log(`Running setup script ${version}...`); + + try { + await db.execute(sql`BEGIN`); + + await db.execute( + sql`ALTER TABLE "subscriptions" ADD COLUMN "type" varchar(50);` + ); + await db.execute( + sql`ALTER TABLE "resources" ADD COLUMN "postAuthPath" text;` + ); + + await db.execute(sql`COMMIT`); + console.log("Migrated database"); + } catch (e) { + await db.execute(sql`ROLLBACK`); + console.log("Unable to migrate database"); + console.log(e); + throw e; + } + + console.log(`${version} migration complete`); +} diff --git a/server/setup/scriptsSqlite/1.15.4.ts b/server/setup/scriptsSqlite/1.15.4.ts new file mode 100644 index 00000000..35a51024 --- /dev/null +++ b/server/setup/scriptsSqlite/1.15.4.ts @@ -0,0 +1,27 @@ +import { __DIRNAME, APP_PATH } from "@server/lib/consts"; +import Database from "better-sqlite3"; +import path from "path"; + +const version = "1.15.4"; + +export default async function migration() { + console.log(`Running setup script ${version}...`); + + const location = path.join(APP_PATH, "db", "db.sqlite"); + const db = new Database(location); + + try { + db.transaction(() => { + db.prepare( + `ALTER TABLE 'resources' ADD 'postAuthPath' text;` + ).run(); + })(); + + console.log(`Migrated database`); + } catch (e) { + console.log("Failed to migrate db:", e); + throw e; + } + + console.log(`${version} migration complete`); +} From d998a8087fc082eaadb7ba9b95cb8f089eb8ce5e Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 12 Feb 2026 15:06:24 -0800 Subject: [PATCH 12/43] fix pg migration --- server/setup/scriptsPg/1.15.4.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/server/setup/scriptsPg/1.15.4.ts b/server/setup/scriptsPg/1.15.4.ts index 8c6cfe89..cec04a1a 100644 --- a/server/setup/scriptsPg/1.15.4.ts +++ b/server/setup/scriptsPg/1.15.4.ts @@ -10,9 +10,6 @@ export default async function migration() { try { await db.execute(sql`BEGIN`); - await db.execute( - sql`ALTER TABLE "subscriptions" ADD COLUMN "type" varchar(50);` - ); await db.execute( sql`ALTER TABLE "resources" ADD COLUMN "postAuthPath" text;` ); From 2767ee9e803d2e7d8d60879792a9c54a73d8a438 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 12 Feb 2026 15:29:08 -0800 Subject: [PATCH 13/43] update pangolin cli links --- src/components/MachineClientsBanner.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MachineClientsBanner.tsx b/src/components/MachineClientsBanner.tsx index 151397c2..cd32e91f 100644 --- a/src/components/MachineClientsBanner.tsx +++ b/src/components/MachineClientsBanner.tsx @@ -37,7 +37,7 @@ export const MachineClientsBanner = ({ orgId }: MachineClientsBannerProps) => { From c2e95a0607cbc7a22c15ea8b4501ca967472850e Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:06 -0800 Subject: [PATCH 14/43] New translations en-us.json (French) --- messages/fr-FR.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/fr-FR.json b/messages/fr-FR.json index 9b2e24d2..3ca7df2c 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Les clients de machine sont conçus pour les serveurs et les systèmes automatisés qui ne sont pas associés à un utilisateur spécifique. Ils s'authentifient avec un identifiant et une clé secrète, et peuvent être exécutés avec Pangolin CLI, Olm CLI ou Olm en tant que conteneur.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Conteneur Olm", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "Utilisateur", "clientsTableMachineClients": "Machine", "licenseTableValidUntil": "Valable jusqu'au", From ca9c7ce555e5ba231dcad4e9cd235686404cb687 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:08 -0800 Subject: [PATCH 15/43] New translations en-us.json (Spanish) --- messages/es-ES.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/es-ES.json b/messages/es-ES.json index 28c0e779..13401b40 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Los clientes de máquinas son para servidores y sistemas automatizados que no están asociados con un usuario específico. Se autentican con una ID y un secreto, y pueden ejecutarse con Pangolin CLI, Olm CLI o Olm como un contenedor.", "machineClientsBannerPangolinCLI": "CLI de Pangolin", "machineClientsBannerOlmCLI": "CLI de Olm", - "machineClientsBannerOlmContainer": "Contenedor de Olm", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "Usuario", "clientsTableMachineClients": "Maquina", "licenseTableValidUntil": "Válido hasta", From 3c2f930e6b7663739aa769a027b804b18663db81 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:10 -0800 Subject: [PATCH 16/43] New translations en-us.json (Bulgarian) --- messages/bg-BG.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/bg-BG.json b/messages/bg-BG.json index 3f48085a..eb45f4da 100644 --- a/messages/bg-BG.json +++ b/messages/bg-BG.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Машинните клиенти са за сървъри и автоматизирани системи, които не са свързани с конкретен потребител. Те се автентифицират с ID и секретен ключ и могат да работят с Pangolin CLI, Olm CLI или Olm като контейнер.", "machineClientsBannerPangolinCLI": "Pangolin CLI.", "machineClientsBannerOlmCLI": "Olm CLI.", - "machineClientsBannerOlmContainer": "Olm Контейнер.", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "Потребител", "clientsTableMachineClients": "Машина", "licenseTableValidUntil": "Валиден до", From b14b68d83c8a1d9fc39f224a2a8b32a20a287d93 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:11 -0800 Subject: [PATCH 17/43] New translations en-us.json (Czech) --- messages/cs-CZ.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index c35488cd..3b2a2521 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Klientské stroje jsou určeny pro servery a automatizované systémy, které nejsou přiřazeny k žádnému specifickému uživateli. Autentizují se pomocí ID a tajemství, a mohou běžet s Pangolin CLI, Olm CLI nebo Olm jako kontejner.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Olm kontejner", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "Uživatel", "clientsTableMachineClients": "Stroj", "licenseTableValidUntil": "Platná do", From cdb6813384f21021954ead13cc737f4102152769 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:13 -0800 Subject: [PATCH 18/43] New translations en-us.json (German) --- messages/de-DE.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/de-DE.json b/messages/de-DE.json index ee3d65ea..4dc8610b 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Maschinelle Clients sind für Server und automatisierte Systeme, die nicht einem bestimmten Benutzer zugeordnet sind. Sie authentifizieren sich mit einer ID und einem Geheimnis und können mit Pangolin CLI, Olm CLI oder Olm als Container laufen.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Olm Container", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "Benutzer", "clientsTableMachineClients": "Maschine", "licenseTableValidUntil": "Gültig bis", From c4b1831cfee393690ab37621868e63f45f816376 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:14 -0800 Subject: [PATCH 19/43] New translations en-us.json (Italian) --- messages/it-IT.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/it-IT.json b/messages/it-IT.json index 30443e98..1fd7a22b 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "I client macchina sono destinati ai server e ai sistemi automatizzati che non sono associati a un utente specifico. Si autenticano con un ID e un segreto e possono funzionare con la CLI di Pangolin, la CLI di Olm o Olm come container.", "machineClientsBannerPangolinCLI": "CLI Pangolin", "machineClientsBannerOlmCLI": "CLI Olm", - "machineClientsBannerOlmContainer": "Container Olm", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "Utente", "clientsTableMachineClients": "Macchina", "licenseTableValidUntil": "Valido Fino A", From 22c3b8f116a470d5e8aec857ddc8d469d38c4fa1 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:15 -0800 Subject: [PATCH 20/43] New translations en-us.json (Korean) --- messages/ko-KR.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/ko-KR.json b/messages/ko-KR.json index 13f0a22a..ddaff7f1 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "머신 클라이언트는 특정 사용자와 연결되지 않은 서버 및 자동화된 시스템을 위한 것입니다. 이들은 ID와 비밀을 통해 인증하며, Pangolin CLI, Olm CLI, 또는 Olm 컨테이너로 실행될 수 있습니다.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Olm 컨테이너", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "사용자", "clientsTableMachineClients": "기계", "licenseTableValidUntil": "유효 기한", From a964a80d85f5532cf43121650c18ed12881cdd80 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:17 -0800 Subject: [PATCH 21/43] New translations en-us.json (Dutch) --- messages/nl-NL.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/nl-NL.json b/messages/nl-NL.json index 78783d92..64bfe66d 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Machineclients zijn bedoeld voor servers en geautomatiseerde systemen die niet aan een specifieke gebruiker zijn gekoppeld. Ze verifiëren met een ID en geheim, en kunnen draaien met Pangolin CLI, Olm CLI, of Olm als een container.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Olm-container", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "Gebruiker", "clientsTableMachineClients": "Machine", "licenseTableValidUntil": "Geldig tot", From 975550c755ca7166a567b65c491f7ab1ef616232 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:18 -0800 Subject: [PATCH 22/43] New translations en-us.json (Polish) --- messages/pl-PL.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/pl-PL.json b/messages/pl-PL.json index b3d3cd94..62f1adf6 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Klienci maszyn służą dla serwerów i systemów zautomatyzowanych, które nie są powiązane z konkretnym użytkownikiem. Uwierzytelniają się za pomocą identyfikatora i sekretu i mogą działać z Pangolin CLI, Olm CLI lub Olm jako kontener.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Kontener Olm", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "Użytkownik", "clientsTableMachineClients": "Maszyna", "licenseTableValidUntil": "Ważny do", From 04dcf57ff32e4f9bb844fc0a054aaad3d45d158d Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:20 -0800 Subject: [PATCH 23/43] New translations en-us.json (Portuguese) --- messages/pt-PT.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/pt-PT.json b/messages/pt-PT.json index e7985138..f1606de9 100644 --- a/messages/pt-PT.json +++ b/messages/pt-PT.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Clientes de máquina são para servidores e sistemas automatizados que não estão associados a um usuário específico. Eles autenticam com um ID e segredo, e podem ser executados com CLI Pangolin, CLI Olm, ou Olm como um contêiner.", "machineClientsBannerPangolinCLI": "CLI de Pangolin", "machineClientsBannerOlmCLI": "CLI Olm", - "machineClientsBannerOlmContainer": "Contêiner Olm", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "Utilizador", "clientsTableMachineClients": "Máquina", "licenseTableValidUntil": "Válido até", From 4d142b93ddac3ec8c49937f94297a081bb05bc4b Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:21 -0800 Subject: [PATCH 24/43] New translations en-us.json (Russian) --- messages/ru-RU.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/ru-RU.json b/messages/ru-RU.json index 4891e0a9..34b57305 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Клиенты для машин предназначены для серверов и автоматизированных систем, которые не связаны с конкретным пользователем. Они аутентифицируются по ID и секрету и могут работать с Pangolin CLI, Olm CLI или Olm как с контейнером.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Olm как контейнер", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "Пользователь", "clientsTableMachineClients": "Машина", "licenseTableValidUntil": "Действителен до", From a91c002274c622278d7e58a72be9808166dbbea5 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:22 -0800 Subject: [PATCH 25/43] New translations en-us.json (Turkish) --- messages/tr-TR.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/tr-TR.json b/messages/tr-TR.json index 0c4c921d..4a4ea855 100644 --- a/messages/tr-TR.json +++ b/messages/tr-TR.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Makine müşterileri, belirli bir kullanıcı ile ilişkilendirilmemiş sunucular ve otomatik sistemler içindir. Kimlik ve şifreyle doğrulama yaparlar ve Pangolin CLI, Olm CLI veya Olm'yi bir konteyner olarak çalıştırabilirler.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Olm Konteyner", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "Kullanıcı", "clientsTableMachineClients": "Makine", "licenseTableValidUntil": "Geçerli İki Tarih Kadar", From d5820c49023d1fe8b883ace9ab0c681b6ff2b03e Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:24 -0800 Subject: [PATCH 26/43] New translations en-us.json (Chinese Simplified) --- messages/zh-CN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/zh-CN.json b/messages/zh-CN.json index 7312ba32..7b90278b 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "机器客户端适用于不与特定用户关联的服务器与自动化系统。它们使用ID和密钥进行身份验证,并可以与Pangolin CLI、Olm CLI或作为容器运行。", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Olm 容器", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "用户", "clientsTableMachineClients": "机", "licenseTableValidUntil": "有效期至", From 899e5aa3955f9ba06d188e34d4c4c3236f24262e Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:25 -0800 Subject: [PATCH 27/43] New translations en-us.json (Norwegian Bokmal) --- messages/nb-NO.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/nb-NO.json b/messages/nb-NO.json index afb9af38..3bc7ae06 100644 --- a/messages/nb-NO.json +++ b/messages/nb-NO.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Maskinklienter er for servere og automatiserte systemer som ikke er tilknyttet en spesifikk bruker. De autentiserer med en ID og et hemmelighetsnummer, og kan kjøre med Pangolin CLI, Olm CLI eller Olm som en container.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Olm Container", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "Bruker", "clientsTableMachineClients": "Maskin", "licenseTableValidUntil": "Gyldig til", From 2800655e331c563017e962e9129764345c7e918f Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:13:45 -0800 Subject: [PATCH 28/43] New translations en-us.json (French) --- messages/fr-FR.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/fr-FR.json b/messages/fr-FR.json index 3ca7df2c..9b2e24d2 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Les clients de machine sont conçus pour les serveurs et les systèmes automatisés qui ne sont pas associés à un utilisateur spécifique. Ils s'authentifient avec un identifiant et une clé secrète, et peuvent être exécutés avec Pangolin CLI, Olm CLI ou Olm en tant que conteneur.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Conteneur Olm", "clientsTableUserClients": "Utilisateur", "clientsTableMachineClients": "Machine", "licenseTableValidUntil": "Valable jusqu'au", From 9d4ace9b3e926d7f6aa8a82510cf9e233e2da02a Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:13:47 -0800 Subject: [PATCH 29/43] New translations en-us.json (Spanish) --- messages/es-ES.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/es-ES.json b/messages/es-ES.json index 13401b40..28c0e779 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Los clientes de máquinas son para servidores y sistemas automatizados que no están asociados con un usuario específico. Se autentican con una ID y un secreto, y pueden ejecutarse con Pangolin CLI, Olm CLI o Olm como un contenedor.", "machineClientsBannerPangolinCLI": "CLI de Pangolin", "machineClientsBannerOlmCLI": "CLI de Olm", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Contenedor de Olm", "clientsTableUserClients": "Usuario", "clientsTableMachineClients": "Maquina", "licenseTableValidUntil": "Válido hasta", From 8d6700d49368d96cbb5ba0eecda37615919fe5ba Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:13:49 -0800 Subject: [PATCH 30/43] New translations en-us.json (Bulgarian) --- messages/bg-BG.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/bg-BG.json b/messages/bg-BG.json index eb45f4da..3f48085a 100644 --- a/messages/bg-BG.json +++ b/messages/bg-BG.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Машинните клиенти са за сървъри и автоматизирани системи, които не са свързани с конкретен потребител. Те се автентифицират с ID и секретен ключ и могат да работят с Pangolin CLI, Olm CLI или Olm като контейнер.", "machineClientsBannerPangolinCLI": "Pangolin CLI.", "machineClientsBannerOlmCLI": "Olm CLI.", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Olm Контейнер.", "clientsTableUserClients": "Потребител", "clientsTableMachineClients": "Машина", "licenseTableValidUntil": "Валиден до", From 516fd0ee8fdd56dc7bba4539ae611a4929d2efca Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:13:50 -0800 Subject: [PATCH 31/43] New translations en-us.json (Czech) --- messages/cs-CZ.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index 3b2a2521..c35488cd 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Klientské stroje jsou určeny pro servery a automatizované systémy, které nejsou přiřazeny k žádnému specifickému uživateli. Autentizují se pomocí ID a tajemství, a mohou běžet s Pangolin CLI, Olm CLI nebo Olm jako kontejner.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Olm kontejner", "clientsTableUserClients": "Uživatel", "clientsTableMachineClients": "Stroj", "licenseTableValidUntil": "Platná do", From f3f8bd3125bd5ffe5f3d1c78b8beea5092693680 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:13:51 -0800 Subject: [PATCH 32/43] New translations en-us.json (German) --- messages/de-DE.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/de-DE.json b/messages/de-DE.json index 4dc8610b..ee3d65ea 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Maschinelle Clients sind für Server und automatisierte Systeme, die nicht einem bestimmten Benutzer zugeordnet sind. Sie authentifizieren sich mit einer ID und einem Geheimnis und können mit Pangolin CLI, Olm CLI oder Olm als Container laufen.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Olm Container", "clientsTableUserClients": "Benutzer", "clientsTableMachineClients": "Maschine", "licenseTableValidUntil": "Gültig bis", From db7971d2f7a4b932b0040c912a5016c703e5dd13 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:13:53 -0800 Subject: [PATCH 33/43] New translations en-us.json (Italian) --- messages/it-IT.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/it-IT.json b/messages/it-IT.json index 1fd7a22b..30443e98 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "I client macchina sono destinati ai server e ai sistemi automatizzati che non sono associati a un utente specifico. Si autenticano con un ID e un segreto e possono funzionare con la CLI di Pangolin, la CLI di Olm o Olm come container.", "machineClientsBannerPangolinCLI": "CLI Pangolin", "machineClientsBannerOlmCLI": "CLI Olm", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Container Olm", "clientsTableUserClients": "Utente", "clientsTableMachineClients": "Macchina", "licenseTableValidUntil": "Valido Fino A", From 16ad60b89ad8b6ba699882105db2cfd2456b9061 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:13:54 -0800 Subject: [PATCH 34/43] New translations en-us.json (Korean) --- messages/ko-KR.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/ko-KR.json b/messages/ko-KR.json index ddaff7f1..13f0a22a 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "머신 클라이언트는 특정 사용자와 연결되지 않은 서버 및 자동화된 시스템을 위한 것입니다. 이들은 ID와 비밀을 통해 인증하며, Pangolin CLI, Olm CLI, 또는 Olm 컨테이너로 실행될 수 있습니다.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Olm 컨테이너", "clientsTableUserClients": "사용자", "clientsTableMachineClients": "기계", "licenseTableValidUntil": "유효 기한", From b7616026dded36d30ba8dc49db6986a059361840 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:13:55 -0800 Subject: [PATCH 35/43] New translations en-us.json (Dutch) --- messages/nl-NL.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/nl-NL.json b/messages/nl-NL.json index 64bfe66d..78783d92 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Machineclients zijn bedoeld voor servers en geautomatiseerde systemen die niet aan een specifieke gebruiker zijn gekoppeld. Ze verifiëren met een ID en geheim, en kunnen draaien met Pangolin CLI, Olm CLI, of Olm als een container.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Olm-container", "clientsTableUserClients": "Gebruiker", "clientsTableMachineClients": "Machine", "licenseTableValidUntil": "Geldig tot", From 3b0fd5c592769b8304b397631dd45f322b015dd6 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:13:57 -0800 Subject: [PATCH 36/43] New translations en-us.json (Polish) --- messages/pl-PL.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/pl-PL.json b/messages/pl-PL.json index 62f1adf6..b3d3cd94 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Klienci maszyn służą dla serwerów i systemów zautomatyzowanych, które nie są powiązane z konkretnym użytkownikiem. Uwierzytelniają się za pomocą identyfikatora i sekretu i mogą działać z Pangolin CLI, Olm CLI lub Olm jako kontener.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Kontener Olm", "clientsTableUserClients": "Użytkownik", "clientsTableMachineClients": "Maszyna", "licenseTableValidUntil": "Ważny do", From 62ea1b40e1e28c3832f38a97c3bcc433c33fb15a Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:13:58 -0800 Subject: [PATCH 37/43] New translations en-us.json (Portuguese) --- messages/pt-PT.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/pt-PT.json b/messages/pt-PT.json index f1606de9..e7985138 100644 --- a/messages/pt-PT.json +++ b/messages/pt-PT.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Clientes de máquina são para servidores e sistemas automatizados que não estão associados a um usuário específico. Eles autenticam com um ID e segredo, e podem ser executados com CLI Pangolin, CLI Olm, ou Olm como um contêiner.", "machineClientsBannerPangolinCLI": "CLI de Pangolin", "machineClientsBannerOlmCLI": "CLI Olm", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Contêiner Olm", "clientsTableUserClients": "Utilizador", "clientsTableMachineClients": "Máquina", "licenseTableValidUntil": "Válido até", From 970ecb52f0c032f1a5e93193950dff437ec70019 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:14:00 -0800 Subject: [PATCH 38/43] New translations en-us.json (Russian) --- messages/ru-RU.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/ru-RU.json b/messages/ru-RU.json index 34b57305..4891e0a9 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Клиенты для машин предназначены для серверов и автоматизированных систем, которые не связаны с конкретным пользователем. Они аутентифицируются по ID и секрету и могут работать с Pangolin CLI, Olm CLI или Olm как с контейнером.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Olm как контейнер", "clientsTableUserClients": "Пользователь", "clientsTableMachineClients": "Машина", "licenseTableValidUntil": "Действителен до", From dd5e834db00503d8e9810ad7845ee6206b43c45f Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:14:01 -0800 Subject: [PATCH 39/43] New translations en-us.json (Turkish) --- messages/tr-TR.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/tr-TR.json b/messages/tr-TR.json index 4a4ea855..0c4c921d 100644 --- a/messages/tr-TR.json +++ b/messages/tr-TR.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Makine müşterileri, belirli bir kullanıcı ile ilişkilendirilmemiş sunucular ve otomatik sistemler içindir. Kimlik ve şifreyle doğrulama yaparlar ve Pangolin CLI, Olm CLI veya Olm'yi bir konteyner olarak çalıştırabilirler.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Olm Konteyner", "clientsTableUserClients": "Kullanıcı", "clientsTableMachineClients": "Makine", "licenseTableValidUntil": "Geçerli İki Tarih Kadar", From 71f63d8e6fea6fce27e4e4a17434d18535dbf901 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:14:03 -0800 Subject: [PATCH 40/43] New translations en-us.json (Chinese Simplified) --- messages/zh-CN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/zh-CN.json b/messages/zh-CN.json index 7b90278b..7312ba32 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "机器客户端适用于不与特定用户关联的服务器与自动化系统。它们使用ID和密钥进行身份验证,并可以与Pangolin CLI、Olm CLI或作为容器运行。", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Olm 容器", "clientsTableUserClients": "用户", "clientsTableMachineClients": "机", "licenseTableValidUntil": "有效期至", From 8250946325b9c9bf2eda6e4090500ec401d1aa07 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:14:04 -0800 Subject: [PATCH 41/43] New translations en-us.json (Norwegian Bokmal) --- messages/nb-NO.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/nb-NO.json b/messages/nb-NO.json index 3bc7ae06..afb9af38 100644 --- a/messages/nb-NO.json +++ b/messages/nb-NO.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Maskinklienter er for servere og automatiserte systemer som ikke er tilknyttet en spesifikk bruker. De autentiserer med en ID og et hemmelighetsnummer, og kan kjøre med Pangolin CLI, Olm CLI eller Olm som en container.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Olm Container", "clientsTableUserClients": "Bruker", "clientsTableMachineClients": "Maskin", "licenseTableValidUntil": "Gyldig til", From dbfd7153814c6f30cb4f2c507f1f5b7a13e0b692 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 12 Feb 2026 16:27:51 -0800 Subject: [PATCH 42/43] Fix windows formatting --- src/components/olm-install-commands.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/olm-install-commands.tsx b/src/components/olm-install-commands.tsx index 9ff9970f..c613d698 100644 --- a/src/components/olm-install-commands.tsx +++ b/src/components/olm-install-commands.tsx @@ -77,7 +77,7 @@ export function OlmInstallCommands({ { title: t("install"), 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"` +curl -o olm.exe -L "https://github.com/fosrl/olm/releases/download/${version}/olm_windows_installer.exe"` }, { title: t("run"), From 333625f199cf6665445dd922b36554466717a7f3 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 12 Feb 2026 20:24:10 -0800 Subject: [PATCH 43/43] rename starter in cloud to basic --- server/lib/billing/limitSet.ts | 8 ++--- .../settings/(private)/billing/page.tsx | 34 +++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/server/lib/billing/limitSet.ts b/server/lib/billing/limitSet.ts index b47b5681..53c88dc6 100644 --- a/server/lib/billing/limitSet.ts +++ b/server/lib/billing/limitSet.ts @@ -15,10 +15,10 @@ export const sandboxLimitSet: LimitSet = { }; export const freeLimitSet: LimitSet = { - [FeatureId.USERS]: { value: 5, description: "Starter limit" }, - [FeatureId.SITES]: { value: 5, description: "Starter limit" }, - [FeatureId.DOMAINS]: { value: 5, description: "Starter limit" }, - [FeatureId.REMOTE_EXIT_NODES]: { value: 1, description: "Starter limit" }, + [FeatureId.SITES]: { value: 5, description: "Basic limit" }, + [FeatureId.USERS]: { value: 5, description: "Basic limit" }, + [FeatureId.DOMAINS]: { value: 5, description: "Basic limit" }, + [FeatureId.REMOTE_EXIT_NODES]: { value: 1, description: "Basic limit" }, }; export const tier1LimitSet: LimitSet = { diff --git a/src/app/[orgId]/settings/(private)/billing/page.tsx b/src/app/[orgId]/settings/(private)/billing/page.tsx index f64c9557..b108d461 100644 --- a/src/app/[orgId]/settings/(private)/billing/page.tsx +++ b/src/app/[orgId]/settings/(private)/billing/page.tsx @@ -61,7 +61,7 @@ import { import { FeatureId } from "@server/lib/billing/features"; // Plan tier definitions matching the mockup -type PlanId = "starter" | "home" | "team" | "business" | "enterprise"; +type PlanId = "basic" | "home" | "team" | "business" | "enterprise"; type PlanOption = { id: PlanId; @@ -73,8 +73,8 @@ type PlanOption = { const planOptions: PlanOption[] = [ { - id: "starter", - name: "Starter", + id: "basic", + name: "Basic", price: "Free", tierType: null }, @@ -109,10 +109,10 @@ const planOptions: PlanOption[] = [ // Tier limits mapping derived from limit sets const tierLimits: Record< - Tier | "starter", + Tier | "basic", { users: number; sites: number; domains: number; remoteNodes: number } > = { - starter: { + basic: { users: freeLimitSet[FeatureId.USERS]?.value ?? 0, sites: freeLimitSet[FeatureId.SITES]?.value ?? 0, domains: freeLimitSet[FeatureId.DOMAINS]?.value ?? 0, @@ -183,7 +183,7 @@ export default function BillingPage() { // Confirmation dialog state const [showConfirmDialog, setShowConfirmDialog] = useState(false); const [pendingTier, setPendingTier] = useState<{ - tier: Tier | "starter"; + tier: Tier | "basic"; action: "upgrade" | "downgrade"; planName: string; price: string; @@ -402,8 +402,8 @@ export default function BillingPage() { pendingTier.action === "upgrade" || pendingTier.action === "downgrade" ) { - // If downgrading to starter (free tier), go to Stripe portal - if (pendingTier.tier === "starter") { + // If downgrading to basic (free tier), go to Stripe portal + if (pendingTier.tier === "basic") { handleModifySubscription(); } else if (hasSubscription) { handleChangeTier(pendingTier.tier); @@ -417,7 +417,7 @@ export default function BillingPage() { }; const showTierConfirmation = ( - tier: Tier | "starter", + tier: Tier | "basic", action: "upgrade" | "downgrade", planName: string, price: string @@ -432,9 +432,9 @@ export default function BillingPage() { // Get current plan ID from tier const getCurrentPlanId = (): PlanId => { - if (!hasSubscription || !currentTier) return "starter"; + if (!hasSubscription || !currentTier) return "basic"; const plan = planOptions.find((p) => p.tierType === currentTier); - return plan?.id || "starter"; + return plan?.id || "basic"; }; const currentPlanId = getCurrentPlanId(); @@ -451,8 +451,8 @@ export default function BillingPage() { } if (plan.id === currentPlanId) { - // If it's the starter plan (starter with no subscription), show as current but disabled - if (plan.id === "starter" && !hasSubscription) { + // If it's the basic plan (basic with no subscription), show as current but disabled + if (plan.id === "basic" && !hasSubscription) { return { label: "Current Plan", action: () => {}, @@ -484,10 +484,10 @@ export default function BillingPage() { plan.name, plan.price + (" " + plan.priceDetail || "") ); - } else if (plan.id === "starter") { - // Show confirmation for downgrading to starter (free tier) + } else if (plan.id === "basic") { + // Show confirmation for downgrading to basic (free tier) showTierConfirmation( - "starter", + "basic", "downgrade", plan.name, plan.price @@ -566,7 +566,7 @@ export default function BillingPage() { }; // Check if downgrading to a tier would violate current usage limits - const checkLimitViolations = (targetTier: Tier | "starter"): Array<{ + const checkLimitViolations = (targetTier: Tier | "basic"): Array<{ feature: string; currentUsage: number; newLimit: number;