From 0e1603c07d33de6d09f4028c202d14ba24b8ea42 Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Mon, 20 Oct 2025 15:22:46 +0000 Subject: [PATCH 01/20] first change to libsql --- package-lock.json | 523 ++++++++---------- package.json | 3 +- server/db/sqlite/driver.ts | 6 +- server/db/sqlite/migrate.ts | 2 +- server/private/routers/auth/quickStart.ts | 4 +- .../remoteExitNode/createRemoteExitNode.ts | 4 +- .../quickStartRemoteExitNode.ts | 4 +- server/routers/auth/signup.ts | 4 +- server/routers/newt/createNewt.ts | 4 +- server/routers/olm/createOlm.ts | 4 +- server/setup/migrationsSqlite.ts | 6 +- 11 files changed, 255 insertions(+), 309 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8e829c956..44aa51046 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@asteasolutions/zod-to-openapi": "^7.3.4", "@aws-sdk/client-s3": "3.908.0", "@hookform/resolvers": "5.2.2", + "@libsql/client": "^0.15.15", "@node-rs/argon2": "^2.0.2", "@oslojs/crypto": "1.0.1", "@oslojs/encoding": "1.1.0", @@ -42,7 +43,6 @@ "@tanstack/react-table": "8.21.3", "arctic": "^3.7.0", "axios": "^1.12.2", - "better-sqlite3": "11.7.0", "canvas-confetti": "1.9.3", "class-variance-authority": "^0.7.1", "clsx": "2.1.1", @@ -107,7 +107,6 @@ "@esbuild-plugins/tsconfig-paths": "0.1.2", "@react-email/preview-server": "4.3.0", "@tailwindcss/postcss": "^4.1.14", - "@types/better-sqlite3": "7.6.12", "@types/cookie-parser": "1.4.9", "@types/cors": "2.8.19", "@types/crypto-js": "^4.2.2", @@ -3834,6 +3833,176 @@ "integrity": "sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==", "license": "MIT" }, + "node_modules/@libsql/client": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/@libsql/client/-/client-0.15.15.tgz", + "integrity": "sha512-twC0hQxPNHPKfeOv3sNT6u2pturQjLcI+CnpTM0SjRpocEGgfiZ7DWKXLNnsothjyJmDqEsBQJ5ztq9Wlu470w==", + "license": "MIT", + "dependencies": { + "@libsql/core": "^0.15.14", + "@libsql/hrana-client": "^0.7.0", + "js-base64": "^3.7.5", + "libsql": "^0.5.22", + "promise-limit": "^2.7.0" + } + }, + "node_modules/@libsql/core": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/@libsql/core/-/core-0.15.15.tgz", + "integrity": "sha512-C88Z6UKl+OyuKKPwz224riz02ih/zHYI3Ho/LAcVOgjsunIRZoBw7fjRfaH9oPMmSNeQfhGklSG2il1URoOIsA==", + "license": "MIT", + "dependencies": { + "js-base64": "^3.7.5" + } + }, + "node_modules/@libsql/darwin-arm64": { + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.5.22.tgz", + "integrity": "sha512-4B8ZlX3nIDPndfct7GNe0nI3Yw6ibocEicWdC4fvQbSs/jdq/RC2oCsoJxJ4NzXkvktX70C1J4FcmmoBy069UA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@libsql/darwin-x64": { + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.5.22.tgz", + "integrity": "sha512-ny2HYWt6lFSIdNFzUFIJ04uiW6finXfMNJ7wypkAD8Pqdm6nAByO+Fdqu8t7sD0sqJGeUCiOg480icjyQ2/8VA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@libsql/hrana-client": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@libsql/hrana-client/-/hrana-client-0.7.0.tgz", + "integrity": "sha512-OF8fFQSkbL7vJY9rfuegK1R7sPgQ6kFMkDamiEccNUvieQ+3urzfDFI616oPl8V7T9zRmnTkSjMOImYCAVRVuw==", + "license": "MIT", + "dependencies": { + "@libsql/isomorphic-fetch": "^0.3.1", + "@libsql/isomorphic-ws": "^0.1.5", + "js-base64": "^3.7.5", + "node-fetch": "^3.3.2" + } + }, + "node_modules/@libsql/isomorphic-fetch": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@libsql/isomorphic-fetch/-/isomorphic-fetch-0.3.1.tgz", + "integrity": "sha512-6kK3SUK5Uu56zPq/Las620n5aS9xJq+jMBcNSOmjhNf/MUvdyji4vrMTqD7ptY7/4/CAVEAYDeotUz60LNQHtw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@libsql/isomorphic-ws": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@libsql/isomorphic-ws/-/isomorphic-ws-0.1.5.tgz", + "integrity": "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==", + "license": "MIT", + "dependencies": { + "@types/ws": "^8.5.4", + "ws": "^8.13.0" + } + }, + "node_modules/@libsql/linux-arm-gnueabihf": { + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm-gnueabihf/-/linux-arm-gnueabihf-0.5.22.tgz", + "integrity": "sha512-3Uo3SoDPJe/zBnyZKosziRGtszXaEtv57raWrZIahtQDsjxBVjuzYQinCm9LRCJCUT5t2r5Z5nLDPJi2CwZVoA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-arm-musleabihf": { + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm-musleabihf/-/linux-arm-musleabihf-0.5.22.tgz", + "integrity": "sha512-LCsXh07jvSojTNJptT9CowOzwITznD+YFGGW+1XxUr7fS+7/ydUrpDfsMX7UqTqjm7xG17eq86VkWJgHJfvpNg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-arm64-gnu": { + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.5.22.tgz", + "integrity": "sha512-KSdnOMy88c9mpOFKUEzPskSaF3VLflfSUCBwas/pn1/sV3pEhtMF6H8VUCd2rsedwoukeeCSEONqX7LLnQwRMA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-arm64-musl": { + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.5.22.tgz", + "integrity": "sha512-mCHSMAsDTLK5YH//lcV3eFEgiR23Ym0U9oEvgZA0667gqRZg/2px+7LshDvErEKv2XZ8ixzw3p1IrBzLQHGSsw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-x64-gnu": { + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.5.22.tgz", + "integrity": "sha512-kNBHaIkSg78Y4BqAdgjcR2mBilZXs4HYkAmi58J+4GRwDQZh5fIUWbnQvB9f95DkWUIGVeenqLRFY2pcTmlsew==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-x64-musl": { + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.5.22.tgz", + "integrity": "sha512-UZ4Xdxm4pu3pQXjvfJiyCzZop/9j/eA2JjmhMaAhe3EVLH2g11Fy4fwyUp9sT1QJYR1kpc2JLuybPM0kuXv/Tg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/win32-x64-msvc": { + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.5.22.tgz", + "integrity": "sha512-Fj0j8RnBpo43tVZUVoNK6BV/9AtDUM5S7DF3LB4qTYg1LMSZqi3yeCneUTLJD6XomQJlZzbI4mst89yspVSAnA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@lottiefiles/dotlottie-react": { "version": "0.13.3", "resolved": "https://registry.npmjs.org/@lottiefiles/dotlottie-react/-/dotlottie-react-0.13.3.tgz", @@ -3866,6 +4035,12 @@ "@tybys/wasm-util": "^0.10.0" } }, + "node_modules/@neon-rs/load": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.0.4.tgz", + "integrity": "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==", + "license": "MIT" + }, "node_modules/@next/env": { "version": "15.5.6", "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.6.tgz", @@ -8484,16 +8659,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@types/better-sqlite3": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.12.tgz", - "integrity": "sha512-fnQmj8lELIj7BSrZQAdBMHEHX8OZLYIHXqAKT1O7tDfLxaINzf00PMjw22r3N/xXh0w/sGHlO6SVaCQ2mj78lg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/body-parser": { "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", @@ -8667,7 +8832,6 @@ "version": "24.7.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.2.tgz", "integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==", - "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~7.14.0" @@ -8817,7 +8981,6 @@ "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -10044,6 +10207,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "devOptional": true, "funding": [ { "type": "github", @@ -10080,17 +10244,6 @@ "baseline-browser-mapping": "dist/cli.js" } }, - "node_modules/better-sqlite3": { - "version": "11.7.0", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.7.0.tgz", - "integrity": "sha512-mXpa5jnIKKHeoGzBrUJrc65cXFKcILGZpU3FXR0pradUEm9MA7UZz02qfEejaMcm9iXrSOCenwwYMJ/tZ1y5Ig==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "bindings": "^1.5.0", - "prebuild-install": "^7.1.1" - } - }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -10104,19 +10257,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "license": "MIT", - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "devOptional": true, "license": "MIT", "dependencies": { "buffer": "^5.5.0", @@ -10217,6 +10362,7 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "devOptional": true, "funding": [ { "type": "github", @@ -10972,30 +11118,6 @@ "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", "license": "MIT" }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -11099,6 +11221,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -11439,15 +11562,6 @@ "node": ">= 0.8" } }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/engine.io": { "version": "6.6.4", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", @@ -12377,15 +12491,6 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "license": "(MIT OR WTFPL)", - "engines": { - "node": ">=6" - } - }, "node_modules/express": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", @@ -12628,12 +12733,6 @@ "moment": "^2.29.1" } }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "license": "MIT" - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -12876,12 +12975,6 @@ "node": ">= 0.8" } }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" - }, "node_modules/fs-monkey": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.1.0.tgz", @@ -13070,12 +13163,6 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "license": "MIT" - }, "node_modules/glob": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", @@ -13405,6 +13492,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "devOptional": true, "funding": [ { "type": "github", @@ -13461,12 +13549,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, "node_modules/input-otp": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.2.tgz", @@ -14082,6 +14164,12 @@ "node": ">= 0.6.0" } }, + "node_modules/js-base64": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", + "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", + "license": "BSD-3-Clause" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -14286,6 +14374,47 @@ "node": ">= 0.8.0" } }, + "node_modules/libsql": { + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/libsql/-/libsql-0.5.22.tgz", + "integrity": "sha512-NscWthMQt7fpU8lqd7LXMvT9pi+KhhmTHAJWUB/Lj6MWa0MKFv0F2V4C6WKKpjCVZl0VwcDz4nOI3CyaT1DDiA==", + "cpu": [ + "x64", + "arm64", + "wasm32", + "arm" + ], + "license": "MIT", + "os": [ + "darwin", + "linux", + "win32" + ], + "dependencies": { + "@neon-rs/load": "^0.0.4", + "detect-libc": "2.0.2" + }, + "optionalDependencies": { + "@libsql/darwin-arm64": "0.5.22", + "@libsql/darwin-x64": "0.5.22", + "@libsql/linux-arm-gnueabihf": "0.5.22", + "@libsql/linux-arm-musleabihf": "0.5.22", + "@libsql/linux-arm64-gnu": "0.5.22", + "@libsql/linux-arm64-musl": "0.5.22", + "@libsql/linux-x64-gnu": "0.5.22", + "@libsql/linux-x64-musl": "0.5.22", + "@libsql/win32-x64-msvc": "0.5.22" + } + }, + "node_modules/libsql/node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/lightningcss": { "version": "1.30.1", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", @@ -14870,18 +14999,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/mini-svg-data-uri": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", @@ -14934,12 +15051,6 @@ "node": ">= 18" } }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" - }, "node_modules/mmdb-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/mmdb-lib/-/mmdb-lib-3.0.1.tgz", @@ -15037,12 +15148,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/napi-build-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "license": "MIT" - }, "node_modules/napi-postinstall": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", @@ -15197,18 +15302,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/node-abi": { - "version": "3.78.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.78.0.tgz", - "integrity": "sha512-E2wEyrgX/CqvicaQYU3Ze1PFGjc4QYPGsjUrlYkqAE0WjHEZwgOsGMPMzkMse4LjJbDmaEuDX3CM036j5K2DSQ==", - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/node-cache": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", @@ -18819,32 +18912,6 @@ "node": ">=20" } }, - "node_modules/prebuild-install": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", - "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^2.0.0", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -18905,6 +18972,12 @@ "node": ">=6" } }, + "node_modules/promise-limit": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz", + "integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==", + "license": "ISC" + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -18949,16 +19022,6 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, - "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -19090,30 +19153,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/react": { "version": "19.2.0", "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", @@ -20200,51 +20239,6 @@ "dev": true, "license": "ISC" }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -21101,40 +21095,6 @@ "node": ">=18" } }, - "node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-fs/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/tar/node_modules/yallist": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", @@ -21489,18 +21449,6 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "license": "0BSD" }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, "node_modules/tw-animate-css": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", @@ -21679,7 +21627,6 @@ "version": "7.14.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", - "devOptional": true, "license": "MIT" }, "node_modules/unpipe": { diff --git a/package.json b/package.json index 4660f6513..e73067ba5 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@asteasolutions/zod-to-openapi": "^7.3.4", "@aws-sdk/client-s3": "3.908.0", "@hookform/resolvers": "5.2.2", + "@libsql/client": "^0.15.15", "@node-rs/argon2": "^2.0.2", "@oslojs/crypto": "1.0.1", "@oslojs/encoding": "1.1.0", @@ -65,7 +66,6 @@ "@tanstack/react-table": "8.21.3", "arctic": "^3.7.0", "axios": "^1.12.2", - "better-sqlite3": "11.7.0", "canvas-confetti": "1.9.3", "class-variance-authority": "^0.7.1", "clsx": "2.1.1", @@ -130,7 +130,6 @@ "@esbuild-plugins/tsconfig-paths": "0.1.2", "@react-email/preview-server": "4.3.0", "@tailwindcss/postcss": "^4.1.14", - "@types/better-sqlite3": "7.6.12", "@types/cookie-parser": "1.4.9", "@types/cors": "2.8.19", "@types/crypto-js": "^4.2.2", diff --git a/server/db/sqlite/driver.ts b/server/db/sqlite/driver.ts index 211ba8ead..3f8a7963a 100644 --- a/server/db/sqlite/driver.ts +++ b/server/db/sqlite/driver.ts @@ -1,5 +1,5 @@ -import { drizzle as DrizzleSqlite } from "drizzle-orm/better-sqlite3"; -import Database from "better-sqlite3"; +import { drizzle as DrizzleSqlite } from "drizzle-orm/libsql"; +import { createClient } from "@libsql/client"; import * as schema from "./schema/schema"; import path from "path"; import fs from "fs"; @@ -12,7 +12,7 @@ export const exists = checkFileExists(location); bootstrapVolume(); function createDb() { - const sqlite = new Database(location); + const sqlite = createClient({ url: "file:" + location }); return DrizzleSqlite(sqlite, { schema }); } diff --git a/server/db/sqlite/migrate.ts b/server/db/sqlite/migrate.ts index e4a730d0a..045e7353a 100644 --- a/server/db/sqlite/migrate.ts +++ b/server/db/sqlite/migrate.ts @@ -1,4 +1,4 @@ -import { migrate } from "drizzle-orm/better-sqlite3/migrator"; +import { migrate } from "drizzle-orm/libsql/migrator"; import { db } from "./driver"; import path from "path"; diff --git a/server/private/routers/auth/quickStart.ts b/server/private/routers/auth/quickStart.ts index 582ac4d58..fbad1fa9d 100644 --- a/server/private/routers/auth/quickStart.ts +++ b/server/private/routers/auth/quickStart.ts @@ -42,7 +42,7 @@ import { users } from "@server/db"; import { fromError } from "zod-validation-error"; import createHttpError from "http-errors"; import response from "@server/lib/response"; -import { SqliteError } from "better-sqlite3"; +import { LibsqlError } from "@libsql/client"; import { eq, and, sql } from "drizzle-orm"; import moment from "moment"; import { generateId } from "@server/auth/sessions/app"; @@ -484,7 +484,7 @@ export async function quickStart( status: HttpCode.OK }); } catch (e) { - if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_UNIQUE") { + if (e instanceof LibsqlError && e.code === "SQLITE_CONSTRAINT_UNIQUE") { if (config.getRawConfig().app.log_failed_attempts) { logger.info( `Account already exists with that email. Email: ${email}. IP: ${req.ip}.` diff --git a/server/private/routers/remoteExitNode/createRemoteExitNode.ts b/server/private/routers/remoteExitNode/createRemoteExitNode.ts index 63209ad97..05581b782 100644 --- a/server/private/routers/remoteExitNode/createRemoteExitNode.ts +++ b/server/private/routers/remoteExitNode/createRemoteExitNode.ts @@ -18,7 +18,7 @@ import { z } from "zod"; import { remoteExitNodes } from "@server/db"; import createHttpError from "http-errors"; import response from "@server/lib/response"; -import { SqliteError } from "better-sqlite3"; +import { LibsqlError } from "@libsql/client"; import moment from "moment"; import { generateSessionToken } from "@server/auth/sessions/app"; import { createRemoteExitNodeSession } from "#private/auth/sessions/remoteExitNode"; @@ -248,7 +248,7 @@ export async function createRemoteExitNode( status: HttpCode.OK }); } catch (e) { - if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_UNIQUE") { + if (e instanceof LibsqlError && e.code === "SQLITE_CONSTRAINT_UNIQUE") { return next( createHttpError( HttpCode.BAD_REQUEST, diff --git a/server/private/routers/remoteExitNode/quickStartRemoteExitNode.ts b/server/private/routers/remoteExitNode/quickStartRemoteExitNode.ts index 4d3681525..c655672a6 100644 --- a/server/private/routers/remoteExitNode/quickStartRemoteExitNode.ts +++ b/server/private/routers/remoteExitNode/quickStartRemoteExitNode.ts @@ -17,7 +17,7 @@ import HttpCode from "@server/types/HttpCode"; import { remoteExitNodes } from "@server/db"; import createHttpError from "http-errors"; import response from "@server/lib/response"; -import { SqliteError } from "better-sqlite3"; +import { LibsqlError } from "@libsql/client"; import moment from "moment"; import { generateId } from "@server/auth/sessions/app"; import { hashPassword } from "@server/auth/password"; @@ -82,7 +82,7 @@ export async function quickStartRemoteExitNode( status: HttpCode.OK }); } catch (e) { - if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_UNIQUE") { + if (e instanceof LibsqlError && e.code === "SQLITE_CONSTRAINT_UNIQUE") { return next( createHttpError( HttpCode.BAD_REQUEST, diff --git a/server/routers/auth/signup.ts b/server/routers/auth/signup.ts index 1f361b793..3ba26af1b 100644 --- a/server/routers/auth/signup.ts +++ b/server/routers/auth/signup.ts @@ -5,7 +5,7 @@ import { z } from "zod"; import { fromError } from "zod-validation-error"; import createHttpError from "http-errors"; import response from "@server/lib/response"; -import { SqliteError } from "better-sqlite3"; +import { LibsqlError } from "@libsql/client"; import { sendEmailVerificationCode } from "../../auth/sendEmailVerificationCode"; import { eq, and } from "drizzle-orm"; import moment from "moment"; @@ -249,7 +249,7 @@ export async function signup( status: HttpCode.OK }); } catch (e) { - if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_UNIQUE") { + if (e instanceof LibsqlError && e.code === "SQLITE_CONSTRAINT_UNIQUE") { if (config.getRawConfig().app.log_failed_attempts) { logger.info( `Account already exists with that email. Email: ${email}. IP: ${req.ip}.` diff --git a/server/routers/newt/createNewt.ts b/server/routers/newt/createNewt.ts index 3066e4eac..94f9842a5 100644 --- a/server/routers/newt/createNewt.ts +++ b/server/routers/newt/createNewt.ts @@ -6,7 +6,7 @@ import { z } from "zod"; import { newts } from "@server/db"; import createHttpError from "http-errors"; import response from "@server/lib/response"; -import { SqliteError } from "better-sqlite3"; +import { LibsqlError } from "@libsql/client"; import moment from "moment"; import { generateSessionToken } from "@server/auth/sessions/app"; import { createNewtSession } from "@server/auth/sessions/newt"; @@ -85,7 +85,7 @@ export async function createNewt( status: HttpCode.OK, }); } catch (e) { - if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_UNIQUE") { + if (e instanceof LibsqlError && e.code === "SQLITE_CONSTRAINT_UNIQUE") { return next( createHttpError( HttpCode.BAD_REQUEST, diff --git a/server/routers/olm/createOlm.ts b/server/routers/olm/createOlm.ts index 3066e4eac..94f9842a5 100644 --- a/server/routers/olm/createOlm.ts +++ b/server/routers/olm/createOlm.ts @@ -6,7 +6,7 @@ import { z } from "zod"; import { newts } from "@server/db"; import createHttpError from "http-errors"; import response from "@server/lib/response"; -import { SqliteError } from "better-sqlite3"; +import { LibsqlError } from "@libsql/client"; import moment from "moment"; import { generateSessionToken } from "@server/auth/sessions/app"; import { createNewtSession } from "@server/auth/sessions/newt"; @@ -85,7 +85,7 @@ export async function createNewt( status: HttpCode.OK, }); } catch (e) { - if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_UNIQUE") { + if (e instanceof LibsqlError && e.code === "SQLITE_CONSTRAINT_UNIQUE") { return next( createHttpError( HttpCode.BAD_REQUEST, diff --git a/server/setup/migrationsSqlite.ts b/server/setup/migrationsSqlite.ts index b987b8331..1611c4926 100644 --- a/server/setup/migrationsSqlite.ts +++ b/server/setup/migrationsSqlite.ts @@ -1,11 +1,11 @@ #! /usr/bin/env node -import { migrate } from "drizzle-orm/better-sqlite3/migrator"; +import { migrate } from "drizzle-orm/libsql/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 { LibsqlError } from "@libsql/client"; import fs from "fs"; import m1 from "./scriptsSqlite/1.0.0-beta1"; import m2 from "./scriptsSqlite/1.0.0-beta2"; @@ -174,7 +174,7 @@ async function executeScripts() { ); } catch (e) { if ( - e instanceof SqliteError && + e instanceof LibsqlError && e.code === "SQLITE_CONSTRAINT_UNIQUE" ) { console.error("Migration has already run! Skipping..."); From acf23cc0253f1c60aa8fa755318e91779ab7c03c Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Tue, 21 Oct 2025 06:06:56 +0000 Subject: [PATCH 02/20] merge upstream --- package-lock.json | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index fde648b23..dbcb4661f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8832,7 +8832,6 @@ "version": "24.8.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.8.1.tgz", "integrity": "sha512-alv65KGRadQVfVcG69MuB4IzdYVpRwMG/mq8KWOaoOdyY617P5ivaDiMCGOFDWD2sAn5Q0mR3mRtUOgm99hL9Q==", - "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~7.14.0" @@ -10208,7 +10207,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "devOptional": true, + "dev": true, "funding": [ { "type": "github", @@ -10262,7 +10261,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "buffer": "^5.5.0", @@ -10363,7 +10362,7 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "devOptional": true, + "dev": true, "funding": [ { "type": "github", @@ -13493,7 +13492,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "devOptional": true, + "dev": true, "funding": [ { "type": "github", From d20deadd1dc0714a04d215c17741240d57ef2189 Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Tue, 21 Oct 2025 06:07:26 +0000 Subject: [PATCH 03/20] add local test --- Makefile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Makefile b/Makefile index c90bd1809..aa3152c99 100644 --- a/Makefile +++ b/Makefile @@ -61,3 +61,10 @@ test: clean: docker rmi pangolin + +test-local: + npm run set:oss + npm run set:sqlite + npx tsc --noEmit -p tsconfig.oss.json + docker build --build-arg DATABASE=pg -t fosrl/pangolin:postgresql-latest . + docker build --build-arg DATABASE=sqlite -t fosrl/pangolin:latest . \ No newline at end of file From ac04eec1466efb70ec2527fc04938b706fb1180a Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Tue, 21 Oct 2025 07:51:24 +0000 Subject: [PATCH 04/20] migrations --- Makefile | 7 +- server/setup/migrationsSqlite.ts | 59 +--- server/setup/scriptsSqlite/1.0.0-beta1.ts | 5 - server/setup/scriptsSqlite/1.0.0-beta10.ts | 44 --- server/setup/scriptsSqlite/1.0.0-beta12.ts | 61 ---- server/setup/scriptsSqlite/1.0.0-beta13.ts | 33 -- server/setup/scriptsSqlite/1.0.0-beta15.ts | 128 -------- server/setup/scriptsSqlite/1.0.0-beta2.ts | 58 ---- server/setup/scriptsSqlite/1.0.0-beta3.ts | 41 --- server/setup/scriptsSqlite/1.0.0-beta5.ts | 100 ------ server/setup/scriptsSqlite/1.0.0-beta6.ts | 51 ---- server/setup/scriptsSqlite/1.0.0-beta9.ts | 290 ------------------ server/setup/scriptsSqlite/1.0.0.ts | 57 ---- server/setup/scriptsSqlite/1.1.0.ts | 28 -- server/setup/scriptsSqlite/1.10.0.ts | 136 --------- server/setup/scriptsSqlite/1.10.1.ts | 69 ----- server/setup/scriptsSqlite/1.10.2.ts | 54 ---- server/setup/scriptsSqlite/1.11.0.ts | 339 --------------------- server/setup/scriptsSqlite/1.11.1.ts | 37 --- server/setup/scriptsSqlite/1.2.0.ts | 114 ------- server/setup/scriptsSqlite/1.3.0.ts | 203 ------------ server/setup/scriptsSqlite/1.5.0.ts | 70 ----- server/setup/scriptsSqlite/1.6.0.ts | 65 ---- server/setup/scriptsSqlite/1.7.0.ts | 187 ------------ server/setup/scriptsSqlite/1.8.0.ts | 30 -- server/setup/scriptsSqlite/1.9.0.ts | 191 ------------ server/setup/scriptsSqlite/2.0.0.ts | 14 + 27 files changed, 31 insertions(+), 2440 deletions(-) delete mode 100644 server/setup/scriptsSqlite/1.0.0-beta1.ts delete mode 100644 server/setup/scriptsSqlite/1.0.0-beta10.ts delete mode 100644 server/setup/scriptsSqlite/1.0.0-beta12.ts delete mode 100644 server/setup/scriptsSqlite/1.0.0-beta13.ts delete mode 100644 server/setup/scriptsSqlite/1.0.0-beta15.ts delete mode 100644 server/setup/scriptsSqlite/1.0.0-beta2.ts delete mode 100644 server/setup/scriptsSqlite/1.0.0-beta3.ts delete mode 100644 server/setup/scriptsSqlite/1.0.0-beta5.ts delete mode 100644 server/setup/scriptsSqlite/1.0.0-beta6.ts delete mode 100644 server/setup/scriptsSqlite/1.0.0-beta9.ts delete mode 100644 server/setup/scriptsSqlite/1.0.0.ts delete mode 100644 server/setup/scriptsSqlite/1.1.0.ts delete mode 100644 server/setup/scriptsSqlite/1.10.0.ts delete mode 100644 server/setup/scriptsSqlite/1.10.1.ts delete mode 100644 server/setup/scriptsSqlite/1.10.2.ts delete mode 100644 server/setup/scriptsSqlite/1.11.0.ts delete mode 100644 server/setup/scriptsSqlite/1.11.1.ts delete mode 100644 server/setup/scriptsSqlite/1.2.0.ts delete mode 100644 server/setup/scriptsSqlite/1.3.0.ts delete mode 100644 server/setup/scriptsSqlite/1.5.0.ts delete mode 100644 server/setup/scriptsSqlite/1.6.0.ts delete mode 100644 server/setup/scriptsSqlite/1.7.0.ts delete mode 100644 server/setup/scriptsSqlite/1.8.0.ts delete mode 100644 server/setup/scriptsSqlite/1.9.0.ts create mode 100644 server/setup/scriptsSqlite/2.0.0.ts diff --git a/Makefile b/Makefile index aa3152c99..e92da3cdd 100644 --- a/Makefile +++ b/Makefile @@ -63,8 +63,9 @@ clean: docker rmi pangolin test-local: + cp config/config.example.yml config/config.yml npm run set:oss npm run set:sqlite - npx tsc --noEmit -p tsconfig.oss.json - docker build --build-arg DATABASE=pg -t fosrl/pangolin:postgresql-latest . - docker build --build-arg DATABASE=sqlite -t fosrl/pangolin:latest . \ No newline at end of file + - npx tsc --noEmit + - docker build --build-arg DATABASE=pg -t fosrl/pangolin:postgresql-latest . + - docker build --build-arg DATABASE=sqlite -t fosrl/pangolin:latest . diff --git a/server/setup/migrationsSqlite.ts b/server/setup/migrationsSqlite.ts index 1611c4926..f98215084 100644 --- a/server/setup/migrationsSqlite.ts +++ b/server/setup/migrationsSqlite.ts @@ -7,58 +7,14 @@ import { versionMigrations } from "../db/sqlite"; import { __DIRNAME, APP_PATH, APP_VERSION } from "@server/lib/consts"; import { LibsqlError } from "@libsql/client"; 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/2.0.0"; // 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: "2.0.0", run: m29 }, // Add new migrations here as they are created ] as const; @@ -129,6 +85,7 @@ export async function runMigrations() { async function executeScripts() { try { + const requriedPreviousVersion = "1.11.1"; // Get the last executed version from the database const lastExecuted = await db.select().from(versionMigrations); @@ -137,6 +94,16 @@ async function executeScripts() { .map((m) => m) .sort((a, b) => semver.compare(b.version, a.version)); const startVersion = pendingMigrations[0]?.version ?? APP_VERSION; + const lastVersion = pendingMigrations[pendingMigrations.length - 1].version; + + if (!semver.eq(lastVersion, requriedPreviousVersion)) { + console.error( + `Starting App not allowed. Your previous version is: ${lastVersion}. ` + + `Please update first to version ${requriedPreviousVersion} due to breaking changes in version 2.0.0.` + ); + process.exit(1); + } + console.log(`Starting migrations from version ${startVersion}`); const migrationsToRun = migrations.filter((migration) => diff --git a/server/setup/scriptsSqlite/1.0.0-beta1.ts b/server/setup/scriptsSqlite/1.0.0-beta1.ts deleted file mode 100644 index 65d9ad1b7..000000000 --- a/server/setup/scriptsSqlite/1.0.0-beta1.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default async function migration() { - console.log("Running setup script 1.0.0-beta.1..."); - // SQL operations would go here in ts format - console.log("Done."); -} diff --git a/server/setup/scriptsSqlite/1.0.0-beta10.ts b/server/setup/scriptsSqlite/1.0.0-beta10.ts deleted file mode 100644 index 400cbc318..000000000 --- a/server/setup/scriptsSqlite/1.0.0-beta10.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { configFilePath1, configFilePath2 } from "@server/lib/consts"; -import fs from "fs"; -import yaml from "js-yaml"; - -export default async function migration() { - console.log("Running setup script 1.0.0-beta.10..."); - - 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 - const fileContents = fs.readFileSync(filePath, "utf8"); - const rawConfig = yaml.load(fileContents) as any; - - delete rawConfig.server.secure_cookies; - - // Write the updated YAML back to the file - const updatedYaml = yaml.dump(rawConfig); - fs.writeFileSync(filePath, updatedYaml, "utf8"); - - console.log(`Removed deprecated config option: secure_cookies.`); - } catch (e) { - console.log( - `Was unable to remove deprecated config option: secure_cookies. Error: ${e}` - ); - return; - } - - console.log("Done."); -} diff --git a/server/setup/scriptsSqlite/1.0.0-beta12.ts b/server/setup/scriptsSqlite/1.0.0-beta12.ts deleted file mode 100644 index 8c96e663c..000000000 --- a/server/setup/scriptsSqlite/1.0.0-beta12.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { db } from "../../db/sqlite"; -import { configFilePath1, configFilePath2 } from "@server/lib/consts"; -import { sql } from "drizzle-orm"; -import fs from "fs"; -import yaml from "js-yaml"; - -export default async function migration() { - console.log("Running setup script 1.0.0-beta.12..."); - - 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 - const fileContents = fs.readFileSync(filePath, "utf8"); - const rawConfig = yaml.load(fileContents) as any; - - if (!rawConfig.flags) { - rawConfig.flags = {}; - } - - rawConfig.flags.allow_base_domain_resources = true; - - // Write the updated YAML back to the file - const updatedYaml = yaml.dump(rawConfig); - fs.writeFileSync(filePath, updatedYaml, "utf8"); - - console.log(`Added new config option: allow_base_domain_resources`); - } catch (e) { - console.log( - `Unable to add new config option: allow_base_domain_resources. This is not critical.` - ); - console.error(e); - } - - try { - db.transaction((trx) => { - trx.run(sql`ALTER TABLE 'resources' ADD 'isBaseDomain' integer;`); - }); - - console.log(`Added new column: isBaseDomain`); - } catch (e) { - console.log("Unable to add new column: isBaseDomain"); - throw e; - } - - console.log("Done."); -} diff --git a/server/setup/scriptsSqlite/1.0.0-beta13.ts b/server/setup/scriptsSqlite/1.0.0-beta13.ts deleted file mode 100644 index 9ced727f7..000000000 --- a/server/setup/scriptsSqlite/1.0.0-beta13.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { db } from "../../db/sqlite"; -import { sql } from "drizzle-orm"; - -const version = "1.0.0-beta.13"; - -export default async function migration() { - console.log(`Running setup script ${version}...`); - - try { - db.transaction((trx) => { - trx.run(sql`CREATE TABLE resourceRules ( - ruleId integer PRIMARY KEY AUTOINCREMENT NOT NULL, - resourceId integer NOT NULL, - priority integer NOT NULL, - enabled integer DEFAULT true NOT NULL, - action text NOT NULL, - match text NOT NULL, - value text NOT NULL, - FOREIGN KEY (resourceId) REFERENCES resources(resourceId) ON UPDATE no action ON DELETE cascade - );`); - trx.run( - sql`ALTER TABLE resources ADD applyRules integer DEFAULT false NOT NULL;` - ); - }); - - console.log(`Added new table and column: resourceRules, applyRules`); - } catch (e) { - console.log("Unable to add new table and column: resourceRules, applyRules"); - throw e; - } - - console.log(`${version} migration complete`); -} diff --git a/server/setup/scriptsSqlite/1.0.0-beta15.ts b/server/setup/scriptsSqlite/1.0.0-beta15.ts deleted file mode 100644 index cf39fd8a3..000000000 --- a/server/setup/scriptsSqlite/1.0.0-beta15.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { db } from "../../db/sqlite"; -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"; - -const version = "1.0.0-beta.15"; - -export default async function migration() { - console.log(`Running setup script ${version}...`); - - let domain = ""; - - 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 - const fileContents = fs.readFileSync(filePath, "utf8"); - const rawConfig = yaml.load(fileContents) as any; - - const baseDomain = rawConfig.app.base_domain; - const certResolver = rawConfig.traefik.cert_resolver; - const preferWildcardCert = rawConfig.traefik.prefer_wildcard_cert; - - delete rawConfig.traefik.prefer_wildcard_cert; - delete rawConfig.traefik.cert_resolver; - delete rawConfig.app.base_domain; - - rawConfig.domains = { - domain1: { - base_domain: baseDomain - } - }; - - if (certResolver) { - rawConfig.domains.domain1.cert_resolver = certResolver; - } - - if (preferWildcardCert) { - rawConfig.domains.domain1.prefer_wildcard_cert = preferWildcardCert; - } - - // Write the updated YAML back to the file - 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; - } - - console.log(`${version} migration complete`); -} diff --git a/server/setup/scriptsSqlite/1.0.0-beta2.ts b/server/setup/scriptsSqlite/1.0.0-beta2.ts deleted file mode 100644 index 1241e9c51..000000000 --- a/server/setup/scriptsSqlite/1.0.0-beta2.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { configFilePath1, configFilePath2 } from "@server/lib/consts"; -import fs from "fs"; -import yaml from "js-yaml"; - -export default async function migration() { - console.log("Running setup script 1.0.0-beta.2..."); - - // 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 - const fileContents = fs.readFileSync(filePath, "utf8"); - const rawConfig = yaml.load(fileContents) as any; - - // Validate the structure - if (!rawConfig.app || !rawConfig.app.base_url) { - throw new Error(`Invalid config file: app.base_url is missing.`); - } - - // Move base_url to dashboard_url and calculate base_domain - const baseUrl = rawConfig.app.base_url; - rawConfig.app.dashboard_url = baseUrl; - rawConfig.app.base_domain = getBaseDomain(baseUrl); - - // Remove the old base_url - delete rawConfig.app.base_url; - - // Write the updated YAML back to the file - const updatedYaml = yaml.dump(rawConfig); - fs.writeFileSync(filePath, updatedYaml, "utf8"); - - console.log("Done."); -} - -function getBaseDomain(url: string): string { - const newUrl = new URL(url); - const hostname = newUrl.hostname; - const parts = hostname.split("."); - - if (parts.length <= 2) { - return parts.join("."); - } - - return parts.slice(-2).join("."); -} diff --git a/server/setup/scriptsSqlite/1.0.0-beta3.ts b/server/setup/scriptsSqlite/1.0.0-beta3.ts deleted file mode 100644 index fccfeb887..000000000 --- a/server/setup/scriptsSqlite/1.0.0-beta3.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { configFilePath1, configFilePath2 } from "@server/lib/consts"; -import fs from "fs"; -import yaml from "js-yaml"; - -export default async function migration() { - console.log("Running setup script 1.0.0-beta.3..."); - - // 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 - const fileContents = fs.readFileSync(filePath, "utf8"); - const rawConfig = yaml.load(fileContents) as any; - - // Validate the structure - if (!rawConfig.gerbil) { - throw new Error(`Invalid config file: gerbil is missing.`); - } - - // Update the config - rawConfig.gerbil.site_block_size = 29; - - // Write the updated YAML back to the file - const updatedYaml = yaml.dump(rawConfig); - fs.writeFileSync(filePath, updatedYaml, "utf8"); - - console.log("Done."); -} \ No newline at end of file diff --git a/server/setup/scriptsSqlite/1.0.0-beta5.ts b/server/setup/scriptsSqlite/1.0.0-beta5.ts deleted file mode 100644 index 1c49503cd..000000000 --- a/server/setup/scriptsSqlite/1.0.0-beta5.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { APP_PATH, configFilePath1, configFilePath2 } from "@server/lib/consts"; -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.5..."); - - // 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 - const fileContents = fs.readFileSync(filePath, "utf8"); - const rawConfig = yaml.load(fileContents) as any; - - // Validate the structure - if (!rawConfig.server) { - throw new Error(`Invalid config file: server is missing.`); - } - - // Update the config - rawConfig.server.resource_access_token_param = "p_token"; - - // Write the updated YAML back to the file - const updatedYaml = yaml.dump(rawConfig); - fs.writeFileSync(filePath, updatedYaml, "utf8"); - - // then try to update badger in traefik config - - try { - const traefikPath = path.join( - APP_PATH, - "traefik", - "traefik_config.yml" - ); - - // read the traefik file - // look for the badger middleware - // set the version to v1.0.0-beta.2 - /* -experimental: - plugins: - badger: - moduleName: "github.com/fosrl/badger" - version: "v1.0.0-beta.2" - */ - - const schema = z.object({ - 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; - - const parsedConfig = schema.safeParse(traefikConfig); - - if (!parsedConfig.success) { - throw new Error(fromZodError(parsedConfig.error).toString()); - } - - traefikConfig.experimental.plugins.badger.version = "v1.0.0-beta.2"; - - const updatedTraefikYaml = yaml.dump(traefikConfig); - - fs.writeFileSync(traefikPath, updatedTraefikYaml, "utf8"); - - console.log( - "Updated the version of Badger in your Traefik configuration to v1.0.0-beta.2." - ); - } catch (e) { - console.log( - "We were unable to update the version of Badger in your Traefik configuration. Please update it manually." - ); - console.error(e); - } - - console.log("Done."); -} diff --git a/server/setup/scriptsSqlite/1.0.0-beta6.ts b/server/setup/scriptsSqlite/1.0.0-beta6.ts deleted file mode 100644 index 891296781..000000000 --- a/server/setup/scriptsSqlite/1.0.0-beta6.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { configFilePath1, configFilePath2 } from "@server/lib/consts"; -import fs from "fs"; -import yaml from "js-yaml"; - -export default async function migration() { - console.log("Running setup script 1.0.0-beta.6..."); - - 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 - const fileContents = fs.readFileSync(filePath, "utf8"); - const rawConfig = yaml.load(fileContents) as any; - - // Validate the structure - if (!rawConfig.server) { - throw new Error(`Invalid config file: server is missing.`); - } - - // Update the config - rawConfig.server.cors = { - origins: [rawConfig.app.dashboard_url], - methods: ["GET", "POST", "PUT", "DELETE", "PATCH"], - headers: ["X-CSRF-Token", "Content-Type"], - credentials: false - }; - - // Write the updated YAML back to the file - const updatedYaml = yaml.dump(rawConfig); - fs.writeFileSync(filePath, updatedYaml, "utf8"); - } catch (error) { - console.log("We were unable to add CORS to your config file. Please add it manually."); - console.error(error); - } - - console.log("Done."); -} diff --git a/server/setup/scriptsSqlite/1.0.0-beta9.ts b/server/setup/scriptsSqlite/1.0.0-beta9.ts deleted file mode 100644 index 7cce1c2dd..000000000 --- a/server/setup/scriptsSqlite/1.0.0-beta9.ts +++ /dev/null @@ -1,290 +0,0 @@ -import { db } from "../../db/sqlite"; -import { - emailVerificationCodes, - passwordResetTokens, - resourceOtp, - resources, - resourceWhitelist, - targets, - userInvites, - users -} from "../../db/sqlite"; -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 - const fileContents = fs.readFileSync(filePath, "utf8"); - const rawConfig = yaml.load(fileContents) as any; - - 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.pangolin.net/self-host/advanced/config-file` - ); - 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; - - const 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; - - const 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."); -} diff --git a/server/setup/scriptsSqlite/1.0.0.ts b/server/setup/scriptsSqlite/1.0.0.ts deleted file mode 100644 index c82966dee..000000000 --- a/server/setup/scriptsSqlite/1.0.0.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { APP_PATH } from "@server/lib/consts"; -import fs from "fs"; -import yaml from "js-yaml"; -import path from "path"; -import { z } from "zod"; -import { fromZodError } from "zod-validation-error"; - -const version = "1.0.0"; - -export default async function migration() { - console.log(`Running setup script ${version}...`); - - try { - const traefikPath = path.join( - APP_PATH, - "traefik", - "traefik_config.yml" - ); - - const schema = z.object({ - 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; - - const parsedConfig = schema.safeParse(traefikConfig); - - if (!parsedConfig.success) { - throw new Error(fromZodError(parsedConfig.error).toString()); - } - - traefikConfig.experimental.plugins.badger.version = "v1.0.0"; - - const updatedTraefikYaml = yaml.dump(traefikConfig); - - fs.writeFileSync(traefikPath, updatedTraefikYaml, "utf8"); - - console.log( - "Updated the version of Badger in your Traefik configuration to 1.0.0" - ); - } catch (e) { - console.log( - "We were unable to update the version of Badger in your Traefik configuration. Please update it manually." - ); - console.error(e); - } - - console.log(`${version} migration complete`); -} diff --git a/server/setup/scriptsSqlite/1.1.0.ts b/server/setup/scriptsSqlite/1.1.0.ts deleted file mode 100644 index 4d1218522..000000000 --- a/server/setup/scriptsSqlite/1.1.0.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { db } from "../../db/sqlite"; -import { sql } from "drizzle-orm"; - -const version = "1.1.0"; - -export default async function migration() { - console.log(`Running setup script ${version}...`); - - try { - db.transaction((trx) => { - trx.run(sql`CREATE TABLE 'supporterKey' ( - 'keyId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, - 'key' text NOT NULL, - 'githubUsername' text NOT NULL, - 'phrase' text, - 'tier' text, - 'valid' integer DEFAULT false NOT NULL -);`); - }); - - console.log(`Migrated database schema`); - } catch (e) { - console.log("Unable to migrate database schema"); - throw e; - } - - console.log(`${version} migration complete`); -} diff --git a/server/setup/scriptsSqlite/1.10.0.ts b/server/setup/scriptsSqlite/1.10.0.ts deleted file mode 100644 index 3065a664d..000000000 --- a/server/setup/scriptsSqlite/1.10.0.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { __DIRNAME, APP_PATH } from "@server/lib/consts"; -import Database from "better-sqlite3"; -import { readFileSync } from "fs"; -import path, { join } from "path"; - -const version = "1.10.0"; - -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 { - const resources = db - .prepare( - "SELECT resourceId FROM resources" - ) - .all() as Array<{ resourceId: number }>; - - const siteResources = db - .prepare( - "SELECT siteResourceId FROM siteResources" - ) - .all() as Array<{ siteResourceId: number }>; - - db.transaction(() => { - db.exec(` - ALTER TABLE 'exitNodes' ADD 'region' text; - ALTER TABLE 'idpOidcConfig' ADD 'variant' text DEFAULT 'oidc' NOT NULL; - ALTER TABLE 'resources' ADD 'niceId' text DEFAULT '' NOT NULL; - ALTER TABLE 'siteResources' ADD 'niceId' text DEFAULT '' NOT NULL; - ALTER TABLE 'userOrgs' ADD 'autoProvisioned' integer DEFAULT false; - ALTER TABLE 'targets' ADD 'pathMatchType' text; - ALTER TABLE 'targets' ADD 'path' text; - ALTER TABLE 'resources' ADD 'headers' text; - `); // this diverges from the schema a bit because the schema does not have a default on niceId but was required for the migration and I dont think it will effect much down the line... - - const usedNiceIds: string[] = []; - - for (const resourceId of resources) { - // Generate a unique name and ensure it's unique - let niceId = ""; - let loops = 0; - while (true) { - if (loops > 100) { - throw new Error("Could not generate a unique name"); - } - - niceId = generateName(); - if (!usedNiceIds.includes(niceId)) { - usedNiceIds.push(niceId); - break; - } - loops++; - } - db.prepare( - `UPDATE resources SET niceId = ? WHERE resourceId = ?` - ).run(niceId, resourceId.resourceId); - } - - for (const resourceId of siteResources) { - // Generate a unique name and ensure it's unique - let niceId = ""; - let loops = 0; - while (true) { - if (loops > 100) { - throw new Error("Could not generate a unique name"); - } - - niceId = generateName(); - if (!usedNiceIds.includes(niceId)) { - usedNiceIds.push(niceId); - break; - } - loops++; - } - db.prepare( - `UPDATE siteResources SET niceId = ? WHERE siteResourceId = ?` - ).run(niceId, resourceId.siteResourceId); - } - - // Handle auto-provisioned users for identity providers - const autoProvisionIdps = db - .prepare( - "SELECT idpId FROM idp WHERE autoProvision = 1" - ) - .all() as Array<{ idpId: number }>; - - for (const idp of autoProvisionIdps) { - // Get all users with this identity provider - const usersWithIdp = db - .prepare( - "SELECT id FROM user WHERE idpId = ?" - ) - .all(idp.idpId) as Array<{ id: string }>; - - // Update userOrgs to set autoProvisioned to true for these users - for (const user of usersWithIdp) { - db.prepare( - "UPDATE userOrgs SET autoProvisioned = 1 WHERE userId = ?" - ).run(user.id); - } - } - })(); - - console.log(`Migrated database`); - } catch (e) { - console.log("Failed to migrate db:", e); - throw e; - } -} - -const dev = process.env.ENVIRONMENT !== "prod"; -let file; -if (!dev) { - file = join(__DIRNAME, "names.json"); -} else { - file = join("server/db/names.json"); -} -export const names = JSON.parse(readFileSync(file, "utf-8")); - -export function generateName(): string { - const name = ( - names.descriptors[ - Math.floor(Math.random() * names.descriptors.length) - ] + - "-" + - names.animals[Math.floor(Math.random() * names.animals.length)] - ) - .toLowerCase() - .replace(/\s/g, "-"); - - // clean out any non-alphanumeric characters except for dashes - return name.replace(/[^a-z0-9-]/g, ""); -} diff --git a/server/setup/scriptsSqlite/1.10.1.ts b/server/setup/scriptsSqlite/1.10.1.ts deleted file mode 100644 index f6f9894ed..000000000 --- a/server/setup/scriptsSqlite/1.10.1.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { APP_PATH } from "@server/lib/consts"; -import Database from "better-sqlite3"; -import path from "path"; - -const version = "1.10.1"; - -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.pragma("foreign_keys = OFF"); - - db.transaction(() => { - db.exec(`ALTER TABLE "targets" RENAME TO "targets_old"; ---> statement-breakpoint -CREATE TABLE "targets" ( - "targetId" INTEGER PRIMARY KEY AUTOINCREMENT, - "resourceId" INTEGER NOT NULL, - "siteId" INTEGER NOT NULL, - "ip" TEXT NOT NULL, - "method" TEXT, - "port" INTEGER NOT NULL, - "internalPort" INTEGER, - "enabled" INTEGER NOT NULL DEFAULT 1, - "path" TEXT, - "pathMatchType" TEXT, - FOREIGN KEY ("resourceId") REFERENCES "resources"("resourceId") ON UPDATE no action ON DELETE cascade, - FOREIGN KEY ("siteId") REFERENCES "sites"("siteId") ON UPDATE no action ON DELETE cascade -); ---> statement-breakpoint -INSERT INTO "targets" ( - "targetId", - "resourceId", - "siteId", - "ip", - "method", - "port", - "internalPort", - "enabled", - "path", - "pathMatchType" -) -SELECT - targetId, - resourceId, - siteId, - ip, - method, - port, - internalPort, - enabled, - path, - pathMatchType -FROM "targets_old"; ---> statement-breakpoint -DROP TABLE "targets_old";`); - })(); - - db.pragma("foreign_keys = ON"); - - console.log(`Migrated database`); - } catch (e) { - console.log("Failed to migrate db:", e); - throw e; - } -} \ No newline at end of file diff --git a/server/setup/scriptsSqlite/1.10.2.ts b/server/setup/scriptsSqlite/1.10.2.ts deleted file mode 100644 index 7978e2621..000000000 --- a/server/setup/scriptsSqlite/1.10.2.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { APP_PATH } from "@server/lib/consts"; -import Database from "better-sqlite3"; -import path from "path"; - -const version = "1.10.2"; - -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); - - const resources = db.prepare("SELECT * FROM resources").all() as Array<{ - resourceId: number; - headers: string | null; - }>; - - try { - db.pragma("foreign_keys = OFF"); - - db.transaction(() => { - for (const resource of resources) { - const headers = resource.headers; - if (headers && headers !== "") { - // lets convert it to json - // fist split at commas - const headersArray = headers - .split(",") - .map((header: string) => { - const [name, ...valueParts] = header.split(":"); - const value = valueParts.join(":").trim(); - return { name: name.trim(), value }; - }); - - db.prepare( - ` - UPDATE "resources" SET "headers" = ? WHERE "resourceId" = ?` - ).run(JSON.stringify(headersArray), resource.resourceId); - - console.log( - `Updated resource ${resource.resourceId} headers to JSON format` - ); - } - } - })(); - - db.pragma("foreign_keys = ON"); - - console.log(`Migrated database`); - } catch (e) { - console.log("Failed to migrate db:", e); - throw e; - } -} diff --git a/server/setup/scriptsSqlite/1.11.0.ts b/server/setup/scriptsSqlite/1.11.0.ts deleted file mode 100644 index c79cfdb46..000000000 --- a/server/setup/scriptsSqlite/1.11.0.ts +++ /dev/null @@ -1,339 +0,0 @@ -import { APP_PATH } from "@server/lib/consts"; -import Database from "better-sqlite3"; -import path from "path"; -import { isoBase64URL } from "@simplewebauthn/server/helpers"; -import { randomUUID } from "crypto"; - -const version = "1.11.0"; - -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); - - db.transaction(() => { - - db.prepare(` - CREATE TABLE 'account' ( - 'accountId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, - 'userId' text NOT NULL, - FOREIGN KEY ('userId') REFERENCES 'user'('id') ON UPDATE no action ON DELETE cascade - ); - `).run(); - - db.prepare(` - CREATE TABLE 'accountDomains' ( - 'accountId' integer NOT NULL, - 'domainId' text NOT NULL, - FOREIGN KEY ('accountId') REFERENCES 'account'('accountId') ON UPDATE no action ON DELETE cascade, - FOREIGN KEY ('domainId') REFERENCES 'domains'('domainId') ON UPDATE no action ON DELETE cascade - ); - `).run(); - - db.prepare(` - CREATE TABLE 'certificates' ( - 'certId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, - 'domain' text NOT NULL, - 'domainId' text, - 'wildcard' integer DEFAULT false, - 'status' text DEFAULT 'pending' NOT NULL, - 'expiresAt' integer, - 'lastRenewalAttempt' integer, - 'createdAt' integer NOT NULL, - 'updatedAt' integer NOT NULL, - 'orderId' text, - 'errorMessage' text, - 'renewalCount' integer DEFAULT 0, - 'certFile' text, - 'keyFile' text, - FOREIGN KEY ('domainId') REFERENCES 'domains'('domainId') ON UPDATE no action ON DELETE cascade - ); - `).run(); - - db.prepare(`CREATE UNIQUE INDEX 'certificates_domain_unique' ON 'certificates' ('domain');`).run(); - - db.prepare(` - CREATE TABLE 'customers' ( - 'customerId' text PRIMARY KEY NOT NULL, - 'orgId' text NOT NULL, - 'email' text, - 'name' text, - 'phone' text, - 'address' text, - 'createdAt' integer NOT NULL, - 'updatedAt' integer NOT NULL, - FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade - ); - `).run(); - - db.prepare(` - CREATE TABLE 'dnsChallenges' ( - 'dnsChallengeId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, - 'domain' text NOT NULL, - 'token' text NOT NULL, - 'keyAuthorization' text NOT NULL, - 'createdAt' integer NOT NULL, - 'expiresAt' integer NOT NULL, - 'completed' integer DEFAULT false - ); - `).run(); - - db.prepare(` - CREATE TABLE 'domainNamespaces' ( - 'domainNamespaceId' text PRIMARY KEY NOT NULL, - 'domainId' text NOT NULL, - FOREIGN KEY ('domainId') REFERENCES 'domains'('domainId') ON UPDATE no action ON DELETE set null - ); - `).run(); - - db.prepare(` - CREATE TABLE 'exitNodeOrgs' ( - 'exitNodeId' integer NOT NULL, - 'orgId' text NOT NULL, - FOREIGN KEY ('exitNodeId') REFERENCES 'exitNodes'('exitNodeId') ON UPDATE no action ON DELETE cascade, - FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade - ); - `).run(); - - db.prepare(` - CREATE TABLE 'loginPage' ( - 'loginPageId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, - 'subdomain' text, - 'fullDomain' text, - 'exitNodeId' integer, - 'domainId' text, - FOREIGN KEY ('exitNodeId') REFERENCES 'exitNodes'('exitNodeId') ON UPDATE no action ON DELETE set null, - FOREIGN KEY ('domainId') REFERENCES 'domains'('domainId') ON UPDATE no action ON DELETE set null - ); - `).run(); - - db.prepare(` - CREATE TABLE 'loginPageOrg' ( - 'loginPageId' integer NOT NULL, - 'orgId' text NOT NULL, - FOREIGN KEY ('loginPageId') REFERENCES 'loginPage'('loginPageId') ON UPDATE no action ON DELETE cascade, - FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade - ); - `).run(); - - db.prepare(` - CREATE TABLE 'remoteExitNodeSession' ( - 'id' text PRIMARY KEY NOT NULL, - 'remoteExitNodeId' text NOT NULL, - 'expiresAt' integer NOT NULL, - FOREIGN KEY ('remoteExitNodeId') REFERENCES 'remoteExitNode'('id') ON UPDATE no action ON DELETE cascade - ); - `).run(); - - db.prepare(` - CREATE TABLE 'remoteExitNode' ( - 'id' text PRIMARY KEY NOT NULL, - 'secretHash' text NOT NULL, - 'dateCreated' text NOT NULL, - 'version' text, - 'exitNodeId' integer, - FOREIGN KEY ('exitNodeId') REFERENCES 'exitNodes'('exitNodeId') ON UPDATE no action ON DELETE cascade - ); - `).run(); - - db.prepare(` - CREATE TABLE 'sessionTransferToken' ( - 'token' text PRIMARY KEY NOT NULL, - 'sessionId' text NOT NULL, - 'encryptedSession' text NOT NULL, - 'expiresAt' integer NOT NULL, - FOREIGN KEY ('sessionId') REFERENCES 'session'('id') ON UPDATE no action ON DELETE cascade - ); - `).run(); - - db.prepare(` - CREATE TABLE 'subscriptionItems' ( - 'subscriptionItemId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, - 'subscriptionId' text NOT NULL, - 'planId' text NOT NULL, - 'priceId' text, - 'meterId' text, - 'unitAmount' real, - 'tiers' text, - 'interval' text, - 'currentPeriodStart' integer, - 'currentPeriodEnd' integer, - 'name' text, - FOREIGN KEY ('subscriptionId') REFERENCES 'subscriptions'('subscriptionId') ON UPDATE no action ON DELETE cascade - ); - `).run(); - - db.prepare(` - CREATE TABLE 'subscriptions' ( - 'subscriptionId' text PRIMARY KEY NOT NULL, - 'customerId' text NOT NULL, - 'status' text DEFAULT 'active' NOT NULL, - 'canceledAt' integer, - 'createdAt' integer NOT NULL, - 'updatedAt' integer, - 'billingCycleAnchor' integer, - FOREIGN KEY ('customerId') REFERENCES 'customers'('customerId') ON UPDATE no action ON DELETE cascade - ); - `).run(); - - db.prepare(` - CREATE TABLE 'usage' ( - 'usageId' text PRIMARY KEY NOT NULL, - 'featureId' text NOT NULL, - 'orgId' text NOT NULL, - 'meterId' text, - 'instantaneousValue' real, - 'latestValue' real NOT NULL, - 'previousValue' real, - 'updatedAt' integer NOT NULL, - 'rolledOverAt' integer, - 'nextRolloverAt' integer, - FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade - ); - `).run(); - - db.prepare(` - CREATE TABLE 'usageNotifications' ( - 'notificationId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, - 'orgId' text NOT NULL, - 'featureId' text NOT NULL, - 'limitId' text NOT NULL, - 'notificationType' text NOT NULL, - 'sentAt' integer NOT NULL, - FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade - ); - `).run(); - - db.prepare(` - CREATE TABLE 'resourceHeaderAuth' ( - 'headerAuthId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, - 'resourceId' integer NOT NULL, - 'headerAuthHash' text NOT NULL, - FOREIGN KEY ('resourceId') REFERENCES 'resources'('resourceId') ON UPDATE no action ON DELETE cascade - ); - `).run(); - - db.prepare(` - CREATE TABLE 'targetHealthCheck' ( - 'targetHealthCheckId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, - 'targetId' integer NOT NULL, - 'hcEnabled' integer DEFAULT false NOT NULL, - 'hcPath' text, - 'hcScheme' text, - 'hcMode' text DEFAULT 'http', - 'hcHostname' text, - 'hcPort' integer, - 'hcInterval' integer DEFAULT 30, - 'hcUnhealthyInterval' integer DEFAULT 30, - 'hcTimeout' integer DEFAULT 5, - 'hcHeaders' text, - 'hcFollowRedirects' integer DEFAULT true, - 'hcMethod' text DEFAULT 'GET', - 'hcStatus' integer, - 'hcHealth' text DEFAULT 'unknown', - FOREIGN KEY ('targetId') REFERENCES 'targets'('targetId') ON UPDATE no action ON DELETE cascade - ); - `).run(); - - db.prepare(`DROP TABLE 'limits';`).run(); - - db.prepare(` - CREATE TABLE 'limits' ( - 'limitId' text PRIMARY KEY NOT NULL, - 'featureId' text NOT NULL, - 'orgId' text NOT NULL, - 'value' real, - 'description' text, - FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade - ); - `).run(); - - db.prepare(`ALTER TABLE 'orgs' ADD 'settings' text;`).run(); - db.prepare(`ALTER TABLE 'targets' ADD 'rewritePath' text;`).run(); - db.prepare(`ALTER TABLE 'targets' ADD 'rewritePathType' text;`).run(); - db.prepare(`ALTER TABLE 'targets' ADD 'priority' integer DEFAULT 100 NOT NULL;`).run(); - - const webauthnCredentials = db - .prepare( - `SELECT credentialId, publicKey, userId, signCount, transports, name, lastUsed, dateCreated FROM 'webauthnCredentials'` - ) - .all() as { - credentialId: string; - publicKey: string; - userId: string; - signCount: number; - transports: string | null; - name: string | null; - lastUsed: string; - dateCreated: string; - }[]; - - db.prepare(`DELETE FROM 'webauthnCredentials';`).run(); - - for (const webauthnCredential of webauthnCredentials) { - const newCredentialId = isoBase64URL.fromBuffer( - new Uint8Array( - Buffer.from(webauthnCredential.credentialId, "base64") - ) - ); - const newPublicKey = isoBase64URL.fromBuffer( - new Uint8Array( - Buffer.from(webauthnCredential.publicKey, "base64") - ) - ); - - // Insert the updated record with converted values - db.prepare( - `INSERT INTO 'webauthnCredentials' (credentialId, publicKey, userId, signCount, transports, name, lastUsed, dateCreated) VALUES (?, ?, ?, ?, ?, ?, ?, ?)` - ).run( - newCredentialId, - newPublicKey, - webauthnCredential.userId, - webauthnCredential.signCount, - webauthnCredential.transports, - webauthnCredential.name, - webauthnCredential.lastUsed, - webauthnCredential.dateCreated - ); - } - - // 1. Add the column (nullable or with placeholder) if it doesn’t exist yet - db.prepare( - `ALTER TABLE resources ADD COLUMN resourceGuid TEXT DEFAULT 'PLACEHOLDER';` - ).run(); - - // 2. Select all rows - const resources = db.prepare(`SELECT resourceId FROM resources`).all() as { - resourceId: number; - }[]; - - // 3. Prefill with random UUIDs - const updateStmt = db.prepare( - `UPDATE resources SET resourceGuid = ? WHERE resourceId = ?` - ); - - for (const row of resources) { - updateStmt.run(randomUUID(), row.resourceId); - } - - // get all of the targets - const targets = db.prepare(`SELECT targetId FROM targets`).all() as { - targetId: number; - }[]; - - const insertTargetHealthCheckStmt = db.prepare( - `INSERT INTO targetHealthCheck (targetId) VALUES (?)` - ); - - for (const target of targets) { - insertTargetHealthCheckStmt.run(target.targetId); - } - - db.prepare( - `CREATE UNIQUE INDEX resources_resourceGuid_unique ON resources ('resourceGuid');` - ).run(); - })(); - - console.log(`${version} migration complete`); -} diff --git a/server/setup/scriptsSqlite/1.11.1.ts b/server/setup/scriptsSqlite/1.11.1.ts deleted file mode 100644 index 7f9065b6e..000000000 --- a/server/setup/scriptsSqlite/1.11.1.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { APP_PATH } from "@server/lib/consts"; -import Database from "better-sqlite3"; -import path from "path"; - -const version = "1.11.1"; - -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); - - db.transaction(() => { - const exitNodes = db.prepare(`SELECT * FROM exitNodes WHERE type = 'gerbil' LIMIT 1`).all() as { - exitNodeId: number; - name: string; - }[]; - - const exitNodeId = exitNodes.length > 0 ? exitNodes[0].exitNodeId : null; - - // get all of the targets - const sites = db.prepare(`SELECT * FROM sites WHERE type = 'local'`).all() as { - siteId: number; - exitNodeId: number | null; - }[]; - - const defineExitNodeOnSite = db.prepare( - `UPDATE sites SET exitNodeId = ? WHERE siteId = ?` - ); - - for (const site of sites) { - defineExitNodeOnSite.run(exitNodeId, site.siteId); - } - })(); - - console.log(`${version} migration complete`); -} \ No newline at end of file diff --git a/server/setup/scriptsSqlite/1.2.0.ts b/server/setup/scriptsSqlite/1.2.0.ts deleted file mode 100644 index e6ba029af..000000000 --- a/server/setup/scriptsSqlite/1.2.0.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { db } from "../../db/sqlite"; -import { APP_PATH, configFilePath1, configFilePath2 } from "@server/lib/consts"; -import { 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"; - -const version = "1.2.0"; - -export default async function migration() { - console.log(`Running setup script ${version}...`); - - try { - db.transaction((trx) => { - trx.run( - sql`ALTER TABLE 'resources' ADD 'enabled' integer DEFAULT true NOT NULL;` - ); - }); - - console.log(`Migrated database schema`); - } catch (e) { - console.log("Unable to migrate database schema"); - throw e; - } - - 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 - const fileContents = fs.readFileSync(filePath, "utf8"); - const rawConfig = yaml.load(fileContents) as any; - - if (!rawConfig.flags) { - rawConfig.flags = {}; - } - - rawConfig.server.resource_access_token_headers = { - id: "P-Access-Token-Id", - token: "P-Access-Token" - }; - - // Write the updated YAML back to the file - const updatedYaml = yaml.dump(rawConfig); - fs.writeFileSync(filePath, updatedYaml, "utf8"); - - console.log(`Added new config option: resource_access_token_headers`); - } catch (e) { - console.log( - `Unable to add new config option: resource_access_token_headers. Please add it manually. https://docs.pangolin.net/self-host/advanced/config-file` - ); - console.error(e); - } - - try { - const traefikPath = path.join( - APP_PATH, - "traefik", - "traefik_config.yml" - ); - - const schema = z.object({ - 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; - - const parsedConfig = schema.safeParse(traefikConfig); - - if (!parsedConfig.success) { - throw new Error(fromZodError(parsedConfig.error).toString()); - } - - traefikConfig.experimental.plugins.badger.version = "v1.1.0"; - - const updatedTraefikYaml = yaml.dump(traefikConfig); - - fs.writeFileSync(traefikPath, updatedTraefikYaml, "utf8"); - - console.log( - "Updated the version of Badger in your Traefik configuration to v1.1.0" - ); - } catch (e) { - console.log( - "We were unable to update the version of Badger in your Traefik configuration. Please update it manually. Check the release notes for this version for more information." - ); - console.error(e); - } - - console.log(`${version} migration complete`); -} diff --git a/server/setup/scriptsSqlite/1.3.0.ts b/server/setup/scriptsSqlite/1.3.0.ts deleted file mode 100644 index a084d59ff..000000000 --- a/server/setup/scriptsSqlite/1.3.0.ts +++ /dev/null @@ -1,203 +0,0 @@ -import Database from "better-sqlite3"; -import path from "path"; -import fs from "fs"; -import yaml from "js-yaml"; -import { encodeBase32LowerCaseNoPadding } from "@oslojs/encoding"; -import { APP_PATH, configFilePath1, configFilePath2 } from "@server/lib/consts"; - -const version = "1.3.0"; -const location = path.join(APP_PATH, "db", "db.sqlite"); - -export default async function migration() { - console.log(`Running setup script ${version}...`); - - const db = new Database(location); - - try { - db.pragma("foreign_keys = OFF"); - db.transaction(() => { - db.exec(` - CREATE TABLE 'apiKeyActions' ( - 'apiKeyId' text NOT NULL, - 'actionId' text NOT NULL, - FOREIGN KEY ('apiKeyId') REFERENCES 'apiKeys'('apiKeyId') ON UPDATE no action ON DELETE cascade, - FOREIGN KEY ('actionId') REFERENCES 'actions'('actionId') ON UPDATE no action ON DELETE cascade - ); - - CREATE TABLE 'apiKeyOrg' ( - 'apiKeyId' text NOT NULL, - 'orgId' text NOT NULL, - FOREIGN KEY ('apiKeyId') REFERENCES 'apiKeys'('apiKeyId') ON UPDATE no action ON DELETE cascade, - FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade - ); - - CREATE TABLE 'apiKeys' ( - 'apiKeyId' text PRIMARY KEY NOT NULL, - 'name' text NOT NULL, - 'apiKeyHash' text NOT NULL, - 'lastChars' text NOT NULL, - 'dateCreated' text NOT NULL, - 'isRoot' integer DEFAULT false NOT NULL - ); - - CREATE TABLE 'hostMeta' ( - 'hostMetaId' text PRIMARY KEY NOT NULL, - 'createdAt' integer NOT NULL - ); - - CREATE TABLE 'idp' ( - 'idpId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, - 'name' text NOT NULL, - 'type' text NOT NULL, - 'defaultRoleMapping' text, - 'defaultOrgMapping' text, - 'autoProvision' integer DEFAULT false NOT NULL - ); - - CREATE TABLE 'idpOidcConfig' ( - 'idpOauthConfigId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, - 'idpId' integer NOT NULL, - 'clientId' text NOT NULL, - 'clientSecret' text NOT NULL, - 'authUrl' text NOT NULL, - 'tokenUrl' text NOT NULL, - 'identifierPath' text NOT NULL, - 'emailPath' text, - 'namePath' text, - 'scopes' text NOT NULL, - FOREIGN KEY ('idpId') REFERENCES 'idp'('idpId') ON UPDATE no action ON DELETE cascade - ); - - CREATE TABLE 'idpOrg' ( - 'idpId' integer NOT NULL, - 'orgId' text NOT NULL, - 'roleMapping' text, - 'orgMapping' text, - FOREIGN KEY ('idpId') REFERENCES 'idp'('idpId') ON UPDATE no action ON DELETE cascade, - FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade - ); - - CREATE TABLE 'licenseKey' ( - 'licenseKeyId' text PRIMARY KEY NOT NULL, - 'instanceId' text NOT NULL, - 'token' text NOT NULL - ); - - CREATE TABLE '__new_user' ( - 'id' text PRIMARY KEY NOT NULL, - 'email' text, - 'username' text NOT NULL, - 'name' text, - 'type' text NOT NULL, - 'idpId' integer, - 'passwordHash' text, - 'twoFactorEnabled' integer DEFAULT false NOT NULL, - 'twoFactorSecret' text, - 'emailVerified' integer DEFAULT false NOT NULL, - 'dateCreated' text NOT NULL, - 'serverAdmin' integer DEFAULT false NOT NULL, - FOREIGN KEY ('idpId') REFERENCES 'idp'('idpId') ON UPDATE no action ON DELETE cascade - ); - - INSERT INTO '__new_user'( - "id", "email", "username", "name", "type", "idpId", "passwordHash", - "twoFactorEnabled", "twoFactorSecret", "emailVerified", "dateCreated", "serverAdmin" - ) - SELECT - "id", - "email", - COALESCE("email", 'unknown'), - NULL, - 'internal', - NULL, - "passwordHash", - "twoFactorEnabled", - "twoFactorSecret", - "emailVerified", - "dateCreated", - "serverAdmin" - FROM 'user'; - - DROP TABLE 'user'; - ALTER TABLE '__new_user' RENAME TO 'user'; - - ALTER TABLE 'resources' ADD 'stickySession' integer DEFAULT false NOT NULL; - ALTER TABLE 'resources' ADD 'tlsServerName' text; - ALTER TABLE 'resources' ADD 'setHostHeader' text; - - CREATE TABLE 'exitNodes_new' ( - 'exitNodeId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, - 'name' text NOT NULL, - 'address' text NOT NULL, - 'endpoint' text NOT NULL, - 'publicKey' text NOT NULL, - 'listenPort' integer NOT NULL, - 'reachableAt' text - ); - - INSERT INTO 'exitNodes_new' ( - 'exitNodeId', 'name', 'address', 'endpoint', 'publicKey', 'listenPort', 'reachableAt' - ) - SELECT - exitNodeId, - name, - address, - endpoint, - pubicKey, - listenPort, - reachableAt - FROM exitNodes; - - DROP TABLE 'exitNodes'; - ALTER TABLE 'exitNodes_new' RENAME TO 'exitNodes'; - `); - })(); // <-- executes the transaction immediately - db.pragma("foreign_keys = ON"); - console.log(`Migrated database schema`); - } catch (e) { - console.log("Unable to migrate database schema"); - throw e; - } - - // Update config file - try { - 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).` - ); - } - - const fileContents = fs.readFileSync(filePath, "utf8"); - const rawConfig = yaml.load(fileContents) as any; - - if (!rawConfig.server.secret) { - rawConfig.server.secret = generateIdFromEntropySize(32); - } - - const updatedYaml = yaml.dump(rawConfig); - fs.writeFileSync(filePath, updatedYaml, "utf8"); - - console.log(`Added new config option: server.secret`); - } catch (e) { - console.log( - `Unable to add new config option: server.secret. Please add it manually.` - ); - console.error(e); - } - - console.log(`${version} migration complete`); -} - -function generateIdFromEntropySize(size: number): string { - const buffer = crypto.getRandomValues(new Uint8Array(size)); - return encodeBase32LowerCaseNoPadding(buffer); -} diff --git a/server/setup/scriptsSqlite/1.5.0.ts b/server/setup/scriptsSqlite/1.5.0.ts deleted file mode 100644 index 46e9cccaa..000000000 --- a/server/setup/scriptsSqlite/1.5.0.ts +++ /dev/null @@ -1,70 +0,0 @@ -import Database from "better-sqlite3"; -import path from "path"; -import { APP_PATH, configFilePath1, configFilePath2 } from "@server/lib/consts"; -import fs from "fs"; -import yaml from "js-yaml"; - -const version = "1.5.0"; -const location = path.join(APP_PATH, "db", "db.sqlite"); - -export default async function migration() { - console.log(`Running setup script ${version}...`); - - const db = new Database(location); - - try { - db.pragma("foreign_keys = OFF"); - db.transaction(() => { - db.exec(` - ALTER TABLE 'sites' ADD 'dockerSocketEnabled' integer DEFAULT true NOT NULL; - `); - })(); // <-- executes the transaction immediately - db.pragma("foreign_keys = ON"); - console.log(`Migrated database schema`); - } catch (e) { - console.log("Unable to migrate database schema"); - throw e; - } - - 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 - const fileContents = fs.readFileSync(filePath, "utf8"); - const rawConfig = yaml.load(fileContents) as any; - - if (rawConfig.cors?.headers) { - const headers = JSON.parse( - JSON.stringify(rawConfig.cors.headers) - ); - rawConfig.cors.allowed_headers = headers; - delete rawConfig.cors.headers; - } - - // Write the updated YAML back to the file - const updatedYaml = yaml.dump(rawConfig); - fs.writeFileSync(filePath, updatedYaml, "utf8"); - - console.log(`Migrated CORS headers to allowed_headers`); - } catch (e) { - console.log( - `Unable to migrate config file. Error: ${e}` - ); - } - - console.log(`${version} migration complete`); -} diff --git a/server/setup/scriptsSqlite/1.6.0.ts b/server/setup/scriptsSqlite/1.6.0.ts deleted file mode 100644 index adab26977..000000000 --- a/server/setup/scriptsSqlite/1.6.0.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { APP_PATH, configFilePath1, configFilePath2 } from "@server/lib/consts"; -import Database from "better-sqlite3"; -import fs from "fs"; -import yaml from "js-yaml"; -import path from "path"; - -const version = "1.6.0"; - -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.pragma("foreign_keys = OFF"); - db.transaction(() => { - db.exec(` - UPDATE 'user' SET email = LOWER(email); - UPDATE 'user' SET username = LOWER(username); - `); - })(); // <-- executes the transaction immediately - db.pragma("foreign_keys = ON"); - console.log(`Migrated database schema`); - } catch (e) { - console.log("Unable to make all usernames and emails lowercase"); - console.log(e); - } - - 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 - const fileContents = fs.readFileSync(filePath, "utf8"); - const rawConfig = yaml.load(fileContents) as any; - - if (rawConfig.server?.trust_proxy) { - rawConfig.server.trust_proxy = 1; - } - - // Write the updated YAML back to the file - const updatedYaml = yaml.dump(rawConfig); - fs.writeFileSync(filePath, updatedYaml, "utf8"); - - console.log(`Set trust_proxy to 1 in config file`); - } catch (e) { - console.log(`Unable to migrate config file. Please do it manually. Error: ${e}`); - } - - console.log(`${version} migration complete`); -} diff --git a/server/setup/scriptsSqlite/1.7.0.ts b/server/setup/scriptsSqlite/1.7.0.ts deleted file mode 100644 index f173d12e9..000000000 --- a/server/setup/scriptsSqlite/1.7.0.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { APP_PATH } from "@server/lib/consts"; -import Database from "better-sqlite3"; -import path from "path"; - -const version = "1.7.0"; - -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.pragma("foreign_keys = OFF"); - - db.transaction(() => { - db.exec(` - CREATE TABLE 'clientSites' ( - 'clientId' integer NOT NULL, - 'siteId' integer NOT NULL, - 'isRelayed' integer DEFAULT 0 NOT NULL, - FOREIGN KEY ('clientId') REFERENCES 'clients'('id') ON UPDATE no action ON DELETE cascade, - FOREIGN KEY ('siteId') REFERENCES 'sites'('siteId') ON UPDATE no action ON DELETE cascade - ); - - CREATE TABLE 'clients' ( - 'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL, - 'orgId' text NOT NULL, - 'exitNode' integer, - 'name' text NOT NULL, - 'pubKey' text, - 'subnet' text NOT NULL, - 'bytesIn' integer, - 'bytesOut' integer, - 'lastBandwidthUpdate' text, - 'lastPing' text, - 'type' text NOT NULL, - 'online' integer DEFAULT 0 NOT NULL, - 'endpoint' text, - 'lastHolePunch' integer, - FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade, - FOREIGN KEY ('exitNode') REFERENCES 'exitNodes'('exitNodeId') ON UPDATE no action ON DELETE set null - ); - - CREATE TABLE 'clientSession' ( - 'id' text PRIMARY KEY NOT NULL, - 'olmId' text NOT NULL, - 'expiresAt' integer NOT NULL, - FOREIGN KEY ('olmId') REFERENCES 'olms'('id') ON UPDATE no action ON DELETE cascade - ); - - CREATE TABLE 'olms' ( - 'id' text PRIMARY KEY NOT NULL, - 'secretHash' text NOT NULL, - 'dateCreated' text NOT NULL, - 'clientId' integer, - FOREIGN KEY ('clientId') REFERENCES 'clients'('id') ON UPDATE no action ON DELETE cascade - ); - - CREATE TABLE 'roleClients' ( - 'roleId' integer NOT NULL, - 'clientId' integer NOT NULL, - FOREIGN KEY ('roleId') REFERENCES 'roles'('roleId') ON UPDATE no action ON DELETE cascade, - FOREIGN KEY ('clientId') REFERENCES 'clients'('id') ON UPDATE no action ON DELETE cascade - ); - - CREATE TABLE 'webauthnCredentials' ( - 'credentialId' text PRIMARY KEY NOT NULL, - 'userId' text NOT NULL, - 'publicKey' text NOT NULL, - 'signCount' integer NOT NULL, - 'transports' text, - 'name' text, - 'lastUsed' text NOT NULL, - 'dateCreated' text NOT NULL, - FOREIGN KEY ('userId') REFERENCES 'user'('id') ON UPDATE no action ON DELETE cascade - ); - - CREATE TABLE 'userClients' ( - 'userId' text NOT NULL, - 'clientId' integer NOT NULL, - FOREIGN KEY ('userId') REFERENCES 'user'('id') ON UPDATE no action ON DELETE cascade, - FOREIGN KEY ('clientId') REFERENCES 'clients'('id') ON UPDATE no action ON DELETE cascade - ); - - CREATE TABLE 'userDomains' ( - 'userId' text NOT NULL, - 'domainId' text NOT NULL, - FOREIGN KEY ('userId') REFERENCES 'user'('id') ON UPDATE no action ON DELETE cascade, - FOREIGN KEY ('domainId') REFERENCES 'domains'('domainId') ON UPDATE no action ON DELETE cascade - ); - - CREATE TABLE 'webauthnChallenge' ( - 'sessionId' text PRIMARY KEY NOT NULL, - 'challenge' text NOT NULL, - 'securityKeyName' text, - 'userId' text, - 'expiresAt' integer NOT NULL, - FOREIGN KEY ('userId') REFERENCES 'user'('id') ON UPDATE no action ON DELETE cascade - ); - - `); - - db.exec(` - CREATE TABLE '__new_sites' ( - 'siteId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, - 'orgId' text NOT NULL, - 'niceId' text NOT NULL, - 'exitNode' integer, - 'name' text NOT NULL, - 'pubKey' text, - 'subnet' text, - 'bytesIn' integer DEFAULT 0, - 'bytesOut' integer DEFAULT 0, - 'lastBandwidthUpdate' text, - 'type' text NOT NULL, - 'online' integer DEFAULT 0 NOT NULL, - 'address' text, - 'endpoint' text, - 'publicKey' text, - 'lastHolePunch' integer, - 'listenPort' integer, - 'dockerSocketEnabled' integer DEFAULT 1 NOT NULL, - FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade, - FOREIGN KEY ('exitNode') REFERENCES 'exitNodes'('exitNodeId') ON UPDATE no action ON DELETE set null - ); - - INSERT INTO '__new_sites' ( - 'siteId', 'orgId', 'niceId', 'exitNode', 'name', 'pubKey', 'subnet', 'bytesIn', 'bytesOut', 'lastBandwidthUpdate', 'type', 'online', 'address', 'endpoint', 'publicKey', 'lastHolePunch', 'listenPort', 'dockerSocketEnabled' - ) - SELECT siteId, orgId, niceId, exitNode, name, pubKey, subnet, bytesIn, bytesOut, lastBandwidthUpdate, type, online, NULL, NULL, NULL, NULL, NULL, dockerSocketEnabled - FROM sites; - - DROP TABLE 'sites'; - ALTER TABLE '__new_sites' RENAME TO 'sites'; - `); - - db.exec(` - ALTER TABLE 'domains' ADD 'type' text; - ALTER TABLE 'domains' ADD 'verified' integer DEFAULT 0 NOT NULL; - ALTER TABLE 'domains' ADD 'failed' integer DEFAULT 0 NOT NULL; - ALTER TABLE 'domains' ADD 'tries' integer DEFAULT 0 NOT NULL; - ALTER TABLE 'exitNodes' ADD 'maxConnections' integer; - ALTER TABLE 'newt' ADD 'version' text; - ALTER TABLE 'orgs' ADD 'subnet' text; - ALTER TABLE 'user' ADD 'twoFactorSetupRequested' integer DEFAULT 0; - ALTER TABLE 'resources' DROP COLUMN 'isBaseDomain'; - `); - })(); - - db.pragma("foreign_keys = ON"); - - console.log(`Migrated database schema`); - } catch (e) { - console.log("Unable to migrate database schema"); - throw e; - } - - db.transaction(() => { - // Update all existing orgs to have the default subnet - db.exec(`UPDATE 'orgs' SET 'subnet' = '100.90.128.0/24'`); - - // Get all orgs and their sites to assign sequential IP addresses - const orgs = db.prepare(`SELECT orgId FROM 'orgs'`).all() as { - orgId: string; - }[]; - - for (const org of orgs) { - const sites = db - .prepare( - `SELECT siteId FROM 'sites' WHERE orgId = ? ORDER BY siteId` - ) - .all(org.orgId) as { siteId: number }[]; - - let ipIndex = 1; - for (const site of sites) { - const address = `100.90.128.${ipIndex}/24`; - db.prepare( - `UPDATE 'sites' SET 'address' = ? WHERE siteId = ?` - ).run(address, site.siteId); - ipIndex++; - } - } - })(); - - console.log(`${version} migration complete`); -} diff --git a/server/setup/scriptsSqlite/1.8.0.ts b/server/setup/scriptsSqlite/1.8.0.ts deleted file mode 100644 index f8ac7c951..000000000 --- a/server/setup/scriptsSqlite/1.8.0.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { APP_PATH } from "@server/lib/consts"; -import Database from "better-sqlite3"; -import path from "path"; - -const version = "1.8.0"; - -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.exec(` - ALTER TABLE 'resources' ADD 'enableProxy' integer DEFAULT 1; - ALTER TABLE 'sites' ADD 'remoteSubnets' text; - ALTER TABLE 'user' ADD 'termsAcceptedTimestamp' text; - ALTER TABLE 'user' ADD 'termsVersion' text; - `); - })(); - - console.log("Migrated database schema"); - } catch (e) { - console.log("Unable to migrate database schema"); - throw e; - } - - console.log(`${version} migration complete`); -} diff --git a/server/setup/scriptsSqlite/1.9.0.ts b/server/setup/scriptsSqlite/1.9.0.ts deleted file mode 100644 index 5f247ea50..000000000 --- a/server/setup/scriptsSqlite/1.9.0.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { APP_PATH } from "@server/lib/consts"; -import Database from "better-sqlite3"; -import path from "path"; - -const version = "1.9.0"; - -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); - - const resourceSiteMap = new Map(); - let firstSiteId: number = 1; - - try { - // Get the first siteId to use as default - const firstSite = db.prepare("SELECT siteId FROM sites LIMIT 1").get() as { siteId: number } | undefined; - if (firstSite) { - firstSiteId = firstSite.siteId; - } - - const resources = db - .prepare( - "SELECT resourceId, siteId FROM resources WHERE siteId IS NOT NULL" - ) - .all() as Array<{ resourceId: number; siteId: number }>; - for (const resource of resources) { - resourceSiteMap.set(resource.resourceId, resource.siteId); - } - } catch (e) { - console.log("Error getting resources:", e); - } - - try { - db.pragma("foreign_keys = OFF"); - - db.transaction(() => { - db.exec(`CREATE TABLE 'setupTokens' ( - 'tokenId' text PRIMARY KEY NOT NULL, - 'token' text NOT NULL, - 'used' integer DEFAULT false NOT NULL, - 'dateCreated' text NOT NULL, - 'dateUsed' text -); ---> statement-breakpoint -CREATE TABLE 'siteResources' ( - 'siteResourceId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, - 'siteId' integer NOT NULL, - 'orgId' text NOT NULL, - 'name' text NOT NULL, - 'protocol' text NOT NULL, - 'proxyPort' integer NOT NULL, - 'destinationPort' integer NOT NULL, - 'destinationIp' text NOT NULL, - 'enabled' integer DEFAULT true NOT NULL, - FOREIGN KEY ('siteId') REFERENCES 'sites'('siteId') ON UPDATE no action ON DELETE cascade, - FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade -); ---> statement-breakpoint -PRAGMA foreign_keys=OFF;--> statement-breakpoint -CREATE TABLE '__new_resources' ( - 'resourceId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, - 'orgId' text NOT NULL, - 'name' text NOT NULL, - 'subdomain' text, - 'fullDomain' text, - 'domainId' text, - 'ssl' integer DEFAULT false NOT NULL, - 'blockAccess' integer DEFAULT false NOT NULL, - 'sso' integer DEFAULT true NOT NULL, - 'http' integer DEFAULT true NOT NULL, - 'protocol' text NOT NULL, - 'proxyPort' integer, - 'emailWhitelistEnabled' integer DEFAULT false NOT NULL, - 'applyRules' integer DEFAULT false NOT NULL, - 'enabled' integer DEFAULT true NOT NULL, - 'stickySession' integer DEFAULT false NOT NULL, - 'tlsServerName' text, - 'setHostHeader' text, - 'enableProxy' integer DEFAULT true, - 'skipToIdpId' integer, - 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 set null, - FOREIGN KEY ('skipToIdpId') REFERENCES 'idp'('idpId') ON UPDATE no action ON DELETE cascade -); ---> statement-breakpoint -INSERT INTO '__new_resources'("resourceId", "orgId", "name", "subdomain", "fullDomain", "domainId", "ssl", "blockAccess", "sso", "http", "protocol", "proxyPort", "emailWhitelistEnabled", "applyRules", "enabled", "stickySession", "tlsServerName", "setHostHeader", "enableProxy", "skipToIdpId") SELECT "resourceId", "orgId", "name", "subdomain", "fullDomain", "domainId", "ssl", "blockAccess", "sso", "http", "protocol", "proxyPort", "emailWhitelistEnabled", "applyRules", "enabled", "stickySession", "tlsServerName", "setHostHeader", "enableProxy", null FROM 'resources';--> statement-breakpoint -DROP TABLE 'resources';--> statement-breakpoint -ALTER TABLE '__new_resources' RENAME TO 'resources';--> statement-breakpoint -PRAGMA foreign_keys=ON;--> statement-breakpoint -CREATE TABLE '__new_clients' ( - 'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL, - 'orgId' text NOT NULL, - 'exitNode' integer, - 'name' text NOT NULL, - 'pubKey' text, - 'subnet' text NOT NULL, - 'bytesIn' integer, - 'bytesOut' integer, - 'lastBandwidthUpdate' text, - 'lastPing' integer, - 'type' text NOT NULL, - 'online' integer DEFAULT false NOT NULL, - 'lastHolePunch' integer, - FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade, - FOREIGN KEY ('exitNode') REFERENCES 'exitNodes'('exitNodeId') ON UPDATE no action ON DELETE set null -); ---> statement-breakpoint -INSERT INTO '__new_clients'("id", "orgId", "exitNode", "name", "pubKey", "subnet", "bytesIn", "bytesOut", "lastBandwidthUpdate", "lastPing", "type", "online", "lastHolePunch") SELECT "id", "orgId", "exitNode", "name", "pubKey", "subnet", "bytesIn", "bytesOut", "lastBandwidthUpdate", NULL, "type", "online", "lastHolePunch" FROM 'clients';--> statement-breakpoint -DROP TABLE 'clients';--> statement-breakpoint -ALTER TABLE '__new_clients' RENAME TO 'clients';--> statement-breakpoint -ALTER TABLE 'clientSites' ADD 'endpoint' text;--> statement-breakpoint -ALTER TABLE 'exitNodes' ADD 'online' integer DEFAULT false NOT NULL;--> statement-breakpoint -ALTER TABLE 'exitNodes' ADD 'lastPing' integer;--> statement-breakpoint -ALTER TABLE 'exitNodes' ADD 'type' text DEFAULT 'gerbil';--> statement-breakpoint -ALTER TABLE 'olms' ADD 'version' text;--> statement-breakpoint -ALTER TABLE 'orgs' ADD 'createdAt' text;--> statement-breakpoint -ALTER TABLE 'targets' ADD 'siteId' integer NOT NULL DEFAULT ${firstSiteId || 1} REFERENCES sites(siteId);`); - - // for each resource, get all of its targets, and update the siteId to be the previously stored siteId - for (const [resourceId, siteId] of resourceSiteMap) { - const targets = db - .prepare( - "SELECT targetId FROM targets WHERE resourceId = ?" - ) - .all(resourceId) as Array<{ targetId: number }>; - for (const target of targets) { - db.prepare( - "UPDATE targets SET siteId = ? WHERE targetId = ?" - ).run(siteId, target.targetId); - } - } - - // list resources that have enableProxy false - // move them to the siteResources table - // remove them from the resources table - const proxyFalseResources = db - .prepare("SELECT * FROM resources WHERE enableProxy = 0") - .all() as Array; - - for (const resource of proxyFalseResources) { - // Get the first target to derive destination IP and port - const firstTarget = db - .prepare( - "SELECT ip, port FROM targets WHERE resourceId = ? LIMIT 1" - ) - .get(resource.resourceId) as - | { ip: string; port: number } - | undefined; - - if (!firstTarget) { - continue; - } - - // Insert into siteResources table - const stmt = db.prepare(` - INSERT INTO siteResources (siteId, orgId, name, protocol, proxyPort, destinationPort, destinationIp, enabled) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) - `); - stmt.run( - resourceSiteMap.get(resource.resourceId), - resource.orgId, - resource.name, - resource.protocol, - resource.proxyPort, - firstTarget.port, - firstTarget.ip, - resource.enabled - ); - - // Delete from resources table - db.prepare("DELETE FROM resources WHERE resourceId = ?").run( - resource.resourceId - ); - - // Delete the targets for this resource - db.prepare("DELETE FROM targets WHERE resourceId = ?").run( - resource.resourceId - ); - } - })(); - - db.pragma("foreign_keys = ON"); - - console.log(`Migrated database`); - } catch (e) { - console.log("Failed to migrate db:", e); - throw e; - } -} diff --git a/server/setup/scriptsSqlite/2.0.0.ts b/server/setup/scriptsSqlite/2.0.0.ts new file mode 100644 index 000000000..2380b6458 --- /dev/null +++ b/server/setup/scriptsSqlite/2.0.0.ts @@ -0,0 +1,14 @@ +import { APP_PATH } from "@server/lib/consts"; +import { createClient } from "@libsql/client"; +import path from "path"; + +const version = "2.0.0"; + +export default async function migration() { + console.log(`Running setup script ${version}...`); + + const location = path.join(APP_PATH, "db", "db.sqlite"); + const db = createClient({ url: "file:" + location }); + + console.log(`${version} migration complete`); +} \ No newline at end of file From fcad3a185506e250c3e35df7432a8d9ca558102a Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Tue, 21 Oct 2025 08:00:03 +0000 Subject: [PATCH 05/20] set min version to 1.11.2 --- server/setup/migrationsSqlite.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/setup/migrationsSqlite.ts b/server/setup/migrationsSqlite.ts index f98215084..197ef25ae 100644 --- a/server/setup/migrationsSqlite.ts +++ b/server/setup/migrationsSqlite.ts @@ -85,7 +85,7 @@ export async function runMigrations() { async function executeScripts() { try { - const requriedPreviousVersion = "1.11.1"; + const requriedPreviousVersion = "1.11.2"; // Get the last executed version from the database const lastExecuted = await db.select().from(versionMigrations); From 80be0da6902402c428e735718c3d2b2268337bc7 Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Tue, 21 Oct 2025 08:22:24 +0000 Subject: [PATCH 06/20] test --- server/lib/consts.ts | 2 +- server/setup/migrationsSqlite.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/server/lib/consts.ts b/server/lib/consts.ts index 8ad98167e..f8d2855a3 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.11.0"; +export const APP_VERSION = "2.0.0"; export const __FILENAME = fileURLToPath(import.meta.url); export const __DIRNAME = path.dirname(__FILENAME); diff --git a/server/setup/migrationsSqlite.ts b/server/setup/migrationsSqlite.ts index 197ef25ae..b2c10294b 100644 --- a/server/setup/migrationsSqlite.ts +++ b/server/setup/migrationsSqlite.ts @@ -98,10 +98,10 @@ async function executeScripts() { if (!semver.eq(lastVersion, requriedPreviousVersion)) { console.error( - `Starting App not allowed. Your previous version is: ${lastVersion}. ` + - `Please update first to version ${requriedPreviousVersion} due to breaking changes in version 2.0.0.` - ); - process.exit(1); + `Starting App not allowed. Your previous version is: ${lastVersion}. ` + + `Please update first to version ${requriedPreviousVersion} due to breaking changes in version 2.0.0.` + ); + process.exit(1); } console.log(`Starting migrations from version ${startVersion}`); From 3555a204d6fc462bc1be1f2bbb0b516835a24563 Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Tue, 21 Oct 2025 08:26:48 +0000 Subject: [PATCH 07/20] merge main --- server/setup/migrationsSqlite.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/setup/migrationsSqlite.ts b/server/setup/migrationsSqlite.ts index b2c10294b..5abeb9468 100644 --- a/server/setup/migrationsSqlite.ts +++ b/server/setup/migrationsSqlite.ts @@ -7,14 +7,14 @@ import { versionMigrations } from "../db/sqlite"; import { __DIRNAME, APP_PATH, APP_VERSION } from "@server/lib/consts"; import { LibsqlError } from "@libsql/client"; import fs from "fs"; -import m29 from "./scriptsSqlite/2.0.0"; +import m30 from "./scriptsSqlite/2.0.0"; // 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: "2.0.0", run: m29 }, + { version: "2.0.0", run: m30 }, // Add new migrations here as they are created ] as const; From c751929545d415067ab6afc89ce647cc65b5b165 Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Tue, 21 Oct 2025 08:41:22 +0000 Subject: [PATCH 08/20] more logs in migration --- server/setup/migrationsSqlite.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/setup/migrationsSqlite.ts b/server/setup/migrationsSqlite.ts index 5abeb9468..e4a860861 100644 --- a/server/setup/migrationsSqlite.ts +++ b/server/setup/migrationsSqlite.ts @@ -96,6 +96,11 @@ async function executeScripts() { const startVersion = pendingMigrations[0]?.version ?? APP_VERSION; const lastVersion = pendingMigrations[pendingMigrations.length - 1].version; + console.log(`Current App Version ${APP_VERSION}`); + console.log(`Latest migration version ${lastVersion}`); + console.log(`Starting migrations from version ${startVersion}`); + console.log(`Required version ${requriedPreviousVersion}`); + if (!semver.eq(lastVersion, requriedPreviousVersion)) { console.error( `Starting App not allowed. Your previous version is: ${lastVersion}. ` + From 6fe59169da375e05fc5ec83b881492fcea05423b Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Tue, 21 Oct 2025 09:50:04 +0000 Subject: [PATCH 09/20] update version --- server/lib/consts.ts | 2 +- server/setup/migrationsSqlite.ts | 16 ++++------------ 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/server/lib/consts.ts b/server/lib/consts.ts index f8d2855a3..8ad98167e 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 = "2.0.0"; +export const APP_VERSION = "1.11.0"; export const __FILENAME = fileURLToPath(import.meta.url); export const __DIRNAME = path.dirname(__FILENAME); diff --git a/server/setup/migrationsSqlite.ts b/server/setup/migrationsSqlite.ts index e4a860861..c14ccd4f5 100644 --- a/server/setup/migrationsSqlite.ts +++ b/server/setup/migrationsSqlite.ts @@ -85,7 +85,7 @@ export async function runMigrations() { async function executeScripts() { try { - const requriedPreviousVersion = "1.11.2"; + const requriedPreviousVersion = "1.11.1"; // Get the last executed version from the database const lastExecuted = await db.select().from(versionMigrations); @@ -94,18 +94,10 @@ async function executeScripts() { .map((m) => m) .sort((a, b) => semver.compare(b.version, a.version)); const startVersion = pendingMigrations[0]?.version ?? APP_VERSION; - const lastVersion = pendingMigrations[pendingMigrations.length - 1].version; - console.log(`Current App Version ${APP_VERSION}`); - console.log(`Latest migration version ${lastVersion}`); - console.log(`Starting migrations from version ${startVersion}`); - console.log(`Required version ${requriedPreviousVersion}`); - - if (!semver.eq(lastVersion, requriedPreviousVersion)) { - console.error( - `Starting App not allowed. Your previous version is: ${lastVersion}. ` + - `Please update first to version ${requriedPreviousVersion} due to breaking changes in version 2.0.0.` - ); + if (!semver.eq(startVersion, requriedPreviousVersion)) { + console.error(`Starting App not allowed. Your previous version is: ${startVersion}.`); + console.error(`Please update first to version ${requriedPreviousVersion} due to breaking changes in version 2.0.0.`); process.exit(1); } From 1d25812c129c2aa5b3dab69e3d2948315cbbbb1b Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Tue, 21 Oct 2025 09:59:34 +0000 Subject: [PATCH 10/20] updatre const --- 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 8ad98167e..f8d2855a3 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.11.0"; +export const APP_VERSION = "2.0.0"; export const __FILENAME = fileURLToPath(import.meta.url); export const __DIRNAME = path.dirname(__FILENAME); From 779f0d3f45425a5274c374025dc061d780d3dfa3 Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Tue, 21 Oct 2025 10:23:40 +0000 Subject: [PATCH 11/20] fix typo --- server/setup/migrationsSqlite.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/setup/migrationsSqlite.ts b/server/setup/migrationsSqlite.ts index c14ccd4f5..17fd83763 100644 --- a/server/setup/migrationsSqlite.ts +++ b/server/setup/migrationsSqlite.ts @@ -85,7 +85,7 @@ export async function runMigrations() { async function executeScripts() { try { - const requriedPreviousVersion = "1.11.1"; + const requiredPreviousVersion = "1.11.1"; // Get the last executed version from the database const lastExecuted = await db.select().from(versionMigrations); @@ -95,9 +95,9 @@ async function executeScripts() { .sort((a, b) => semver.compare(b.version, a.version)); const startVersion = pendingMigrations[0]?.version ?? APP_VERSION; - if (!semver.eq(startVersion, requriedPreviousVersion)) { + if (!semver.eq(startVersion, requiredPreviousVersion)) { console.error(`Starting App not allowed. Your previous version is: ${startVersion}.`); - console.error(`Please update first to version ${requriedPreviousVersion} due to breaking changes in version 2.0.0.`); + console.error(`Please update first to version ${requiredPreviousVersion} due to breaking changes in version 2.0.0.`); process.exit(1); } From ab989442cae04cca550474722bb1e5dfb7585aa5 Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Tue, 21 Oct 2025 11:50:44 +0000 Subject: [PATCH 12/20] fix migration check --- server/setup/migrationsSqlite.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/setup/migrationsSqlite.ts b/server/setup/migrationsSqlite.ts index 17fd83763..e6cbf0bfc 100644 --- a/server/setup/migrationsSqlite.ts +++ b/server/setup/migrationsSqlite.ts @@ -85,7 +85,7 @@ export async function runMigrations() { async function executeScripts() { try { - const requiredPreviousVersion = "1.11.1"; + const requiredMinimumVersion = "1.11.1"; // Get the last executed version from the database const lastExecuted = await db.select().from(versionMigrations); @@ -95,9 +95,9 @@ async function executeScripts() { .sort((a, b) => semver.compare(b.version, a.version)); const startVersion = pendingMigrations[0]?.version ?? APP_VERSION; - if (!semver.eq(startVersion, requiredPreviousVersion)) { + if (semver.lt(startVersion, requiredMinimumVersion)) { console.error(`Starting App not allowed. Your previous version is: ${startVersion}.`); - console.error(`Please update first to version ${requiredPreviousVersion} due to breaking changes in version 2.0.0.`); + console.error(`Please update first to version ${requiredMinimumVersion} due to breaking changes in version 2.0.0.`); process.exit(1); } From 5209f2dd99f0ef70724822303e8dc911658dd65e Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Sun, 16 Nov 2025 09:59:07 +0000 Subject: [PATCH 13/20] update migration --- package-lock.json | 52 ++++-- server/setup/migrationsSqlite.ts | 8 +- server/setup/scriptsSqlite/1.12.0.ts | 250 --------------------------- 3 files changed, 38 insertions(+), 272 deletions(-) delete mode 100644 server/setup/scriptsSqlite/1.12.0.ts diff --git a/package-lock.json b/package-lock.json index 23a8953b7..bbf519ca3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -115,8 +115,8 @@ "@dotenvx/dotenvx": "1.51.1", "@esbuild-plugins/tsconfig-paths": "0.1.2", "@react-email/preview-server": "4.3.2", - "@tanstack/react-query-devtools": "^5.90.2", "@tailwindcss/postcss": "^4.1.17", + "@tanstack/react-query-devtools": "^5.90.2", "@types/cookie-parser": "1.4.10", "@types/cors": "2.8.19", "@types/crypto-js": "^4.2.2", @@ -1643,6 +1643,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -3873,6 +3874,7 @@ "resolved": "https://registry.npmjs.org/@libsql/client/-/client-0.15.15.tgz", "integrity": "sha512-twC0hQxPNHPKfeOv3sNT6u2pturQjLcI+CnpTM0SjRpocEGgfiZ7DWKXLNnsothjyJmDqEsBQJ5ztq9Wlu470w==", "license": "MIT", + "peer": true, "dependencies": { "@libsql/core": "^0.15.14", "@libsql/hrana-client": "^0.7.0", @@ -4248,6 +4250,7 @@ "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -7144,6 +7147,7 @@ "integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -7349,6 +7353,7 @@ "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -7359,6 +7364,7 @@ "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.25.0" }, @@ -8793,6 +8799,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.6.tgz", "integrity": "sha512-gB1sljYjcobZKxjPbKSa31FUTyr+ROaBdoH+wSSs9Dk+yDCmMs+TkTV3PybRRVLC7ax7q0erJ9LvRWnMktnRAw==", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/query-core": "5.90.6" }, @@ -8974,6 +8981,7 @@ "integrity": "sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", @@ -9066,6 +9074,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.2.tgz", "integrity": "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -9101,6 +9110,7 @@ "integrity": "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*", "pg-protocol": "*", @@ -9134,6 +9144,7 @@ "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -9144,6 +9155,7 @@ "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -9286,6 +9298,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.3.tgz", "integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==", "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.3", "@typescript-eslint/types": "8.46.3", @@ -9959,6 +9972,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -10582,6 +10596,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -11560,8 +11575,7 @@ "version": "3.1.7", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.7.tgz", "integrity": "sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ==", - "license": "(MPL-2.0 OR Apache-2.0)", - "peer": true + "license": "(MPL-2.0 OR Apache-2.0)" }, "node_modules/domutils": { "version": "3.2.2", @@ -12208,6 +12222,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -12304,6 +12319,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.0.tgz", "integrity": "sha512-iy2GE3MHrYTL5lrCtMZ0X1KLEKKUjmK0kzwcnefhR66txcEmXZD2YWgR5GNdcEwkNx3a0siYkSvl0vIC+Svjmg==", "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -12481,6 +12497,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -12768,6 +12785,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", @@ -15383,7 +15401,6 @@ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.54.0.tgz", "integrity": "sha512-hx45SEUoLatgWxHKCmlLJH81xBo0uXP4sRkESUpmDQevfi+e7K1VuiSprK6UpQ8u4zOcKNiH0pMvHvlMWA/4cw==", "license": "MIT", - "peer": true, "dependencies": { "dompurify": "3.1.7", "marked": "14.0.0" @@ -15394,7 +15411,6 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", "license": "MIT", - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -15511,6 +15527,7 @@ "resolved": "https://registry.npmjs.org/next/-/next-15.5.6.tgz", "integrity": "sha512-zTxsnI3LQo3c9HSdSf91O1jMNsEzIXDShXd4wVdg9y5shwLqBXi4ZtUUJyB86KGVSJLZx0PFONvO54aheGX8QQ==", "license": "MIT", + "peer": true, "dependencies": { "@next/env": "15.5.6", "@swc/helpers": "0.5.15", @@ -15641,18 +15658,6 @@ "react-dom": ">= 16.0.0" } }, - "node_modules/node-abi": { - "version": "3.78.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.78.0.tgz", - "integrity": "sha512-E2wEyrgX/CqvicaQYU3Ze1PFGjc4QYPGsjUrlYkqAE0WjHEZwgOsGMPMzkMse4LjJbDmaEuDX3CM036j5K2DSQ==", - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/node-cache": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", @@ -17957,6 +17962,7 @@ "version": "4.0.3", "inBundle": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -18941,6 +18947,7 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", "license": "MIT", + "peer": true, "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", @@ -19117,6 +19124,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -19520,6 +19528,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -19550,6 +19559,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -19841,6 +19851,7 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.66.0.tgz", "integrity": "sha512-xXBqsWGKrY46ZqaHDo+ZUYiMUgi8suYu5kdrS20EG8KiL7VRQitEbNjm+UcrDYrNi1YLyfpmAeGjCZYXLT9YBw==", "license": "MIT", + "peer": true, "engines": { "node": ">=18.0.0" }, @@ -20334,6 +20345,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -21508,7 +21520,8 @@ "version": "4.1.17", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz", "integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tapable": { "version": "2.3.0", @@ -21992,6 +22005,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -22504,6 +22518,7 @@ "resolved": "https://registry.npmjs.org/winston/-/winston-3.18.3.tgz", "integrity": "sha512-NoBZauFNNWENgsnC9YpgyYwOVrl2m58PpQ8lNHjV3kosGs7KJ7Npk9pCUE+WJlawVSe8mykWDKWFSVfs3QO9ww==", "license": "MIT", + "peer": true, "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.8", @@ -22810,6 +22825,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/server/setup/migrationsSqlite.ts b/server/setup/migrationsSqlite.ts index e6cbf0bfc..9a7c3dec6 100644 --- a/server/setup/migrationsSqlite.ts +++ b/server/setup/migrationsSqlite.ts @@ -7,14 +7,14 @@ import { versionMigrations } from "../db/sqlite"; import { __DIRNAME, APP_PATH, APP_VERSION } from "@server/lib/consts"; import { LibsqlError } from "@libsql/client"; import fs from "fs"; -import m30 from "./scriptsSqlite/2.0.0"; +import m31 from "./scriptsSqlite/1.13.0"; // 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: "2.0.0", run: m30 }, + { version: "1.13.0", run: m31 }, // Add new migrations here as they are created ] as const; @@ -85,7 +85,7 @@ export async function runMigrations() { async function executeScripts() { try { - const requiredMinimumVersion = "1.11.1"; + const requiredMinimumVersion = "1.12.2"; // Get the last executed version from the database const lastExecuted = await db.select().from(versionMigrations); @@ -97,7 +97,7 @@ async function executeScripts() { if (semver.lt(startVersion, requiredMinimumVersion)) { console.error(`Starting App not allowed. Your previous version is: ${startVersion}.`); - console.error(`Please update first to version ${requiredMinimumVersion} due to breaking changes in version 2.0.0.`); + console.error(`Please update first to version ${requiredMinimumVersion} due to breaking changes in version 1.13.0.`); process.exit(1); } diff --git a/server/setup/scriptsSqlite/1.12.0.ts b/server/setup/scriptsSqlite/1.12.0.ts deleted file mode 100644 index bb357c81f..000000000 --- a/server/setup/scriptsSqlite/1.12.0.ts +++ /dev/null @@ -1,250 +0,0 @@ -import { APP_PATH } from "@server/lib/consts"; -import Database from "better-sqlite3"; -import path from "path"; - -const version = "1.12.0"; - -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.pragma("foreign_keys = OFF"); - - db.transaction(() => { - db.prepare( - `UPDATE 'resourceRules' SET match = 'COUNTRY' WHERE match = 'GEOIP'` - ).run(); - - db.prepare( - ` - CREATE TABLE 'accessAuditLog' ( - 'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL, - 'timestamp' integer NOT NULL, - 'orgId' text NOT NULL, - 'actorType' text, - 'actor' text, - 'actorId' text, - 'resourceId' integer, - 'ip' text, - 'location' text, - 'type' text NOT NULL, - 'action' integer NOT NULL, - 'userAgent' text, - 'metadata' text, - FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade - ); - ` - ).run(); - - db.prepare( - `CREATE INDEX 'idx_identityAuditLog_timestamp' ON 'accessAuditLog' ('timestamp');` - ).run(); - db.prepare( - `CREATE INDEX 'idx_identityAuditLog_org_timestamp' ON 'accessAuditLog' ('orgId','timestamp');` - ).run(); - - db.prepare( - ` - CREATE TABLE 'actionAuditLog' ( - 'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL, - 'timestamp' integer NOT NULL, - 'orgId' text NOT NULL, - 'actorType' text NOT NULL, - 'actor' text NOT NULL, - 'actorId' text NOT NULL, - 'action' text NOT NULL, - 'metadata' text, - FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade - ); - ` - ).run(); - - db.prepare( - `CREATE INDEX 'idx_actionAuditLog_timestamp' ON 'actionAuditLog' ('timestamp');` - ).run(); - db.prepare( - `CREATE INDEX 'idx_actionAuditLog_org_timestamp' ON 'actionAuditLog' ('orgId','timestamp');` - ).run(); - - db.prepare( - ` - CREATE TABLE 'dnsRecords' ( - 'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL, - 'domainId' text NOT NULL, - 'recordType' text NOT NULL, - 'baseDomain' text, - 'value' text NOT NULL, - 'verified' integer DEFAULT false NOT NULL, - FOREIGN KEY ('domainId') REFERENCES 'domains'('domainId') ON UPDATE no action ON DELETE cascade - ); - ` - ).run(); - - db.prepare( - ` - CREATE TABLE 'requestAuditLog' ( - 'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL, - 'timestamp' integer NOT NULL, - 'orgId' text, - 'action' integer NOT NULL, - 'reason' integer NOT NULL, - 'actorType' text, - 'actor' text, - 'actorId' text, - 'resourceId' integer, - 'ip' text, - 'location' text, - 'userAgent' text, - 'metadata' text, - 'headers' text, - 'query' text, - 'originalRequestURL' text, - 'scheme' text, - 'host' text, - 'path' text, - 'method' text, - 'tls' integer, - FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade - ); - ` - ).run(); - - - db.prepare( - ` - CREATE TABLE 'blueprints' ( - 'blueprintId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, - 'orgId' text NOT NULL, - 'name' text NOT NULL, - 'source' text NOT NULL, - 'createdAt' integer NOT NULL, - 'succeeded' integer NOT NULL, - 'contents' text NOT NULL, - 'message' text, - FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade - ); - ` - ).run(); - - db.prepare( - `CREATE INDEX 'idx_requestAuditLog_timestamp' ON 'requestAuditLog' ('timestamp');` - ).run(); - db.prepare( - `CREATE INDEX 'idx_requestAuditLog_org_timestamp' ON 'requestAuditLog' ('orgId','timestamp');` - ).run(); - - db.prepare( - ` - CREATE TABLE '__new_resources' ( - 'resourceId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, - 'resourceGuid' text(36) NOT NULL, - 'orgId' text NOT NULL, - 'niceId' text NOT NULL, - 'name' text NOT NULL, - 'subdomain' text, - 'fullDomain' text, - 'domainId' text, - 'ssl' integer DEFAULT false NOT NULL, - 'blockAccess' integer DEFAULT false NOT NULL, - 'sso' integer DEFAULT true NOT NULL, - 'http' integer DEFAULT true NOT NULL, - 'protocol' text NOT NULL, - 'proxyPort' integer, - 'emailWhitelistEnabled' integer DEFAULT false NOT NULL, - 'applyRules' integer DEFAULT false NOT NULL, - 'enabled' integer DEFAULT true NOT NULL, - 'stickySession' integer DEFAULT false NOT NULL, - 'tlsServerName' text, - 'setHostHeader' text, - 'enableProxy' integer DEFAULT true, - 'skipToIdpId' integer, - 'headers' text, - 'proxyProtocol' integer DEFAULT false NOT NULL, - 'proxyProtocolVersion' integer DEFAULT 1, - 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 set null, - FOREIGN KEY ('skipToIdpId') REFERENCES 'idp'('idpId') ON UPDATE no action ON DELETE set null - ); - ` - ).run(); - - db.prepare( - `INSERT INTO '__new_resources'("resourceId", "resourceGuid", "orgId", "niceId", "name", "subdomain", "fullDomain", "domainId", "ssl", "blockAccess", "sso", "http", "protocol", "proxyPort", "emailWhitelistEnabled", "applyRules", "enabled", "stickySession", "tlsServerName", "setHostHeader", "enableProxy", "skipToIdpId", "headers") SELECT "resourceId", "resourceGuid", "orgId", "niceId", "name", "subdomain", "fullDomain", "domainId", "ssl", "blockAccess", "sso", "http", "protocol", "proxyPort", "emailWhitelistEnabled", "applyRules", "enabled", "stickySession", "tlsServerName", "setHostHeader", "enableProxy", "skipToIdpId", "headers" FROM 'resources';` - ).run(); - db.prepare(`DROP TABLE 'resources';`).run(); - db.prepare( - `ALTER TABLE '__new_resources' RENAME TO 'resources';` - ).run(); - - db.prepare( - `CREATE UNIQUE INDEX 'resources_resourceGuid_unique' ON 'resources' ('resourceGuid');` - ).run(); - db.prepare(`ALTER TABLE 'domains' ADD 'certResolver' text;`).run(); - db.prepare( - `ALTER TABLE 'domains' ADD 'preferWildcardCert' integer;` - ).run(); - db.prepare( - `ALTER TABLE 'orgs' ADD 'requireTwoFactor' integer;` - ).run(); - db.prepare( - `ALTER TABLE 'orgs' ADD 'maxSessionLengthHours' integer;` - ).run(); - db.prepare( - `ALTER TABLE 'orgs' ADD 'passwordExpiryDays' integer;` - ).run(); - db.prepare( - `ALTER TABLE 'orgs' ADD 'settingsLogRetentionDaysRequest' integer DEFAULT 7 NOT NULL;` - ).run(); - db.prepare( - `ALTER TABLE 'orgs' ADD 'settingsLogRetentionDaysAccess' integer DEFAULT 0 NOT NULL;` - ).run(); - db.prepare( - `ALTER TABLE 'orgs' ADD 'settingsLogRetentionDaysAction' integer DEFAULT 0 NOT NULL;` - ).run(); - db.prepare(`ALTER TABLE 'orgs' DROP COLUMN 'settings';`).run(); - db.prepare( - `ALTER TABLE 'resourceSessions' ADD 'issuedAt' integer;` - ).run(); - db.prepare(`ALTER TABLE 'session' ADD 'issuedAt' integer;`).run(); - db.prepare( - `ALTER TABLE 'user' ADD 'lastPasswordChange' integer;` - ).run(); - db.prepare(`ALTER TABLE 'remoteExitNode' ADD 'secondaryVersion' text;`).run(); - - // get all of the domains - const domains = db.prepare(`SELECT domainId, baseDomain from domains`).all() as { - domainId: number; - baseDomain: string; - }[]; - - for (const domain of domains) { - // insert two records into the dnsRecords table for each domain - const insert = db.prepare( - `INSERT INTO 'dnsRecords' (domainId, recordType, baseDomain, value, verified) VALUES (?, 'A', ?, ?, 1)` - ); - insert.run( - domain.domainId, - `*.${domain.baseDomain}`, - `Server IP Address` - ); - insert.run( - domain.domainId, - `${domain.baseDomain}`, - `Server IP Address` - ); - } - })(); - - db.pragma("foreign_keys = ON"); - - console.log(`Migrated database`); - } catch (e) { - console.log("Failed to migrate db:", e); - throw e; - } - - console.log(`${version} migration complete`); -} From b8a2aef0232b53c0f96a713615224b8a2189f32e Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Sun, 16 Nov 2025 10:02:08 +0000 Subject: [PATCH 14/20] change migration version --- server/setup/scriptsSqlite/{2.0.0.ts => 1.13.0.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename server/setup/scriptsSqlite/{2.0.0.ts => 1.13.0.ts} (100%) diff --git a/server/setup/scriptsSqlite/2.0.0.ts b/server/setup/scriptsSqlite/1.13.0.ts similarity index 100% rename from server/setup/scriptsSqlite/2.0.0.ts rename to server/setup/scriptsSqlite/1.13.0.ts From 40c0d118ad4ca0d096b3419f1a65f0db56b5f348 Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Sun, 16 Nov 2025 10:03:48 +0000 Subject: [PATCH 15/20] remove temp test --- Makefile | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Makefile b/Makefile index 4c52e7776..6c538a477 100644 --- a/Makefile +++ b/Makefile @@ -91,11 +91,3 @@ test: clean: docker rmi pangolin - -test-local: - cp config/config.example.yml config/config.yml - npm run set:oss - npm run set:sqlite - - npx tsc --noEmit - - docker build --build-arg DATABASE=pg -t fosrl/pangolin:postgresql-latest . - - docker build --build-arg DATABASE=sqlite -t fosrl/pangolin:latest . From 951260405d91c572917d3032e1311d4d2e554c9e Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Sun, 16 Nov 2025 14:01:06 +0000 Subject: [PATCH 16/20] change version --- server/lib/consts.ts | 2 +- server/setup/scriptsSqlite/1.13.0.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/lib/consts.ts b/server/lib/consts.ts index c2cf6698a..d93cf2249 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.12.1"; +export const APP_VERSION = "1.13.0"; export const __FILENAME = fileURLToPath(import.meta.url); export const __DIRNAME = path.dirname(__FILENAME); diff --git a/server/setup/scriptsSqlite/1.13.0.ts b/server/setup/scriptsSqlite/1.13.0.ts index 2380b6458..ad6e97550 100644 --- a/server/setup/scriptsSqlite/1.13.0.ts +++ b/server/setup/scriptsSqlite/1.13.0.ts @@ -2,7 +2,7 @@ import { APP_PATH } from "@server/lib/consts"; import { createClient } from "@libsql/client"; import path from "path"; -const version = "2.0.0"; +const version = "1.13.0"; export default async function migration() { console.log(`Running setup script ${version}...`); From 7335c20a2c620cfb4ca8577cfee8b22c390298b7 Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Thu, 20 Nov 2025 13:45:45 +0000 Subject: [PATCH 17/20] Revert "migrations" This reverts commit ac04eec1466efb70ec2527fc04938b706fb1180a. --- server/setup/migrationsSqlite.ts | 56 +++- server/setup/scriptsSqlite/1.0.0-beta1.ts | 5 + server/setup/scriptsSqlite/1.0.0-beta10.ts | 44 +++ server/setup/scriptsSqlite/1.0.0-beta12.ts | 61 ++++ server/setup/scriptsSqlite/1.0.0-beta13.ts | 33 ++ server/setup/scriptsSqlite/1.0.0-beta15.ts | 128 ++++++++ server/setup/scriptsSqlite/1.0.0-beta2.ts | 58 ++++ server/setup/scriptsSqlite/1.0.0-beta3.ts | 41 +++ server/setup/scriptsSqlite/1.0.0-beta5.ts | 100 ++++++ server/setup/scriptsSqlite/1.0.0-beta6.ts | 51 ++++ server/setup/scriptsSqlite/1.0.0-beta9.ts | 290 ++++++++++++++++++ server/setup/scriptsSqlite/1.0.0.ts | 57 ++++ server/setup/scriptsSqlite/1.1.0.ts | 28 ++ server/setup/scriptsSqlite/1.10.0.ts | 136 +++++++++ server/setup/scriptsSqlite/1.10.1.ts | 69 +++++ server/setup/scriptsSqlite/1.10.2.ts | 54 ++++ server/setup/scriptsSqlite/1.11.0.ts | 339 +++++++++++++++++++++ server/setup/scriptsSqlite/1.11.1.ts | 37 +++ server/setup/scriptsSqlite/1.13.0.ts | 14 - server/setup/scriptsSqlite/1.2.0.ts | 114 +++++++ server/setup/scriptsSqlite/1.3.0.ts | 203 ++++++++++++ server/setup/scriptsSqlite/1.5.0.ts | 70 +++++ server/setup/scriptsSqlite/1.6.0.ts | 65 ++++ server/setup/scriptsSqlite/1.7.0.ts | 187 ++++++++++++ server/setup/scriptsSqlite/1.8.0.ts | 30 ++ server/setup/scriptsSqlite/1.9.0.ts | 191 ++++++++++++ 26 files changed, 2437 insertions(+), 24 deletions(-) create mode 100644 server/setup/scriptsSqlite/1.0.0-beta1.ts create mode 100644 server/setup/scriptsSqlite/1.0.0-beta10.ts create mode 100644 server/setup/scriptsSqlite/1.0.0-beta12.ts create mode 100644 server/setup/scriptsSqlite/1.0.0-beta13.ts create mode 100644 server/setup/scriptsSqlite/1.0.0-beta15.ts create mode 100644 server/setup/scriptsSqlite/1.0.0-beta2.ts create mode 100644 server/setup/scriptsSqlite/1.0.0-beta3.ts create mode 100644 server/setup/scriptsSqlite/1.0.0-beta5.ts create mode 100644 server/setup/scriptsSqlite/1.0.0-beta6.ts create mode 100644 server/setup/scriptsSqlite/1.0.0-beta9.ts create mode 100644 server/setup/scriptsSqlite/1.0.0.ts create mode 100644 server/setup/scriptsSqlite/1.1.0.ts create mode 100644 server/setup/scriptsSqlite/1.10.0.ts create mode 100644 server/setup/scriptsSqlite/1.10.1.ts create mode 100644 server/setup/scriptsSqlite/1.10.2.ts create mode 100644 server/setup/scriptsSqlite/1.11.0.ts create mode 100644 server/setup/scriptsSqlite/1.11.1.ts delete mode 100644 server/setup/scriptsSqlite/1.13.0.ts create mode 100644 server/setup/scriptsSqlite/1.2.0.ts create mode 100644 server/setup/scriptsSqlite/1.3.0.ts create mode 100644 server/setup/scriptsSqlite/1.5.0.ts create mode 100644 server/setup/scriptsSqlite/1.6.0.ts create mode 100644 server/setup/scriptsSqlite/1.7.0.ts create mode 100644 server/setup/scriptsSqlite/1.8.0.ts create mode 100644 server/setup/scriptsSqlite/1.9.0.ts diff --git a/server/setup/migrationsSqlite.ts b/server/setup/migrationsSqlite.ts index 9a7c3dec6..1611c4926 100644 --- a/server/setup/migrationsSqlite.ts +++ b/server/setup/migrationsSqlite.ts @@ -7,14 +7,58 @@ import { versionMigrations } from "../db/sqlite"; import { __DIRNAME, APP_PATH, APP_VERSION } from "@server/lib/consts"; import { LibsqlError } from "@libsql/client"; import fs from "fs"; -import m31 from "./scriptsSqlite/1.13.0"; +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"; // 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.13.0", run: m31 }, + { 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 }, // Add new migrations here as they are created ] as const; @@ -85,7 +129,6 @@ export async function runMigrations() { async function executeScripts() { try { - const requiredMinimumVersion = "1.12.2"; // Get the last executed version from the database const lastExecuted = await db.select().from(versionMigrations); @@ -94,13 +137,6 @@ async function executeScripts() { .map((m) => m) .sort((a, b) => semver.compare(b.version, a.version)); const startVersion = pendingMigrations[0]?.version ?? APP_VERSION; - - if (semver.lt(startVersion, requiredMinimumVersion)) { - console.error(`Starting App not allowed. Your previous version is: ${startVersion}.`); - console.error(`Please update first to version ${requiredMinimumVersion} due to breaking changes in version 1.13.0.`); - process.exit(1); - } - console.log(`Starting migrations from version ${startVersion}`); const migrationsToRun = migrations.filter((migration) => diff --git a/server/setup/scriptsSqlite/1.0.0-beta1.ts b/server/setup/scriptsSqlite/1.0.0-beta1.ts new file mode 100644 index 000000000..65d9ad1b7 --- /dev/null +++ b/server/setup/scriptsSqlite/1.0.0-beta1.ts @@ -0,0 +1,5 @@ +export default async function migration() { + console.log("Running setup script 1.0.0-beta.1..."); + // SQL operations would go here in ts format + console.log("Done."); +} diff --git a/server/setup/scriptsSqlite/1.0.0-beta10.ts b/server/setup/scriptsSqlite/1.0.0-beta10.ts new file mode 100644 index 000000000..400cbc318 --- /dev/null +++ b/server/setup/scriptsSqlite/1.0.0-beta10.ts @@ -0,0 +1,44 @@ +import { configFilePath1, configFilePath2 } from "@server/lib/consts"; +import fs from "fs"; +import yaml from "js-yaml"; + +export default async function migration() { + console.log("Running setup script 1.0.0-beta.10..."); + + 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 + const fileContents = fs.readFileSync(filePath, "utf8"); + const rawConfig = yaml.load(fileContents) as any; + + delete rawConfig.server.secure_cookies; + + // Write the updated YAML back to the file + const updatedYaml = yaml.dump(rawConfig); + fs.writeFileSync(filePath, updatedYaml, "utf8"); + + console.log(`Removed deprecated config option: secure_cookies.`); + } catch (e) { + console.log( + `Was unable to remove deprecated config option: secure_cookies. Error: ${e}` + ); + return; + } + + console.log("Done."); +} diff --git a/server/setup/scriptsSqlite/1.0.0-beta12.ts b/server/setup/scriptsSqlite/1.0.0-beta12.ts new file mode 100644 index 000000000..8c96e663c --- /dev/null +++ b/server/setup/scriptsSqlite/1.0.0-beta12.ts @@ -0,0 +1,61 @@ +import { db } from "../../db/sqlite"; +import { configFilePath1, configFilePath2 } from "@server/lib/consts"; +import { sql } from "drizzle-orm"; +import fs from "fs"; +import yaml from "js-yaml"; + +export default async function migration() { + console.log("Running setup script 1.0.0-beta.12..."); + + 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 + const fileContents = fs.readFileSync(filePath, "utf8"); + const rawConfig = yaml.load(fileContents) as any; + + if (!rawConfig.flags) { + rawConfig.flags = {}; + } + + rawConfig.flags.allow_base_domain_resources = true; + + // Write the updated YAML back to the file + const updatedYaml = yaml.dump(rawConfig); + fs.writeFileSync(filePath, updatedYaml, "utf8"); + + console.log(`Added new config option: allow_base_domain_resources`); + } catch (e) { + console.log( + `Unable to add new config option: allow_base_domain_resources. This is not critical.` + ); + console.error(e); + } + + try { + db.transaction((trx) => { + trx.run(sql`ALTER TABLE 'resources' ADD 'isBaseDomain' integer;`); + }); + + console.log(`Added new column: isBaseDomain`); + } catch (e) { + console.log("Unable to add new column: isBaseDomain"); + throw e; + } + + console.log("Done."); +} diff --git a/server/setup/scriptsSqlite/1.0.0-beta13.ts b/server/setup/scriptsSqlite/1.0.0-beta13.ts new file mode 100644 index 000000000..9ced727f7 --- /dev/null +++ b/server/setup/scriptsSqlite/1.0.0-beta13.ts @@ -0,0 +1,33 @@ +import { db } from "../../db/sqlite"; +import { sql } from "drizzle-orm"; + +const version = "1.0.0-beta.13"; + +export default async function migration() { + console.log(`Running setup script ${version}...`); + + try { + db.transaction((trx) => { + trx.run(sql`CREATE TABLE resourceRules ( + ruleId integer PRIMARY KEY AUTOINCREMENT NOT NULL, + resourceId integer NOT NULL, + priority integer NOT NULL, + enabled integer DEFAULT true NOT NULL, + action text NOT NULL, + match text NOT NULL, + value text NOT NULL, + FOREIGN KEY (resourceId) REFERENCES resources(resourceId) ON UPDATE no action ON DELETE cascade + );`); + trx.run( + sql`ALTER TABLE resources ADD applyRules integer DEFAULT false NOT NULL;` + ); + }); + + console.log(`Added new table and column: resourceRules, applyRules`); + } catch (e) { + console.log("Unable to add new table and column: resourceRules, applyRules"); + throw e; + } + + console.log(`${version} migration complete`); +} diff --git a/server/setup/scriptsSqlite/1.0.0-beta15.ts b/server/setup/scriptsSqlite/1.0.0-beta15.ts new file mode 100644 index 000000000..cf39fd8a3 --- /dev/null +++ b/server/setup/scriptsSqlite/1.0.0-beta15.ts @@ -0,0 +1,128 @@ +import { db } from "../../db/sqlite"; +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"; + +const version = "1.0.0-beta.15"; + +export default async function migration() { + console.log(`Running setup script ${version}...`); + + let domain = ""; + + 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 + const fileContents = fs.readFileSync(filePath, "utf8"); + const rawConfig = yaml.load(fileContents) as any; + + const baseDomain = rawConfig.app.base_domain; + const certResolver = rawConfig.traefik.cert_resolver; + const preferWildcardCert = rawConfig.traefik.prefer_wildcard_cert; + + delete rawConfig.traefik.prefer_wildcard_cert; + delete rawConfig.traefik.cert_resolver; + delete rawConfig.app.base_domain; + + rawConfig.domains = { + domain1: { + base_domain: baseDomain + } + }; + + if (certResolver) { + rawConfig.domains.domain1.cert_resolver = certResolver; + } + + if (preferWildcardCert) { + rawConfig.domains.domain1.prefer_wildcard_cert = preferWildcardCert; + } + + // Write the updated YAML back to the file + 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; + } + + console.log(`${version} migration complete`); +} diff --git a/server/setup/scriptsSqlite/1.0.0-beta2.ts b/server/setup/scriptsSqlite/1.0.0-beta2.ts new file mode 100644 index 000000000..1241e9c51 --- /dev/null +++ b/server/setup/scriptsSqlite/1.0.0-beta2.ts @@ -0,0 +1,58 @@ +import { configFilePath1, configFilePath2 } from "@server/lib/consts"; +import fs from "fs"; +import yaml from "js-yaml"; + +export default async function migration() { + console.log("Running setup script 1.0.0-beta.2..."); + + // 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 + const fileContents = fs.readFileSync(filePath, "utf8"); + const rawConfig = yaml.load(fileContents) as any; + + // Validate the structure + if (!rawConfig.app || !rawConfig.app.base_url) { + throw new Error(`Invalid config file: app.base_url is missing.`); + } + + // Move base_url to dashboard_url and calculate base_domain + const baseUrl = rawConfig.app.base_url; + rawConfig.app.dashboard_url = baseUrl; + rawConfig.app.base_domain = getBaseDomain(baseUrl); + + // Remove the old base_url + delete rawConfig.app.base_url; + + // Write the updated YAML back to the file + const updatedYaml = yaml.dump(rawConfig); + fs.writeFileSync(filePath, updatedYaml, "utf8"); + + console.log("Done."); +} + +function getBaseDomain(url: string): string { + const newUrl = new URL(url); + const hostname = newUrl.hostname; + const parts = hostname.split("."); + + if (parts.length <= 2) { + return parts.join("."); + } + + return parts.slice(-2).join("."); +} diff --git a/server/setup/scriptsSqlite/1.0.0-beta3.ts b/server/setup/scriptsSqlite/1.0.0-beta3.ts new file mode 100644 index 000000000..fccfeb887 --- /dev/null +++ b/server/setup/scriptsSqlite/1.0.0-beta3.ts @@ -0,0 +1,41 @@ +import { configFilePath1, configFilePath2 } from "@server/lib/consts"; +import fs from "fs"; +import yaml from "js-yaml"; + +export default async function migration() { + console.log("Running setup script 1.0.0-beta.3..."); + + // 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 + const fileContents = fs.readFileSync(filePath, "utf8"); + const rawConfig = yaml.load(fileContents) as any; + + // Validate the structure + if (!rawConfig.gerbil) { + throw new Error(`Invalid config file: gerbil is missing.`); + } + + // Update the config + rawConfig.gerbil.site_block_size = 29; + + // Write the updated YAML back to the file + const updatedYaml = yaml.dump(rawConfig); + fs.writeFileSync(filePath, updatedYaml, "utf8"); + + console.log("Done."); +} \ No newline at end of file diff --git a/server/setup/scriptsSqlite/1.0.0-beta5.ts b/server/setup/scriptsSqlite/1.0.0-beta5.ts new file mode 100644 index 000000000..1c49503cd --- /dev/null +++ b/server/setup/scriptsSqlite/1.0.0-beta5.ts @@ -0,0 +1,100 @@ +import { APP_PATH, configFilePath1, configFilePath2 } from "@server/lib/consts"; +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.5..."); + + // 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 + const fileContents = fs.readFileSync(filePath, "utf8"); + const rawConfig = yaml.load(fileContents) as any; + + // Validate the structure + if (!rawConfig.server) { + throw new Error(`Invalid config file: server is missing.`); + } + + // Update the config + rawConfig.server.resource_access_token_param = "p_token"; + + // Write the updated YAML back to the file + const updatedYaml = yaml.dump(rawConfig); + fs.writeFileSync(filePath, updatedYaml, "utf8"); + + // then try to update badger in traefik config + + try { + const traefikPath = path.join( + APP_PATH, + "traefik", + "traefik_config.yml" + ); + + // read the traefik file + // look for the badger middleware + // set the version to v1.0.0-beta.2 + /* +experimental: + plugins: + badger: + moduleName: "github.com/fosrl/badger" + version: "v1.0.0-beta.2" + */ + + const schema = z.object({ + 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; + + const parsedConfig = schema.safeParse(traefikConfig); + + if (!parsedConfig.success) { + throw new Error(fromZodError(parsedConfig.error).toString()); + } + + traefikConfig.experimental.plugins.badger.version = "v1.0.0-beta.2"; + + const updatedTraefikYaml = yaml.dump(traefikConfig); + + fs.writeFileSync(traefikPath, updatedTraefikYaml, "utf8"); + + console.log( + "Updated the version of Badger in your Traefik configuration to v1.0.0-beta.2." + ); + } catch (e) { + console.log( + "We were unable to update the version of Badger in your Traefik configuration. Please update it manually." + ); + console.error(e); + } + + console.log("Done."); +} diff --git a/server/setup/scriptsSqlite/1.0.0-beta6.ts b/server/setup/scriptsSqlite/1.0.0-beta6.ts new file mode 100644 index 000000000..891296781 --- /dev/null +++ b/server/setup/scriptsSqlite/1.0.0-beta6.ts @@ -0,0 +1,51 @@ +import { configFilePath1, configFilePath2 } from "@server/lib/consts"; +import fs from "fs"; +import yaml from "js-yaml"; + +export default async function migration() { + console.log("Running setup script 1.0.0-beta.6..."); + + 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 + const fileContents = fs.readFileSync(filePath, "utf8"); + const rawConfig = yaml.load(fileContents) as any; + + // Validate the structure + if (!rawConfig.server) { + throw new Error(`Invalid config file: server is missing.`); + } + + // Update the config + rawConfig.server.cors = { + origins: [rawConfig.app.dashboard_url], + methods: ["GET", "POST", "PUT", "DELETE", "PATCH"], + headers: ["X-CSRF-Token", "Content-Type"], + credentials: false + }; + + // Write the updated YAML back to the file + const updatedYaml = yaml.dump(rawConfig); + fs.writeFileSync(filePath, updatedYaml, "utf8"); + } catch (error) { + console.log("We were unable to add CORS to your config file. Please add it manually."); + console.error(error); + } + + console.log("Done."); +} diff --git a/server/setup/scriptsSqlite/1.0.0-beta9.ts b/server/setup/scriptsSqlite/1.0.0-beta9.ts new file mode 100644 index 000000000..7cce1c2dd --- /dev/null +++ b/server/setup/scriptsSqlite/1.0.0-beta9.ts @@ -0,0 +1,290 @@ +import { db } from "../../db/sqlite"; +import { + emailVerificationCodes, + passwordResetTokens, + resourceOtp, + resources, + resourceWhitelist, + targets, + userInvites, + users +} from "../../db/sqlite"; +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 + const fileContents = fs.readFileSync(filePath, "utf8"); + const rawConfig = yaml.load(fileContents) as any; + + 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.pangolin.net/self-host/advanced/config-file` + ); + 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; + + const 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; + + const 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."); +} diff --git a/server/setup/scriptsSqlite/1.0.0.ts b/server/setup/scriptsSqlite/1.0.0.ts new file mode 100644 index 000000000..c82966dee --- /dev/null +++ b/server/setup/scriptsSqlite/1.0.0.ts @@ -0,0 +1,57 @@ +import { APP_PATH } from "@server/lib/consts"; +import fs from "fs"; +import yaml from "js-yaml"; +import path from "path"; +import { z } from "zod"; +import { fromZodError } from "zod-validation-error"; + +const version = "1.0.0"; + +export default async function migration() { + console.log(`Running setup script ${version}...`); + + try { + const traefikPath = path.join( + APP_PATH, + "traefik", + "traefik_config.yml" + ); + + const schema = z.object({ + 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; + + const parsedConfig = schema.safeParse(traefikConfig); + + if (!parsedConfig.success) { + throw new Error(fromZodError(parsedConfig.error).toString()); + } + + traefikConfig.experimental.plugins.badger.version = "v1.0.0"; + + const updatedTraefikYaml = yaml.dump(traefikConfig); + + fs.writeFileSync(traefikPath, updatedTraefikYaml, "utf8"); + + console.log( + "Updated the version of Badger in your Traefik configuration to 1.0.0" + ); + } catch (e) { + console.log( + "We were unable to update the version of Badger in your Traefik configuration. Please update it manually." + ); + console.error(e); + } + + console.log(`${version} migration complete`); +} diff --git a/server/setup/scriptsSqlite/1.1.0.ts b/server/setup/scriptsSqlite/1.1.0.ts new file mode 100644 index 000000000..4d1218522 --- /dev/null +++ b/server/setup/scriptsSqlite/1.1.0.ts @@ -0,0 +1,28 @@ +import { db } from "../../db/sqlite"; +import { sql } from "drizzle-orm"; + +const version = "1.1.0"; + +export default async function migration() { + console.log(`Running setup script ${version}...`); + + try { + db.transaction((trx) => { + trx.run(sql`CREATE TABLE 'supporterKey' ( + 'keyId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'key' text NOT NULL, + 'githubUsername' text NOT NULL, + 'phrase' text, + 'tier' text, + 'valid' integer DEFAULT false NOT NULL +);`); + }); + + console.log(`Migrated database schema`); + } catch (e) { + console.log("Unable to migrate database schema"); + throw e; + } + + console.log(`${version} migration complete`); +} diff --git a/server/setup/scriptsSqlite/1.10.0.ts b/server/setup/scriptsSqlite/1.10.0.ts new file mode 100644 index 000000000..3065a664d --- /dev/null +++ b/server/setup/scriptsSqlite/1.10.0.ts @@ -0,0 +1,136 @@ +import { __DIRNAME, APP_PATH } from "@server/lib/consts"; +import Database from "better-sqlite3"; +import { readFileSync } from "fs"; +import path, { join } from "path"; + +const version = "1.10.0"; + +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 { + const resources = db + .prepare( + "SELECT resourceId FROM resources" + ) + .all() as Array<{ resourceId: number }>; + + const siteResources = db + .prepare( + "SELECT siteResourceId FROM siteResources" + ) + .all() as Array<{ siteResourceId: number }>; + + db.transaction(() => { + db.exec(` + ALTER TABLE 'exitNodes' ADD 'region' text; + ALTER TABLE 'idpOidcConfig' ADD 'variant' text DEFAULT 'oidc' NOT NULL; + ALTER TABLE 'resources' ADD 'niceId' text DEFAULT '' NOT NULL; + ALTER TABLE 'siteResources' ADD 'niceId' text DEFAULT '' NOT NULL; + ALTER TABLE 'userOrgs' ADD 'autoProvisioned' integer DEFAULT false; + ALTER TABLE 'targets' ADD 'pathMatchType' text; + ALTER TABLE 'targets' ADD 'path' text; + ALTER TABLE 'resources' ADD 'headers' text; + `); // this diverges from the schema a bit because the schema does not have a default on niceId but was required for the migration and I dont think it will effect much down the line... + + const usedNiceIds: string[] = []; + + for (const resourceId of resources) { + // Generate a unique name and ensure it's unique + let niceId = ""; + let loops = 0; + while (true) { + if (loops > 100) { + throw new Error("Could not generate a unique name"); + } + + niceId = generateName(); + if (!usedNiceIds.includes(niceId)) { + usedNiceIds.push(niceId); + break; + } + loops++; + } + db.prepare( + `UPDATE resources SET niceId = ? WHERE resourceId = ?` + ).run(niceId, resourceId.resourceId); + } + + for (const resourceId of siteResources) { + // Generate a unique name and ensure it's unique + let niceId = ""; + let loops = 0; + while (true) { + if (loops > 100) { + throw new Error("Could not generate a unique name"); + } + + niceId = generateName(); + if (!usedNiceIds.includes(niceId)) { + usedNiceIds.push(niceId); + break; + } + loops++; + } + db.prepare( + `UPDATE siteResources SET niceId = ? WHERE siteResourceId = ?` + ).run(niceId, resourceId.siteResourceId); + } + + // Handle auto-provisioned users for identity providers + const autoProvisionIdps = db + .prepare( + "SELECT idpId FROM idp WHERE autoProvision = 1" + ) + .all() as Array<{ idpId: number }>; + + for (const idp of autoProvisionIdps) { + // Get all users with this identity provider + const usersWithIdp = db + .prepare( + "SELECT id FROM user WHERE idpId = ?" + ) + .all(idp.idpId) as Array<{ id: string }>; + + // Update userOrgs to set autoProvisioned to true for these users + for (const user of usersWithIdp) { + db.prepare( + "UPDATE userOrgs SET autoProvisioned = 1 WHERE userId = ?" + ).run(user.id); + } + } + })(); + + console.log(`Migrated database`); + } catch (e) { + console.log("Failed to migrate db:", e); + throw e; + } +} + +const dev = process.env.ENVIRONMENT !== "prod"; +let file; +if (!dev) { + file = join(__DIRNAME, "names.json"); +} else { + file = join("server/db/names.json"); +} +export const names = JSON.parse(readFileSync(file, "utf-8")); + +export function generateName(): string { + const name = ( + names.descriptors[ + Math.floor(Math.random() * names.descriptors.length) + ] + + "-" + + names.animals[Math.floor(Math.random() * names.animals.length)] + ) + .toLowerCase() + .replace(/\s/g, "-"); + + // clean out any non-alphanumeric characters except for dashes + return name.replace(/[^a-z0-9-]/g, ""); +} diff --git a/server/setup/scriptsSqlite/1.10.1.ts b/server/setup/scriptsSqlite/1.10.1.ts new file mode 100644 index 000000000..f6f9894ed --- /dev/null +++ b/server/setup/scriptsSqlite/1.10.1.ts @@ -0,0 +1,69 @@ +import { APP_PATH } from "@server/lib/consts"; +import Database from "better-sqlite3"; +import path from "path"; + +const version = "1.10.1"; + +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.pragma("foreign_keys = OFF"); + + db.transaction(() => { + db.exec(`ALTER TABLE "targets" RENAME TO "targets_old"; +--> statement-breakpoint +CREATE TABLE "targets" ( + "targetId" INTEGER PRIMARY KEY AUTOINCREMENT, + "resourceId" INTEGER NOT NULL, + "siteId" INTEGER NOT NULL, + "ip" TEXT NOT NULL, + "method" TEXT, + "port" INTEGER NOT NULL, + "internalPort" INTEGER, + "enabled" INTEGER NOT NULL DEFAULT 1, + "path" TEXT, + "pathMatchType" TEXT, + FOREIGN KEY ("resourceId") REFERENCES "resources"("resourceId") ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ("siteId") REFERENCES "sites"("siteId") ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +INSERT INTO "targets" ( + "targetId", + "resourceId", + "siteId", + "ip", + "method", + "port", + "internalPort", + "enabled", + "path", + "pathMatchType" +) +SELECT + targetId, + resourceId, + siteId, + ip, + method, + port, + internalPort, + enabled, + path, + pathMatchType +FROM "targets_old"; +--> statement-breakpoint +DROP TABLE "targets_old";`); + })(); + + db.pragma("foreign_keys = ON"); + + console.log(`Migrated database`); + } catch (e) { + console.log("Failed to migrate db:", e); + throw e; + } +} \ No newline at end of file diff --git a/server/setup/scriptsSqlite/1.10.2.ts b/server/setup/scriptsSqlite/1.10.2.ts new file mode 100644 index 000000000..7978e2621 --- /dev/null +++ b/server/setup/scriptsSqlite/1.10.2.ts @@ -0,0 +1,54 @@ +import { APP_PATH } from "@server/lib/consts"; +import Database from "better-sqlite3"; +import path from "path"; + +const version = "1.10.2"; + +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); + + const resources = db.prepare("SELECT * FROM resources").all() as Array<{ + resourceId: number; + headers: string | null; + }>; + + try { + db.pragma("foreign_keys = OFF"); + + db.transaction(() => { + for (const resource of resources) { + const headers = resource.headers; + if (headers && headers !== "") { + // lets convert it to json + // fist split at commas + const headersArray = headers + .split(",") + .map((header: string) => { + const [name, ...valueParts] = header.split(":"); + const value = valueParts.join(":").trim(); + return { name: name.trim(), value }; + }); + + db.prepare( + ` + UPDATE "resources" SET "headers" = ? WHERE "resourceId" = ?` + ).run(JSON.stringify(headersArray), resource.resourceId); + + console.log( + `Updated resource ${resource.resourceId} headers to JSON format` + ); + } + } + })(); + + db.pragma("foreign_keys = ON"); + + console.log(`Migrated database`); + } catch (e) { + console.log("Failed to migrate db:", e); + throw e; + } +} diff --git a/server/setup/scriptsSqlite/1.11.0.ts b/server/setup/scriptsSqlite/1.11.0.ts new file mode 100644 index 000000000..c79cfdb46 --- /dev/null +++ b/server/setup/scriptsSqlite/1.11.0.ts @@ -0,0 +1,339 @@ +import { APP_PATH } from "@server/lib/consts"; +import Database from "better-sqlite3"; +import path from "path"; +import { isoBase64URL } from "@simplewebauthn/server/helpers"; +import { randomUUID } from "crypto"; + +const version = "1.11.0"; + +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); + + db.transaction(() => { + + db.prepare(` + CREATE TABLE 'account' ( + 'accountId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'userId' text NOT NULL, + FOREIGN KEY ('userId') REFERENCES 'user'('id') ON UPDATE no action ON DELETE cascade + ); + `).run(); + + db.prepare(` + CREATE TABLE 'accountDomains' ( + 'accountId' integer NOT NULL, + 'domainId' text NOT NULL, + FOREIGN KEY ('accountId') REFERENCES 'account'('accountId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('domainId') REFERENCES 'domains'('domainId') ON UPDATE no action ON DELETE cascade + ); + `).run(); + + db.prepare(` + CREATE TABLE 'certificates' ( + 'certId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'domain' text NOT NULL, + 'domainId' text, + 'wildcard' integer DEFAULT false, + 'status' text DEFAULT 'pending' NOT NULL, + 'expiresAt' integer, + 'lastRenewalAttempt' integer, + 'createdAt' integer NOT NULL, + 'updatedAt' integer NOT NULL, + 'orderId' text, + 'errorMessage' text, + 'renewalCount' integer DEFAULT 0, + 'certFile' text, + 'keyFile' text, + FOREIGN KEY ('domainId') REFERENCES 'domains'('domainId') ON UPDATE no action ON DELETE cascade + ); + `).run(); + + db.prepare(`CREATE UNIQUE INDEX 'certificates_domain_unique' ON 'certificates' ('domain');`).run(); + + db.prepare(` + CREATE TABLE 'customers' ( + 'customerId' text PRIMARY KEY NOT NULL, + 'orgId' text NOT NULL, + 'email' text, + 'name' text, + 'phone' text, + 'address' text, + 'createdAt' integer NOT NULL, + 'updatedAt' integer NOT NULL, + FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade + ); + `).run(); + + db.prepare(` + CREATE TABLE 'dnsChallenges' ( + 'dnsChallengeId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'domain' text NOT NULL, + 'token' text NOT NULL, + 'keyAuthorization' text NOT NULL, + 'createdAt' integer NOT NULL, + 'expiresAt' integer NOT NULL, + 'completed' integer DEFAULT false + ); + `).run(); + + db.prepare(` + CREATE TABLE 'domainNamespaces' ( + 'domainNamespaceId' text PRIMARY KEY NOT NULL, + 'domainId' text NOT NULL, + FOREIGN KEY ('domainId') REFERENCES 'domains'('domainId') ON UPDATE no action ON DELETE set null + ); + `).run(); + + db.prepare(` + CREATE TABLE 'exitNodeOrgs' ( + 'exitNodeId' integer NOT NULL, + 'orgId' text NOT NULL, + FOREIGN KEY ('exitNodeId') REFERENCES 'exitNodes'('exitNodeId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade + ); + `).run(); + + db.prepare(` + CREATE TABLE 'loginPage' ( + 'loginPageId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'subdomain' text, + 'fullDomain' text, + 'exitNodeId' integer, + 'domainId' text, + FOREIGN KEY ('exitNodeId') REFERENCES 'exitNodes'('exitNodeId') ON UPDATE no action ON DELETE set null, + FOREIGN KEY ('domainId') REFERENCES 'domains'('domainId') ON UPDATE no action ON DELETE set null + ); + `).run(); + + db.prepare(` + CREATE TABLE 'loginPageOrg' ( + 'loginPageId' integer NOT NULL, + 'orgId' text NOT NULL, + FOREIGN KEY ('loginPageId') REFERENCES 'loginPage'('loginPageId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade + ); + `).run(); + + db.prepare(` + CREATE TABLE 'remoteExitNodeSession' ( + 'id' text PRIMARY KEY NOT NULL, + 'remoteExitNodeId' text NOT NULL, + 'expiresAt' integer NOT NULL, + FOREIGN KEY ('remoteExitNodeId') REFERENCES 'remoteExitNode'('id') ON UPDATE no action ON DELETE cascade + ); + `).run(); + + db.prepare(` + CREATE TABLE 'remoteExitNode' ( + 'id' text PRIMARY KEY NOT NULL, + 'secretHash' text NOT NULL, + 'dateCreated' text NOT NULL, + 'version' text, + 'exitNodeId' integer, + FOREIGN KEY ('exitNodeId') REFERENCES 'exitNodes'('exitNodeId') ON UPDATE no action ON DELETE cascade + ); + `).run(); + + db.prepare(` + CREATE TABLE 'sessionTransferToken' ( + 'token' text PRIMARY KEY NOT NULL, + 'sessionId' text NOT NULL, + 'encryptedSession' text NOT NULL, + 'expiresAt' integer NOT NULL, + FOREIGN KEY ('sessionId') REFERENCES 'session'('id') ON UPDATE no action ON DELETE cascade + ); + `).run(); + + db.prepare(` + CREATE TABLE 'subscriptionItems' ( + 'subscriptionItemId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'subscriptionId' text NOT NULL, + 'planId' text NOT NULL, + 'priceId' text, + 'meterId' text, + 'unitAmount' real, + 'tiers' text, + 'interval' text, + 'currentPeriodStart' integer, + 'currentPeriodEnd' integer, + 'name' text, + FOREIGN KEY ('subscriptionId') REFERENCES 'subscriptions'('subscriptionId') ON UPDATE no action ON DELETE cascade + ); + `).run(); + + db.prepare(` + CREATE TABLE 'subscriptions' ( + 'subscriptionId' text PRIMARY KEY NOT NULL, + 'customerId' text NOT NULL, + 'status' text DEFAULT 'active' NOT NULL, + 'canceledAt' integer, + 'createdAt' integer NOT NULL, + 'updatedAt' integer, + 'billingCycleAnchor' integer, + FOREIGN KEY ('customerId') REFERENCES 'customers'('customerId') ON UPDATE no action ON DELETE cascade + ); + `).run(); + + db.prepare(` + CREATE TABLE 'usage' ( + 'usageId' text PRIMARY KEY NOT NULL, + 'featureId' text NOT NULL, + 'orgId' text NOT NULL, + 'meterId' text, + 'instantaneousValue' real, + 'latestValue' real NOT NULL, + 'previousValue' real, + 'updatedAt' integer NOT NULL, + 'rolledOverAt' integer, + 'nextRolloverAt' integer, + FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade + ); + `).run(); + + db.prepare(` + CREATE TABLE 'usageNotifications' ( + 'notificationId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'orgId' text NOT NULL, + 'featureId' text NOT NULL, + 'limitId' text NOT NULL, + 'notificationType' text NOT NULL, + 'sentAt' integer NOT NULL, + FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade + ); + `).run(); + + db.prepare(` + CREATE TABLE 'resourceHeaderAuth' ( + 'headerAuthId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'resourceId' integer NOT NULL, + 'headerAuthHash' text NOT NULL, + FOREIGN KEY ('resourceId') REFERENCES 'resources'('resourceId') ON UPDATE no action ON DELETE cascade + ); + `).run(); + + db.prepare(` + CREATE TABLE 'targetHealthCheck' ( + 'targetHealthCheckId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'targetId' integer NOT NULL, + 'hcEnabled' integer DEFAULT false NOT NULL, + 'hcPath' text, + 'hcScheme' text, + 'hcMode' text DEFAULT 'http', + 'hcHostname' text, + 'hcPort' integer, + 'hcInterval' integer DEFAULT 30, + 'hcUnhealthyInterval' integer DEFAULT 30, + 'hcTimeout' integer DEFAULT 5, + 'hcHeaders' text, + 'hcFollowRedirects' integer DEFAULT true, + 'hcMethod' text DEFAULT 'GET', + 'hcStatus' integer, + 'hcHealth' text DEFAULT 'unknown', + FOREIGN KEY ('targetId') REFERENCES 'targets'('targetId') ON UPDATE no action ON DELETE cascade + ); + `).run(); + + db.prepare(`DROP TABLE 'limits';`).run(); + + db.prepare(` + CREATE TABLE 'limits' ( + 'limitId' text PRIMARY KEY NOT NULL, + 'featureId' text NOT NULL, + 'orgId' text NOT NULL, + 'value' real, + 'description' text, + FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade + ); + `).run(); + + db.prepare(`ALTER TABLE 'orgs' ADD 'settings' text;`).run(); + db.prepare(`ALTER TABLE 'targets' ADD 'rewritePath' text;`).run(); + db.prepare(`ALTER TABLE 'targets' ADD 'rewritePathType' text;`).run(); + db.prepare(`ALTER TABLE 'targets' ADD 'priority' integer DEFAULT 100 NOT NULL;`).run(); + + const webauthnCredentials = db + .prepare( + `SELECT credentialId, publicKey, userId, signCount, transports, name, lastUsed, dateCreated FROM 'webauthnCredentials'` + ) + .all() as { + credentialId: string; + publicKey: string; + userId: string; + signCount: number; + transports: string | null; + name: string | null; + lastUsed: string; + dateCreated: string; + }[]; + + db.prepare(`DELETE FROM 'webauthnCredentials';`).run(); + + for (const webauthnCredential of webauthnCredentials) { + const newCredentialId = isoBase64URL.fromBuffer( + new Uint8Array( + Buffer.from(webauthnCredential.credentialId, "base64") + ) + ); + const newPublicKey = isoBase64URL.fromBuffer( + new Uint8Array( + Buffer.from(webauthnCredential.publicKey, "base64") + ) + ); + + // Insert the updated record with converted values + db.prepare( + `INSERT INTO 'webauthnCredentials' (credentialId, publicKey, userId, signCount, transports, name, lastUsed, dateCreated) VALUES (?, ?, ?, ?, ?, ?, ?, ?)` + ).run( + newCredentialId, + newPublicKey, + webauthnCredential.userId, + webauthnCredential.signCount, + webauthnCredential.transports, + webauthnCredential.name, + webauthnCredential.lastUsed, + webauthnCredential.dateCreated + ); + } + + // 1. Add the column (nullable or with placeholder) if it doesn’t exist yet + db.prepare( + `ALTER TABLE resources ADD COLUMN resourceGuid TEXT DEFAULT 'PLACEHOLDER';` + ).run(); + + // 2. Select all rows + const resources = db.prepare(`SELECT resourceId FROM resources`).all() as { + resourceId: number; + }[]; + + // 3. Prefill with random UUIDs + const updateStmt = db.prepare( + `UPDATE resources SET resourceGuid = ? WHERE resourceId = ?` + ); + + for (const row of resources) { + updateStmt.run(randomUUID(), row.resourceId); + } + + // get all of the targets + const targets = db.prepare(`SELECT targetId FROM targets`).all() as { + targetId: number; + }[]; + + const insertTargetHealthCheckStmt = db.prepare( + `INSERT INTO targetHealthCheck (targetId) VALUES (?)` + ); + + for (const target of targets) { + insertTargetHealthCheckStmt.run(target.targetId); + } + + db.prepare( + `CREATE UNIQUE INDEX resources_resourceGuid_unique ON resources ('resourceGuid');` + ).run(); + })(); + + console.log(`${version} migration complete`); +} diff --git a/server/setup/scriptsSqlite/1.11.1.ts b/server/setup/scriptsSqlite/1.11.1.ts new file mode 100644 index 000000000..7f9065b6e --- /dev/null +++ b/server/setup/scriptsSqlite/1.11.1.ts @@ -0,0 +1,37 @@ +import { APP_PATH } from "@server/lib/consts"; +import Database from "better-sqlite3"; +import path from "path"; + +const version = "1.11.1"; + +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); + + db.transaction(() => { + const exitNodes = db.prepare(`SELECT * FROM exitNodes WHERE type = 'gerbil' LIMIT 1`).all() as { + exitNodeId: number; + name: string; + }[]; + + const exitNodeId = exitNodes.length > 0 ? exitNodes[0].exitNodeId : null; + + // get all of the targets + const sites = db.prepare(`SELECT * FROM sites WHERE type = 'local'`).all() as { + siteId: number; + exitNodeId: number | null; + }[]; + + const defineExitNodeOnSite = db.prepare( + `UPDATE sites SET exitNodeId = ? WHERE siteId = ?` + ); + + for (const site of sites) { + defineExitNodeOnSite.run(exitNodeId, site.siteId); + } + })(); + + console.log(`${version} migration complete`); +} \ No newline at end of file diff --git a/server/setup/scriptsSqlite/1.13.0.ts b/server/setup/scriptsSqlite/1.13.0.ts deleted file mode 100644 index ad6e97550..000000000 --- a/server/setup/scriptsSqlite/1.13.0.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { APP_PATH } from "@server/lib/consts"; -import { createClient } from "@libsql/client"; -import path from "path"; - -const version = "1.13.0"; - -export default async function migration() { - console.log(`Running setup script ${version}...`); - - const location = path.join(APP_PATH, "db", "db.sqlite"); - const db = createClient({ url: "file:" + location }); - - console.log(`${version} migration complete`); -} \ No newline at end of file diff --git a/server/setup/scriptsSqlite/1.2.0.ts b/server/setup/scriptsSqlite/1.2.0.ts new file mode 100644 index 000000000..e6ba029af --- /dev/null +++ b/server/setup/scriptsSqlite/1.2.0.ts @@ -0,0 +1,114 @@ +import { db } from "../../db/sqlite"; +import { APP_PATH, configFilePath1, configFilePath2 } from "@server/lib/consts"; +import { 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"; + +const version = "1.2.0"; + +export default async function migration() { + console.log(`Running setup script ${version}...`); + + try { + db.transaction((trx) => { + trx.run( + sql`ALTER TABLE 'resources' ADD 'enabled' integer DEFAULT true NOT NULL;` + ); + }); + + console.log(`Migrated database schema`); + } catch (e) { + console.log("Unable to migrate database schema"); + throw e; + } + + 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 + const fileContents = fs.readFileSync(filePath, "utf8"); + const rawConfig = yaml.load(fileContents) as any; + + if (!rawConfig.flags) { + rawConfig.flags = {}; + } + + rawConfig.server.resource_access_token_headers = { + id: "P-Access-Token-Id", + token: "P-Access-Token" + }; + + // Write the updated YAML back to the file + const updatedYaml = yaml.dump(rawConfig); + fs.writeFileSync(filePath, updatedYaml, "utf8"); + + console.log(`Added new config option: resource_access_token_headers`); + } catch (e) { + console.log( + `Unable to add new config option: resource_access_token_headers. Please add it manually. https://docs.pangolin.net/self-host/advanced/config-file` + ); + console.error(e); + } + + try { + const traefikPath = path.join( + APP_PATH, + "traefik", + "traefik_config.yml" + ); + + const schema = z.object({ + 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; + + const parsedConfig = schema.safeParse(traefikConfig); + + if (!parsedConfig.success) { + throw new Error(fromZodError(parsedConfig.error).toString()); + } + + traefikConfig.experimental.plugins.badger.version = "v1.1.0"; + + const updatedTraefikYaml = yaml.dump(traefikConfig); + + fs.writeFileSync(traefikPath, updatedTraefikYaml, "utf8"); + + console.log( + "Updated the version of Badger in your Traefik configuration to v1.1.0" + ); + } catch (e) { + console.log( + "We were unable to update the version of Badger in your Traefik configuration. Please update it manually. Check the release notes for this version for more information." + ); + console.error(e); + } + + console.log(`${version} migration complete`); +} diff --git a/server/setup/scriptsSqlite/1.3.0.ts b/server/setup/scriptsSqlite/1.3.0.ts new file mode 100644 index 000000000..a084d59ff --- /dev/null +++ b/server/setup/scriptsSqlite/1.3.0.ts @@ -0,0 +1,203 @@ +import Database from "better-sqlite3"; +import path from "path"; +import fs from "fs"; +import yaml from "js-yaml"; +import { encodeBase32LowerCaseNoPadding } from "@oslojs/encoding"; +import { APP_PATH, configFilePath1, configFilePath2 } from "@server/lib/consts"; + +const version = "1.3.0"; +const location = path.join(APP_PATH, "db", "db.sqlite"); + +export default async function migration() { + console.log(`Running setup script ${version}...`); + + const db = new Database(location); + + try { + db.pragma("foreign_keys = OFF"); + db.transaction(() => { + db.exec(` + CREATE TABLE 'apiKeyActions' ( + 'apiKeyId' text NOT NULL, + 'actionId' text NOT NULL, + FOREIGN KEY ('apiKeyId') REFERENCES 'apiKeys'('apiKeyId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('actionId') REFERENCES 'actions'('actionId') ON UPDATE no action ON DELETE cascade + ); + + CREATE TABLE 'apiKeyOrg' ( + 'apiKeyId' text NOT NULL, + 'orgId' text NOT NULL, + FOREIGN KEY ('apiKeyId') REFERENCES 'apiKeys'('apiKeyId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade + ); + + CREATE TABLE 'apiKeys' ( + 'apiKeyId' text PRIMARY KEY NOT NULL, + 'name' text NOT NULL, + 'apiKeyHash' text NOT NULL, + 'lastChars' text NOT NULL, + 'dateCreated' text NOT NULL, + 'isRoot' integer DEFAULT false NOT NULL + ); + + CREATE TABLE 'hostMeta' ( + 'hostMetaId' text PRIMARY KEY NOT NULL, + 'createdAt' integer NOT NULL + ); + + CREATE TABLE 'idp' ( + 'idpId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'name' text NOT NULL, + 'type' text NOT NULL, + 'defaultRoleMapping' text, + 'defaultOrgMapping' text, + 'autoProvision' integer DEFAULT false NOT NULL + ); + + CREATE TABLE 'idpOidcConfig' ( + 'idpOauthConfigId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'idpId' integer NOT NULL, + 'clientId' text NOT NULL, + 'clientSecret' text NOT NULL, + 'authUrl' text NOT NULL, + 'tokenUrl' text NOT NULL, + 'identifierPath' text NOT NULL, + 'emailPath' text, + 'namePath' text, + 'scopes' text NOT NULL, + FOREIGN KEY ('idpId') REFERENCES 'idp'('idpId') ON UPDATE no action ON DELETE cascade + ); + + CREATE TABLE 'idpOrg' ( + 'idpId' integer NOT NULL, + 'orgId' text NOT NULL, + 'roleMapping' text, + 'orgMapping' text, + FOREIGN KEY ('idpId') REFERENCES 'idp'('idpId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade + ); + + CREATE TABLE 'licenseKey' ( + 'licenseKeyId' text PRIMARY KEY NOT NULL, + 'instanceId' text NOT NULL, + 'token' text NOT NULL + ); + + CREATE TABLE '__new_user' ( + 'id' text PRIMARY KEY NOT NULL, + 'email' text, + 'username' text NOT NULL, + 'name' text, + 'type' text NOT NULL, + 'idpId' integer, + 'passwordHash' text, + 'twoFactorEnabled' integer DEFAULT false NOT NULL, + 'twoFactorSecret' text, + 'emailVerified' integer DEFAULT false NOT NULL, + 'dateCreated' text NOT NULL, + 'serverAdmin' integer DEFAULT false NOT NULL, + FOREIGN KEY ('idpId') REFERENCES 'idp'('idpId') ON UPDATE no action ON DELETE cascade + ); + + INSERT INTO '__new_user'( + "id", "email", "username", "name", "type", "idpId", "passwordHash", + "twoFactorEnabled", "twoFactorSecret", "emailVerified", "dateCreated", "serverAdmin" + ) + SELECT + "id", + "email", + COALESCE("email", 'unknown'), + NULL, + 'internal', + NULL, + "passwordHash", + "twoFactorEnabled", + "twoFactorSecret", + "emailVerified", + "dateCreated", + "serverAdmin" + FROM 'user'; + + DROP TABLE 'user'; + ALTER TABLE '__new_user' RENAME TO 'user'; + + ALTER TABLE 'resources' ADD 'stickySession' integer DEFAULT false NOT NULL; + ALTER TABLE 'resources' ADD 'tlsServerName' text; + ALTER TABLE 'resources' ADD 'setHostHeader' text; + + CREATE TABLE 'exitNodes_new' ( + 'exitNodeId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'name' text NOT NULL, + 'address' text NOT NULL, + 'endpoint' text NOT NULL, + 'publicKey' text NOT NULL, + 'listenPort' integer NOT NULL, + 'reachableAt' text + ); + + INSERT INTO 'exitNodes_new' ( + 'exitNodeId', 'name', 'address', 'endpoint', 'publicKey', 'listenPort', 'reachableAt' + ) + SELECT + exitNodeId, + name, + address, + endpoint, + pubicKey, + listenPort, + reachableAt + FROM exitNodes; + + DROP TABLE 'exitNodes'; + ALTER TABLE 'exitNodes_new' RENAME TO 'exitNodes'; + `); + })(); // <-- executes the transaction immediately + db.pragma("foreign_keys = ON"); + console.log(`Migrated database schema`); + } catch (e) { + console.log("Unable to migrate database schema"); + throw e; + } + + // Update config file + try { + 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).` + ); + } + + const fileContents = fs.readFileSync(filePath, "utf8"); + const rawConfig = yaml.load(fileContents) as any; + + if (!rawConfig.server.secret) { + rawConfig.server.secret = generateIdFromEntropySize(32); + } + + const updatedYaml = yaml.dump(rawConfig); + fs.writeFileSync(filePath, updatedYaml, "utf8"); + + console.log(`Added new config option: server.secret`); + } catch (e) { + console.log( + `Unable to add new config option: server.secret. Please add it manually.` + ); + console.error(e); + } + + console.log(`${version} migration complete`); +} + +function generateIdFromEntropySize(size: number): string { + const buffer = crypto.getRandomValues(new Uint8Array(size)); + return encodeBase32LowerCaseNoPadding(buffer); +} diff --git a/server/setup/scriptsSqlite/1.5.0.ts b/server/setup/scriptsSqlite/1.5.0.ts new file mode 100644 index 000000000..46e9cccaa --- /dev/null +++ b/server/setup/scriptsSqlite/1.5.0.ts @@ -0,0 +1,70 @@ +import Database from "better-sqlite3"; +import path from "path"; +import { APP_PATH, configFilePath1, configFilePath2 } from "@server/lib/consts"; +import fs from "fs"; +import yaml from "js-yaml"; + +const version = "1.5.0"; +const location = path.join(APP_PATH, "db", "db.sqlite"); + +export default async function migration() { + console.log(`Running setup script ${version}...`); + + const db = new Database(location); + + try { + db.pragma("foreign_keys = OFF"); + db.transaction(() => { + db.exec(` + ALTER TABLE 'sites' ADD 'dockerSocketEnabled' integer DEFAULT true NOT NULL; + `); + })(); // <-- executes the transaction immediately + db.pragma("foreign_keys = ON"); + console.log(`Migrated database schema`); + } catch (e) { + console.log("Unable to migrate database schema"); + throw e; + } + + 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 + const fileContents = fs.readFileSync(filePath, "utf8"); + const rawConfig = yaml.load(fileContents) as any; + + if (rawConfig.cors?.headers) { + const headers = JSON.parse( + JSON.stringify(rawConfig.cors.headers) + ); + rawConfig.cors.allowed_headers = headers; + delete rawConfig.cors.headers; + } + + // Write the updated YAML back to the file + const updatedYaml = yaml.dump(rawConfig); + fs.writeFileSync(filePath, updatedYaml, "utf8"); + + console.log(`Migrated CORS headers to allowed_headers`); + } catch (e) { + console.log( + `Unable to migrate config file. Error: ${e}` + ); + } + + console.log(`${version} migration complete`); +} diff --git a/server/setup/scriptsSqlite/1.6.0.ts b/server/setup/scriptsSqlite/1.6.0.ts new file mode 100644 index 000000000..adab26977 --- /dev/null +++ b/server/setup/scriptsSqlite/1.6.0.ts @@ -0,0 +1,65 @@ +import { APP_PATH, configFilePath1, configFilePath2 } from "@server/lib/consts"; +import Database from "better-sqlite3"; +import fs from "fs"; +import yaml from "js-yaml"; +import path from "path"; + +const version = "1.6.0"; + +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.pragma("foreign_keys = OFF"); + db.transaction(() => { + db.exec(` + UPDATE 'user' SET email = LOWER(email); + UPDATE 'user' SET username = LOWER(username); + `); + })(); // <-- executes the transaction immediately + db.pragma("foreign_keys = ON"); + console.log(`Migrated database schema`); + } catch (e) { + console.log("Unable to make all usernames and emails lowercase"); + console.log(e); + } + + 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 + const fileContents = fs.readFileSync(filePath, "utf8"); + const rawConfig = yaml.load(fileContents) as any; + + if (rawConfig.server?.trust_proxy) { + rawConfig.server.trust_proxy = 1; + } + + // Write the updated YAML back to the file + const updatedYaml = yaml.dump(rawConfig); + fs.writeFileSync(filePath, updatedYaml, "utf8"); + + console.log(`Set trust_proxy to 1 in config file`); + } catch (e) { + console.log(`Unable to migrate config file. Please do it manually. Error: ${e}`); + } + + console.log(`${version} migration complete`); +} diff --git a/server/setup/scriptsSqlite/1.7.0.ts b/server/setup/scriptsSqlite/1.7.0.ts new file mode 100644 index 000000000..f173d12e9 --- /dev/null +++ b/server/setup/scriptsSqlite/1.7.0.ts @@ -0,0 +1,187 @@ +import { APP_PATH } from "@server/lib/consts"; +import Database from "better-sqlite3"; +import path from "path"; + +const version = "1.7.0"; + +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.pragma("foreign_keys = OFF"); + + db.transaction(() => { + db.exec(` + CREATE TABLE 'clientSites' ( + 'clientId' integer NOT NULL, + 'siteId' integer NOT NULL, + 'isRelayed' integer DEFAULT 0 NOT NULL, + FOREIGN KEY ('clientId') REFERENCES 'clients'('id') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('siteId') REFERENCES 'sites'('siteId') ON UPDATE no action ON DELETE cascade + ); + + CREATE TABLE 'clients' ( + 'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'orgId' text NOT NULL, + 'exitNode' integer, + 'name' text NOT NULL, + 'pubKey' text, + 'subnet' text NOT NULL, + 'bytesIn' integer, + 'bytesOut' integer, + 'lastBandwidthUpdate' text, + 'lastPing' text, + 'type' text NOT NULL, + 'online' integer DEFAULT 0 NOT NULL, + 'endpoint' text, + 'lastHolePunch' integer, + FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('exitNode') REFERENCES 'exitNodes'('exitNodeId') ON UPDATE no action ON DELETE set null + ); + + CREATE TABLE 'clientSession' ( + 'id' text PRIMARY KEY NOT NULL, + 'olmId' text NOT NULL, + 'expiresAt' integer NOT NULL, + FOREIGN KEY ('olmId') REFERENCES 'olms'('id') ON UPDATE no action ON DELETE cascade + ); + + CREATE TABLE 'olms' ( + 'id' text PRIMARY KEY NOT NULL, + 'secretHash' text NOT NULL, + 'dateCreated' text NOT NULL, + 'clientId' integer, + FOREIGN KEY ('clientId') REFERENCES 'clients'('id') ON UPDATE no action ON DELETE cascade + ); + + CREATE TABLE 'roleClients' ( + 'roleId' integer NOT NULL, + 'clientId' integer NOT NULL, + FOREIGN KEY ('roleId') REFERENCES 'roles'('roleId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('clientId') REFERENCES 'clients'('id') ON UPDATE no action ON DELETE cascade + ); + + CREATE TABLE 'webauthnCredentials' ( + 'credentialId' text PRIMARY KEY NOT NULL, + 'userId' text NOT NULL, + 'publicKey' text NOT NULL, + 'signCount' integer NOT NULL, + 'transports' text, + 'name' text, + 'lastUsed' text NOT NULL, + 'dateCreated' text NOT NULL, + FOREIGN KEY ('userId') REFERENCES 'user'('id') ON UPDATE no action ON DELETE cascade + ); + + CREATE TABLE 'userClients' ( + 'userId' text NOT NULL, + 'clientId' integer NOT NULL, + FOREIGN KEY ('userId') REFERENCES 'user'('id') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('clientId') REFERENCES 'clients'('id') ON UPDATE no action ON DELETE cascade + ); + + CREATE TABLE 'userDomains' ( + 'userId' text NOT NULL, + 'domainId' text NOT NULL, + FOREIGN KEY ('userId') REFERENCES 'user'('id') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('domainId') REFERENCES 'domains'('domainId') ON UPDATE no action ON DELETE cascade + ); + + CREATE TABLE 'webauthnChallenge' ( + 'sessionId' text PRIMARY KEY NOT NULL, + 'challenge' text NOT NULL, + 'securityKeyName' text, + 'userId' text, + 'expiresAt' integer NOT NULL, + FOREIGN KEY ('userId') REFERENCES 'user'('id') ON UPDATE no action ON DELETE cascade + ); + + `); + + db.exec(` + CREATE TABLE '__new_sites' ( + 'siteId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'orgId' text NOT NULL, + 'niceId' text NOT NULL, + 'exitNode' integer, + 'name' text NOT NULL, + 'pubKey' text, + 'subnet' text, + 'bytesIn' integer DEFAULT 0, + 'bytesOut' integer DEFAULT 0, + 'lastBandwidthUpdate' text, + 'type' text NOT NULL, + 'online' integer DEFAULT 0 NOT NULL, + 'address' text, + 'endpoint' text, + 'publicKey' text, + 'lastHolePunch' integer, + 'listenPort' integer, + 'dockerSocketEnabled' integer DEFAULT 1 NOT NULL, + FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('exitNode') REFERENCES 'exitNodes'('exitNodeId') ON UPDATE no action ON DELETE set null + ); + + INSERT INTO '__new_sites' ( + 'siteId', 'orgId', 'niceId', 'exitNode', 'name', 'pubKey', 'subnet', 'bytesIn', 'bytesOut', 'lastBandwidthUpdate', 'type', 'online', 'address', 'endpoint', 'publicKey', 'lastHolePunch', 'listenPort', 'dockerSocketEnabled' + ) + SELECT siteId, orgId, niceId, exitNode, name, pubKey, subnet, bytesIn, bytesOut, lastBandwidthUpdate, type, online, NULL, NULL, NULL, NULL, NULL, dockerSocketEnabled + FROM sites; + + DROP TABLE 'sites'; + ALTER TABLE '__new_sites' RENAME TO 'sites'; + `); + + db.exec(` + ALTER TABLE 'domains' ADD 'type' text; + ALTER TABLE 'domains' ADD 'verified' integer DEFAULT 0 NOT NULL; + ALTER TABLE 'domains' ADD 'failed' integer DEFAULT 0 NOT NULL; + ALTER TABLE 'domains' ADD 'tries' integer DEFAULT 0 NOT NULL; + ALTER TABLE 'exitNodes' ADD 'maxConnections' integer; + ALTER TABLE 'newt' ADD 'version' text; + ALTER TABLE 'orgs' ADD 'subnet' text; + ALTER TABLE 'user' ADD 'twoFactorSetupRequested' integer DEFAULT 0; + ALTER TABLE 'resources' DROP COLUMN 'isBaseDomain'; + `); + })(); + + db.pragma("foreign_keys = ON"); + + console.log(`Migrated database schema`); + } catch (e) { + console.log("Unable to migrate database schema"); + throw e; + } + + db.transaction(() => { + // Update all existing orgs to have the default subnet + db.exec(`UPDATE 'orgs' SET 'subnet' = '100.90.128.0/24'`); + + // Get all orgs and their sites to assign sequential IP addresses + const orgs = db.prepare(`SELECT orgId FROM 'orgs'`).all() as { + orgId: string; + }[]; + + for (const org of orgs) { + const sites = db + .prepare( + `SELECT siteId FROM 'sites' WHERE orgId = ? ORDER BY siteId` + ) + .all(org.orgId) as { siteId: number }[]; + + let ipIndex = 1; + for (const site of sites) { + const address = `100.90.128.${ipIndex}/24`; + db.prepare( + `UPDATE 'sites' SET 'address' = ? WHERE siteId = ?` + ).run(address, site.siteId); + ipIndex++; + } + } + })(); + + console.log(`${version} migration complete`); +} diff --git a/server/setup/scriptsSqlite/1.8.0.ts b/server/setup/scriptsSqlite/1.8.0.ts new file mode 100644 index 000000000..f8ac7c951 --- /dev/null +++ b/server/setup/scriptsSqlite/1.8.0.ts @@ -0,0 +1,30 @@ +import { APP_PATH } from "@server/lib/consts"; +import Database from "better-sqlite3"; +import path from "path"; + +const version = "1.8.0"; + +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.exec(` + ALTER TABLE 'resources' ADD 'enableProxy' integer DEFAULT 1; + ALTER TABLE 'sites' ADD 'remoteSubnets' text; + ALTER TABLE 'user' ADD 'termsAcceptedTimestamp' text; + ALTER TABLE 'user' ADD 'termsVersion' text; + `); + })(); + + console.log("Migrated database schema"); + } catch (e) { + console.log("Unable to migrate database schema"); + throw e; + } + + console.log(`${version} migration complete`); +} diff --git a/server/setup/scriptsSqlite/1.9.0.ts b/server/setup/scriptsSqlite/1.9.0.ts new file mode 100644 index 000000000..5f247ea50 --- /dev/null +++ b/server/setup/scriptsSqlite/1.9.0.ts @@ -0,0 +1,191 @@ +import { APP_PATH } from "@server/lib/consts"; +import Database from "better-sqlite3"; +import path from "path"; + +const version = "1.9.0"; + +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); + + const resourceSiteMap = new Map(); + let firstSiteId: number = 1; + + try { + // Get the first siteId to use as default + const firstSite = db.prepare("SELECT siteId FROM sites LIMIT 1").get() as { siteId: number } | undefined; + if (firstSite) { + firstSiteId = firstSite.siteId; + } + + const resources = db + .prepare( + "SELECT resourceId, siteId FROM resources WHERE siteId IS NOT NULL" + ) + .all() as Array<{ resourceId: number; siteId: number }>; + for (const resource of resources) { + resourceSiteMap.set(resource.resourceId, resource.siteId); + } + } catch (e) { + console.log("Error getting resources:", e); + } + + try { + db.pragma("foreign_keys = OFF"); + + db.transaction(() => { + db.exec(`CREATE TABLE 'setupTokens' ( + 'tokenId' text PRIMARY KEY NOT NULL, + 'token' text NOT NULL, + 'used' integer DEFAULT false NOT NULL, + 'dateCreated' text NOT NULL, + 'dateUsed' text +); +--> statement-breakpoint +CREATE TABLE 'siteResources' ( + 'siteResourceId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'siteId' integer NOT NULL, + 'orgId' text NOT NULL, + 'name' text NOT NULL, + 'protocol' text NOT NULL, + 'proxyPort' integer NOT NULL, + 'destinationPort' integer NOT NULL, + 'destinationIp' text NOT NULL, + 'enabled' integer DEFAULT true NOT NULL, + FOREIGN KEY ('siteId') REFERENCES 'sites'('siteId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +PRAGMA foreign_keys=OFF;--> statement-breakpoint +CREATE TABLE '__new_resources' ( + 'resourceId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'orgId' text NOT NULL, + 'name' text NOT NULL, + 'subdomain' text, + 'fullDomain' text, + 'domainId' text, + 'ssl' integer DEFAULT false NOT NULL, + 'blockAccess' integer DEFAULT false NOT NULL, + 'sso' integer DEFAULT true NOT NULL, + 'http' integer DEFAULT true NOT NULL, + 'protocol' text NOT NULL, + 'proxyPort' integer, + 'emailWhitelistEnabled' integer DEFAULT false NOT NULL, + 'applyRules' integer DEFAULT false NOT NULL, + 'enabled' integer DEFAULT true NOT NULL, + 'stickySession' integer DEFAULT false NOT NULL, + 'tlsServerName' text, + 'setHostHeader' text, + 'enableProxy' integer DEFAULT true, + 'skipToIdpId' integer, + 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 set null, + FOREIGN KEY ('skipToIdpId') REFERENCES 'idp'('idpId') ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +INSERT INTO '__new_resources'("resourceId", "orgId", "name", "subdomain", "fullDomain", "domainId", "ssl", "blockAccess", "sso", "http", "protocol", "proxyPort", "emailWhitelistEnabled", "applyRules", "enabled", "stickySession", "tlsServerName", "setHostHeader", "enableProxy", "skipToIdpId") SELECT "resourceId", "orgId", "name", "subdomain", "fullDomain", "domainId", "ssl", "blockAccess", "sso", "http", "protocol", "proxyPort", "emailWhitelistEnabled", "applyRules", "enabled", "stickySession", "tlsServerName", "setHostHeader", "enableProxy", null FROM 'resources';--> statement-breakpoint +DROP TABLE 'resources';--> statement-breakpoint +ALTER TABLE '__new_resources' RENAME TO 'resources';--> statement-breakpoint +PRAGMA foreign_keys=ON;--> statement-breakpoint +CREATE TABLE '__new_clients' ( + 'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'orgId' text NOT NULL, + 'exitNode' integer, + 'name' text NOT NULL, + 'pubKey' text, + 'subnet' text NOT NULL, + 'bytesIn' integer, + 'bytesOut' integer, + 'lastBandwidthUpdate' text, + 'lastPing' integer, + 'type' text NOT NULL, + 'online' integer DEFAULT false NOT NULL, + 'lastHolePunch' integer, + FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('exitNode') REFERENCES 'exitNodes'('exitNodeId') ON UPDATE no action ON DELETE set null +); +--> statement-breakpoint +INSERT INTO '__new_clients'("id", "orgId", "exitNode", "name", "pubKey", "subnet", "bytesIn", "bytesOut", "lastBandwidthUpdate", "lastPing", "type", "online", "lastHolePunch") SELECT "id", "orgId", "exitNode", "name", "pubKey", "subnet", "bytesIn", "bytesOut", "lastBandwidthUpdate", NULL, "type", "online", "lastHolePunch" FROM 'clients';--> statement-breakpoint +DROP TABLE 'clients';--> statement-breakpoint +ALTER TABLE '__new_clients' RENAME TO 'clients';--> statement-breakpoint +ALTER TABLE 'clientSites' ADD 'endpoint' text;--> statement-breakpoint +ALTER TABLE 'exitNodes' ADD 'online' integer DEFAULT false NOT NULL;--> statement-breakpoint +ALTER TABLE 'exitNodes' ADD 'lastPing' integer;--> statement-breakpoint +ALTER TABLE 'exitNodes' ADD 'type' text DEFAULT 'gerbil';--> statement-breakpoint +ALTER TABLE 'olms' ADD 'version' text;--> statement-breakpoint +ALTER TABLE 'orgs' ADD 'createdAt' text;--> statement-breakpoint +ALTER TABLE 'targets' ADD 'siteId' integer NOT NULL DEFAULT ${firstSiteId || 1} REFERENCES sites(siteId);`); + + // for each resource, get all of its targets, and update the siteId to be the previously stored siteId + for (const [resourceId, siteId] of resourceSiteMap) { + const targets = db + .prepare( + "SELECT targetId FROM targets WHERE resourceId = ?" + ) + .all(resourceId) as Array<{ targetId: number }>; + for (const target of targets) { + db.prepare( + "UPDATE targets SET siteId = ? WHERE targetId = ?" + ).run(siteId, target.targetId); + } + } + + // list resources that have enableProxy false + // move them to the siteResources table + // remove them from the resources table + const proxyFalseResources = db + .prepare("SELECT * FROM resources WHERE enableProxy = 0") + .all() as Array; + + for (const resource of proxyFalseResources) { + // Get the first target to derive destination IP and port + const firstTarget = db + .prepare( + "SELECT ip, port FROM targets WHERE resourceId = ? LIMIT 1" + ) + .get(resource.resourceId) as + | { ip: string; port: number } + | undefined; + + if (!firstTarget) { + continue; + } + + // Insert into siteResources table + const stmt = db.prepare(` + INSERT INTO siteResources (siteId, orgId, name, protocol, proxyPort, destinationPort, destinationIp, enabled) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + `); + stmt.run( + resourceSiteMap.get(resource.resourceId), + resource.orgId, + resource.name, + resource.protocol, + resource.proxyPort, + firstTarget.port, + firstTarget.ip, + resource.enabled + ); + + // Delete from resources table + db.prepare("DELETE FROM resources WHERE resourceId = ?").run( + resource.resourceId + ); + + // Delete the targets for this resource + db.prepare("DELETE FROM targets WHERE resourceId = ?").run( + resource.resourceId + ); + } + })(); + + db.pragma("foreign_keys = ON"); + + console.log(`Migrated database`); + } catch (e) { + console.log("Failed to migrate db:", e); + throw e; + } +} From cc2326baddb0f7765aeb70aac886f3a092ddcefd Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Thu, 20 Nov 2025 13:47:49 +0000 Subject: [PATCH 18/20] add current migrations --- server/setup/migrationsSqlite.ts | 12 +- server/setup/scriptsSqlite/1.12.0.ts | 250 +++++++++++++++++++++++++++ 2 files changed, 258 insertions(+), 4 deletions(-) create mode 100644 server/setup/scriptsSqlite/1.12.0.ts diff --git a/server/setup/migrationsSqlite.ts b/server/setup/migrationsSqlite.ts index 1611c4926..4e31ab906 100644 --- a/server/setup/migrationsSqlite.ts +++ b/server/setup/migrationsSqlite.ts @@ -1,11 +1,11 @@ #! /usr/bin/env node -import { migrate } from "drizzle-orm/libsql/migrator"; +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 { LibsqlError } from "@libsql/client"; +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"; @@ -30,6 +30,8 @@ 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"; // THIS CANNOT IMPORT ANYTHING FROM THE SERVER // EXCEPT FOR THE DATABASE AND THE SCHEMA @@ -59,6 +61,8 @@ const migrations = [ { 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 } // Add new migrations here as they are created ] as const; @@ -174,7 +178,7 @@ async function executeScripts() { ); } catch (e) { if ( - e instanceof LibsqlError && + e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_UNIQUE" ) { console.error("Migration has already run! Skipping..."); @@ -193,4 +197,4 @@ async function executeScripts() { console.error("Migration process failed:", error); throw error; } -} +} \ No newline at end of file diff --git a/server/setup/scriptsSqlite/1.12.0.ts b/server/setup/scriptsSqlite/1.12.0.ts new file mode 100644 index 000000000..0f2c884d2 --- /dev/null +++ b/server/setup/scriptsSqlite/1.12.0.ts @@ -0,0 +1,250 @@ +import { APP_PATH } from "@server/lib/consts"; +import Database from "better-sqlite3"; +import path from "path"; + +const version = "1.12.0"; + +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.pragma("foreign_keys = OFF"); + + db.transaction(() => { + db.prepare( + `UPDATE 'resourceRules' SET match = 'COUNTRY' WHERE match = 'GEOIP'` + ).run(); + + db.prepare( + ` + CREATE TABLE 'accessAuditLog' ( + 'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'timestamp' integer NOT NULL, + 'orgId' text NOT NULL, + 'actorType' text, + 'actor' text, + 'actorId' text, + 'resourceId' integer, + 'ip' text, + 'location' text, + 'type' text NOT NULL, + 'action' integer NOT NULL, + 'userAgent' text, + 'metadata' text, + FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade + ); + ` + ).run(); + + db.prepare( + `CREATE INDEX 'idx_identityAuditLog_timestamp' ON 'accessAuditLog' ('timestamp');` + ).run(); + db.prepare( + `CREATE INDEX 'idx_identityAuditLog_org_timestamp' ON 'accessAuditLog' ('orgId','timestamp');` + ).run(); + + db.prepare( + ` + CREATE TABLE 'actionAuditLog' ( + 'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'timestamp' integer NOT NULL, + 'orgId' text NOT NULL, + 'actorType' text NOT NULL, + 'actor' text NOT NULL, + 'actorId' text NOT NULL, + 'action' text NOT NULL, + 'metadata' text, + FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade + ); + ` + ).run(); + + db.prepare( + `CREATE INDEX 'idx_actionAuditLog_timestamp' ON 'actionAuditLog' ('timestamp');` + ).run(); + db.prepare( + `CREATE INDEX 'idx_actionAuditLog_org_timestamp' ON 'actionAuditLog' ('orgId','timestamp');` + ).run(); + + db.prepare( + ` + CREATE TABLE 'dnsRecords' ( + 'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'domainId' text NOT NULL, + 'recordType' text NOT NULL, + 'baseDomain' text, + 'value' text NOT NULL, + 'verified' integer DEFAULT false NOT NULL, + FOREIGN KEY ('domainId') REFERENCES 'domains'('domainId') ON UPDATE no action ON DELETE cascade + ); + ` + ).run(); + + db.prepare( + ` + CREATE TABLE 'requestAuditLog' ( + 'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'timestamp' integer NOT NULL, + 'orgId' text, + 'action' integer NOT NULL, + 'reason' integer NOT NULL, + 'actorType' text, + 'actor' text, + 'actorId' text, + 'resourceId' integer, + 'ip' text, + 'location' text, + 'userAgent' text, + 'metadata' text, + 'headers' text, + 'query' text, + 'originalRequestURL' text, + 'scheme' text, + 'host' text, + 'path' text, + 'method' text, + 'tls' integer, + FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade + ); + ` + ).run(); + + + db.prepare( + ` + CREATE TABLE 'blueprints' ( + 'blueprintId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'orgId' text NOT NULL, + 'name' text NOT NULL, + 'source' text NOT NULL, + 'createdAt' integer NOT NULL, + 'succeeded' integer NOT NULL, + 'contents' text NOT NULL, + 'message' text, + FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade + ); + ` + ).run(); + + db.prepare( + `CREATE INDEX 'idx_requestAuditLog_timestamp' ON 'requestAuditLog' ('timestamp');` + ).run(); + db.prepare( + `CREATE INDEX 'idx_requestAuditLog_org_timestamp' ON 'requestAuditLog' ('orgId','timestamp');` + ).run(); + + db.prepare( + ` + CREATE TABLE '__new_resources' ( + 'resourceId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'resourceGuid' text(36) NOT NULL, + 'orgId' text NOT NULL, + 'niceId' text NOT NULL, + 'name' text NOT NULL, + 'subdomain' text, + 'fullDomain' text, + 'domainId' text, + 'ssl' integer DEFAULT false NOT NULL, + 'blockAccess' integer DEFAULT false NOT NULL, + 'sso' integer DEFAULT true NOT NULL, + 'http' integer DEFAULT true NOT NULL, + 'protocol' text NOT NULL, + 'proxyPort' integer, + 'emailWhitelistEnabled' integer DEFAULT false NOT NULL, + 'applyRules' integer DEFAULT false NOT NULL, + 'enabled' integer DEFAULT true NOT NULL, + 'stickySession' integer DEFAULT false NOT NULL, + 'tlsServerName' text, + 'setHostHeader' text, + 'enableProxy' integer DEFAULT true, + 'skipToIdpId' integer, + 'headers' text, + 'proxyProtocol' integer DEFAULT false NOT NULL, + 'proxyProtocolVersion' integer DEFAULT 1, + 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 set null, + FOREIGN KEY ('skipToIdpId') REFERENCES 'idp'('idpId') ON UPDATE no action ON DELETE set null + ); + ` + ).run(); + + db.prepare( + `INSERT INTO '__new_resources'("resourceId", "resourceGuid", "orgId", "niceId", "name", "subdomain", "fullDomain", "domainId", "ssl", "blockAccess", "sso", "http", "protocol", "proxyPort", "emailWhitelistEnabled", "applyRules", "enabled", "stickySession", "tlsServerName", "setHostHeader", "enableProxy", "skipToIdpId", "headers") SELECT "resourceId", "resourceGuid", "orgId", "niceId", "name", "subdomain", "fullDomain", "domainId", "ssl", "blockAccess", "sso", "http", "protocol", "proxyPort", "emailWhitelistEnabled", "applyRules", "enabled", "stickySession", "tlsServerName", "setHostHeader", "enableProxy", "skipToIdpId", "headers" FROM 'resources';` + ).run(); + db.prepare(`DROP TABLE 'resources';`).run(); + db.prepare( + `ALTER TABLE '__new_resources' RENAME TO 'resources';` + ).run(); + + db.prepare( + `CREATE UNIQUE INDEX 'resources_resourceGuid_unique' ON 'resources' ('resourceGuid');` + ).run(); + db.prepare(`ALTER TABLE 'domains' ADD 'certResolver' text;`).run(); + db.prepare( + `ALTER TABLE 'domains' ADD 'preferWildcardCert' integer;` + ).run(); + db.prepare( + `ALTER TABLE 'orgs' ADD 'requireTwoFactor' integer;` + ).run(); + db.prepare( + `ALTER TABLE 'orgs' ADD 'maxSessionLengthHours' integer;` + ).run(); + db.prepare( + `ALTER TABLE 'orgs' ADD 'passwordExpiryDays' integer;` + ).run(); + db.prepare( + `ALTER TABLE 'orgs' ADD 'settingsLogRetentionDaysRequest' integer DEFAULT 7 NOT NULL;` + ).run(); + db.prepare( + `ALTER TABLE 'orgs' ADD 'settingsLogRetentionDaysAccess' integer DEFAULT 0 NOT NULL;` + ).run(); + db.prepare( + `ALTER TABLE 'orgs' ADD 'settingsLogRetentionDaysAction' integer DEFAULT 0 NOT NULL;` + ).run(); + db.prepare(`ALTER TABLE 'orgs' DROP COLUMN 'settings';`).run(); + db.prepare( + `ALTER TABLE 'resourceSessions' ADD 'issuedAt' integer;` + ).run(); + db.prepare(`ALTER TABLE 'session' ADD 'issuedAt' integer;`).run(); + db.prepare( + `ALTER TABLE 'user' ADD 'lastPasswordChange' integer;` + ).run(); + db.prepare(`ALTER TABLE 'remoteExitNode' ADD 'secondaryVersion' text;`).run(); + + // get all of the domains + const domains = db.prepare(`SELECT domainId, baseDomain from domains`).all() as { + domainId: number; + baseDomain: string; + }[]; + + for (const domain of domains) { + // insert two records into the dnsRecords table for each domain + const insert = db.prepare( + `INSERT INTO 'dnsRecords' (domainId, recordType, baseDomain, value, verified) VALUES (?, 'A', ?, ?, 1)` + ); + insert.run( + domain.domainId, + `*.${domain.baseDomain}`, + `Server IP Address` + ); + insert.run( + domain.domainId, + `${domain.baseDomain}`, + `Server IP Address` + ); + } + })(); + + db.pragma("foreign_keys = ON"); + + console.log(`Migrated database`); + } catch (e) { + console.log("Failed to migrate db:", e); + throw e; + } + + console.log(`${version} migration complete`); +} \ No newline at end of file From 704b4cccfb718de7c73caebf70a04a9ac8cd7703 Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Thu, 20 Nov 2025 13:58:48 +0000 Subject: [PATCH 19/20] update migrations --- server/setup/scriptsSqlite/1.11.1.ts | 23 ++--------------------- server/setup/scriptsSqlite/1.12.0.ts | 2 +- 2 files changed, 3 insertions(+), 22 deletions(-) diff --git a/server/setup/scriptsSqlite/1.11.1.ts b/server/setup/scriptsSqlite/1.11.1.ts index 7f9065b6e..3aa4ec3d6 100644 --- a/server/setup/scriptsSqlite/1.11.1.ts +++ b/server/setup/scriptsSqlite/1.11.1.ts @@ -11,27 +11,8 @@ export default async function migration() { const db = new Database(location); db.transaction(() => { - const exitNodes = db.prepare(`SELECT * FROM exitNodes WHERE type = 'gerbil' LIMIT 1`).all() as { - exitNodeId: number; - name: string; - }[]; - - const exitNodeId = exitNodes.length > 0 ? exitNodes[0].exitNodeId : null; - - // get all of the targets - const sites = db.prepare(`SELECT * FROM sites WHERE type = 'local'`).all() as { - siteId: number; - exitNodeId: number | null; - }[]; - - const defineExitNodeOnSite = db.prepare( - `UPDATE sites SET exitNodeId = ? WHERE siteId = ?` - ); - - for (const site of sites) { - defineExitNodeOnSite.run(exitNodeId, site.siteId); - } + db.prepare(`UPDATE exitNodes SET online = 1`).run(); // mark exit nodes as online })(); console.log(`${version} migration complete`); -} \ No newline at end of file +} diff --git a/server/setup/scriptsSqlite/1.12.0.ts b/server/setup/scriptsSqlite/1.12.0.ts index 0f2c884d2..bb357c81f 100644 --- a/server/setup/scriptsSqlite/1.12.0.ts +++ b/server/setup/scriptsSqlite/1.12.0.ts @@ -247,4 +247,4 @@ export default async function migration() { } console.log(`${version} migration complete`); -} \ No newline at end of file +} From 2ad634eb96e632c97a4433175832540b9d7dbb32 Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Thu, 20 Nov 2025 13:59:34 +0000 Subject: [PATCH 20/20] fix revert --- server/setup/migrationsSqlite.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/setup/migrationsSqlite.ts b/server/setup/migrationsSqlite.ts index 4e31ab906..dd546db29 100644 --- a/server/setup/migrationsSqlite.ts +++ b/server/setup/migrationsSqlite.ts @@ -197,4 +197,4 @@ async function executeScripts() { console.error("Migration process failed:", error); throw error; } -} \ No newline at end of file +}