diff --git a/server/lib/config.ts b/server/lib/config.ts index e35c0e8d..7d8d9c8b 100644 --- a/server/lib/config.ts +++ b/server/lib/config.ts @@ -38,14 +38,45 @@ const configSchema = z.object({ save_logs: z.boolean(), log_failed_attempts: z.boolean().optional() }), - domains: z.record( - z.string(), - z.object({ - base_domain: hostnameSchema.transform((url) => url.toLowerCase()), - cert_resolver: z.string().optional(), - prefer_wildcard_cert: z.boolean().optional() - }) - ), + domains: z + .record( + z.string(), + z.object({ + base_domain: hostnameSchema.transform((url) => + url.toLowerCase() + ), + cert_resolver: z.string().optional(), + prefer_wildcard_cert: z.boolean().optional() + }) + ) + .refine( + (domains) => { + const keys = Object.keys(domains); + + if (keys.length === 0) { + return false; + } + + return true; + }, + { + message: "At least one domain must be defined" + } + ) + .refine( + (domains) => { + const envBaseDomain = process.env.APP_BASE_DOMAIN; + + if (envBaseDomain) { + return hostnameSchema.safeParse(envBaseDomain).success; + } + + return true; + }, + { + message: "APP_BASE_DOMAIN must be a valid hostname" + } + ), server: z.object({ external_port: portSchema .optional() @@ -169,8 +200,6 @@ export class Config { } } - public loadEnvironment() {} - public loadConfig() { const loadConfig = (configPath: string) => { try { @@ -277,6 +306,17 @@ export class Config { : "false"; process.env.DASHBOARD_URL = parsedConfig.data.app.dashboard_url; + if (process.env.APP_BASE_DOMAIN) { + console.log( + `DEPRECATED! APP_BASE_DOMAIN is deprecated and will be removed in a future release. Use the domains section in the configuration file instead. See https://docs.fossorial.io/Pangolin/Configuration/config for more information.` + ); + + parsedConfig.data.domains.domain1 = { + base_domain: process.env.APP_BASE_DOMAIN, + cert_resolver: "letsencrypt" + }; + } + this.rawConfig = parsedConfig.data; } diff --git a/server/lib/consts.ts b/server/lib/consts.ts index 20376f8e..3855ce72 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.0.0-beta.13"; +export const APP_VERSION = "1.0.0-beta.15"; export const __FILENAME = fileURLToPath(import.meta.url); export const __DIRNAME = path.dirname(__FILENAME); diff --git a/server/setup/copyInConfig.ts b/server/setup/copyInConfig.ts index d1860677..ae77152c 100644 --- a/server/setup/copyInConfig.ts +++ b/server/setup/copyInConfig.ts @@ -51,6 +51,32 @@ export async function copyInConfig() { } } + const allOrgs = await trx.select().from(orgs); + + const existingOrgDomains = await trx.select().from(orgDomains); + const existingOrgDomainSet = new Set( + existingOrgDomains.map((od) => `${od.orgId}-${od.domainId}`) + ); + + const newOrgDomains = []; + for (const org of allOrgs) { + for (const domain of configDomains) { + const key = `${org.orgId}-${domain.domainId}`; + if (!existingOrgDomainSet.has(key)) { + newOrgDomains.push({ + orgId: org.orgId, + domainId: domain.domainId + }); + } + } + } + + if (newOrgDomains.length > 0) { + await trx.insert(orgDomains).values(newOrgDomains).execute(); + } + }); + + await db.transaction(async (trx) => { const allResources = await trx .select() .from(resources) @@ -77,30 +103,6 @@ export async function copyInConfig() { .set({ fullDomain }) .where(eq(resources.resourceId, resource.resourceId)); } - - const allOrgs = await trx.select().from(orgs); - - const existingOrgDomains = await trx.select().from(orgDomains); - const existingOrgDomainSet = new Set( - existingOrgDomains.map((od) => `${od.orgId}-${od.domainId}`) - ); - - const newOrgDomains = []; - for (const org of allOrgs) { - for (const domain of configDomains) { - const key = `${org.orgId}-${domain.domainId}`; - if (!existingOrgDomainSet.has(key)) { - newOrgDomains.push({ - orgId: org.orgId, - domainId: domain.domainId - }); - } - } - } - - if (newOrgDomains.length > 0) { - await trx.insert(orgDomains).values(newOrgDomains).execute(); - } }); // TODO: eventually each exit node could have a different endpoint diff --git a/server/setup/migrations.ts b/server/setup/migrations.ts index 52a82ad4..99451797 100644 --- a/server/setup/migrations.ts +++ b/server/setup/migrations.ts @@ -15,6 +15,7 @@ import m6 from "./scripts/1.0.0-beta9"; import m7 from "./scripts/1.0.0-beta10"; import m8 from "./scripts/1.0.0-beta12"; import m13 from "./scripts/1.0.0-beta13"; +import m15 from "./scripts/1.0.0-beta15"; // THIS CANNOT IMPORT ANYTHING FROM THE SERVER // EXCEPT FOR THE DATABASE AND THE SCHEMA @@ -29,7 +30,8 @@ const migrations = [ { 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.13", run: m13 }, + { version: "1.0.0-beta.15", run: m15 } // Add new migrations here as they are created ] as const; diff --git a/server/setup/scripts/1.0.0-beta15.ts b/server/setup/scripts/1.0.0-beta15.ts index 35f5b425..aa2044a4 100644 --- a/server/setup/scripts/1.0.0-beta15.ts +++ b/server/setup/scripts/1.0.0-beta15.ts @@ -3,38 +3,14 @@ import { configFilePath1, configFilePath2 } from "@server/lib/consts"; import fs from "fs"; import yaml from "js-yaml"; import { sql } from "drizzle-orm"; +import { domains, orgDomains, resources } from "@server/db/schema"; const version = "1.0.0-beta.15"; export default async function migration() { console.log(`Running setup script ${version}...`); - try { - db.transaction((trx) => { - trx.run(sql`CREATE TABLE 'domains' ( - 'domainId' text PRIMARY KEY NOT NULL, - 'baseDomain' text NOT NULL, - 'configManaged' integer DEFAULT false NOT NULL -);`); - - trx.run(sql`CREATE TABLE 'orgDomains' ( - 'orgId' text NOT NULL, - 'domainId' text NOT NULL, - FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade, - FOREIGN KEY ('domainId') REFERENCES 'domains'('domainId') ON UPDATE no action ON DELETE cascade -);`); - - trx.run( - sql`ALTER TABLE 'resources' ADD 'domainId' text REFERENCES domains(domainId);` - ); - trx.run(sql`ALTER TABLE 'orgs' DROP COLUMN 'domain';`); - }); - - console.log(`Migrated database schema`); - } catch (e) { - console.log("Unable to migrate database schema"); - throw e; - } + let domain = ""; try { // Determine which config file exists @@ -84,11 +60,68 @@ export default async function migration() { const updatedYaml = yaml.dump(rawConfig); fs.writeFileSync(filePath, updatedYaml, "utf8"); + domain = baseDomain; + console.log(`Moved base_domain to new domains section`); } catch (e) { console.log( `Unable to migrate config file and move base_domain to domains. Error: ${e}` ); + throw e; + } + + try { + db.transaction((trx) => { + trx.run(sql`CREATE TABLE 'domains' ( + 'domainId' text PRIMARY KEY NOT NULL, + 'baseDomain' text NOT NULL, + 'configManaged' integer DEFAULT false NOT NULL +);`); + + trx.run(sql`CREATE TABLE 'orgDomains' ( + 'orgId' text NOT NULL, + 'domainId' text NOT NULL, + FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('domainId') REFERENCES 'domains'('domainId') ON UPDATE no action ON DELETE cascade +);`); + + trx.run( + sql`ALTER TABLE 'resources' ADD 'domainId' text REFERENCES domains(domainId);` + ); + trx.run(sql`ALTER TABLE 'orgs' DROP COLUMN 'domain';`); + }); + + console.log(`Migrated database schema`); + } catch (e) { + console.log("Unable to migrate database schema"); + throw e; + } + + try { + await db.transaction(async (trx) => { + await trx + .insert(domains) + .values({ + domainId: "domain1", + baseDomain: domain, + configManaged: true + }) + .execute(); + await trx.update(resources).set({ domainId: "domain1" }); + const existingOrgDomains = await trx.select().from(orgDomains); + for (const orgDomain of existingOrgDomains) { + await trx + .insert(orgDomains) + .values({ orgId: orgDomain.orgId, domainId: "domain1" }) + .execute(); + } + }); + + console.log(`Updated resources table with new domainId`); + } catch (e) { + console.log( + `Unable to update resources table with new domainId. Error: ${e}` + ); return; }