Compare commits

..

1 Commits

Author SHA1 Message Date
github-actions[bot]
de304bb9b6 chore: add VERSION v0.4.10 2025-11-07 14:00:15 +00:00
16 changed files with 289 additions and 828 deletions

View File

@@ -1 +1 @@
0.4.10.3
0.4.10

View File

@@ -52,14 +52,6 @@ const config = {
}
return config;
},
// Ignore ESLint errors during build (they can be fixed separately)
eslint: {
ignoreDuringBuilds: true,
},
// Ignore TypeScript errors during build (they can be fixed separately)
typescript: {
ignoreBuildErrors: true,
},
};
export default config;

286
package-lock.json generated
View File

@@ -29,8 +29,8 @@
"cron-validator": "^1.2.0",
"dotenv": "^17.2.3",
"jsonwebtoken": "^9.0.2",
"lucide-react": "^0.553.0",
"next": "^15.1.6",
"lucide-react": "^0.548.0",
"next": "^15.5.6",
"node-cron": "^3.0.3",
"node-pty": "^1.0.0",
"react": "^19.0.0",
@@ -63,25 +63,18 @@
"@vitest/coverage-v8": "^3.2.4",
"@vitest/ui": "^3.2.4",
"eslint": "^9.38.0",
"eslint-config-next": "^15.1.6",
"jsdom": "^27.1.0",
"eslint-config-next": "^16.0.0",
"jsdom": "^27.0.1",
"postcss": "^8.5.3",
"prettier": "^3.5.3",
"prettier-plugin-tailwindcss": "^0.7.1",
"prisma": "^6.19.0",
"tailwindcss": "^4.1.17",
"prisma": "^6.18.0",
"tailwindcss": "^4.1.16",
"typescript": "^5.8.2",
"typescript-eslint": "^8.46.2",
"vitest": "^3.2.4"
}
},
"node_modules/@acemir/cssom": {
"version": "0.9.23",
"resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.23.tgz",
"integrity": "sha512-2kJ1HxBKzPLbmhZpxBiTZggjtgCwKg1ma5RHShxvd6zgqhDEdEkzpiwe7jLkI2p2BrZvFCXIihdoMkl1H39VnA==",
"dev": true,
"license": "MIT"
},
"node_modules/@adobe/css-tools": {
"version": "4.4.4",
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz",
@@ -141,9 +134,9 @@
}
},
"node_modules/@asamuzakjp/dom-selector": {
"version": "6.7.4",
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.4.tgz",
"integrity": "sha512-buQDjkm+wDPXd6c13534URWZqbz0RP5PAhXZ+LIoa5LgwInT9HVJvGIJivg75vi8I13CxDGdTnz+aY5YUJlIAA==",
"version": "6.7.2",
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.2.tgz",
"integrity": "sha512-ccKogJI+0aiDhOahdjANIc9SDixSud1gbwdVrhn7kMopAtLXqsz9MKmQQtIl6Y5aC2IYq+j4dz/oedL2AVMmVQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -324,9 +317,9 @@
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
"dev": true,
"license": "MIT",
"engines": {
@@ -358,13 +351,13 @@
}
},
"node_modules/@babel/parser": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
"integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz",
"integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.28.5"
"@babel/types": "^7.28.4"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -449,14 +442,14 @@
}
},
"node_modules/@babel/types": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
"integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz",
"integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.28.5"
"@babel/helper-validator-identifier": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@@ -568,9 +561,9 @@
}
},
"node_modules/@csstools/css-syntax-patches-for-csstree": {
"version": "1.0.15",
"resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.15.tgz",
"integrity": "sha512-q0p6zkVq2lJnmzZVPR33doA51G7YOja+FBvRdp5ISIthL0MtFCgYHHhR563z9WFGxcOn0WfjSkPDJ5Qig3H3Sw==",
"version": "1.0.14",
"resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.14.tgz",
"integrity": "sha512-zSlIxa20WvMojjpCSy8WrNpcZ61RqfTfX3XTaOeVlGJrt/8HF3YbzgFZa01yTbT4GWQLwfTcC3EB8i3XnB647Q==",
"dev": true,
"funding": [
{
@@ -585,6 +578,9 @@
"license": "MIT-0",
"engines": {
"node": ">=18"
},
"peerDependencies": {
"postcss": "^8.4"
}
},
"node_modules/@csstools/css-tokenizer": {
@@ -1842,9 +1838,9 @@
"license": "MIT"
},
"node_modules/@next/eslint-plugin-next": {
"version": "15.5.6",
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.5.6.tgz",
"integrity": "sha512-YxDvsT2fwy1j5gMqk3ppXlsgDopHnkM4BoxSVASbvvgh5zgsK8lvWerDzPip8k3WVzsTZ1O7A7si1KNfN4OZfQ==",
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.0.0.tgz",
"integrity": "sha512-IB7RzmmtrPOrpAgEBR1PIQPD0yea5lggh5cq54m51jHjjljU80Ia+czfxJYMlSDl1DPvpzb8S9TalCc0VMo9Hw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2068,9 +2064,9 @@
}
},
"node_modules/@prisma/config": {
"version": "6.19.0",
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.19.0.tgz",
"integrity": "sha512-zwCayme+NzI/WfrvFEtkFhhOaZb/hI+X8TTjzjJ252VbPxAl2hWHK5NMczmnG9sXck2lsXrxIZuK524E25UNmg==",
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.18.0.tgz",
"integrity": "sha512-rgFzspCpwsE+q3OF/xkp0fI2SJ3PfNe9LLMmuSVbAZ4nN66WfBiKqJKo/hLz3ysxiPQZf8h1SMf2ilqPMeWATQ==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
@@ -2081,53 +2077,53 @@
}
},
"node_modules/@prisma/debug": {
"version": "6.19.0",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.19.0.tgz",
"integrity": "sha512-8hAdGG7JmxrzFcTzXZajlQCidX0XNkMJkpqtfbLV54wC6LSSX6Vni25W/G+nAANwLnZ2TmwkfIuWetA7jJxJFA==",
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.18.0.tgz",
"integrity": "sha512-PMVPMmxPj0ps1VY75DIrT430MoOyQx9hmm174k6cmLZpcI95rAPXOQ+pp8ANQkJtNyLVDxnxVJ0QLbrm/ViBcg==",
"devOptional": true,
"license": "Apache-2.0"
},
"node_modules/@prisma/engines": {
"version": "6.19.0",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.19.0.tgz",
"integrity": "sha512-pMRJ+1S6NVdXoB8QJAPIGpKZevFjxhKt0paCkRDTZiczKb7F4yTgRP8M4JdVkpQwmaD4EoJf6qA+p61godDokw==",
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.18.0.tgz",
"integrity": "sha512-i5RzjGF/ex6AFgqEe2o1IW8iIxJGYVQJVRau13kHPYEL1Ck8Zvwuzamqed/1iIljs5C7L+Opiz5TzSsUebkriA==",
"devOptional": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "6.19.0",
"@prisma/engines-version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773",
"@prisma/fetch-engine": "6.19.0",
"@prisma/get-platform": "6.19.0"
"@prisma/debug": "6.18.0",
"@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f",
"@prisma/fetch-engine": "6.18.0",
"@prisma/get-platform": "6.18.0"
}
},
"node_modules/@prisma/engines-version": {
"version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773.tgz",
"integrity": "sha512-gV7uOBQfAFlWDvPJdQxMT1aSRur3a0EkU/6cfbAC5isV67tKDWUrPauyaHNpB+wN1ebM4A9jn/f4gH+3iHSYSQ==",
"version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f.tgz",
"integrity": "sha512-T7Af4QsJQnSgWN1zBbX+Cha5t4qjHRxoeoWpK4JugJzG/ipmmDMY5S+O0N1ET6sCBNVkf6lz+Y+ZNO9+wFU8pQ==",
"devOptional": true,
"license": "Apache-2.0"
},
"node_modules/@prisma/fetch-engine": {
"version": "6.19.0",
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.19.0.tgz",
"integrity": "sha512-OOx2Lda0DGrZ1rodADT06ZGqHzr7HY7LNMaFE2Vp8dp146uJld58sRuasdX0OiwpHgl8SqDTUKHNUyzEq7pDdQ==",
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.18.0.tgz",
"integrity": "sha512-TdaBvTtBwP3IoqVYoGIYpD4mWlk0pJpjTJjir/xLeNWlwog7Sl3bD2J0jJ8+5+q/6RBg+acb9drsv5W6lqae7A==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "6.19.0",
"@prisma/engines-version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773",
"@prisma/get-platform": "6.19.0"
"@prisma/debug": "6.18.0",
"@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f",
"@prisma/get-platform": "6.18.0"
}
},
"node_modules/@prisma/get-platform": {
"version": "6.19.0",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.19.0.tgz",
"integrity": "sha512-ym85WDO2yDhC3fIXHWYpG3kVMBA49cL1XD2GCsCF8xbwoy2OkDQY44gEbAt2X46IQ4Apq9H6g0Ex1iFfPqEkHA==",
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.18.0.tgz",
"integrity": "sha512-uXNJCJGhxTCXo2B25Ta91Rk1/Nmlqg9p7G9GKh8TPhxvAyXCvMNQoogj4JLEUy+3ku8g59cpyQIKFhqY2xO2bg==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "6.19.0"
"@prisma/debug": "6.18.0"
}
},
"node_modules/@radix-ui/primitive": {
@@ -2985,13 +2981,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@rushstack/eslint-patch": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.14.1.tgz",
"integrity": "sha512-jGTk8UD/RdjsNZW8qq10r0RBvxL8OWtoT+kImlzPDFilmozzM+9QmIJsmze9UiSBrFU45ZxhTYBypn9q9z/VfQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@standard-schema/spec": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
@@ -3079,13 +3068,6 @@
"tailwindcss": "4.1.16"
}
},
"node_modules/@tailwindcss/node/node_modules/tailwindcss": {
"version": "4.1.16",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.16.tgz",
"integrity": "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==",
"dev": true,
"license": "MIT"
},
"node_modules/@tailwindcss/oxide": {
"version": "4.1.16",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.16.tgz",
@@ -3401,13 +3383,6 @@
"tailwindcss": "4.1.16"
}
},
"node_modules/@tailwindcss/postcss/node_modules/tailwindcss": {
"version": "4.1.16",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.16.tgz",
"integrity": "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==",
"dev": true,
"license": "MIT"
},
"node_modules/@tailwindcss/typography": {
"version": "0.5.19",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz",
@@ -4893,13 +4868,13 @@
"license": "MIT"
},
"node_modules/ast-v8-to-istanbul": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.8.tgz",
"integrity": "sha512-szgSZqUxI5T8mLKvS7WTjF9is+MVbOeLADU73IseOcrqhxr/VAvy6wfoVE39KnKzA7JRhjF5eUagNlHwvZPlKQ==",
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.5.tgz",
"integrity": "sha512-9SdXjNheSiE8bALAQCQQuT6fgQaoxJh7IRYrRGZ8/9nv8WhJeC1aXAwN8TbaOssGOukUvyvnkgD9+Yuykvl1aA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.31",
"@jridgewell/trace-mapping": "^0.3.30",
"estree-walker": "^3.0.3",
"js-tokens": "^9.0.1"
}
@@ -5505,9 +5480,9 @@
}
},
"node_modules/cssstyle": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.3.tgz",
"integrity": "sha512-OytmFH+13/QXONJcC75QNdMtKpceNk3u8ThBjyyYjkEcy/ekBwR1mMAuNvi3gdBPW3N5TlCzQ0WZw8H0lN/bDw==",
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.1.tgz",
"integrity": "sha512-g5PC9Aiph9eiczFpcgUhd9S4UUO3F+LHGRIi5NUMZ+4xtoIYbHNZwZnWA2JsFGe8OU8nl4WyaEFiZuGuxlutJQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6192,25 +6167,24 @@
}
},
"node_modules/eslint-config-next": {
"version": "15.5.6",
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.5.6.tgz",
"integrity": "sha512-cGr3VQlPsZBEv8rtYp4BpG1KNXDqGvPo9VC1iaCgIA11OfziC/vczng+TnAS3WpRIR3Q5ye/6yl+CRUuZ1fPGg==",
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.0.0.tgz",
"integrity": "sha512-DWKT1YAO9ex2rK0/EeiPpKU++ghTiG59z6m08/ReLRECOYIaEv17maSCYT8zmFQLwIrY5lhJ+iaJPQdT4sJd4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@next/eslint-plugin-next": "15.5.6",
"@rushstack/eslint-patch": "^1.10.3",
"@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0",
"@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0",
"@next/eslint-plugin-next": "16.0.0",
"eslint-import-resolver-node": "^0.3.6",
"eslint-import-resolver-typescript": "^3.5.2",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jsx-a11y": "^6.10.0",
"eslint-plugin-react": "^7.37.0",
"eslint-plugin-react-hooks": "^5.0.0"
"eslint-plugin-react-hooks": "^7.0.0",
"globals": "16.4.0",
"typescript-eslint": "^8.46.0"
},
"peerDependencies": {
"eslint": "^7.23.0 || ^8.0.0 || ^9.0.0",
"eslint": ">=9.0.0",
"typescript": ">=3.3.1"
},
"peerDependenciesMeta": {
@@ -6219,6 +6193,19 @@
}
}
},
"node_modules/eslint-config-next/node_modules/globals": {
"version": "16.4.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz",
"integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/eslint-import-resolver-node": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
@@ -6422,13 +6409,20 @@
}
},
"node_modules/eslint-plugin-react-hooks": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz",
"integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.0.tgz",
"integrity": "sha512-fNXaOwvKwq2+pXiRpXc825Vd63+KM4DLL40Rtlycb8m7fYpp6efrTp1sa6ZbP/Ap58K2bEKFXRmhURE+CJAQWw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/core": "^7.24.4",
"@babel/parser": "^7.24.4",
"hermes-parser": "^0.25.1",
"zod": "^3.22.4 || ^4.0.0",
"zod-validation-error": "^3.0.3 || ^4.0.0"
},
"engines": {
"node": ">=10"
"node": ">=18"
},
"peerDependencies": {
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
@@ -7281,6 +7275,23 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/hermes-estree": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
"integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
"dev": true,
"license": "MIT"
},
"node_modules/hermes-parser": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
"integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
"dev": true,
"license": "MIT",
"dependencies": {
"hermes-estree": "0.25.1"
}
},
"node_modules/highlight.js": {
"version": "10.7.3",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
@@ -8068,15 +8079,14 @@
}
},
"node_modules/jsdom": {
"version": "27.1.0",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.1.0.tgz",
"integrity": "sha512-Pcfm3eZ+eO4JdZCXthW9tCDT3nF4K+9dmeZ+5X39n+Kqz0DDIABRP5CAEOHRFZk8RGuC2efksTJxrjp8EXCunQ==",
"version": "27.0.1",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.0.1.tgz",
"integrity": "sha512-SNSQteBL1IlV2zqhwwolaG9CwhIhTvVHWg3kTss/cLE7H/X4644mtPQqYvCfsSrGQWt9hSZcgOXX8bOZaMN+kA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@acemir/cssom": "^0.9.19",
"@asamuzakjp/dom-selector": "^6.7.3",
"cssstyle": "^5.3.2",
"@asamuzakjp/dom-selector": "^6.7.2",
"cssstyle": "^5.3.1",
"data-urls": "^6.0.0",
"decimal.js": "^10.6.0",
"html-encoding-sniffer": "^4.0.0",
@@ -8084,6 +8094,7 @@
"https-proxy-agent": "^7.0.6",
"is-potential-custom-element-name": "^1.0.1",
"parse5": "^8.0.0",
"rrweb-cssom": "^0.8.0",
"saxes": "^6.0.0",
"symbol-tree": "^3.2.4",
"tough-cookie": "^6.0.0",
@@ -8096,7 +8107,7 @@
"xml-name-validator": "^5.0.0"
},
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
"node": ">=20"
},
"peerDependencies": {
"canvas": "^3.0.0"
@@ -8650,9 +8661,9 @@
}
},
"node_modules/lucide-react": {
"version": "0.553.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.553.0.tgz",
"integrity": "sha512-BRgX5zrWmNy/lkVAe0dXBgd7XQdZ3HTf+Hwe3c9WK6dqgnj9h+hxV+MDncM88xDWlCq27+TKvHGE70ViODNILw==",
"version": "0.548.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.548.0.tgz",
"integrity": "sha512-63b16z63jM9yc1MwxajHeuu0FRZFsDtljtDjYm26Kd86UQ5HQzu9ksEtoUUw4RBuewodw/tGFmvipePvRsKeDA==",
"license": "ISC",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
@@ -9879,14 +9890,11 @@
}
},
"node_modules/nypm/node_modules/tinyexec": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz",
"integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz",
"integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==",
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">=18"
}
"license": "MIT"
},
"node_modules/object-assign": {
"version": "4.1.1",
@@ -10438,15 +10446,15 @@
}
},
"node_modules/prisma": {
"version": "6.19.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.19.0.tgz",
"integrity": "sha512-F3eX7K+tWpkbhl3l4+VkFtrwJlLXbAM+f9jolgoUZbFcm1DgHZ4cq9AgVEgUym2au5Ad/TDLN8lg83D+M10ycw==",
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.18.0.tgz",
"integrity": "sha512-bXWy3vTk8mnRmT+SLyZBQoC2vtV9Z8u7OHvEu+aULYxwiop/CPiFZ+F56KsNRNf35jw+8wcu8pmLsjxpBxAO9g==",
"devOptional": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/config": "6.19.0",
"@prisma/engines": "6.19.0"
"@prisma/config": "6.18.0",
"@prisma/engines": "6.18.0"
},
"bin": {
"prisma": "build/index.js"
@@ -11158,6 +11166,13 @@
"fsevents": "~2.3.2"
}
},
"node_modules/rrweb-cssom": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
"integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
"dev": true,
"license": "MIT"
},
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -11572,9 +11587,9 @@
"license": "MIT"
},
"node_modules/std-env": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
"integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz",
"integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==",
"dev": true,
"license": "MIT"
},
@@ -11967,9 +11982,9 @@
}
},
"node_modules/tailwindcss": {
"version": "4.1.17",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz",
"integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==",
"version": "4.1.16",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.16.tgz",
"integrity": "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==",
"license": "MIT"
},
"node_modules/tapable": {
@@ -13223,6 +13238,19 @@
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/zod-validation-error": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz",
"integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
}
},
"node_modules/zwitch": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",

