123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- import db from "@server/db";
- import {
- emailVerificationCodes,
- passwordResetTokens,
- resourceOtp,
- resources,
- resourceWhitelist,
- targets,
- userInvites,
- users
- } from "@server/db/schema";
- import { APP_PATH, configFilePath1, configFilePath2 } from "@server/lib/consts";
- import { eq, sql } from "drizzle-orm";
- import fs from "fs";
- import yaml from "js-yaml";
- import path from "path";
- import { z } from "zod";
- import { fromZodError } from "zod-validation-error";
- export default async function migration() {
- console.log("Running setup script 1.0.0-beta.9...");
- // 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);
- await db.transaction(async (trx) => {
- try {
- // Determine which config file exists
- const filePaths = [configFilePath1, configFilePath2];
- let filePath = "";
- for (const path of filePaths) {
- if (fs.existsSync(path)) {
- filePath = path;
- break;
- }
- }
- if (!filePath) {
- throw new Error(
- `No config file found (expected config.yml or config.yaml).`
- );
- }
- // Read and parse the YAML file
- let rawConfig: any;
- const fileContents = fs.readFileSync(filePath, "utf8");
- rawConfig = yaml.load(fileContents);
- rawConfig.server.resource_session_request_param =
- "p_session_request";
- rawConfig.server.session_cookie_name = "p_session_token"; // rename to prevent conflicts
- delete rawConfig.server.resource_session_cookie_name;
- if (!rawConfig.flags) {
- rawConfig.flags = {};
- }
- rawConfig.flags.allow_raw_resources = true;
- // Write the updated YAML back to the file
- const updatedYaml = yaml.dump(rawConfig);
- fs.writeFileSync(filePath, updatedYaml, "utf8");
- } catch (e) {
- console.log(
- `Failed to add resource_session_request_param to config. Please add it manually. https://docs.fossorial.io/Pangolin/Configuration/config`
- );
- trx.rollback();
- return;
- }
- try {
- const traefikPath = path.join(
- APP_PATH,
- "traefik",
- "traefik_config.yml"
- );
- // Define schema for traefik config validation
- const schema = z.object({
- entryPoints: z
- .object({
- websecure: z
- .object({
- address: z.string(),
- transport: z
- .object({
- respondingTimeouts: z.object({
- readTimeout: z.string()
- })
- })
- .optional()
- })
- .optional()
- })
- .optional(),
- experimental: z.object({
- plugins: z.object({
- badger: z.object({
- moduleName: z.string(),
- version: z.string()
- })
- })
- })
- });
- const traefikFileContents = fs.readFileSync(traefikPath, "utf8");
- const traefikConfig = yaml.load(traefikFileContents) as any;
- let parsedConfig: any = schema.safeParse(traefikConfig);
- if (parsedConfig.success) {
- // Ensure websecure entrypoint exists
- if (traefikConfig.entryPoints?.websecure) {
- // Add transport configuration
- traefikConfig.entryPoints.websecure.transport = {
- respondingTimeouts: {
- readTimeout: "30m"
- }
- };
- }
- traefikConfig.experimental.plugins.badger.version =
- "v1.0.0-beta.3";
- const updatedTraefikYaml = yaml.dump(traefikConfig);
- fs.writeFileSync(traefikPath, updatedTraefikYaml, "utf8");
- console.log("Updated Badger version in Traefik config.");
- } else {
- console.log(fromZodError(parsedConfig.error));
- console.log(
- "We were unable to update the version of Badger in your Traefik configuration. Please update it manually to at least v1.0.0-beta.3. https://github.com/fosrl/badger"
- );
- }
- } catch (e) {
- console.log(
- "We were unable to update the version of Badger in your Traefik configuration. Please update it manually to at least v1.0.0-beta.3. https://github.com/fosrl/badger"
- );
- trx.rollback();
- return;
- }
- try {
- const traefikPath = path.join(
- APP_PATH,
- "traefik",
- "dynamic_config.yml"
- );
- const schema = z.object({
- http: z.object({
- middlewares: z.object({
- "redirect-to-https": z.object({
- redirectScheme: z.object({
- scheme: z.string(),
- permanent: z.boolean()
- })
- })
- })
- })
- });
- const traefikFileContents = fs.readFileSync(traefikPath, "utf8");
- const traefikConfig = yaml.load(traefikFileContents) as any;
- let parsedConfig: any = schema.safeParse(traefikConfig);
- if (parsedConfig.success) {
- // delete permanent from redirect-to-https middleware
- delete traefikConfig.http.middlewares["redirect-to-https"].redirectScheme.permanent;
- const updatedTraefikYaml = yaml.dump(traefikConfig);
- fs.writeFileSync(traefikPath, updatedTraefikYaml, "utf8");
- console.log("Deleted permanent from redirect-to-https middleware.");
- } else {
- console.log(fromZodError(parsedConfig.error));
- console.log(
- "We were unable to delete the permanent field from the redirect-to-https middleware in your Traefik configuration. Please delete it manually."
- );
- }
- } catch (e) {
- console.log(
- "We were unable to delete the permanent field from the redirect-to-https middleware in your Traefik configuration. Please delete it manually. Note that this is not a critical change but recommended."
- );
- }
- trx.run(sql`UPDATE ${users} SET email = LOWER(email);`);
- trx.run(
- sql`UPDATE ${emailVerificationCodes} SET email = LOWER(email);`
- );
- trx.run(sql`UPDATE ${passwordResetTokens} SET email = LOWER(email);`);
- trx.run(sql`UPDATE ${userInvites} SET email = LOWER(email);`);
- trx.run(sql`UPDATE ${resourceWhitelist} SET email = LOWER(email);`);
- trx.run(sql`UPDATE ${resourceOtp} SET email = LOWER(email);`);
- const resourcesAll = await trx
- .select({
- resourceId: resources.resourceId,
- fullDomain: resources.fullDomain,
- subdomain: resources.subdomain
- })
- .from(resources);
- trx.run(`DROP INDEX resources_fullDomain_unique;`);
- trx.run(`ALTER TABLE resources
- DROP COLUMN fullDomain;
- `);
- trx.run(`ALTER TABLE resources
- DROP COLUMN subdomain;
- `);
- trx.run(sql`ALTER TABLE resources
- ADD COLUMN fullDomain TEXT;
- `);
- trx.run(sql`ALTER TABLE resources
- ADD COLUMN subdomain TEXT;
- `);
- trx.run(sql`ALTER TABLE resources
- ADD COLUMN http INTEGER DEFAULT true NOT NULL;
- `);
- trx.run(sql`ALTER TABLE resources
- ADD COLUMN protocol TEXT DEFAULT 'tcp' NOT NULL;
- `);
- trx.run(sql`ALTER TABLE resources
- ADD COLUMN proxyPort INTEGER;
- `);
- // write the new fullDomain and subdomain values back to the database
- for (const resource of resourcesAll) {
- await trx
- .update(resources)
- .set({
- fullDomain: resource.fullDomain,
- subdomain: resource.subdomain
- })
- .where(eq(resources.resourceId, resource.resourceId));
- }
- const targetsAll = await trx
- .select({
- targetId: targets.targetId,
- method: targets.method
- })
- .from(targets);
- trx.run(`ALTER TABLE targets
- DROP COLUMN method;
- `);
- trx.run(`ALTER TABLE targets
- DROP COLUMN protocol;
- `);
- trx.run(sql`ALTER TABLE targets
- ADD COLUMN method TEXT;
- `);
- // write the new method and protocol values back to the database
- for (const target of targetsAll) {
- await trx
- .update(targets)
- .set({
- method: target.method
- })
- .where(eq(targets.targetId, target.targetId));
- }
- trx.run(
- sql`ALTER TABLE 'resourceSessions' ADD 'isRequestToken' integer;`
- );
- trx.run(
- sql`ALTER TABLE 'resourceSessions' ADD 'userSessionId' text REFERENCES session(id);`
- );
- });
- console.log("Done.");
- }
|