2025-01-21 18:36:50 -05:00
import db from "@server/db" ;
import {
emailVerificationCodes ,
passwordResetTokens ,
resourceOtp ,
2025-01-28 22:26:45 -05:00
resources ,
2025-01-21 18:36:50 -05:00
resourceWhitelist ,
2025-01-28 22:26:45 -05:00
targets ,
2025-01-21 18:36:50 -05:00
userInvites ,
users
} from "@server/db/schema" ;
2025-01-26 14:42:02 -05:00
import { APP_PATH , configFilePath1 , configFilePath2 } from "@server/lib/consts" ;
2025-01-28 22:26:45 -05:00
import { eq , sql } from "drizzle-orm" ;
2025-01-26 14:42:02 -05:00
import fs from "fs" ;
import yaml from "js-yaml" ;
import path from "path" ;
import { z } from "zod" ;
import { fromZodError } from "zod-validation-error" ;
2025-01-21 18:36:50 -05:00
export default async function migration() {
console . log ( "Running setup script 1.0.0-beta.9..." ) ;
2025-01-30 00:03:11 -05:00
// 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 ) ;
2025-01-29 19:55:08 -05:00
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 ;
2025-01-30 00:03:11 -05:00
if ( ! rawConfig . flags ) {
rawConfig . flags = { } ;
}
rawConfig . flags . allow_raw_resources = true ;
2025-01-29 19:55:08 -05:00
// 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"
2025-01-25 12:55:19 -05:00
) ;
2025-01-29 19:55:08 -05:00
// 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 ;
2025-01-30 00:03:11 -05:00
let parsedConfig : any = schema . safeParse ( traefikConfig ) ;
2025-01-29 19:55:08 -05:00
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" ) ;
2025-01-30 00:03:11 -05:00
console . log ( "Updated Badger version in Traefik config." ) ;
2025-01-29 19:55:08 -05:00
} 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"
2025-01-25 12:55:19 -05:00
) ;
2025-01-29 19:55:08 -05:00
trx . rollback ( ) ;
return ;
}
2025-01-30 00:03:11 -05:00
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."
) ;
}
2025-01-29 19:55:08 -05:00
trx . run ( sql ` UPDATE ${ users } SET email = LOWER(email); ` ) ;
trx . run (
sql ` UPDATE ${ emailVerificationCodes } SET email = LOWER(email); `
2025-01-21 18:36:50 -05:00
) ;
2025-01-29 19:55:08 -05:00
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); ` ) ;
2025-01-21 18:36:50 -05:00
2025-01-29 19:55:08 -05:00
const resourcesAll = await trx
. select ( {
2025-01-28 22:26:45 -05:00
resourceId : resources.resourceId ,
fullDomain : resources.fullDomain ,
subdomain : resources.subdomain
2025-01-29 19:55:08 -05:00
} )
. from ( resources ) ;
2025-01-28 22:26:45 -05:00
2025-01-29 19:55:08 -05:00
trx . run ( ` DROP INDEX resources_fullDomain_unique; ` ) ;
trx . run ( ` ALTER TABLE resources
2025-01-28 22:26:45 -05:00
DROP COLUMN fullDomain ;
2025-01-29 19:55:08 -05:00
` );
trx . run ( ` ALTER TABLE resources
2025-01-28 22:26:45 -05:00
DROP COLUMN subdomain ;
2025-01-29 19:55:08 -05:00
` );
trx . run ( sql ` ALTER TABLE resources
2025-01-28 22:26:45 -05:00
ADD COLUMN fullDomain TEXT ;
2025-01-29 19:55:08 -05:00
` );
trx . run ( sql ` ALTER TABLE resources
2025-01-28 22:26:45 -05:00
ADD COLUMN subdomain TEXT ;
2025-01-29 19:55:08 -05:00
` );
trx . run ( sql ` ALTER TABLE resources
2025-01-28 22:26:45 -05:00
ADD COLUMN http INTEGER DEFAULT true NOT NULL ;
2025-01-29 19:55:08 -05:00
` );
trx . run ( sql ` ALTER TABLE resources
2025-01-28 22:26:45 -05:00
ADD COLUMN protocol TEXT DEFAULT 'tcp' NOT NULL ;
2025-01-29 19:55:08 -05:00
` );
trx . run ( sql ` ALTER TABLE resources
2025-01-28 22:26:45 -05:00
ADD COLUMN proxyPort INTEGER ;
2025-01-29 19:55:08 -05:00
` );
2025-01-28 22:26:45 -05:00
2025-01-29 19:55:08 -05:00
// write the new fullDomain and subdomain values back to the database
for ( const resource of resourcesAll ) {
await trx
. update ( resources )
. set ( {
2025-01-28 22:26:45 -05:00
fullDomain : resource.fullDomain ,
subdomain : resource.subdomain
2025-01-29 19:55:08 -05:00
} )
. where ( eq ( resources . resourceId , resource . resourceId ) ) ;
}
2025-01-28 22:26:45 -05:00
2025-01-29 19:55:08 -05:00
const targetsAll = await trx
. select ( {
2025-01-28 22:26:45 -05:00
targetId : targets.targetId ,
method : targets.method
2025-01-29 19:55:08 -05:00
} )
. from ( targets ) ;
2025-01-28 22:26:45 -05:00
2025-01-29 19:55:08 -05:00
trx . run ( ` ALTER TABLE targets
2025-01-28 22:26:45 -05:00
DROP COLUMN method ;
2025-01-29 19:55:08 -05:00
` );
trx . run ( ` ALTER TABLE targets
2025-01-28 22:26:45 -05:00
DROP COLUMN protocol ;
2025-01-29 19:55:08 -05:00
` );
trx . run ( sql ` ALTER TABLE targets
2025-01-28 22:26:45 -05:00
ADD COLUMN method TEXT ;
2025-01-29 19:55:08 -05:00
` );
2025-01-28 22:26:45 -05:00
2025-01-29 19:55:08 -05:00
// write the new method and protocol values back to the database
for ( const target of targetsAll ) {
await trx
. update ( targets )
. set ( {
2025-01-28 22:26:45 -05:00
method : target.method
2025-01-26 14:42:02 -05:00
} )
2025-01-29 19:55:08 -05:00
. where ( eq ( targets . targetId , target . targetId ) ) ;
2025-01-28 22:26:45 -05:00
}
2025-01-29 19:55:08 -05:00
trx . run (
sql ` ALTER TABLE 'resourceSessions' ADD 'isRequestToken' integer; `
2025-01-26 14:42:02 -05:00
) ;
2025-01-29 19:55:08 -05:00
trx . run (
sql ` ALTER TABLE 'resourceSessions' ADD 'userSessionId' text REFERENCES session(id); `
2025-01-26 14:42:02 -05:00
) ;
2025-01-29 19:55:08 -05:00
} ) ;
2025-01-26 14:42:02 -05:00
2025-01-21 18:36:50 -05:00
console . log ( "Done." ) ;
}