View File

@@ -8,7 +8,7 @@
"check": "next lint && tsc --noEmit",
"dev": "next dev",
"dev:server": "node server.js",
"dev:next": "next dev",
"dev:next": "next dev --turbo",
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
"format:write": "prettier --write \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
"lint": "next lint",
@@ -43,8 +43,8 @@
"cron-validator": "^1.2.0",
"dotenv": "^17.2.3",
"jsonwebtoken": "^9.0.2",
"lucide-react": "^0.553.0",
"next": "^15.1.6",
"lucide-react": "^0.548.0",
"next": "^15.5.6",
"node-cron": "^3.0.3",
"node-pty": "^1.0.0",
"react": "^19.0.0",
@@ -77,13 +77,13 @@
"@vitest/coverage-v8": "^3.2.4",
"@vitest/ui": "^3.2.4",
"eslint": "^9.38.0",
"eslint-config-next": "^15.1.6",
"jsdom": "^27.1.0",
"eslint-config-next": "^16.0.0",
"jsdom": "^27.0.1",
"postcss": "^8.5.3",
"prettier": "^3.5.3",
"prettier-plugin-tailwindcss": "^0.7.1",
"prisma": "^6.19.0",
"tailwindcss": "^4.1.17",
"prisma": "^6.18.0",
"tailwindcss": "^4.1.16",
"typescript": "^5.8.2",
"typescript-eslint": "^8.46.2",
"vitest": "^3.2.4"

View File

@@ -1,132 +0,0 @@
# Copyright (c) 2021-2025 community-scripts ORG
# Author: michelroegl-brunner
# License: MIT | https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/LICENSE
post_to_api() {
if ! command -v curl &>/dev/null; then
return
fi
if [ "$DIAGNOSTICS" = "no" ]; then
return
fi
if [ -z "$RANDOM_UUID" ]; then
return
fi
local API_URL="http://api.community-scripts.org/upload"
local pve_version="not found"
pve_version=$(pveversion | awk -F'[/ ]' '{print $2}')
JSON_PAYLOAD=$(
cat <<EOF
{
"ct_type": $CT_TYPE,
"type":"lxc",
"disk_size": $DISK_SIZE,
"core_count": $CORE_COUNT,
"ram_size": $RAM_SIZE,
"os_type": "$var_os",
"os_version": "$var_version",
"disableip6": "",
"nsapp": "$NSAPP",
"method": "$METHOD(PVE-Local)",
"pve_version": "$pve_version",
"status": "installing",
"random_id": "$RANDOM_UUID"
}
EOF
)
if [[ "$DIAGNOSTICS" == "yes" ]]; then
RESPONSE=$(curl -s -w "%{http_code}" -L -X POST "$API_URL" --post301 --post302 \
-H "Content-Type: application/json" \
-d "$JSON_PAYLOAD") || true
fi
}
post_to_api_vm() {
if [[ ! -f /usr/local/community-scripts/diagnostics ]]; then
return
fi
DIAGNOSTICS=$(grep -i "^DIAGNOSTICS=" /usr/local/community-scripts/diagnostics | awk -F'=' '{print $2}')
if ! command -v curl &>/dev/null; then
return
fi
if [ "$DIAGNOSTICS" = "no" ]; then
return
fi
if [ -z "$RANDOM_UUID" ]; then
return
fi
local API_URL="http://api.community-scripts.org/upload"
local pve_version="not found"
pve_version=$(pveversion | awk -F'[/ ]' '{print $2}')
DISK_SIZE_API=${DISK_SIZE%G}
JSON_PAYLOAD=$(
cat <<EOF
{
"ct_type": 2,
"type":"vm",
"disk_size": $DISK_SIZE_API,
"core_count": $CORE_COUNT,
"ram_size": $RAM_SIZE,
"os_type": "$var_os",
"os_version": "$var_version",
"disableip6": "",
"nsapp": "$NSAPP",
"method": "$METHOD(PVE-Local)",
"pve_version": "$pve_version",
"status": "installing",
"random_id": "$RANDOM_UUID"
}
EOF
)
if [[ "$DIAGNOSTICS" == "yes" ]]; then
RESPONSE=$(curl -s -w "%{http_code}" -L -X POST "$API_URL" --post301 --post302 \
-H "Content-Type: application/json" \
-d "$JSON_PAYLOAD") || true
fi
}
POST_UPDATE_DONE=false
post_update_to_api() {
if ! command -v curl &>/dev/null; then
return
fi
if [ "$POST_UPDATE_DONE" = true ]; then
return 0
fi
local API_URL="http://api.community-scripts.org/upload/updatestatus"
local status="${1:-failed}"
local error="${2:-No error message}"
JSON_PAYLOAD=$(
cat <<EOF
{
"status": "$status",
"error": "$error",
"random_id": "$RANDOM_UUID"
}
EOF
)
if [[ "$DIAGNOSTICS" == "yes" ]]; then
RESPONSE=$(curl -s -w "%{http_code}" -L -X POST "$API_URL" --post301 --post302 \
-H "Content-Type: application/json" \
-d "$JSON_PAYLOAD") || true
fi
POST_UPDATE_DONE=true
}

View File

@@ -17,9 +17,9 @@ variables() {
# Get absolute path to core directory
CORE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$CORE_DIR/api.func"
source "$CORE_DIR/core.func"
load_functions
# This function enables error handling in the script by setting options and defining a trap for the ERR signal.
catch_errors() {
set -Eeo pipefail
@@ -33,15 +33,15 @@ error_handler() {
local line_number="$1"
local command="$2"
local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}"
post_update_to_api "failed" "${command}"
echo -e "\n$error_message\n"
}
# Check if the current shell is using bash
# Check if the shell is using bash
shell_check() {
if [[ "$(ps -p $$ -o comm=)" != "bash" ]]; then
if [[ "$(basename "$SHELL")" != "bash" ]]; then
clear
msg_error "Your default shell is not bash. Please report this to our github issues or discord."
msg_error "Your default shell is currently not set to Bash. To use these scripts, please switch to the Bash shell."
echo -e "\nExiting..."
sleep 2
exit
@@ -439,17 +439,14 @@ advanced_settings() {
exit_script
fi
done
if CT_ID=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Container ID" 8 58 "$NEXTID" --title "CONTAINER ID" 3>&1 1>&2 2>&3); then
if [ -z "$CT_ID" ]; then
CT_ID="$NEXTID"
echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}$CT_ID${CL}"
else
echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}$CT_ID${CL}"
fi
else
exit_script
fi
echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}$CT_ID${CL}"
while true; do
if CT_NAME=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Hostname" 8 58 "$NSAPP" --title "HOSTNAME" 3>&1 1>&2 2>&3); then
@@ -914,7 +911,6 @@ install_script() {
shell_check
root_check
arch_check
#ssh_check
maxkeys_check
diagnostics_check
@@ -1049,6 +1045,7 @@ start() {
source "$CORE_DIR/tools.func"
if command -v pveversion >/dev/null 2>&1; then
install_script
else
CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "${APP} LXC Update/Setting" --menu \
"Support/Update functions for ${APP} LXC. Choose an option:" \
@@ -1078,6 +1075,7 @@ start() {
# This function collects user settings and integrates all the collected information.
build_container() {
# if [ "$VERBOSE" == "yes" ]; then set -x; fi
NET_STRING="-net0 name=eth0,bridge=$BRG$MAC,ip=$NET$GATE$VLAN$MTU"
@@ -1101,12 +1099,18 @@ build_container() {
fi
if [[ $DIAGNOSTICS == "yes" ]]; then
post_to_api
echo "Diagnostics enabled (post_to_api function not available)"
fi
TEMP_DIR=$(mktemp -d)
pushd "$TEMP_DIR" >/dev/null
# CORE_DIR is already defined at the top of the file
if [ "$var_os" == "alpine" ]; then
export FUNCTIONS_FILE_PATH="$(cat "$CORE_DIR/core.func" && echo && cat "$CORE_DIR/tools.func" && echo && cat "$CORE_DIR/alpine-install.func")"
else
export FUNCTIONS_FILE_PATH="$(cat "$CORE_DIR/core.func" && echo && cat "$CORE_DIR/tools.func" && echo && cat "$CORE_DIR/install.func")"
fi
export DIAGNOSTICS="$DIAGNOSTICS"
export RANDOM_UUID="$RANDOM_UUID"
@@ -1126,10 +1130,21 @@ build_container() {
export PCT_OSTYPE="$var_os"
export PCT_OSVERSION="$var_version"
export PCT_DISK_SIZE="$DISK_SIZE"
export PCT_OPTIONS="-features $FEATURES -hostname $HN -tags $TAGS $SD $NS $NET_STRING -onboot 1 -cores $CORE_COUNT -memory $RAM_SIZE -unprivileged $CT_TYPE $PW"
export PCT_OPTIONS="
-features $FEATURES
-hostname $HN
-tags $TAGS
$SD
$NS
$NET_STRING
-onboot 1
-cores $CORE_COUNT
-memory $RAM_SIZE
-unprivileged $CT_TYPE
$PW
"
# This executes create_lxc.sh and creates the container and .conf file
bash "$CORE_DIR/create_lxc.sh"
bash "$CORE_DIR/create_lxc.sh" $?
LXC_CONFIG="/etc/pve/lxc/${CTID}.conf"
@@ -1322,64 +1337,8 @@ EOF'
fi
msg_ok "Customized LXC Container"
if [ "$var_os" == "alpine" ]; then
FUNCTIONS_FILE_PATH="$(cat "$CORE_DIR/core.func" && echo && cat "$CORE_DIR/tools.func" && echo && cat "$CORE_DIR/alpine-install.func")"
else
FUNCTIONS_FILE_PATH="$(cat "$CORE_DIR/core.func" && echo && cat "$CORE_DIR/tools.func" && echo && cat "$CORE_DIR/install.func")"
fi
FUNCTIONS_FILE="/tmp/functions.sh"
echo "$FUNCTIONS_FILE_PATH" | pct exec "$CTID" -- bash -c "cat > $FUNCTIONS_FILE"
pct exec "$CTID" -- test -f "$FUNCTIONS_FILE" || {
msg_error "Failed to write functions file to container"
exit 1
}
INSTALL_SCRIPT_PATH="$(dirname "$CORE_DIR")/install/${var_install}.sh"
INSTALL_SCRIPT_CONTENT=$(cat "$INSTALL_SCRIPT_PATH")
# Replace the old pattern: source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
# with direct file source: source "/tmp/functions.sh"
# Use sed with a simpler pattern that matches the exact line
MODIFIED_INSTALL_SCRIPT=$(echo "$INSTALL_SCRIPT_CONTENT" | \
sed "s|source /dev/stdin <<<\"\$FUNCTIONS_FILE_PATH\"|source \"$FUNCTIONS_FILE\"|")
# Verify replacement worked - if not, force add the source line
if ! echo "$MODIFIED_INSTALL_SCRIPT" | grep -q "source \"$FUNCTIONS_FILE\""; then
# If replacement didn't work, add source line after the last comment
MODIFIED_INSTALL_SCRIPT=$(echo "$MODIFIED_INSTALL_SCRIPT" | \
awk -v func_file="$FUNCTIONS_FILE" '
BEGIN { replaced = 0 }
{
if (/source.*dev\/stdin/ || /source.*FUNCTIONS_FILE_PATH/) {
print "source \"" func_file "\""
replaced = 1
next
}
if (!replaced && /^#/ && !/^#!/) {
print
next
}
if (!replaced && !/^#!/ && !/^#/) {
print "source \"" func_file "\""
replaced = 1
}
print
}
END {
if (!replaced) {
print "source \"" func_file "\""
}
}')
fi
# Write the modified script to a file in the container and execute it
INSTALL_SCRIPT_FILE="/tmp/install_script.sh"
echo "$MODIFIED_INSTALL_SCRIPT" | pct exec "$CTID" -- bash -c "cat > $INSTALL_SCRIPT_FILE"
pct exec "$CTID" -- chmod +x "$INSTALL_SCRIPT_FILE"
lxc-attach -n "$CTID" -- bash "$INSTALL_SCRIPT_FILE"
lxc-attach -n "$CTID" -- bash -c "$(cat "$(dirname "$CORE_DIR")/install/${var_install}.sh")"
}
# This function sets the description of the container.
@@ -1424,37 +1383,6 @@ EOF
if [[ -f /etc/systemd/system/ping-instances.service ]]; then
systemctl start ping-instances.service
fi
post_update_to_api "done" "none"
}
api_exit_script() {
exit_code=$? # Capture the exit status of the last executed command
#200 exit codes indicate error in create_lxc.sh
#100 exit codes indicate error in install.func
if [ $exit_code -ne 0 ]; then
case $exit_code in
100) post_update_to_api "failed" "100: Unexpected error in create_lxc.sh" ;;
101) post_update_to_api "failed" "101: No network connection detected in create_lxc.sh" ;;
200) post_update_to_api "failed" "200: LXC creation failed in create_lxc.sh" ;;
201) post_update_to_api "failed" "201: Invalid Storage class in create_lxc.sh" ;;
202) post_update_to_api "failed" "202: User aborted menu in create_lxc.sh" ;;
203) post_update_to_api "failed" "203: CTID not set in create_lxc.sh" ;;
204) post_update_to_api "failed" "204: PCT_OSTYPE not set in create_lxc.sh" ;;
205) post_update_to_api "failed" "205: CTID cannot be less than 100 in create_lxc.sh" ;;
206) post_update_to_api "failed" "206: CTID already in use in create_lxc.sh" ;;
207) post_update_to_api "failed" "207: Template not found in create_lxc.sh" ;;
208) post_update_to_api "failed" "208: Error downloading template in create_lxc.sh" ;;
209) post_update_to_api "failed" "209: Container creation failed, but template is intact in create_lxc.sh" ;;
*) post_update_to_api "failed" "Unknown error, exit code: $exit_code in create_lxc.sh" ;;
esac
fi
}
if command -v pveversion >/dev/null 2>&1; then
trap 'api_exit_script' EXIT
fi
trap 'post_update_to_api "failed" "$BASH_COMMAND"' ERR
trap 'post_update_to_api "failed" "INTERRUPTED"' SIGINT
trap 'post_update_to_api "failed" "TERMINATED"' SIGTERM

View File

@@ -368,54 +368,6 @@ run_container_safe() {
" || __handle_general_error "lxc-attach to CT $ct"
}
cleanup_lxc() {
msg_info "Cleaning up"
if is_alpine; then
$STD apk cache clean || true
rm -rf /var/cache/apk/*
else
$STD apt -y autoremove || true
$STD apt -y autoclean || true
$STD apt -y clean || true
fi
# Clear temp artifacts (keep sockets/FIFOs; ignore errors)
find /tmp /var/tmp -type f -name 'tmp*' -delete 2>/dev/null || true
find /tmp /var/tmp -type f -name 'tempfile*' -delete 2>/dev/null || true
# Truncate writable log files silently (permission errors ignored)
if command -v truncate >/dev/null 2>&1; then
find /var/log -type f -writable -print0 2>/dev/null |
xargs -0 -n1 truncate -s 0 2>/dev/null || true
fi
# Python pip
if command -v pip &>/dev/null; then $STD pip cache purge || true; fi
# Python uv
if command -v uv &>/dev/null; then $STD uv cache clear || true; fi
# Node.js npm
if command -v npm &>/dev/null; then $STD npm cache clean --force || true; fi
# Node.js yarn
if command -v yarn &>/dev/null; then $STD yarn cache clean || true; fi
# Node.js pnpm
if command -v pnpm &>/dev/null; then $STD pnpm store prune || true; fi
# Go
if command -v go &>/dev/null; then $STD go clean -cache -modcache || true; fi
# Rust cargo
if command -v cargo &>/dev/null; then $STD cargo clean || true; fi
# Ruby gem
if command -v gem &>/dev/null; then $STD gem cleanup || true; fi
# Composer (PHP)
if command -v composer &>/dev/null; then $STD composer clear-cache || true; fi
if command -v journalctl &>/dev/null; then
$STD journalctl --rotate || true
$STD journalctl --vacuum-time=10m || true
fi
msg_ok "Cleaned"
}
check_or_create_swap() {
msg_info "Checking for active swap"
@@ -457,4 +409,4 @@ check_or_create_swap() {
trap 'stop_spinner' EXIT INT TERM
# Initialize functions when core.func is sourced
load_functions
load_functions

View File

@@ -72,23 +72,15 @@ stop_all_services() {
local service_patterns=("$@")
for pattern in "${service_patterns[@]}"; do
# Find all matching services (use || true to avoid pipeline failures)
local services
services=$(systemctl list-units --type=service --all 2>/dev/null |
grep -oE "${pattern}[^ ]*\.service" 2>/dev/null |
sort -u 2>/dev/null || true)
# Only process if we found any services
if [[ -n "$services" ]]; then
while IFS= read -r service; do
[[ -z "$service" ]] && continue
# Find all matching services
systemctl list-units --type=service --all 2>/dev/null |
grep -oE "${pattern}[^ ]*\.service" |
sort -u |
while read -r service; do
$STD systemctl stop "$service" 2>/dev/null || true
$STD systemctl disable "$service" 2>/dev/null || true
done <<<"$services"
fi
done
done
return 0
}
# ------------------------------------------------------------------------------
@@ -1206,49 +1198,65 @@ ensure_apt_working() {
}
# ------------------------------------------------------------------------------
# Standardized deb822 repository setup (with optional Architectures)
# Always runs apt update after repo creation to ensure package availability
# Standardized deb822 repository setup
# Validates all parameters and fails safely if any are empty
# ------------------------------------------------------------------------------
setup_deb822_repo() {
local name="$1"
local gpg_url="$2"
local repo_url="$3"
local suite="$4"
local component="${5-main}"
local architectures="${6-}" # optional
local component="${5:-main}"
local architectures="${6:-$(dpkg --print-architecture)}"
# Validate required parameters
if [[ -z "$name" || -z "$gpg_url" || -z "$repo_url" || -z "$suite" ]]; then
msg_error "setup_deb822_repo: missing required parameters (name=$name repo=$repo_url suite=$suite)"
msg_error "setup_deb822_repo: missing required parameters (name=$name, gpg=$gpg_url, repo=$repo_url, suite=$suite)"
return 1
fi
# Cleanup
# Cleanup old configs for this app
cleanup_old_repo_files "$name"
# Cleanup any orphaned .sources files from other apps
cleanup_orphaned_sources
# Ensure keyring directory exists
mkdir -p /etc/apt/keyrings || {
msg_error "Failed to create /etc/apt/keyrings"
msg_error "Failed to create /etc/apt/keyrings directory"
return 1
}
# Import GPG
curl -fsSL "$gpg_url" | gpg --dearmor --yes -o "/etc/apt/keyrings/${name}.gpg" || {
msg_error "Failed to import GPG key for ${name}"
# Download GPG key (with --yes to avoid interactive prompts)
curl -fsSL "$gpg_url" | gpg --dearmor --yes -o "/etc/apt/keyrings/${name}.gpg" 2>/dev/null || {
msg_error "Failed to download or import GPG key for ${name} from $gpg_url"
return 1
}
# Write deb822
{
echo "Types: deb"
echo "URIs: $repo_url"
echo "Suites: $suite"
echo "Components: $component"
[[ -n "$architectures" ]] && echo "Architectures: $architectures"
echo "Signed-By: /etc/apt/keyrings/${name}.gpg"
} >/etc/apt/sources.list.d/${name}.sources
# Create deb822 sources file
cat <<EOF >/etc/apt/sources.list.d/${name}.sources
Types: deb
URIs: $repo_url
Suites: $suite
Components: $component
Architectures: $architectures
Signed-By: /etc/apt/keyrings/${name}.gpg
EOF
$STD apt update
# Use cached apt update
local apt_cache_file="/var/cache/apt-update-timestamp"
local current_time=$(date +%s)
local last_update=0
if [[ -f "$apt_cache_file" ]]; then
last_update=$(cat "$apt_cache_file" 2>/dev/null || echo 0)
fi
# For repo changes, always update but respect short-term cache (30s)
if ((current_time - last_update > 30)); then
$STD apt update
echo "$current_time" >"$apt_cache_file"
fi
}
# ------------------------------------------------------------------------------
@@ -1407,7 +1415,7 @@ verify_gpg_fingerprint() {
}
# ==============================================================================
# INSTALL FUNCTIONS
# EXISTING FUNCTIONS
# ==============================================================================
# ------------------------------------------------------------------------------
@@ -1509,7 +1517,7 @@ check_for_gh_release() {
return 0
fi
msg_ok "No update available: ${app} is already on pinned version (${current})"
msg_error "No update available: ${app} is not installed!"
return 1
fi
@@ -2787,9 +2795,8 @@ function setup_java() {
fi
# Validate INSTALLED_VERSION is not empty if matched
local JDK_COUNT=0
JDK_COUNT=$(dpkg -l 2>/dev/null | grep -c "temurin-.*-jdk")
if [[ -z "$INSTALLED_VERSION" && "${JDK_COUNT:-0}" -gt 0 ]]; then
local JDK_COUNT=$(dpkg -l 2>/dev/null | grep -c "temurin-.*-jdk" || echo "0")
if [[ -z "$INSTALLED_VERSION" && "$JDK_COUNT" -gt 0 ]]; then
msg_warn "Found Temurin JDK but cannot determine version"
INSTALLED_VERSION="0"
fi
@@ -3053,85 +3060,6 @@ setup_mariadb() {
msg_ok "Setup MariaDB $MARIADB_VERSION"
}
# ------------------------------------------------------------------------------
# Creates MariaDB database with user, charset and optional extra grants/modes
#
# Description:
# - Generates password if empty
# - Creates database with utf8mb4_unicode_ci
# - Creates local user with password
# - Grants full access to this DB
# - Optional: apply extra GRANT statements (comma-separated)
# - Optional: apply custom GLOBAL sql_mode
# - Saves credentials to file
# - Exports variables for use in calling script
#
# Usage:
# MARIADB_DB_NAME="myapp_db" MARIADB_DB_USER="myapp_user" setup_mariadb_db
# MARIADB_DB_NAME="domain_monitor" MARIADB_DB_USER="domainmonitor" setup_mariadb_db
# MARIADB_DB_NAME="myapp" MARIADB_DB_USER="myapp" MARIADB_DB_EXTRA_GRANTS="GRANT SELECT ON \`mysql\`.\`time_zone_name\`" setup_mariadb_db
# MARIADB_DB_NAME="ghostfolio" MARIADB_DB_USER="ghostfolio" MARIADB_DB_SQL_MODE="" setup_mariadb_db
#
# Variables:
# MARIADB_DB_NAME - Database name (required)
# MARIADB_DB_USER - Database user (required)
# MARIADB_DB_PASS - User password (optional, auto-generated if empty)
# MARIADB_DB_EXTRA_GRANTS - Comma-separated GRANT statements (optional)
# Example: "GRANT SELECT ON \`mysql\`.\`time_zone_name\`"
# MARIADB_DB_SQL_MODE - Optional global sql_mode override (e.g. "", "STRICT_TRANS_TABLES")
# MARIADB_DB_CREDS_FILE - Credentials file path (optional, default: ~/${APPLICATION}.creds)
#
# Exports:
# MARIADB_DB_NAME, MARIADB_DB_USER, MARIADB_DB_PASS
# ------------------------------------------------------------------------------
function setup_mariadb_db() {
if [[ -z "${MARIADB_DB_NAME:-}" || -z "${MARIADB_DB_USER:-}" ]]; then
msg_error "MARIADB_DB_NAME and MARIADB_DB_USER must be set before calling setup_mariadb_db"
return 1
fi
if [[ -z "${MARIADB_DB_PASS:-}" ]]; then
MARIADB_DB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)
fi
msg_info "Setting up MariaDB Database"
$STD mariadb -u root -e "CREATE DATABASE \`$MARIADB_DB_NAME\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
$STD mariadb -u root -e "CREATE USER '$MARIADB_DB_USER'@'localhost' IDENTIFIED BY '$MARIADB_DB_PASS';"
$STD mariadb -u root -e "GRANT ALL ON \`$MARIADB_DB_NAME\`.* TO '$MARIADB_DB_USER'@'localhost';"
# Optional extra grants
if [[ -n "${MARIADB_DB_EXTRA_GRANTS:-}" ]]; then
IFS=',' read -ra G_LIST <<<"${MARIADB_DB_EXTRA_GRANTS:-}"
for g in "${G_LIST[@]}"; do
g=$(echo "$g" | xargs)
$STD mariadb -u root -e "$g TO '$MARIADB_DB_USER'@'localhost';"
done
fi
# Optional sql_mode override
if [[ -n "${MARIADB_DB_SQL_MODE:-}" ]]; then
$STD mariadb -u root -e "SET GLOBAL sql_mode='${MARIADB_DB_SQL_MODE:-}';"
fi
$STD mariadb -u root -e "FLUSH PRIVILEGES;"
local CREDS_FILE="${MARIADB_DB_CREDS_FILE:-${HOME}/${APPLICATION}.creds}"
{
echo "MariaDB Credentials"
echo "Database: $MARIADB_DB_NAME"
echo "User: $MARIADB_DB_USER"
echo "Password: $MARIADB_DB_PASS"
} >>"$CREDS_FILE"
msg_ok "Set up MariaDB Database"
export MARIADB_DB_NAME
export MARIADB_DB_USER
export MARIADB_DB_PASS
}
# ------------------------------------------------------------------------------
# Installs or updates MongoDB to specified major version.
#
@@ -3891,103 +3819,6 @@ function setup_postgresql() {
fi
}
# ------------------------------------------------------------------------------
# Creates PostgreSQL database with user and optional extensions
#
# Description:
# - Creates PostgreSQL role with login and password
# - Creates database with UTF8 encoding and template0
# - Installs optional extensions (postgis, pgvector, etc.)
# - Configures ALTER ROLE settings for Django/Rails compatibility
# - Saves credentials to file
# - Exports variables for use in calling script
#
# Usage:
# PG_DB_NAME="myapp_db" PG_DB_USER="myapp_user" setup_postgresql_db
# PG_DB_NAME="immich" PG_DB_USER="immich" PG_DB_EXTENSIONS="pgvector" setup_postgresql_db
# PG_DB_NAME="ghostfolio" PG_DB_USER="ghostfolio" PG_DB_GRANT_SUPERUSER="true" setup_postgresql_db
# PG_DB_NAME="adventurelog" PG_DB_USER="adventurelog" PG_DB_EXTENSIONS="postgis" setup_postgresql_db
#
# Variables:
# PG_DB_NAME - Database name (required)
# PG_DB_USER - Database user (required)
# PG_DB_PASS - Database password (optional, auto-generated if empty)
# PG_DB_EXTENSIONS - Comma-separated list of extensions (optional, e.g. "postgis,pgvector")
# PG_DB_GRANT_SUPERUSER - Grant SUPERUSER privilege (optional, "true" to enable, security risk!)
# PG_DB_SCHEMA_PERMS - Grant schema-level permissions (optional, "true" to enable)
# PG_DB_SKIP_ALTER_ROLE - Skip ALTER ROLE settings (optional, "true" to skip)
# PG_DB_CREDS_FILE - Credentials file path (optional, default: ~/${APPLICATION}.creds)
#
# Exports:
# PG_DB_NAME, PG_DB_USER, PG_DB_PASS - For use in calling script
# ------------------------------------------------------------------------------
function setup_postgresql_db() {
# Validation
if [[ -z "${PG_DB_NAME:-}" || -z "${PG_DB_USER:-}" ]]; then
msg_error "PG_DB_NAME and PG_DB_USER must be set before calling setup_postgresql_db"
return 1
fi
# Generate password if not provided
if [[ -z "${PG_DB_PASS:-}" ]]; then
PG_DB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)
fi
msg_info "Setting up PostgreSQL Database"
$STD sudo -u postgres psql -c "CREATE ROLE $PG_DB_USER WITH LOGIN PASSWORD '$PG_DB_PASS';"
$STD sudo -u postgres psql -c "CREATE DATABASE $PG_DB_NAME WITH OWNER $PG_DB_USER ENCODING 'UTF8' TEMPLATE template0;"
# Install extensions (comma-separated)
if [[ -n "${PG_DB_EXTENSIONS:-}" ]]; then
IFS=',' read -ra EXT_LIST <<<"${PG_DB_EXTENSIONS:-}"
for ext in "${EXT_LIST[@]}"; do
ext=$(echo "$ext" | xargs) # Trim whitespace
$STD sudo -u postgres psql -d "$PG_DB_NAME" -c "CREATE EXTENSION IF NOT EXISTS $ext;"
done
fi
# ALTER ROLE settings for Django/Rails compatibility (unless skipped)
if [[ "${PG_DB_SKIP_ALTER_ROLE:-}" != "true" ]]; then
$STD sudo -u postgres psql -c "ALTER ROLE $PG_DB_USER SET client_encoding TO 'utf8';"
$STD sudo -u postgres psql -c "ALTER ROLE $PG_DB_USER SET default_transaction_isolation TO 'read committed';"
$STD sudo -u postgres psql -c "ALTER ROLE $PG_DB_USER SET timezone TO 'UTC';"
fi
# Schema permissions (if requested)
if [[ "${PG_DB_SCHEMA_PERMS:-}" == "true" ]]; then
$STD sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE $PG_DB_NAME TO $PG_DB_USER;"
$STD sudo -u postgres psql -c "ALTER USER $PG_DB_USER CREATEDB;"
$STD sudo -u postgres psql -d "$PG_DB_NAME" -c "GRANT ALL ON SCHEMA public TO $PG_DB_USER;"
$STD sudo -u postgres psql -d "$PG_DB_NAME" -c "GRANT CREATE ON SCHEMA public TO $PG_DB_USER;"
$STD sudo -u postgres psql -d "$PG_DB_NAME" -c "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO $PG_DB_USER;"
$STD sudo -u postgres psql -d "$PG_DB_NAME" -c "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO $PG_DB_USER;"
fi
# Superuser grant (if requested - WARNING!)
if [[ "${PG_DB_GRANT_SUPERUSER:-}" == "true" ]]; then
msg_warn "Granting SUPERUSER privilege (security risk!)"
$STD sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE $PG_DB_NAME to $PG_DB_USER;"
$STD sudo -u postgres psql -c "ALTER USER $PG_DB_USER WITH SUPERUSER;"
fi
# Save credentials
local CREDS_FILE="${PG_DB_CREDS_FILE:-${HOME}/${APPLICATION}.creds}"
{
echo "PostgreSQL Credentials"
echo "Database: $PG_DB_NAME"
echo "User: $PG_DB_USER"
echo "Password: $PG_DB_PASS"
} >>"$CREDS_FILE"
msg_ok "Set up PostgreSQL Database"
# Export for use in calling script
export PG_DB_NAME
export PG_DB_USER
export PG_DB_PASS
}
# ------------------------------------------------------------------------------
# Installs rbenv and ruby-build, installs Ruby and optionally Rails.
#
@@ -4341,63 +4172,31 @@ function setup_rust() {
}
export PATH="$CARGO_BIN:$PATH"
echo 'export PATH="$HOME/.cargo/bin:$PATH"' >>"$HOME/.profile"
# Verify installation
if ! command -v rustc >/dev/null 2>&1; then
msg_error "Rust binary not found after installation"
return 1
fi
local RUST_VERSION=$(rustc --version 2>/dev/null | awk '{print $2}')
if [[ -z "$RUST_VERSION" ]]; then
msg_error "Failed to determine Rust version"
return 1
fi
cache_installed_version "rust" "$RUST_VERSION"
msg_ok "Setup Rust $RUST_VERSION"
else
# Scenario 2: Rustup already installed - update/maintain
msg_info "Update Rust ($RUST_TOOLCHAIN)"
# Ensure default toolchain is set
$STD rustup default "$RUST_TOOLCHAIN" 2>/dev/null || {
# If default fails, install the toolchain first
$STD rustup install "$RUST_TOOLCHAIN" || {
msg_error "Failed to install Rust toolchain $RUST_TOOLCHAIN"
return 1
}
$STD rustup default "$RUST_TOOLCHAIN" || {
msg_error "Failed to set default Rust toolchain"
return 1
}
}
# Update to latest patch version
$STD rustup update "$RUST_TOOLCHAIN" || true
# Ensure PATH is updated for current shell session
export PATH="$CARGO_BIN:$PATH"
local RUST_VERSION=$(rustc --version 2>/dev/null | awk '{print $2}')
if [[ -z "$RUST_VERSION" ]]; then
msg_error "Failed to determine Rust version after update"
$STD rustup install "$RUST_TOOLCHAIN" || {
msg_error "Failed to install Rust toolchain $RUST_TOOLCHAIN"
return 1
fi
}
$STD rustup default "$RUST_TOOLCHAIN" || {
msg_error "Failed to set default Rust toolchain"
return 1
}
$STD rustup update "$RUST_TOOLCHAIN" || true
local RUST_VERSION=$(rustc --version 2>/dev/null | awk '{print $2}')
cache_installed_version "rust" "$RUST_VERSION"
msg_ok "Update Rust $RUST_VERSION"
fi
# Install global crates
if [[ -n "$RUST_CRATES" ]]; then
msg_info "Processing Rust crates: $RUST_CRATES"
IFS=',' read -ra CRATES <<<"$RUST_CRATES"
for crate in "${CRATES[@]}"; do
crate=$(echo "$crate" | xargs) # trim whitespace
[[ -z "$crate" ]] && continue # skip empty entries
local NAME VER INSTALLED_VER CRATE_LIST
local NAME VER INSTALLED_VER
if [[ "$crate" == *"@"* ]]; then
NAME="${crate%@*}"
VER="${crate##*@}"
@@ -4406,50 +4205,18 @@ function setup_rust() {
VER=""
fi
# Get list of installed crates once
CRATE_LIST=$(cargo install --list 2>/dev/null || echo "")
# Check if already installed
if echo "$CRATE_LIST" | grep -q "^${NAME} "; then
INSTALLED_VER=$(echo "$CRATE_LIST" | grep "^${NAME} " | head -1 | awk '{print $2}' | tr -d 'v:')
INSTALLED_VER=$(cargo install --list 2>/dev/null | awk "/^$NAME v[0-9]/ {print \$2}" | tr -d 'v')
if [[ -n "$INSTALLED_VER" ]]; then
if [[ -n "$VER" && "$VER" != "$INSTALLED_VER" ]]; then
msg_info "Upgrading $NAME from v$INSTALLED_VER to v$VER"
$STD cargo install "$NAME" --version "$VER" --force || {
msg_error "Failed to install $NAME@$VER"
return 1
}
msg_ok "Upgraded $NAME to v$VER"
$STD cargo install "$NAME" --version "$VER" --force
elif [[ -z "$VER" ]]; then
msg_info "Upgrading $NAME to latest"
$STD cargo install "$NAME" --force || {
msg_error "Failed to upgrade $NAME"
return 1
}
local NEW_VER=$(cargo install --list 2>/dev/null | grep "^${NAME} " | head -1 | awk '{print $2}' | tr -d 'v:')
msg_ok "Upgraded $NAME to v$NEW_VER"
else
msg_ok "$NAME v$INSTALLED_VER already installed"
$STD cargo install "$NAME" --force
fi
else
msg_info "Installing $NAME${VER:+@$VER}"
if [[ -n "$VER" ]]; then
$STD cargo install "$NAME" --version "$VER" || {
msg_error "Failed to install $NAME@$VER"
return 1
}
msg_ok "Installed $NAME v$VER"
else
$STD cargo install "$NAME" || {
msg_error "Failed to install $NAME"
return 1
}
local NEW_VER=$(cargo install --list 2>/dev/null | grep "^${NAME} " | head -1 | awk '{print $2}' | tr -d 'v:')
msg_ok "Installed $NAME v$NEW_VER"
fi
$STD cargo install "$NAME" ${VER:+--version "$VER"}
fi
done
msg_ok "Processed Rust crates"
fi
}
@@ -4586,9 +4353,7 @@ function setup_uv() {
# Optional: Generate shell completions
$STD uv generate-shell-completion bash >/etc/bash_completion.d/uv 2>/dev/null || true
if [[ -d /usr/share/zsh/site-functions ]]; then
$STD uv generate-shell-completion zsh >/usr/share/zsh/site-functions/_uv 2>/dev/null || true
fi
$STD uv generate-shell-completion zsh >/usr/share/zsh/site-functions/_uv 2>/dev/null || true
# Optional: Install specific Python version if requested
if [[ -n "${PYTHON_VERSION:-}" ]]; then
@@ -4701,4 +4466,4 @@ function setup_yq() {
FINAL_VERSION=$("$BINARY_PATH" --version 2>/dev/null | awk '{print $NF}' | sed 's/^v//')
cache_installed_version "yq" "$FINAL_VERSION"
msg_ok "Setup yq $FINAL_VERSION"
}
}

View File

@@ -106,12 +106,7 @@ export function AuthProvider({ children }: AuthProviderProps) {
setUsername(data.username);
// Check auth again to get expiration time
// Add a small delay to ensure the httpOnly cookie is available
await new Promise<void>((resolve) => {
setTimeout(() => {
void checkAuth().then(() => resolve());
}, 150);
});
await checkAuth();
return true;
} else {
const errorData = await response.json();

View File

@@ -185,19 +185,13 @@ export function DownloadedScriptsTab({ onInstallScript }: DownloadedScriptsTabPr
// Check if there's a corresponding local script
const hasLocalVersion = localScriptsData?.scripts?.some(local => {
if (!local?.name) return false;
// Primary: Exact slug-to-slug matching (most reliable, prevents false positives)
if (local.slug && script.slug) {
if (local.slug.toLowerCase() === script.slug.toLowerCase()) {
return true;
}
}
// Secondary: Check install basenames (for edge cases where install script names differ from slugs)
// Only use normalized matching for install basenames, not for slug/name matching
const normalizedLocal = normalizeId(local.name);
const matchesNameOrSlug = (
normalizedLocal === normalizeId(script.name) ||
normalizedLocal === normalizeId(script.slug)
);
const matchesInstallBasename = (script as any)?.install_basenames?.some((base: string) => normalizeId(base) === normalizedLocal) ?? false;
return matchesInstallBasename;
return matchesNameOrSlug || matchesInstallBasename;
}) ?? false;
return {

View File

@@ -517,26 +517,9 @@ export function InstalledScriptsTab() {
}
}
const handleDeleteScript = (id: number, script?: InstalledScript) => {
const scriptToDelete = script ?? scripts.find(s => s.id === id);
if (scriptToDelete && scriptToDelete.container_id && scriptToDelete.execution_mode === 'ssh') {
// For SSH scripts with container_id, use confirmation modal
setConfirmationModal({
isOpen: true,
variant: 'simple',
title: 'Delete Database Record Only',
message: `This will only delete the database record for "${scriptToDelete.script_name}" (Container ID: ${scriptToDelete.container_id}).\n\nThe container will remain intact and can be re-detected later via auto-detect.`,
onConfirm: () => {
void deleteScriptMutation.mutate({ id });
setConfirmationModal(null);
}
});
} else {
// For non-SSH scripts or scripts without container_id, use simple confirm
if (confirm('Are you sure you want to delete this installation record?')) {
void deleteScriptMutation.mutate({ id });
}
const handleDeleteScript = (id: number) => {
if (confirm('Are you sure you want to delete this installation record?')) {
void deleteScriptMutation.mutate({ id });
}
};
@@ -1585,14 +1568,6 @@ export function InstalledScriptsTab() {
>
{controllingScriptId === script.id ? 'Working...' : 'Destroy'}
</DropdownMenuItem>
<DropdownMenuSeparator className="bg-border" />
<DropdownMenuItem
onClick={() => handleDeleteScript(script.id, script)}
disabled={deleteScriptMutation.isPending}
className="text-muted-foreground hover:text-foreground hover:bg-muted/20 focus:bg-muted/20"
>
{deleteScriptMutation.isPending ? 'Deleting...' : 'Delete only from DB'}
</DropdownMenuItem>
</>
)}
{(!script.container_id || script.execution_mode !== 'ssh') && (

View File

@@ -216,19 +216,13 @@ export function ScriptsGrid({ onInstallScript }: ScriptsGridProps) {
// Check if there's a corresponding local script
const hasLocalVersion = localScriptsData?.scripts?.some(local => {
if (!local?.name) return false;
// Primary: Exact slug-to-slug matching (most reliable, prevents false positives)
if (local.slug && script.slug) {
if (local.slug.toLowerCase() === script.slug.toLowerCase()) {
return true;
}
}
// Secondary: Check install basenames (for edge cases where install script names differ from slugs)
// Only use normalized matching for install basenames, not for slug/name matching
const normalizedLocal = normalizeId(local.name);
const matchesNameOrSlug = (
normalizedLocal === normalizeId(script.name) ||
normalizedLocal === normalizeId(script.slug)
);
const matchesInstallBasename = (script as any)?.install_basenames?.some((base: string) => normalizeId(base) === normalizedLocal) ?? false;
return matchesInstallBasename;
return matchesNameOrSlug || matchesInstallBasename;
}) ?? false;
return {

View File

@@ -63,26 +63,14 @@ export function ServerForm({ onSubmit, initialData, isEditing = false, onCancel
return true;
}
// Check for IPv6 with zone identifier (link-local addresses like fe80::...%eth0)
let ipv6Address = trimmed;
const zoneIdMatch = trimmed.match(/^(.+)%([a-zA-Z0-9_\-]+)$/);
if (zoneIdMatch) {
ipv6Address = zoneIdMatch[1];
// Zone identifier should be a valid interface name (alphanumeric, underscore, hyphen)
const zoneId = zoneIdMatch[2];
if (!/^[a-zA-Z0-9_\-]+$/.test(zoneId)) {
return false;
}
}
// IPv6 validation (supports compressed format like ::1 and full format)
// Matches: 2001:0db8:85a3:0000:0000:8a2e:0370:7334, ::1, 2001:db8::1, etc.
// Also supports IPv4-mapped IPv6 addresses like ::ffff:192.168.1.1
// Simplified validation: check for valid hex segments separated by colons
const ipv6Pattern = /^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^::1$|^::$|^(?:[0-9a-fA-F]{1,4}:)*::(?:[0-9a-fA-F]{1,4}:)*[0-9a-fA-F]{1,4}$|^(?:[0-9a-fA-F]{1,4}:)*::[0-9a-fA-F]{1,4}$|^::(?:[0-9a-fA-F]{1,4}:)+[0-9a-fA-F]{1,4}$|^::ffff:(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|^(?:[0-9a-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
if (ipv6Pattern.test(ipv6Address)) {
if (ipv6Pattern.test(trimmed)) {
// Additional validation: ensure only one :: compression exists
const compressionCount = (ipv6Address.match(/::/g) || []).length;
const compressionCount = (trimmed.match(/::/g) || []).length;
if (compressionCount <= 1) {
return true;
}
@@ -285,7 +273,7 @@ export function ServerForm({ onSubmit, initialData, isEditing = false, onCancel
className={`w-full px-3 py-2 border rounded-md shadow-sm bg-card text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring ${
errors.ip ? 'border-destructive' : 'border-border'
}`}
placeholder="e.g., 192.168.1.100, server.example.com, 2001:db8::1, or fe80::...%eth0"
placeholder="e.g., 192.168.1.100, server.example.com, or 2001:db8::1"
/>
{errors.ip && <p className="mt-1 text-sm text-destructive">{errors.ip}</p>}
</div>

View File

@@ -47,13 +47,10 @@ export async function POST(request: NextRequest) {
username
});
// Determine if request is over HTTPS
const isSecure = request.url.startsWith('https://');
// Set httpOnly cookie with configured duration
response.cookies.set('auth-token', token, {
httpOnly: true,
secure: isSecure, // Only secure if actually over HTTPS
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: sessionDurationDays * 24 * 60 * 60, // Use configured duration
path: '/',

View File

@@ -9,35 +9,26 @@
"moduleDetection": "force",
"isolatedModules": true,
"verbatimModuleSyntax": true,
/* Strictness */
"strict": true,
"noUncheckedIndexedAccess": true,
"checkJs": true,
/* Bundled projects */
"lib": [
"dom",
"dom.iterable",
"ES2022"
],
"lib": ["dom", "dom.iterable", "ES2022"],
"noEmit": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "preserve",
"plugins": [
{
"name": "next"
}
],
"plugins": [{ "name": "next" }],
"incremental": true,
/* Path Aliases */
"baseUrl": ".",
"paths": {
"~/*": [
"./src/*"
],
"@/*": [
"./src/*"
]
"~/*": ["./src/*"],
"@/*": ["./src/*"]
}
},
"include": [
@@ -46,10 +37,7 @@
"**/*.tsx",
"**/*.cjs",
"**/*.js",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
"exclude": ["node_modules"]
}

View File

@@ -550,10 +550,7 @@ update_files() {
"update.log"
"*.backup"
"*.bak"
"scripts/ct"
"scripts/install"
"scripts/tools"
"scripts/vm"
"scripts"
)
# Find the actual source directory (strip the top-level directory)