Compare commits
230 Commits
v0.4.10
...
get_update
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60f81fabc3 | ||
|
|
414c356446 | ||
|
|
c38ded7a39 | ||
|
|
0cfed84cd0 | ||
|
|
9611bc9bcf | ||
|
|
6fe2a790fd | ||
|
|
5ea71837e7 | ||
|
|
bf5ebc72b6 | ||
|
|
a32c7bcbba | ||
|
|
98c6e79db6 | ||
|
|
c962a9cd5a | ||
|
|
5d20a6d694 | ||
|
|
cb4e8c543a | ||
|
|
2ba213de49 | ||
|
|
849aabb575 | ||
|
|
dd33df2033 | ||
|
|
94eb2820fd | ||
|
|
e49708770c | ||
|
|
5eafa01843 | ||
|
|
0c1477e087 | ||
|
|
ef73d98873 | ||
|
|
ec92c0ea6d | ||
|
|
ee14b89868 | ||
|
|
be68160cd9 | ||
|
|
dbc15b1bc3 | ||
|
|
dc6ce16e5a | ||
|
|
0c9d4ad6e2 | ||
|
|
13d57b77d4 | ||
|
|
f9e5bd5bf0 | ||
|
|
adf2b06efa | ||
|
|
80e3966e4e | ||
|
|
3662a057dc | ||
|
|
bdf336f9bf | ||
|
|
f6c310fa22 | ||
|
|
d658894b7f | ||
|
|
783744b497 | ||
|
|
de9ac41f76 | ||
|
|
060202e557 | ||
|
|
8d45ac14cc | ||
|
|
47ee2247c8 | ||
|
|
c16c8d54db | ||
|
|
3e669a0739 | ||
|
|
02e175c8a0 | ||
|
|
b4e98e7624 | ||
|
|
2392529092 | ||
|
|
f9f5772d92 | ||
|
|
4267d7340e | ||
|
|
dcf923551b | ||
|
|
69a5ac3a56 | ||
|
|
7b8c1ebdf1 | ||
|
|
580b623939 | ||
|
|
ac21fbb181 | ||
|
|
588ae65dfd | ||
|
|
30acba39a5 | ||
|
|
3a5bb3dc45 | ||
|
|
f42c0d956e | ||
|
|
0ed13fcf0f | ||
|
|
afc87910e6 | ||
|
|
b97eca9620 | ||
|
|
f4aa8661c4 | ||
|
|
8f0ae3a341 | ||
|
|
b5450bd221 | ||
|
|
88dbe4ea85 | ||
|
|
f0b5956b54 | ||
|
|
e5000246b3 | ||
|
|
9dacf1e530 | ||
|
|
f248ed2875 | ||
|
|
4e6295885b | ||
|
|
2357232cae | ||
|
|
39d8115dda | ||
|
|
bd71b04a9d | ||
|
|
c0b03cd832 | ||
|
|
9b7c740145 | ||
|
|
4f929fb8da | ||
|
|
24ee87d14e | ||
|
|
55862628fb | ||
|
|
fbd731f020 | ||
|
|
a8b750ad75 | ||
|
|
1054b6d2f5 | ||
|
|
669ce41c2e | ||
|
|
7c4683012f | ||
|
|
cfcdc1e342 | ||
|
|
07cf03a408 | ||
|
|
dd17d2cbec | ||
|
|
f3d14c6746 | ||
|
|
447332e558 | ||
|
|
9bbc19ae44 | ||
|
|
5564ae0393 | ||
|
|
93d7842f6c | ||
|
|
84c02048bc | ||
|
|
66a3bb3203 | ||
|
|
0da802be42 | ||
|
|
5bc3933d11 | ||
|
|
1c6d1ac120 | ||
|
|
ba1e6478d7 | ||
|
|
e3af248456 | ||
|
|
43bafb610f | ||
|
|
8e22568efb | ||
|
|
6bb9ed5182 | ||
|
|
b6c3954f98 | ||
|
|
f73b303172 | ||
|
|
50d066669e | ||
|
|
68541c0046 | ||
|
|
644222e958 | ||
|
|
31a5fd97d4 | ||
|
|
b54fbf15f6 | ||
|
|
a787e60e7c | ||
|
|
1e250306dc | ||
|
|
d64a296ebe | ||
|
|
691b27c924 | ||
|
|
dbc591aa63 | ||
|
|
5ea6828f8c | ||
|
|
3dabacd055 | ||
|
|
e8ee829577 | ||
|
|
aebc8a6171 | ||
|
|
c5db169441 | ||
|
|
bef5bef875 | ||
|
|
3a4f86942f | ||
|
|
94eb772467 | ||
|
|
3a2a1b2cd6 | ||
|
|
69c10b05ac | ||
|
|
7833d5d408 | ||
|
|
e0baa79d6b | ||
|
|
737c9c94f3 | ||
|
|
c57586acae | ||
|
|
74030b5806 | ||
|
|
cc276ddff3 | ||
|
|
375c551a3a | ||
|
|
e3e4556f83 | ||
|
|
7fa132e09c | ||
|
|
1a1dbe6975 | ||
|
|
1a5881c935 | ||
|
|
2d7176914e | ||
|
|
987ac3da1b | ||
|
|
03e31d66a7 | ||
|
|
7547dff67d | ||
|
|
1945b14694 | ||
|
|
ec23600861 | ||
|
|
41a9c0ae11 | ||
|
|
c266c4cb3c | ||
|
|
b5bce88398 | ||
|
|
48cf86a449 | ||
|
|
d40aeb6c82 | ||
|
|
9c759ba99b | ||
|
|
f467b9ad7b | ||
|
|
7fe2a8b453 | ||
|
|
5274737ab8 | ||
|
|
40805f39f7 | ||
|
|
f9af7536d0 | ||
|
|
0d39a9bbd0 | ||
|
|
66f8a84260 | ||
|
|
2a9921a4e1 | ||
|
|
50f657ba00 | ||
|
|
5d5eba72de | ||
|
|
577b96518e | ||
|
|
c6c27271d6 | ||
|
|
72c0246d8c | ||
|
|
06d4786e0a | ||
|
|
bc31896586 | ||
|
|
213a606fc0 | ||
|
|
3579f2258e | ||
|
|
5b861ade05 | ||
|
|
553eae6ce7 | ||
|
|
c2ca88f033 | ||
|
|
67d44a6a5f | ||
|
|
fe6cca5c63 | ||
|
|
3a8088ded6 | ||
|
|
5d48c7b61c | ||
|
|
5be88d361f | ||
|
|
014e5b69e9 | ||
|
|
f37b2cb26f | ||
|
|
81c00f5d40 | ||
|
|
9bae95d0aa | ||
|
|
088d354fd2 | ||
|
|
0d47fa5d2a | ||
|
|
57fd5f802b | ||
|
|
a1825302fa | ||
|
|
570eea41b9 | ||
|
|
33a5b8e4d0 | ||
|
|
63174d2ea1 | ||
|
|
eda41e5101 | ||
|
|
4a50da4968 | ||
|
|
d50ea55e6d | ||
|
|
f558aa4f43 | ||
|
|
4ea49be97d | ||
|
|
e8c27077fd | ||
|
|
e21d1a6eb6 | ||
|
|
9608affcf6 | ||
|
|
ac23d015e0 | ||
|
|
5cb7bc95fa | ||
|
|
955d0e72d7 | ||
|
|
498249a25b | ||
|
|
72ffc5597f | ||
|
|
4a00d43d82 | ||
|
|
2a0738e102 | ||
|
|
a45f9ae5e6 | ||
|
|
dab2da4b70 | ||
|
|
fe00be2e16 | ||
|
|
f88479f6f6 | ||
|
|
4692c84f7e | ||
|
|
36033add28 | ||
|
|
f092de2934 | ||
|
|
c92c737c2b | ||
|
|
70314fdb6d | ||
|
|
735b3dcfa0 | ||
|
|
815024fbc0 | ||
|
|
03fd7bd1e2 | ||
|
|
09868ba651 | ||
|
|
b192c46d8d | ||
|
|
8c474785a0 | ||
|
|
7bf67091b7 | ||
|
|
c28e1d8dae | ||
|
|
e0a7a44470 | ||
|
|
430355aa4b | ||
|
|
baedcf8581 | ||
|
|
2735d7ecc9 | ||
|
|
e478a66c75 | ||
|
|
a9f23f2d28 | ||
|
|
3ec045de25 | ||
|
|
d9ceac03a2 | ||
|
|
fbc59d8964 | ||
|
|
8c27eacff7 | ||
|
|
86056c984d | ||
|
|
8af011a191 | ||
|
|
1dcf4159d6 | ||
|
|
84f664af96 | ||
|
|
5bb10145ad | ||
|
|
08fe6a7843 | ||
|
|
ed3b4c7b19 | ||
|
|
930254d4cb |
2
.github/workflows/node.js.yml
vendored
2
.github/workflows/node.js.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [22.x]
|
node-version: [24.x]
|
||||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -16,6 +16,9 @@
|
|||||||
db.sqlite
|
db.sqlite
|
||||||
data/settings.db
|
data/settings.db
|
||||||
|
|
||||||
|
# prisma generated client
|
||||||
|
/prisma/generated/
|
||||||
|
|
||||||
# ssh keys (sensitive)
|
# ssh keys (sensitive)
|
||||||
data/ssh-keys/
|
data/ssh-keys/
|
||||||
|
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ apt install -y nodejs
|
|||||||
```bash
|
```bash
|
||||||
# Clone the repository
|
# Clone the repository
|
||||||
git clone https://github.com/community-scripts/ProxmoxVE-Local.git /opt/PVESciptslocal
|
git clone https://github.com/community-scripts/ProxmoxVE-Local.git /opt/PVESciptslocal
|
||||||
cd PVESciptslocal
|
cd /opt/PVESciptslocal
|
||||||
|
|
||||||
# Install dependencies and build
|
# Install dependencies and build
|
||||||
npm install
|
npm install
|
||||||
|
|||||||
@@ -1,15 +1,23 @@
|
|||||||
import { FlatCompat } from "@eslint/eslintrc";
|
import eslintPluginNext from "@next/eslint-plugin-next";
|
||||||
import tseslint from "typescript-eslint";
|
import tseslint from "typescript-eslint";
|
||||||
|
import reactPlugin from "eslint-plugin-react";
|
||||||
const compat = new FlatCompat({
|
import reactHooksPlugin from "eslint-plugin-react-hooks";
|
||||||
baseDirectory: import.meta.dirname,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default tseslint.config(
|
export default tseslint.config(
|
||||||
{
|
{
|
||||||
ignores: [".next"],
|
ignores: [".next", "next-env.d.ts", "postcss.config.js", "prettier.config.js"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
plugins: {
|
||||||
|
"@next/next": eslintPluginNext,
|
||||||
|
"react": reactPlugin,
|
||||||
|
"react-hooks": reactHooksPlugin,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...eslintPluginNext.configs.recommended.rules,
|
||||||
|
...eslintPluginNext.configs["core-web-vitals"].rules,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
...compat.extends("next/core-web-vitals"),
|
|
||||||
{
|
{
|
||||||
files: ["**/*.ts", "**/*.tsx"],
|
files: ["**/*.ts", "**/*.tsx"],
|
||||||
extends: [
|
extends: [
|
||||||
|
|||||||
@@ -18,31 +18,25 @@ const config = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
// Allow cross-origin requests from local network ranges
|
// Allow cross-origin requests from local network in dev mode
|
||||||
allowedDevOrigins: [
|
// Note: In Next.js 16, we disable this check entirely for dev
|
||||||
'http://localhost:3000',
|
async headers() {
|
||||||
'http://127.0.0.1:3000',
|
return [
|
||||||
'http://[::1]:3000',
|
{
|
||||||
'http://10.*',
|
source: '/:path*',
|
||||||
'http://172.16.*',
|
headers: [
|
||||||
'http://172.17.*',
|
{ key: 'Access-Control-Allow-Origin', value: '*' },
|
||||||
'http://172.18.*',
|
{ key: 'Access-Control-Allow-Methods', value: 'GET,POST,PUT,DELETE,OPTIONS' },
|
||||||
'http://172.19.*',
|
{ key: 'Access-Control-Allow-Headers', value: 'Content-Type, Authorization' },
|
||||||
'http://172.20.*',
|
|
||||||
'http://172.21.*',
|
|
||||||
'http://172.22.*',
|
|
||||||
'http://172.23.*',
|
|
||||||
'http://172.24.*',
|
|
||||||
'http://172.25.*',
|
|
||||||
'http://172.26.*',
|
|
||||||
'http://172.27.*',
|
|
||||||
'http://172.28.*',
|
|
||||||
'http://172.29.*',
|
|
||||||
'http://172.30.*',
|
|
||||||
'http://172.31.*',
|
|
||||||
'http://192.168.*',
|
|
||||||
],
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
turbopack: {
|
||||||
|
// Disable Turbopack and use Webpack instead for compatibility
|
||||||
|
// This is necessary for server-side code that uses child_process
|
||||||
|
},
|
||||||
webpack: (config, { dev, isServer }) => {
|
webpack: (config, { dev, isServer }) => {
|
||||||
if (dev && !isServer) {
|
if (dev && !isServer) {
|
||||||
config.watchOptions = {
|
config.watchOptions = {
|
||||||
@@ -50,8 +44,19 @@ const config = {
|
|||||||
aggregateTimeout: 300,
|
aggregateTimeout: 300,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
// Handle server-side modules
|
||||||
|
if (isServer) {
|
||||||
|
config.externals = config.externals || [];
|
||||||
|
if (!config.externals.includes('child_process')) {
|
||||||
|
config.externals.push('child_process');
|
||||||
|
}
|
||||||
|
}
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
|
// TypeScript errors will fail the build
|
||||||
|
typescript: {
|
||||||
|
ignoreBuildErrors: false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|||||||
4500
package-lock.json
generated
4500
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
117
package.json
117
package.json
@@ -4,17 +4,20 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "next build",
|
"build": "prisma generate && next build --webpack",
|
||||||
"check": "next lint && tsc --noEmit",
|
"check": "eslint . && tsc --noEmit",
|
||||||
"dev": "next dev",
|
"dev": "next dev --webpack",
|
||||||
"dev:server": "node server.js",
|
"dev:server": "node --import tsx server.js",
|
||||||
"dev:next": "next dev --turbo",
|
"dev:next": "next dev --webpack",
|
||||||
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
|
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
|
||||||
"format:write": "prettier --write \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
|
"format:write": "prettier --write \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
|
||||||
"lint": "next lint",
|
"generate": "prisma generate",
|
||||||
"lint:fix": "next lint --fix",
|
"lint": "eslint .",
|
||||||
|
"lint:fix": "eslint --fix .",
|
||||||
|
"migrate": "prisma migrate dev",
|
||||||
"preview": "next build && next start",
|
"preview": "next build && next start",
|
||||||
"start": "node server.js",
|
"postinstall": "prisma generate",
|
||||||
|
"start": "node --import tsx server.js",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"test:ui": "vitest --ui",
|
"test:ui": "vitest --ui",
|
||||||
"test:run": "vitest run",
|
"test:run": "vitest run",
|
||||||
@@ -22,76 +25,82 @@
|
|||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/client": "^6.18.0",
|
"@prisma/adapter-better-sqlite3": "^7.2.0",
|
||||||
|
"@prisma/client": "^7.2.0",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.4",
|
||||||
"@t3-oss/env-nextjs": "^0.13.8",
|
"@t3-oss/env-nextjs": "^0.13.10",
|
||||||
"@tailwindcss/typography": "^0.5.19",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@tanstack/react-query": "^5.90.5",
|
"@tanstack/react-query": "^5.90.16",
|
||||||
"@trpc/client": "^11.6.0",
|
"@trpc/client": "^11.8.1",
|
||||||
"@trpc/react-query": "^11.6.0",
|
"@trpc/react-query": "^11.8.1",
|
||||||
"@trpc/server": "^11.6.0",
|
"@trpc/server": "^11.8.1",
|
||||||
"@types/react-syntax-highlighter": "^15.5.13",
|
"@types/react-syntax-highlighter": "^15.5.13",
|
||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
"@xterm/addon-fit": "^0.10.0",
|
"@xterm/addon-fit": "^0.11.0",
|
||||||
"@xterm/addon-web-links": "^0.11.0",
|
"@xterm/addon-web-links": "^0.12.0",
|
||||||
"@xterm/xterm": "^5.5.0",
|
"@xterm/xterm": "^6.0.0",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.13.2",
|
||||||
"bcryptjs": "^3.0.2",
|
"bcryptjs": "^3.0.3",
|
||||||
|
"better-sqlite3": "^12.6.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cron-validator": "^1.2.0",
|
"cron-validator": "^1.4.0",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.3",
|
||||||
"lucide-react": "^0.548.0",
|
"lucide-react": "^0.562.0",
|
||||||
"next": "^15.5.6",
|
"next": "^16.1.1",
|
||||||
"node-cron": "^3.0.3",
|
"node-cron": "^4.2.1",
|
||||||
"node-pty": "^1.0.0",
|
"node-pty": "^1.1.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.2.3",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.2.3",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"react-syntax-highlighter": "^15.6.6",
|
"react-syntax-highlighter": "^16.1.0",
|
||||||
"refractor": "^5.0.0",
|
"refractor": "^5.0.0",
|
||||||
"remark-gfm": "^4.0.1",
|
"remark-gfm": "^4.0.1",
|
||||||
"server-only": "^0.0.1",
|
"server-only": "^0.0.1",
|
||||||
"strip-ansi": "^7.1.2",
|
"strip-ansi": "^7.1.2",
|
||||||
"superjson": "^2.2.3",
|
"superjson": "^2.2.6",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.4.0",
|
||||||
"ws": "^8.18.3",
|
"ws": "^8.19.0",
|
||||||
"zod": "^4.1.12"
|
"zod": "^4.3.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.3.1",
|
"@tailwindcss/postcss": "^4.1.18",
|
||||||
"@tailwindcss/postcss": "^4.1.16",
|
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.1",
|
||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
"@types/bcryptjs": "^3.0.0",
|
"@types/bcryptjs": "^3.0.0",
|
||||||
"@types/better-sqlite3": "^7.6.8",
|
"@types/better-sqlite3": "^7.6.13",
|
||||||
"@types/jsonwebtoken": "^9.0.10",
|
"@types/jsonwebtoken": "^9.0.10",
|
||||||
"@types/node": "^24.9.1",
|
"@types/node": "^24.10.4",
|
||||||
"@types/node-cron": "^3.0.11",
|
"@types/node-cron": "^3.0.11",
|
||||||
"@types/react": "^19.0.0",
|
"@types/react": "^19.2.8",
|
||||||
"@types/react-dom": "^19.2.2",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^5.1.0",
|
"@vitejs/plugin-react": "^5.1.2",
|
||||||
"@vitest/coverage-v8": "^3.2.4",
|
"@vitest/coverage-v8": "^4.0.17",
|
||||||
"@vitest/ui": "^3.2.4",
|
"@vitest/ui": "^4.0.17",
|
||||||
"eslint": "^9.38.0",
|
"baseline-browser-mapping": "^2.9.14",
|
||||||
"eslint-config-next": "^16.0.0",
|
"eslint": "^9.39.2",
|
||||||
"jsdom": "^27.0.1",
|
"eslint-config-next": "^16.1.1",
|
||||||
"postcss": "^8.5.3",
|
"jsdom": "^27.4.0",
|
||||||
"prettier": "^3.5.3",
|
"postcss": "^8.5.6",
|
||||||
"prettier-plugin-tailwindcss": "^0.7.1",
|
"prettier": "^3.7.4",
|
||||||
"prisma": "^6.18.0",
|
"prettier-plugin-tailwindcss": "^0.7.2",
|
||||||
"tailwindcss": "^4.1.16",
|
"prisma": "^7.2.0",
|
||||||
"typescript": "^5.8.2",
|
"tailwindcss": "^4.1.18",
|
||||||
"typescript-eslint": "^8.46.2",
|
"tsx": "^4.21.0",
|
||||||
"vitest": "^3.2.4"
|
"typescript": "^5.9.3",
|
||||||
|
"typescript-eslint": "^8.53.0",
|
||||||
|
"vitest": "^4.0.17"
|
||||||
},
|
},
|
||||||
"ct3aMetadata": {
|
"ct3aMetadata": {
|
||||||
"initVersion": "7.39.3"
|
"initVersion": "7.39.3"
|
||||||
},
|
},
|
||||||
"packageManager": "npm@10.9.3",
|
"packageManager": "npm@10.9.3",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=24.0.0"
|
||||||
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"prismjs": "^1.30.0"
|
"prismjs": "^1.30.0"
|
||||||
}
|
}
|
||||||
|
|||||||
20
prisma.config.ts
Normal file
20
prisma.config.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import 'dotenv/config'
|
||||||
|
import path from 'path'
|
||||||
|
import { defineConfig } from 'prisma/config'
|
||||||
|
|
||||||
|
// Resolve database path
|
||||||
|
const dbPath = process.env.DATABASE_URL ?? `file:${path.join(process.cwd(), 'data', 'pve-scripts.db')}`
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
schema: 'prisma/schema.prisma',
|
||||||
|
datasource: {
|
||||||
|
url: dbPath,
|
||||||
|
},
|
||||||
|
// @ts-expect-error - Prisma 7 config types are incomplete
|
||||||
|
studio: {
|
||||||
|
adapter: async () => {
|
||||||
|
const { PrismaBetterSqlite3 } = await import('@prisma/adapter-better-sqlite3')
|
||||||
|
return new PrismaBetterSqlite3({ url: dbPath })
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "repositories" (
|
||||||
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"url" TEXT NOT NULL,
|
||||||
|
"enabled" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"is_default" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"is_removable" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"priority" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updated_at" DATETIME NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "repositories_url_key" ON "repositories"("url");
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE IF NOT EXISTS "backups" (
|
||||||
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"container_id" TEXT NOT NULL,
|
||||||
|
"server_id" INTEGER NOT NULL,
|
||||||
|
"hostname" TEXT NOT NULL,
|
||||||
|
"backup_name" TEXT NOT NULL,
|
||||||
|
"backup_path" TEXT NOT NULL,
|
||||||
|
"size" BIGINT,
|
||||||
|
"created_at" DATETIME,
|
||||||
|
"storage_name" TEXT NOT NULL,
|
||||||
|
"storage_type" TEXT NOT NULL,
|
||||||
|
"discovered_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT "backups_server_id_fkey" FOREIGN KEY ("server_id") REFERENCES "servers" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE IF NOT EXISTS "pbs_storage_credentials" (
|
||||||
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"server_id" INTEGER NOT NULL,
|
||||||
|
"storage_name" TEXT NOT NULL,
|
||||||
|
"pbs_ip" TEXT NOT NULL,
|
||||||
|
"pbs_datastore" TEXT NOT NULL,
|
||||||
|
"pbs_password" TEXT NOT NULL,
|
||||||
|
"pbs_fingerprint" TEXT NOT NULL,
|
||||||
|
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updated_at" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "pbs_storage_credentials_server_id_fkey" FOREIGN KEY ("server_id") REFERENCES "servers" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX IF NOT EXISTS "backups_container_id_idx" ON "backups"("container_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX IF NOT EXISTS "backups_server_id_idx" ON "backups"("server_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX IF NOT EXISTS "pbs_storage_credentials_server_id_idx" ON "pbs_storage_credentials"("server_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "pbs_storage_credentials_server_id_storage_name_key" ON "pbs_storage_credentials"("server_id", "storage_name");
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
generator client {
|
generator client {
|
||||||
provider = "prisma-client-js"
|
provider = "prisma-client"
|
||||||
|
output = "./generated/prisma"
|
||||||
}
|
}
|
||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
provider = "sqlite"
|
provider = "sqlite"
|
||||||
url = env("DATABASE_URL")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model InstalledScript {
|
model InstalledScript {
|
||||||
@@ -41,6 +41,8 @@ model Server {
|
|||||||
ssh_key_path String?
|
ssh_key_path String?
|
||||||
key_generated Boolean? @default(false)
|
key_generated Boolean? @default(false)
|
||||||
installed_scripts InstalledScript[]
|
installed_scripts InstalledScript[]
|
||||||
|
backups Backup[]
|
||||||
|
pbs_credentials PBSStorageCredential[]
|
||||||
|
|
||||||
@@map("servers")
|
@@map("servers")
|
||||||
}
|
}
|
||||||
@@ -95,3 +97,52 @@ model LXCConfig {
|
|||||||
|
|
||||||
@@map("lxc_configs")
|
@@map("lxc_configs")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model Backup {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
container_id String
|
||||||
|
server_id Int
|
||||||
|
hostname String
|
||||||
|
backup_name String
|
||||||
|
backup_path String
|
||||||
|
size BigInt?
|
||||||
|
created_at DateTime?
|
||||||
|
storage_name String
|
||||||
|
storage_type String // 'local', 'storage', or 'pbs'
|
||||||
|
discovered_at DateTime @default(now())
|
||||||
|
server Server @relation(fields: [server_id], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@index([container_id])
|
||||||
|
@@index([server_id])
|
||||||
|
@@map("backups")
|
||||||
|
}
|
||||||
|
|
||||||
|
model PBSStorageCredential {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
server_id Int
|
||||||
|
storage_name String
|
||||||
|
pbs_ip String
|
||||||
|
pbs_datastore String
|
||||||
|
pbs_password String
|
||||||
|
pbs_fingerprint String
|
||||||
|
created_at DateTime @default(now())
|
||||||
|
updated_at DateTime @updatedAt
|
||||||
|
server Server @relation(fields: [server_id], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@unique([server_id, storage_name])
|
||||||
|
@@index([server_id])
|
||||||
|
@@map("pbs_storage_credentials")
|
||||||
|
}
|
||||||
|
|
||||||
|
model Repository {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
url String @unique
|
||||||
|
enabled Boolean @default(true)
|
||||||
|
is_default Boolean @default(false)
|
||||||
|
is_removable Boolean @default(true)
|
||||||
|
priority Int @default(0)
|
||||||
|
created_at DateTime @default(now())
|
||||||
|
updated_at DateTime @updatedAt
|
||||||
|
|
||||||
|
@@map("repositories")
|
||||||
|
}
|
||||||
|
|||||||
10
restore.log
Normal file
10
restore.log
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
Starting restore...
|
||||||
|
Reading container configuration...
|
||||||
|
Stopping container...
|
||||||
|
Destroying container...
|
||||||
|
Logging into PBS...
|
||||||
|
Downloading backup from PBS...
|
||||||
|
Packing backup folder...
|
||||||
|
Restoring container...
|
||||||
|
Cleaning up temporary files...
|
||||||
|
Restore completed successfully
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Copyright (c) 2021-2025 community-scripts ORG
|
# Copyright (c) 2021-2026 community-scripts ORG
|
||||||
# Author: tteck (tteckster)
|
# Author: tteck (tteckster)
|
||||||
# Co-Author: MickLesk
|
# Co-Author: MickLesk
|
||||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||||
@@ -6,33 +6,65 @@
|
|||||||
if ! command -v curl >/dev/null 2>&1; then
|
if ! command -v curl >/dev/null 2>&1; then
|
||||||
apk update && apk add curl >/dev/null 2>&1
|
apk update && apk add curl >/dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
|
source "$(dirname "${BASH_SOURCE[0]}")/core.func"
|
||||||
|
source "$(dirname "${BASH_SOURCE[0]}")/error-handler.func"
|
||||||
load_functions
|
load_functions
|
||||||
|
catch_errors
|
||||||
|
|
||||||
# This function enables IPv6 if it's not disabled and sets verbose mode
|
# This function enables IPv6 if it's not disabled and sets verbose mode
|
||||||
verb_ip6() {
|
verb_ip6() {
|
||||||
set_std_mode # Set STD mode based on VERBOSE
|
set_std_mode # Set STD mode based on VERBOSE
|
||||||
|
|
||||||
if [ "$DISABLEIPV6" == "yes" ]; then
|
if [ "${IPV6_METHOD:-}" = "disable" ]; then
|
||||||
|
msg_info "Disabling IPv6 (this may affect some services)"
|
||||||
$STD sysctl -w net.ipv6.conf.all.disable_ipv6=1
|
$STD sysctl -w net.ipv6.conf.all.disable_ipv6=1
|
||||||
echo "net.ipv6.conf.all.disable_ipv6 = 1" >>/etc/sysctl.conf
|
$STD sysctl -w net.ipv6.conf.default.disable_ipv6=1
|
||||||
|
$STD sysctl -w net.ipv6.conf.lo.disable_ipv6=1
|
||||||
|
mkdir -p /etc/sysctl.d
|
||||||
|
$STD tee /etc/sysctl.d/99-disable-ipv6.conf >/dev/null <<EOF
|
||||||
|
net.ipv6.conf.all.disable_ipv6 = 1
|
||||||
|
net.ipv6.conf.default.disable_ipv6 = 1
|
||||||
|
net.ipv6.conf.lo.disable_ipv6 = 1
|
||||||
|
EOF
|
||||||
$STD rc-update add sysctl default
|
$STD rc-update add sysctl default
|
||||||
|
msg_ok "Disabled IPv6"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# This function catches errors and handles them with the error handler function
|
|
||||||
catch_errors() {
|
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
|
trap 'error_handler $? $LINENO "$BASH_COMMAND"' ERR
|
||||||
|
trap on_exit EXIT
|
||||||
|
trap on_interrupt INT
|
||||||
|
trap on_terminate TERM
|
||||||
|
|
||||||
|
error_handler() {
|
||||||
|
local exit_code="$1"
|
||||||
|
local line_number="$2"
|
||||||
|
local command="$3"
|
||||||
|
|
||||||
|
if [[ "$exit_code" -eq 0 ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "\e[?25h"
|
||||||
|
echo -e "\n${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}\n"
|
||||||
|
exit "$exit_code"
|
||||||
}
|
}
|
||||||
|
|
||||||
# This function handles errors
|
on_exit() {
|
||||||
error_handler() {
|
|
||||||
local exit_code="$?"
|
local exit_code="$?"
|
||||||
local line_number="$1"
|
[[ -n "${lockfile:-}" && -e "$lockfile" ]] && rm -f "$lockfile"
|
||||||
local command="$2"
|
exit "$exit_code"
|
||||||
local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}"
|
}
|
||||||
echo -e "\n$error_message\n"
|
|
||||||
|
on_interrupt() {
|
||||||
|
echo -e "\n${RD}Interrupted by user (SIGINT)${CL}"
|
||||||
|
exit 130
|
||||||
|
}
|
||||||
|
|
||||||
|
on_terminate() {
|
||||||
|
echo -e "\n${RD}Terminated by signal (SIGTERM)${CL}"
|
||||||
|
exit 143
|
||||||
}
|
}
|
||||||
|
|
||||||
# This function sets up the Container OS by generating the locale, setting the timezone, and checking the network connection
|
# This function sets up the Container OS by generating the locale, setting the timezone, and checking the network connection
|
||||||
@@ -61,10 +93,10 @@ network_check() {
|
|||||||
set +e
|
set +e
|
||||||
trap - ERR
|
trap - ERR
|
||||||
if ping -c 1 -W 1 1.1.1.1 &>/dev/null || ping -c 1 -W 1 8.8.8.8 &>/dev/null || ping -c 1 -W 1 9.9.9.9 &>/dev/null; then
|
if ping -c 1 -W 1 1.1.1.1 &>/dev/null || ping -c 1 -W 1 8.8.8.8 &>/dev/null || ping -c 1 -W 1 9.9.9.9 &>/dev/null; then
|
||||||
msg_ok "Internet Connected"
|
ipv4_status="${GN}✔${CL} IPv4"
|
||||||
else
|
else
|
||||||
msg_error "Internet NOT Connected"
|
ipv4_status="${RD}✖${CL} IPv4"
|
||||||
read -r -p "Would you like to continue anyway? <y/N> " prompt
|
read -r -p "Internet NOT connected. Continue anyway? <y/N> " prompt
|
||||||
if [[ "${prompt,,}" =~ ^(y|yes)$ ]]; then
|
if [[ "${prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||||
echo -e "${INFO}${RD}Expect Issues Without Internet${CL}"
|
echo -e "${INFO}${RD}Expect Issues Without Internet${CL}"
|
||||||
else
|
else
|
||||||
@@ -73,7 +105,11 @@ network_check() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
RESOLVEDIP=$(getent hosts github.com | awk '{ print $1 }')
|
RESOLVEDIP=$(getent hosts github.com | awk '{ print $1 }')
|
||||||
if [[ -z "$RESOLVEDIP" ]]; then msg_error "DNS Lookup Failure"; else msg_ok "DNS Resolved github.com to ${BL}$RESOLVEDIP${CL}"; fi
|
if [[ -z "$RESOLVEDIP" ]]; then
|
||||||
|
msg_error "Internet: ${ipv4_status} DNS Failed"
|
||||||
|
else
|
||||||
|
msg_ok "Internet: ${ipv4_status} DNS: ${BL}${RESOLVEDIP}${CL}"
|
||||||
|
fi
|
||||||
set -e
|
set -e
|
||||||
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
|
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
|
||||||
}
|
}
|
||||||
@@ -82,7 +118,7 @@ network_check() {
|
|||||||
update_os() {
|
update_os() {
|
||||||
msg_info "Updating Container OS"
|
msg_info "Updating Container OS"
|
||||||
$STD apk -U upgrade
|
$STD apk -U upgrade
|
||||||
#source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/tools.func)
|
source "$(dirname "${BASH_SOURCE[0]}")/tools.func"
|
||||||
msg_ok "Updated Container OS"
|
msg_ok "Updated Container OS"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,10 +190,4 @@ EOF
|
|||||||
echo "bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)\"" >/usr/bin/update
|
echo "bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)\"" >/usr/bin/update
|
||||||
chmod +x /usr/bin/update
|
chmod +x /usr/bin/update
|
||||||
|
|
||||||
if [[ -n "${SSH_AUTHORIZED_KEY}" ]]; then
|
|
||||||
mkdir -p /root/.ssh
|
|
||||||
echo "${SSH_AUTHORIZED_KEY}" >/root/.ssh/authorized_keys
|
|
||||||
chmod 700 /root/.ssh
|
|
||||||
chmod 600 /root/.ssh/authorized_keys
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
507
scripts/core/alpine-tools.func
Normal file
507
scripts/core/alpine-tools.func
Normal file
@@ -0,0 +1,507 @@
|
|||||||
|
#!/bin/ash
|
||||||
|
# shellcheck shell=ash
|
||||||
|
|
||||||
|
# Expects existing msg_* functions and optional $STD from the framework.
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# helpers
|
||||||
|
# ------------------------------
|
||||||
|
lower() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]'; }
|
||||||
|
has() { command -v "$1" >/dev/null 2>&1; }
|
||||||
|
|
||||||
|
need_tool() {
|
||||||
|
# usage: need_tool curl jq unzip ...
|
||||||
|
# setup missing tools via apk
|
||||||
|
local missing=0 t
|
||||||
|
for t in "$@"; do
|
||||||
|
if ! has "$t"; then missing=1; fi
|
||||||
|
done
|
||||||
|
if [ "$missing" -eq 1 ]; then
|
||||||
|
msg_info "Installing tools: $*"
|
||||||
|
apk add --no-cache "$@" >/dev/null 2>&1 || {
|
||||||
|
msg_error "apk add failed for: $*"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
msg_ok "Tools ready: $*"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
net_resolves() {
|
||||||
|
# better handling for missing getent on Alpine
|
||||||
|
# usage: net_resolves api.github.com
|
||||||
|
local host="$1"
|
||||||
|
ping -c1 -W1 "$host" >/dev/null 2>&1 || nslookup "$host" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_usr_local_bin_persist() {
|
||||||
|
local PROFILE_FILE="/etc/profile.d/10-localbin.sh"
|
||||||
|
if [ ! -f "$PROFILE_FILE" ]; then
|
||||||
|
echo 'case ":$PATH:" in *:/usr/local/bin:*) ;; *) export PATH="/usr/local/bin:$PATH";; esac' >"$PROFILE_FILE"
|
||||||
|
chmod +x "$PROFILE_FILE"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
download_with_progress() {
|
||||||
|
# $1 url, $2 dest
|
||||||
|
local url="$1" out="$2" cl
|
||||||
|
need_tool curl pv || return 1
|
||||||
|
cl=$(curl -fsSLI "$url" 2>/dev/null | awk 'tolower($0) ~ /^content-length:/ {print $2}' | tr -d '\r')
|
||||||
|
if [ -n "$cl" ]; then
|
||||||
|
curl -fsSL "$url" | pv -s "$cl" >"$out" || {
|
||||||
|
msg_error "Download failed: $url"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
else
|
||||||
|
curl -fL# -o "$out" "$url" || {
|
||||||
|
msg_error "Download failed: $url"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# GitHub: check Release
|
||||||
|
# ------------------------------
|
||||||
|
check_for_gh_release() {
|
||||||
|
# app, repo, [pinned]
|
||||||
|
local app="$1" source="$2" pinned="${3:-}"
|
||||||
|
local app_lc
|
||||||
|
app_lc="$(lower "$app" | tr -d ' ')"
|
||||||
|
local current_file="$HOME/.${app_lc}"
|
||||||
|
local current="" release tag
|
||||||
|
|
||||||
|
msg_info "Check for update: $app"
|
||||||
|
|
||||||
|
net_resolves api.github.com || {
|
||||||
|
msg_error "DNS/network error: api.github.com"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
need_tool curl jq || return 1
|
||||||
|
|
||||||
|
tag=$(curl -fsSL "https://api.github.com/repos/${source}/releases/latest" | jq -r '.tag_name // empty')
|
||||||
|
[ -z "$tag" ] && {
|
||||||
|
msg_error "Unable to fetch latest tag for $app"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
release="${tag#v}"
|
||||||
|
|
||||||
|
[ -f "$current_file" ] && current="$(cat "$current_file")"
|
||||||
|
|
||||||
|
if [ -n "$pinned" ]; then
|
||||||
|
if [ "$pinned" = "$release" ]; then
|
||||||
|
msg_ok "$app pinned to v$pinned (no update)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [ "$current" = "$pinned" ]; then
|
||||||
|
msg_ok "$app pinned v$pinned installed (upstream v$release)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
msg_info "$app pinned v$pinned (upstream v$release) → update/downgrade"
|
||||||
|
CHECK_UPDATE_RELEASE="$pinned"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$release" != "$current" ] || [ ! -f "$current_file" ]; then
|
||||||
|
CHECK_UPDATE_RELEASE="$release"
|
||||||
|
msg_info "New release available: v$release (current: v${current:-none})"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg_ok "$app is up to date (v$release)"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# GitHub: get Release & deploy (Alpine)
|
||||||
|
# modes: tarball | prebuild | singlefile
|
||||||
|
# ------------------------------
|
||||||
|
fetch_and_deploy_gh() {
|
||||||
|
# $1 app, $2 repo, [$3 mode], [$4 version], [$5 target], [$6 asset_pattern
|
||||||
|
local app="$1" repo="$2" mode="${3:-tarball}" version="${4:-latest}" target="${5:-/opt/$1}" pattern="${6:-}"
|
||||||
|
local app_lc
|
||||||
|
app_lc="$(lower "$app" | tr -d ' ')"
|
||||||
|
local vfile="$HOME/.${app_lc}"
|
||||||
|
local json url filename tmpd unpack
|
||||||
|
|
||||||
|
net_resolves api.github.com || {
|
||||||
|
msg_error "DNS/network error"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
need_tool curl jq tar || return 1
|
||||||
|
[ "$mode" = "prebuild" ] || [ "$mode" = "singlefile" ] && need_tool unzip >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
tmpd="$(mktemp -d)" || return 1
|
||||||
|
mkdir -p "$target"
|
||||||
|
|
||||||
|
# Release JSON
|
||||||
|
if [ "$version" = "latest" ]; then
|
||||||
|
json="$(curl -fsSL "https://api.github.com/repos/$repo/releases/latest")" || {
|
||||||
|
msg_error "GitHub API failed"
|
||||||
|
rm -rf "$tmpd"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
else
|
||||||
|
json="$(curl -fsSL "https://api.github.com/repos/$repo/releases/tags/$version")" || {
|
||||||
|
msg_error "GitHub API failed"
|
||||||
|
rm -rf "$tmpd"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# correct Version
|
||||||
|
version="$(printf '%s' "$json" | jq -r '.tag_name // empty')"
|
||||||
|
version="${version#v}"
|
||||||
|
|
||||||
|
[ -z "$version" ] && {
|
||||||
|
msg_error "No tag in release json"
|
||||||
|
rm -rf "$tmpd"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$mode" in
|
||||||
|
tarball | source)
|
||||||
|
url="$(printf '%s' "$json" | jq -r '.tarball_url // empty')"
|
||||||
|
[ -z "$url" ] && url="https://github.com/$repo/archive/refs/tags/v$version.tar.gz"
|
||||||
|
filename="${app_lc}-${version}.tar.gz"
|
||||||
|
download_with_progress "$url" "$tmpd/$filename" || {
|
||||||
|
rm -rf "$tmpd"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
tar -xzf "$tmpd/$filename" -C "$tmpd" || {
|
||||||
|
msg_error "tar extract failed"
|
||||||
|
rm -rf "$tmpd"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
unpack="$(find "$tmpd" -mindepth 1 -maxdepth 1 -type d | head -n1)"
|
||||||
|
# copy content of unpack to target
|
||||||
|
(cd "$unpack" && tar -cf - .) | (cd "$target" && tar -xf -) || {
|
||||||
|
msg_error "copy failed"
|
||||||
|
rm -rf "$tmpd"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
;;
|
||||||
|
prebuild)
|
||||||
|
[ -n "$pattern" ] || {
|
||||||
|
msg_error "prebuild requires asset pattern"
|
||||||
|
rm -rf "$tmpd"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
url="$(printf '%s' "$json" | jq -r '.assets[].browser_download_url' | awk -v p="$pattern" '
|
||||||
|
BEGIN{IGNORECASE=1}
|
||||||
|
$0 ~ p {print; exit}
|
||||||
|
')"
|
||||||
|
[ -z "$url" ] && {
|
||||||
|
msg_error "asset not found for pattern: $pattern"
|
||||||
|
rm -rf "$tmpd"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
filename="${url##*/}"
|
||||||
|
download_with_progress "$url" "$tmpd/$filename" || {
|
||||||
|
rm -rf "$tmpd"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
# unpack archive (Zip or tarball)
|
||||||
|
case "$filename" in
|
||||||
|
*.zip)
|
||||||
|
need_tool unzip || {
|
||||||
|
rm -rf "$tmpd"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
mkdir -p "$tmpd/unp"
|
||||||
|
unzip -q "$tmpd/$filename" -d "$tmpd/unp"
|
||||||
|
;;
|
||||||
|
*.tar.gz | *.tgz | *.tar.xz | *.tar.zst | *.tar.bz2)
|
||||||
|
mkdir -p "$tmpd/unp"
|
||||||
|
tar -xf "$tmpd/$filename" -C "$tmpd/unp"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
msg_error "unsupported archive: $filename"
|
||||||
|
rm -rf "$tmpd"
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
# top-level folder strippen
|
||||||
|
if [ "$(find "$tmpd/unp" -mindepth 1 -maxdepth 1 -type d | wc -l)" -eq 1 ] && [ -z "$(find "$tmpd/unp" -mindepth 1 -maxdepth 1 -type f | head -n1)" ]; then
|
||||||
|
unpack="$(find "$tmpd/unp" -mindepth 1 -maxdepth 1 -type d)"
|
||||||
|
(cd "$unpack" && tar -cf - .) | (cd "$target" && tar -xf -) || {
|
||||||
|
msg_error "copy failed"
|
||||||
|
rm -rf "$tmpd"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
else
|
||||||
|
(cd "$tmpd/unp" && tar -cf - .) | (cd "$target" && tar -xf -) || {
|
||||||
|
msg_error "copy failed"
|
||||||
|
rm -rf "$tmpd"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
singlefile)
|
||||||
|
[ -n "$pattern" ] || {
|
||||||
|
msg_error "singlefile requires asset pattern"
|
||||||
|
rm -rf "$tmpd"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
url="$(printf '%s' "$json" | jq -r '.assets[].browser_download_url' | awk -v p="$pattern" '
|
||||||
|
BEGIN{IGNORECASE=1}
|
||||||
|
$0 ~ p {print; exit}
|
||||||
|
')"
|
||||||
|
[ -z "$url" ] && {
|
||||||
|
msg_error "asset not found for pattern: $pattern"
|
||||||
|
rm -rf "$tmpd"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
filename="${url##*/}"
|
||||||
|
download_with_progress "$url" "$target/$app" || {
|
||||||
|
rm -rf "$tmpd"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
chmod +x "$target/$app"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
msg_error "Unknown mode: $mode"
|
||||||
|
rm -rf "$tmpd"
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "$version" >"$vfile"
|
||||||
|
ensure_usr_local_bin_persist
|
||||||
|
rm -rf "$tmpd"
|
||||||
|
msg_ok "Deployed $app ($version) → $target"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# yq (mikefarah) – Alpine
|
||||||
|
# ------------------------------
|
||||||
|
setup_yq() {
|
||||||
|
# prefer apk, unless FORCE_GH=1
|
||||||
|
if [ "${FORCE_GH:-0}" != "1" ] && apk info -e yq >/dev/null 2>&1; then
|
||||||
|
msg_info "Updating yq via apk"
|
||||||
|
apk add --no-cache --upgrade yq >/dev/null 2>&1 || true
|
||||||
|
msg_ok "yq ready ($(yq --version 2>/dev/null))"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
need_tool curl || return 1
|
||||||
|
local arch bin url tmp
|
||||||
|
case "$(uname -m)" in
|
||||||
|
x86_64) arch="amd64" ;;
|
||||||
|
aarch64) arch="arm64" ;;
|
||||||
|
*)
|
||||||
|
msg_error "Unsupported arch for yq: $(uname -m)"
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
url="https://github.com/mikefarah/yq/releases/latest/download/yq_linux_${arch}"
|
||||||
|
tmp="$(mktemp)"
|
||||||
|
download_with_progress "$url" "$tmp" || return 1
|
||||||
|
install -m 0755 "$tmp" /usr/local/bin/yq
|
||||||
|
rm -f "$tmp"
|
||||||
|
msg_ok "Setup yq ($(yq --version 2>/dev/null))"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Adminer – Alpine
|
||||||
|
# ------------------------------
|
||||||
|
setup_adminer() {
|
||||||
|
need_tool curl || return 1
|
||||||
|
msg_info "Setup Adminer (Alpine)"
|
||||||
|
mkdir -p /var/www/localhost/htdocs/adminer
|
||||||
|
curl -fsSL https://github.com/vrana/adminer/releases/latest/download/adminer.php \
|
||||||
|
-o /var/www/localhost/htdocs/adminer/index.php || {
|
||||||
|
msg_error "Adminer download failed"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
msg_ok "Adminer at /adminer (served by your webserver)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# uv – Alpine (musl tarball)
|
||||||
|
# optional: PYTHON_VERSION="3.12"
|
||||||
|
# ------------------------------
|
||||||
|
setup_uv() {
|
||||||
|
need_tool curl tar || return 1
|
||||||
|
local UV_BIN="/usr/local/bin/uv"
|
||||||
|
local arch tarball url tmpd ver installed
|
||||||
|
|
||||||
|
case "$(uname -m)" in
|
||||||
|
x86_64) arch="x86_64-unknown-linux-musl" ;;
|
||||||
|
aarch64) arch="aarch64-unknown-linux-musl" ;;
|
||||||
|
*)
|
||||||
|
msg_error "Unsupported arch for uv: $(uname -m)"
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
ver="$(curl -fsSL https://api.github.com/repos/astral-sh/uv/releases/latest | jq -r '.tag_name' 2>/dev/null)"
|
||||||
|
ver="${ver#v}"
|
||||||
|
[ -z "$ver" ] && {
|
||||||
|
msg_error "uv: cannot determine latest version"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if has "$UV_BIN"; then
|
||||||
|
installed="$($UV_BIN -V 2>/dev/null | awk '{print $2}')"
|
||||||
|
[ "$installed" = "$ver" ] && {
|
||||||
|
msg_ok "uv $ver already installed"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
msg_info "Updating uv $installed → $ver"
|
||||||
|
else
|
||||||
|
msg_info "Setup uv $ver"
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmpd="$(mktemp -d)" || return 1
|
||||||
|
tarball="uv-${arch}.tar.gz"
|
||||||
|
url="https://github.com/astral-sh/uv/releases/download/v${ver}/${tarball}"
|
||||||
|
|
||||||
|
download_with_progress "$url" "$tmpd/uv.tar.gz" || {
|
||||||
|
rm -rf "$tmpd"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
tar -xzf "$tmpd/uv.tar.gz" -C "$tmpd" || {
|
||||||
|
msg_error "uv: extract failed"
|
||||||
|
rm -rf "$tmpd"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# tar contains ./uv
|
||||||
|
if [ -x "$tmpd/uv" ]; then
|
||||||
|
install -m 0755 "$tmpd/uv" "$UV_BIN"
|
||||||
|
else
|
||||||
|
# fallback: in subfolder
|
||||||
|
install -m 0755 "$tmpd"/*/uv "$UV_BIN" 2>/dev/null || {
|
||||||
|
msg_error "uv binary not found in tar"
|
||||||
|
rm -rf "$tmpd"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
rm -rf "$tmpd"
|
||||||
|
ensure_usr_local_bin_persist
|
||||||
|
msg_ok "Setup uv $ver"
|
||||||
|
|
||||||
|
if [ -n "${PYTHON_VERSION:-}" ]; then
|
||||||
|
local match
|
||||||
|
match="$(uv python list --only-downloads 2>/dev/null | awk -v maj="$PYTHON_VERSION" '
|
||||||
|
$0 ~ "^cpython-"maj"\\." { print $0 }' | awk -F- '{print $2}' | sort -V | tail -n1)"
|
||||||
|
[ -z "$match" ] && {
|
||||||
|
msg_error "No matching Python for $PYTHON_VERSION"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if ! uv python list | grep -q "cpython-${match}-linux"; then
|
||||||
|
msg_info "Installing Python $match via uv"
|
||||||
|
uv python install "$match" || {
|
||||||
|
msg_error "uv python install failed"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
msg_ok "Python $match installed (uv)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Java – Alpine (OpenJDK)
|
||||||
|
# JAVA_VERSION: 17|21 (Default 21)
|
||||||
|
# ------------------------------
|
||||||
|
setup_java() {
|
||||||
|
local JAVA_VERSION="${JAVA_VERSION:-21}" pkg
|
||||||
|
case "$JAVA_VERSION" in
|
||||||
|
17) pkg="openjdk17-jdk" ;;
|
||||||
|
21 | *) pkg="openjdk21-jdk" ;;
|
||||||
|
esac
|
||||||
|
msg_info "Setup Java (OpenJDK $JAVA_VERSION)"
|
||||||
|
apk add --no-cache "$pkg" >/dev/null 2>&1 || {
|
||||||
|
msg_error "apk add $pkg failed"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
# set JAVA_HOME
|
||||||
|
local prof="/etc/profile.d/20-java.sh"
|
||||||
|
if [ ! -f "$prof" ]; then
|
||||||
|
echo 'export JAVA_HOME=$(dirname $(dirname $(readlink -f $(command -v java))))' >"$prof"
|
||||||
|
echo 'case ":$PATH:" in *:$JAVA_HOME/bin:*) ;; *) export PATH="$JAVA_HOME/bin:$PATH";; esac' >>"$prof"
|
||||||
|
chmod +x "$prof"
|
||||||
|
fi
|
||||||
|
msg_ok "Java ready: $(java -version 2>&1 | head -n1)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Go – Alpine (apk prefers, else tarball)
|
||||||
|
# ------------------------------
|
||||||
|
setup_go() {
|
||||||
|
if [ -z "${GO_VERSION:-}" ]; then
|
||||||
|
msg_info "Setup Go (apk)"
|
||||||
|
apk add --no-cache go >/dev/null 2>&1 || {
|
||||||
|
msg_error "apk add go failed"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
msg_ok "Go ready: $(go version 2>/dev/null)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
need_tool curl tar || return 1
|
||||||
|
local ARCH TARBALL URL TMP
|
||||||
|
case "$(uname -m)" in
|
||||||
|
x86_64) ARCH="amd64" ;;
|
||||||
|
aarch64) ARCH="arm64" ;;
|
||||||
|
*)
|
||||||
|
msg_error "Unsupported arch for Go: $(uname -m)"
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
TARBALL="go${GO_VERSION}.linux-${ARCH}.tar.gz"
|
||||||
|
URL="https://go.dev/dl/${TARBALL}"
|
||||||
|
msg_info "Setup Go $GO_VERSION (tarball)"
|
||||||
|
TMP="$(mktemp)"
|
||||||
|
download_with_progress "$URL" "$TMP" || return 1
|
||||||
|
rm -rf /usr/local/go
|
||||||
|
tar -C /usr/local -xzf "$TMP" || {
|
||||||
|
msg_error "extract go failed"
|
||||||
|
rm -f "$TMP"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
rm -f "$TMP"
|
||||||
|
ln -sf /usr/local/go/bin/go /usr/local/bin/go
|
||||||
|
ln -sf /usr/local/go/bin/gofmt /usr/local/bin/gofmt
|
||||||
|
ensure_usr_local_bin_persist
|
||||||
|
msg_ok "Go ready: $(go version 2>/dev/null)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Composer – Alpine
|
||||||
|
# uses php83-cli + openssl + phar
|
||||||
|
# ------------------------------
|
||||||
|
setup_composer() {
|
||||||
|
local COMPOSER_BIN="/usr/local/bin/composer"
|
||||||
|
if ! has php; then
|
||||||
|
# prefers php83
|
||||||
|
msg_info "Installing PHP CLI for Composer"
|
||||||
|
apk add --no-cache php83-cli php83-openssl php83-phar php83-iconv >/dev/null 2>&1 || {
|
||||||
|
# Fallback to generic php if 83 not available
|
||||||
|
apk add --no-cache php-cli php-openssl php-phar php-iconv >/dev/null 2>&1 || {
|
||||||
|
msg_error "Failed to install php-cli for composer"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msg_ok "PHP CLI ready: $(php -v | head -n1)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -x "$COMPOSER_BIN" ]; then
|
||||||
|
msg_info "Updating Composer"
|
||||||
|
else
|
||||||
|
msg_info "Setup Composer"
|
||||||
|
fi
|
||||||
|
|
||||||
|
need_tool curl || return 1
|
||||||
|
curl -fsSL https://getcomposer.org/installer -o /tmp/composer-setup.php || {
|
||||||
|
msg_error "composer installer download failed"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer >/dev/null 2>&1 || {
|
||||||
|
msg_error "composer install failed"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
rm -f /tmp/composer-setup.php
|
||||||
|
ensure_usr_local_bin_persist
|
||||||
|
msg_ok "Composer ready: $(composer --version 2>/dev/null)"
|
||||||
|
}
|
||||||
322
scripts/core/api.func
Normal file
322
scripts/core/api.func
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
# Copyright (c) 2021-2026 community-scripts ORG
|
||||||
|
# Author: michelroegl-brunner
|
||||||
|
# License: MIT | https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/LICENSE
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# API.FUNC - TELEMETRY & DIAGNOSTICS API
|
||||||
|
# ==============================================================================
|
||||||
|
#
|
||||||
|
# Provides functions for sending anonymous telemetry data to Community-Scripts
|
||||||
|
# API for analytics and diagnostics purposes.
|
||||||
|
#
|
||||||
|
# Features:
|
||||||
|
# - Container/VM creation statistics
|
||||||
|
# - Installation success/failure tracking
|
||||||
|
# - Error code mapping and reporting
|
||||||
|
# - Privacy-respecting anonymous telemetry
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# source <(curl -fsSL .../api.func)
|
||||||
|
# post_to_api # Report container creation
|
||||||
|
# post_update_to_api # Report installation status
|
||||||
|
#
|
||||||
|
# Privacy:
|
||||||
|
# - Only anonymous statistics (no personal data)
|
||||||
|
# - User can opt-out via diagnostics settings
|
||||||
|
# - Random UUID for session tracking only
|
||||||
|
#
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# SECTION 1: ERROR CODE DESCRIPTIONS
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# explain_exit_code()
|
||||||
|
#
|
||||||
|
# - Maps numeric exit codes to human-readable error descriptions
|
||||||
|
# - Supports:
|
||||||
|
# * Generic/Shell errors (1, 2, 126, 127, 128, 130, 137, 139, 143)
|
||||||
|
# * Package manager errors (APT, DPKG: 100, 101, 255)
|
||||||
|
# * Node.js/npm errors (243-249, 254)
|
||||||
|
# * Python/pip/uv errors (210-212)
|
||||||
|
# * PostgreSQL errors (231-234)
|
||||||
|
# * MySQL/MariaDB errors (241-244)
|
||||||
|
# * MongoDB errors (251-254)
|
||||||
|
# * Proxmox custom codes (200-231)
|
||||||
|
# - Returns description string for given exit code
|
||||||
|
# - Shared function with error_handler.func for consistency
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
explain_exit_code() {
|
||||||
|
local code="$1"
|
||||||
|
case "$code" in
|
||||||
|
# --- Generic / Shell ---
|
||||||
|
1) echo "General error / Operation not permitted" ;;
|
||||||
|
2) echo "Misuse of shell builtins (e.g. syntax error)" ;;
|
||||||
|
126) echo "Command invoked cannot execute (permission problem?)" ;;
|
||||||
|
127) echo "Command not found" ;;
|
||||||
|
128) echo "Invalid argument to exit" ;;
|
||||||
|
130) echo "Terminated by Ctrl+C (SIGINT)" ;;
|
||||||
|
137) echo "Killed (SIGKILL / Out of memory?)" ;;
|
||||||
|
139) echo "Segmentation fault (core dumped)" ;;
|
||||||
|
143) echo "Terminated (SIGTERM)" ;;
|
||||||
|
|
||||||
|
# --- Package manager / APT / DPKG ---
|
||||||
|
100) echo "APT: Package manager error (broken packages / dependency problems)" ;;
|
||||||
|
101) echo "APT: Configuration error (bad sources.list, malformed config)" ;;
|
||||||
|
255) echo "DPKG: Fatal internal error" ;;
|
||||||
|
|
||||||
|
# --- Node.js / npm / pnpm / yarn ---
|
||||||
|
243) echo "Node.js: Out of memory (JavaScript heap out of memory)" ;;
|
||||||
|
245) echo "Node.js: Invalid command-line option" ;;
|
||||||
|
246) echo "Node.js: Internal JavaScript Parse Error" ;;
|
||||||
|
247) echo "Node.js: Fatal internal error" ;;
|
||||||
|
248) echo "Node.js: Invalid C++ addon / N-API failure" ;;
|
||||||
|
249) echo "Node.js: Inspector error" ;;
|
||||||
|
254) echo "npm/pnpm/yarn: Unknown fatal error" ;;
|
||||||
|
|
||||||
|
# --- Python / pip / uv ---
|
||||||
|
210) echo "Python: Virtualenv / uv environment missing or broken" ;;
|
||||||
|
211) echo "Python: Dependency resolution failed" ;;
|
||||||
|
212) echo "Python: Installation aborted (permissions or EXTERNALLY-MANAGED)" ;;
|
||||||
|
|
||||||
|
# --- PostgreSQL ---
|
||||||
|
231) echo "PostgreSQL: Connection failed (server not running / wrong socket)" ;;
|
||||||
|
232) echo "PostgreSQL: Authentication failed (bad user/password)" ;;
|
||||||
|
233) echo "PostgreSQL: Database does not exist" ;;
|
||||||
|
234) echo "PostgreSQL: Fatal error in query / syntax" ;;
|
||||||
|
|
||||||
|
# --- MySQL / MariaDB ---
|
||||||
|
241) echo "MySQL/MariaDB: Connection failed (server not running / wrong socket)" ;;
|
||||||
|
242) echo "MySQL/MariaDB: Authentication failed (bad user/password)" ;;
|
||||||
|
243) echo "MySQL/MariaDB: Database does not exist" ;;
|
||||||
|
244) echo "MySQL/MariaDB: Fatal error in query / syntax" ;;
|
||||||
|
|
||||||
|
# --- MongoDB ---
|
||||||
|
251) echo "MongoDB: Connection failed (server not running)" ;;
|
||||||
|
252) echo "MongoDB: Authentication failed (bad user/password)" ;;
|
||||||
|
253) echo "MongoDB: Database not found" ;;
|
||||||
|
254) echo "MongoDB: Fatal query error" ;;
|
||||||
|
|
||||||
|
# --- Proxmox Custom Codes ---
|
||||||
|
200) echo "Custom: Failed to create lock file" ;;
|
||||||
|
203) echo "Custom: Missing CTID variable" ;;
|
||||||
|
204) echo "Custom: Missing PCT_OSTYPE variable" ;;
|
||||||
|
205) echo "Custom: Invalid CTID (<100)" ;;
|
||||||
|
206) echo "Custom: CTID already in use (check 'pct list' and /etc/pve/lxc/)" ;;
|
||||||
|
207) echo "Custom: Password contains unescaped special characters (-, /, \\, *, etc.)" ;;
|
||||||
|
208) echo "Custom: Invalid configuration (DNS/MAC/Network format error)" ;;
|
||||||
|
209) echo "Custom: Container creation failed (check logs for pct create output)" ;;
|
||||||
|
210) echo "Custom: Cluster not quorate" ;;
|
||||||
|
211) echo "Custom: Timeout waiting for template lock (concurrent download in progress)" ;;
|
||||||
|
214) echo "Custom: Not enough storage space" ;;
|
||||||
|
215) echo "Custom: Container created but not listed (ghost state - check /etc/pve/lxc/)" ;;
|
||||||
|
216) echo "Custom: RootFS entry missing in config (incomplete creation)" ;;
|
||||||
|
217) echo "Custom: Storage does not support rootdir (check storage capabilities)" ;;
|
||||||
|
218) echo "Custom: Template file corrupted or incomplete download (size <1MB or invalid archive)" ;;
|
||||||
|
220) echo "Custom: Unable to resolve template path" ;;
|
||||||
|
221) echo "Custom: Template file exists but not readable (check file permissions)" ;;
|
||||||
|
222) echo "Custom: Template download failed after 3 attempts (network/storage issue)" ;;
|
||||||
|
223) echo "Custom: Template not available after download (storage sync issue)" ;;
|
||||||
|
225) echo "Custom: No template available for OS/Version (check 'pveam available')" ;;
|
||||||
|
231) echo "Custom: LXC stack upgrade/retry failed (outdated pve-container - check https://github.com/community-scripts/ProxmoxVE/discussions/8126)" ;;
|
||||||
|
|
||||||
|
# --- Default ---
|
||||||
|
*) echo "Unknown error" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# SECTION 2: TELEMETRY FUNCTIONS
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# post_to_api()
|
||||||
|
#
|
||||||
|
# - Sends LXC container creation statistics to Community-Scripts API
|
||||||
|
# - Only executes if:
|
||||||
|
# * curl is available
|
||||||
|
# * DIAGNOSTICS=yes
|
||||||
|
# * RANDOM_UUID is set
|
||||||
|
# - Payload includes:
|
||||||
|
# * Container type, disk size, CPU cores, RAM
|
||||||
|
# * OS type and version
|
||||||
|
# * IPv6 disable status
|
||||||
|
# * Application name (NSAPP)
|
||||||
|
# * Installation method
|
||||||
|
# * PVE version
|
||||||
|
# * Status: "installing"
|
||||||
|
# * Random UUID for session tracking
|
||||||
|
# - Anonymous telemetry (no personal data)
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
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",
|
||||||
|
"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()
|
||||||
|
#
|
||||||
|
# - Sends VM creation statistics to Community-Scripts API
|
||||||
|
# - Similar to post_to_api() but for virtual machines (not containers)
|
||||||
|
# - Reads DIAGNOSTICS from /usr/local/community-scripts/diagnostics file
|
||||||
|
# - Payload differences:
|
||||||
|
# * ct_type=2 (VM instead of LXC)
|
||||||
|
# * type="vm"
|
||||||
|
# * Disk size without 'G' suffix (parsed from DISK_SIZE variable)
|
||||||
|
# - Only executes if DIAGNOSTICS=yes and RANDOM_UUID is set
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
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",
|
||||||
|
"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_to_api()
|
||||||
|
#
|
||||||
|
# - Reports installation completion status to API
|
||||||
|
# - Prevents duplicate submissions via POST_UPDATE_DONE flag
|
||||||
|
# - Arguments:
|
||||||
|
# * $1: status ("success" or "failed")
|
||||||
|
# * $2: exit_code (default: 1 for failed, 0 for success)
|
||||||
|
# - Payload includes:
|
||||||
|
# * Final status (success/failed)
|
||||||
|
# * Error description via get_error_description()
|
||||||
|
# * Random UUID for session correlation
|
||||||
|
# - Only executes once per session
|
||||||
|
# - Silently returns if:
|
||||||
|
# * curl not available
|
||||||
|
# * Already reported (POST_UPDATE_DONE=true)
|
||||||
|
# * DIAGNOSTICS=no
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
post_update_to_api() {
|
||||||
|
|
||||||
|
if ! command -v curl &>/dev/null; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Initialize flag if not set (prevents 'unbound variable' error with set -u)
|
||||||
|
POST_UPDATE_DONE=${POST_UPDATE_DONE:-false}
|
||||||
|
|
||||||
|
if [ "$POST_UPDATE_DONE" = true ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
exit_code=${2:-1}
|
||||||
|
local API_URL="http://api.community-scripts.org/upload/updatestatus"
|
||||||
|
local status="${1:-failed}"
|
||||||
|
if [[ "$status" == "failed" ]]; then
|
||||||
|
local exit_code="${2:-1}"
|
||||||
|
elif [[ "$status" == "success" ]]; then
|
||||||
|
local exit_code="${2:-0}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$exit_code" ]]; then
|
||||||
|
exit_code=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
error=$(explain_exit_code "$exit_code")
|
||||||
|
|
||||||
|
if [ -z "$error" ]; then
|
||||||
|
error="Unknown error"
|
||||||
|
fi
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
505
scripts/core/cloud-init.func
Normal file
505
scripts/core/cloud-init.func
Normal file
@@ -0,0 +1,505 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Copyright (c) 2021-2026 community-scripts ORG
|
||||||
|
# Author: community-scripts ORG
|
||||||
|
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/branch/main/LICENSE
|
||||||
|
# Revision: 1
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# CLOUD-INIT.FUNC - VM CLOUD-INIT CONFIGURATION LIBRARY
|
||||||
|
# ==============================================================================
|
||||||
|
#
|
||||||
|
# Universal helper library for Cloud-Init configuration in Proxmox VMs.
|
||||||
|
# Provides functions for:
|
||||||
|
#
|
||||||
|
# - Native Proxmox Cloud-Init setup (user, password, network, SSH keys)
|
||||||
|
# - Interactive configuration dialogs (whiptail)
|
||||||
|
# - IP address retrieval via qemu-guest-agent
|
||||||
|
# - Cloud-Init status monitoring and waiting
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/cloud-init.func)
|
||||||
|
# setup_cloud_init "$VMID" "$STORAGE" "$HN" "yes"
|
||||||
|
#
|
||||||
|
# Compatible with: Debian, Ubuntu, and all Cloud-Init enabled distributions
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# SECTION 1: CONFIGURATION DEFAULTS
|
||||||
|
# ==============================================================================
|
||||||
|
# These can be overridden before sourcing this library
|
||||||
|
|
||||||
|
CLOUDINIT_DEFAULT_USER="${CLOUDINIT_DEFAULT_USER:-root}"
|
||||||
|
CLOUDINIT_DNS_SERVERS="${CLOUDINIT_DNS_SERVERS:-1.1.1.1 8.8.8.8}"
|
||||||
|
CLOUDINIT_SEARCH_DOMAIN="${CLOUDINIT_SEARCH_DOMAIN:-local}"
|
||||||
|
CLOUDINIT_SSH_KEYS="${CLOUDINIT_SSH_KEYS:-/root/.ssh/authorized_keys}"
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# SECTION 2: HELPER FUNCTIONS
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# _ci_msg - Internal message helper with fallback
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
function _ci_msg_info() { msg_info "$1" 2>/dev/null || echo "[INFO] $1"; }
|
||||||
|
function _ci_msg_ok() { msg_ok "$1" 2>/dev/null || echo "[OK] $1"; }
|
||||||
|
function _ci_msg_warn() { msg_warn "$1" 2>/dev/null || echo "[WARN] $1"; }
|
||||||
|
function _ci_msg_error() { msg_error "$1" 2>/dev/null || echo "[ERROR] $1"; }
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# validate_ip_cidr - Validate IP address in CIDR format
|
||||||
|
# Usage: validate_ip_cidr "192.168.1.100/24" && echo "Valid"
|
||||||
|
# Returns: 0 if valid, 1 if invalid
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
function validate_ip_cidr() {
|
||||||
|
local ip_cidr="$1"
|
||||||
|
# Match: 0-255.0-255.0-255.0-255/0-32
|
||||||
|
if [[ "$ip_cidr" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$ ]]; then
|
||||||
|
# Validate each octet is 0-255
|
||||||
|
local ip="${ip_cidr%/*}"
|
||||||
|
IFS='.' read -ra octets <<<"$ip"
|
||||||
|
for octet in "${octets[@]}"; do
|
||||||
|
((octet > 255)) && return 1
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# validate_ip - Validate plain IP address (no CIDR)
|
||||||
|
# Usage: validate_ip "192.168.1.1" && echo "Valid"
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
function validate_ip() {
|
||||||
|
local ip="$1"
|
||||||
|
if [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
|
||||||
|
IFS='.' read -ra octets <<<"$ip"
|
||||||
|
for octet in "${octets[@]}"; do
|
||||||
|
((octet > 255)) && return 1
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# SECTION 3: MAIN CLOUD-INIT FUNCTIONS
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# setup_cloud_init - Configures Proxmox Native Cloud-Init
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Parameters:
|
||||||
|
# $1 - VMID (required)
|
||||||
|
# $2 - Storage name (required)
|
||||||
|
# $3 - Hostname (optional, default: vm-<vmid>)
|
||||||
|
# $4 - Enable Cloud-Init (yes/no, default: no)
|
||||||
|
# $5 - User (optional, default: root)
|
||||||
|
# $6 - Network mode (dhcp/static, default: dhcp)
|
||||||
|
# $7 - Static IP (optional, format: 192.168.1.100/24)
|
||||||
|
# $8 - Gateway (optional)
|
||||||
|
# $9 - Nameservers (optional, default: 1.1.1.1 8.8.8.8)
|
||||||
|
#
|
||||||
|
# Returns: 0 on success, 1 on failure
|
||||||
|
# Exports: CLOUDINIT_USER, CLOUDINIT_PASSWORD, CLOUDINIT_CRED_FILE
|
||||||
|
# ==============================================================================
|
||||||
|
function setup_cloud_init() {
|
||||||
|
local vmid="$1"
|
||||||
|
local storage="$2"
|
||||||
|
local hostname="${3:-vm-${vmid}}"
|
||||||
|
local enable="${4:-no}"
|
||||||
|
local ciuser="${5:-$CLOUDINIT_DEFAULT_USER}"
|
||||||
|
local network_mode="${6:-dhcp}"
|
||||||
|
local static_ip="${7:-}"
|
||||||
|
local gateway="${8:-}"
|
||||||
|
local nameservers="${9:-$CLOUDINIT_DNS_SERVERS}"
|
||||||
|
|
||||||
|
# Skip if not enabled
|
||||||
|
if [ "$enable" != "yes" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate static IP if provided
|
||||||
|
if [ "$network_mode" = "static" ]; then
|
||||||
|
if [ -n "$static_ip" ] && ! validate_ip_cidr "$static_ip"; then
|
||||||
|
_ci_msg_error "Invalid static IP format: $static_ip (expected: x.x.x.x/xx)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [ -n "$gateway" ] && ! validate_ip "$gateway"; then
|
||||||
|
_ci_msg_error "Invalid gateway IP format: $gateway"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
_ci_msg_info "Configuring Cloud-Init"
|
||||||
|
|
||||||
|
# Create Cloud-Init drive (try ide2 first, then scsi1 as fallback)
|
||||||
|
if ! qm set "$vmid" --ide2 "${storage}:cloudinit" >/dev/null 2>&1; then
|
||||||
|
qm set "$vmid" --scsi1 "${storage}:cloudinit" >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set user
|
||||||
|
qm set "$vmid" --ciuser "$ciuser" >/dev/null
|
||||||
|
|
||||||
|
# Generate and set secure random password
|
||||||
|
local cipassword=$(openssl rand -base64 16)
|
||||||
|
qm set "$vmid" --cipassword "$cipassword" >/dev/null
|
||||||
|
|
||||||
|
# Add SSH keys if available
|
||||||
|
if [ -f "$CLOUDINIT_SSH_KEYS" ]; then
|
||||||
|
qm set "$vmid" --sshkeys "$CLOUDINIT_SSH_KEYS" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Configure network
|
||||||
|
if [ "$network_mode" = "static" ] && [ -n "$static_ip" ] && [ -n "$gateway" ]; then
|
||||||
|
qm set "$vmid" --ipconfig0 "ip=${static_ip},gw=${gateway}" >/dev/null
|
||||||
|
else
|
||||||
|
qm set "$vmid" --ipconfig0 "ip=dhcp" >/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set DNS servers
|
||||||
|
qm set "$vmid" --nameserver "$nameservers" >/dev/null
|
||||||
|
|
||||||
|
# Set search domain
|
||||||
|
qm set "$vmid" --searchdomain "$CLOUDINIT_SEARCH_DOMAIN" >/dev/null
|
||||||
|
|
||||||
|
# Enable package upgrades on first boot (if supported by Proxmox version)
|
||||||
|
qm set "$vmid" --ciupgrade 1 >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
# Save credentials to file (with restrictive permissions)
|
||||||
|
local cred_file="/tmp/${hostname}-${vmid}-cloud-init-credentials.txt"
|
||||||
|
umask 077
|
||||||
|
cat >"$cred_file" <<EOF
|
||||||
|
╔══════════════════════════════════════════════════════════════════╗
|
||||||
|
║ ⚠️ SECURITY WARNING: DELETE THIS FILE AFTER NOTING CREDENTIALS ║
|
||||||
|
╚══════════════════════════════════════════════════════════════════╝
|
||||||
|
|
||||||
|
Cloud-Init Credentials
|
||||||
|
────────────────────────────────────────
|
||||||
|
VM ID: ${vmid}
|
||||||
|
Hostname: ${hostname}
|
||||||
|
Created: $(date)
|
||||||
|
|
||||||
|
Username: ${ciuser}
|
||||||
|
Password: ${cipassword}
|
||||||
|
|
||||||
|
Network: ${network_mode}$([ "$network_mode" = "static" ] && echo " (IP: ${static_ip}, GW: ${gateway})" || echo " (DHCP)")
|
||||||
|
DNS: ${nameservers}
|
||||||
|
|
||||||
|
────────────────────────────────────────
|
||||||
|
SSH Access (if keys configured):
|
||||||
|
ssh ${ciuser}@<vm-ip>
|
||||||
|
|
||||||
|
Proxmox UI Configuration:
|
||||||
|
VM ${vmid} > Cloud-Init > Edit
|
||||||
|
- User, Password, SSH Keys
|
||||||
|
- Network (IP Config)
|
||||||
|
- DNS, Search Domain
|
||||||
|
|
||||||
|
────────────────────────────────────────
|
||||||
|
🗑️ To delete this file:
|
||||||
|
rm -f ${cred_file}
|
||||||
|
────────────────────────────────────────
|
||||||
|
EOF
|
||||||
|
chmod 600 "$cred_file"
|
||||||
|
|
||||||
|
_ci_msg_ok "Cloud-Init configured (User: ${ciuser})"
|
||||||
|
|
||||||
|
# Export for use in calling script (DO NOT display password here - will be shown in summary)
|
||||||
|
export CLOUDINIT_USER="$ciuser"
|
||||||
|
export CLOUDINIT_PASSWORD="$cipassword"
|
||||||
|
export CLOUDINIT_CRED_FILE="$cred_file"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# SECTION 4: INTERACTIVE CONFIGURATION
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# configure_cloud_init_interactive - Whiptail dialog for Cloud-Init setup
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Prompts user for Cloud-Init configuration choices
|
||||||
|
# Returns configuration via exported variables:
|
||||||
|
# - CLOUDINIT_ENABLE (yes/no)
|
||||||
|
# - CLOUDINIT_USER
|
||||||
|
# - CLOUDINIT_NETWORK_MODE (dhcp/static)
|
||||||
|
# - CLOUDINIT_IP (if static)
|
||||||
|
# - CLOUDINIT_GW (if static)
|
||||||
|
# - CLOUDINIT_DNS
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
function configure_cloud_init_interactive() {
|
||||||
|
local default_user="${1:-root}"
|
||||||
|
|
||||||
|
# Check if whiptail is available
|
||||||
|
if ! command -v whiptail >/dev/null 2>&1; then
|
||||||
|
echo "Warning: whiptail not available, skipping interactive configuration"
|
||||||
|
export CLOUDINIT_ENABLE="no"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ask if user wants to enable Cloud-Init
|
||||||
|
if ! (whiptail --backtitle "Proxmox VE Helper Scripts" --title "CLOUD-INIT" \
|
||||||
|
--yesno "Enable Cloud-Init for VM configuration?\n\nCloud-Init allows automatic configuration of:\n• User accounts and passwords\n• SSH keys\n• Network settings (DHCP/Static)\n• DNS configuration\n\nYou can also configure these settings later in Proxmox UI." 16 68); then
|
||||||
|
export CLOUDINIT_ENABLE="no"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
export CLOUDINIT_ENABLE="yes"
|
||||||
|
|
||||||
|
# Username
|
||||||
|
if CLOUDINIT_USER=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox \
|
||||||
|
"Cloud-Init Username" 8 58 "$default_user" --title "USERNAME" 3>&1 1>&2 2>&3); then
|
||||||
|
export CLOUDINIT_USER="${CLOUDINIT_USER:-$default_user}"
|
||||||
|
else
|
||||||
|
export CLOUDINIT_USER="$default_user"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Network configuration
|
||||||
|
if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "NETWORK MODE" \
|
||||||
|
--yesno "Use DHCP for network configuration?\n\nSelect 'No' for static IP configuration." 10 58); then
|
||||||
|
export CLOUDINIT_NETWORK_MODE="dhcp"
|
||||||
|
else
|
||||||
|
export CLOUDINIT_NETWORK_MODE="static"
|
||||||
|
|
||||||
|
# Static IP with validation
|
||||||
|
while true; do
|
||||||
|
if CLOUDINIT_IP=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox \
|
||||||
|
"Static IP Address (CIDR format)\nExample: 192.168.1.100/24" 9 58 "" --title "IP ADDRESS" 3>&1 1>&2 2>&3); then
|
||||||
|
if validate_ip_cidr "$CLOUDINIT_IP"; then
|
||||||
|
export CLOUDINIT_IP
|
||||||
|
break
|
||||||
|
else
|
||||||
|
whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID IP" \
|
||||||
|
--msgbox "Invalid IP format: $CLOUDINIT_IP\n\nPlease use CIDR format: x.x.x.x/xx\nExample: 192.168.1.100/24" 10 50
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
_ci_msg_warn "Static IP required, falling back to DHCP"
|
||||||
|
export CLOUDINIT_NETWORK_MODE="dhcp"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Gateway with validation
|
||||||
|
if [ "$CLOUDINIT_NETWORK_MODE" = "static" ]; then
|
||||||
|
while true; do
|
||||||
|
if CLOUDINIT_GW=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox \
|
||||||
|
"Gateway IP Address\nExample: 192.168.1.1" 8 58 "" --title "GATEWAY" 3>&1 1>&2 2>&3); then
|
||||||
|
if validate_ip "$CLOUDINIT_GW"; then
|
||||||
|
export CLOUDINIT_GW
|
||||||
|
break
|
||||||
|
else
|
||||||
|
whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID GATEWAY" \
|
||||||
|
--msgbox "Invalid gateway format: $CLOUDINIT_GW\n\nPlease use format: x.x.x.x\nExample: 192.168.1.1" 10 50
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
_ci_msg_warn "Gateway required, falling back to DHCP"
|
||||||
|
export CLOUDINIT_NETWORK_MODE="dhcp"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# DNS Servers
|
||||||
|
if CLOUDINIT_DNS=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox \
|
||||||
|
"DNS Servers (space-separated)" 8 58 "1.1.1.1 8.8.8.8" --title "DNS SERVERS" 3>&1 1>&2 2>&3); then
|
||||||
|
export CLOUDINIT_DNS="${CLOUDINIT_DNS:-1.1.1.1 8.8.8.8}"
|
||||||
|
else
|
||||||
|
export CLOUDINIT_DNS="1.1.1.1 8.8.8.8"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# SECTION 5: UTILITY FUNCTIONS
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# display_cloud_init_info - Show Cloud-Init summary after setup
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
function display_cloud_init_info() {
|
||||||
|
local vmid="$1"
|
||||||
|
local hostname="${2:-}"
|
||||||
|
|
||||||
|
if [ -n "$CLOUDINIT_CRED_FILE" ] && [ -f "$CLOUDINIT_CRED_FILE" ]; then
|
||||||
|
if [ -n "${INFO:-}" ]; then
|
||||||
|
echo -e "\n${INFO}${BOLD:-}${GN:-} Cloud-Init Configuration:${CL:-}"
|
||||||
|
echo -e "${TAB:- }${DGN:-}User: ${BGN:-}${CLOUDINIT_USER:-root}${CL:-}"
|
||||||
|
echo -e "${TAB:- }${DGN:-}Password: ${BGN:-}${CLOUDINIT_PASSWORD}${CL:-}"
|
||||||
|
echo -e "${TAB:- }${DGN:-}Credentials: ${BL:-}${CLOUDINIT_CRED_FILE}${CL:-}"
|
||||||
|
echo -e "${TAB:- }${RD:-}⚠️ Delete credentials file after noting password!${CL:-}"
|
||||||
|
echo -e "${TAB:- }${YW:-}💡 Configure in Proxmox UI: VM ${vmid} > Cloud-Init${CL:-}"
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo "[INFO] Cloud-Init Configuration:"
|
||||||
|
echo " User: ${CLOUDINIT_USER:-root}"
|
||||||
|
echo " Password: ${CLOUDINIT_PASSWORD}"
|
||||||
|
echo " Credentials: ${CLOUDINIT_CRED_FILE}"
|
||||||
|
echo " ⚠️ Delete credentials file after noting password!"
|
||||||
|
echo " Configure in Proxmox UI: VM ${vmid} > Cloud-Init"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# cleanup_cloud_init_credentials - Remove credentials file
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Usage: cleanup_cloud_init_credentials
|
||||||
|
# Call this after user has noted/saved the credentials
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
function cleanup_cloud_init_credentials() {
|
||||||
|
if [ -n "$CLOUDINIT_CRED_FILE" ] && [ -f "$CLOUDINIT_CRED_FILE" ]; then
|
||||||
|
rm -f "$CLOUDINIT_CRED_FILE"
|
||||||
|
_ci_msg_ok "Credentials file removed: $CLOUDINIT_CRED_FILE"
|
||||||
|
unset CLOUDINIT_CRED_FILE
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# has_cloud_init - Check if VM has Cloud-Init configured
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
function has_cloud_init() {
|
||||||
|
local vmid="$1"
|
||||||
|
qm config "$vmid" 2>/dev/null | grep -qE "(ide2|scsi1):.*cloudinit"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# regenerate_cloud_init - Regenerate Cloud-Init configuration
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
function regenerate_cloud_init() {
|
||||||
|
local vmid="$1"
|
||||||
|
|
||||||
|
if has_cloud_init "$vmid"; then
|
||||||
|
_ci_msg_info "Regenerating Cloud-Init configuration"
|
||||||
|
qm cloudinit update "$vmid" >/dev/null 2>&1 || true
|
||||||
|
_ci_msg_ok "Cloud-Init configuration regenerated"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
_ci_msg_warn "VM $vmid does not have Cloud-Init configured"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# get_vm_ip - Get VM IP address via qemu-guest-agent
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
function get_vm_ip() {
|
||||||
|
local vmid="$1"
|
||||||
|
local timeout="${2:-30}"
|
||||||
|
|
||||||
|
local elapsed=0
|
||||||
|
while [ $elapsed -lt $timeout ]; do
|
||||||
|
local vm_ip=$(qm guest cmd "$vmid" network-get-interfaces 2>/dev/null |
|
||||||
|
jq -r '.[] | select(.name != "lo") | ."ip-addresses"[]? | select(."ip-address-type" == "ipv4") | ."ip-address"' 2>/dev/null | head -1)
|
||||||
|
|
||||||
|
if [ -n "$vm_ip" ]; then
|
||||||
|
echo "$vm_ip"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 2
|
||||||
|
elapsed=$((elapsed + 2))
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# wait_for_cloud_init - Wait for Cloud-Init to complete (requires SSH access)
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
function wait_for_cloud_init() {
|
||||||
|
local vmid="$1"
|
||||||
|
local timeout="${2:-300}"
|
||||||
|
local vm_ip="${3:-}"
|
||||||
|
|
||||||
|
# Get IP if not provided
|
||||||
|
if [ -z "$vm_ip" ]; then
|
||||||
|
vm_ip=$(get_vm_ip "$vmid" 60)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$vm_ip" ]; then
|
||||||
|
_ci_msg_warn "Unable to determine VM IP address"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_ci_msg_info "Waiting for Cloud-Init to complete on ${vm_ip}"
|
||||||
|
|
||||||
|
local elapsed=0
|
||||||
|
while [ $elapsed -lt $timeout ]; do
|
||||||
|
if timeout 10 ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
||||||
|
"${CLOUDINIT_USER:-root}@${vm_ip}" "cloud-init status --wait" 2>/dev/null; then
|
||||||
|
_ci_msg_ok "Cloud-Init completed successfully"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 10
|
||||||
|
elapsed=$((elapsed + 10))
|
||||||
|
done
|
||||||
|
|
||||||
|
_ci_msg_warn "Cloud-Init did not complete within ${timeout}s"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# SECTION 6: EXPORTS
|
||||||
|
# ==============================================================================
|
||||||
|
# Export all functions for use in other scripts
|
||||||
|
|
||||||
|
export -f setup_cloud_init 2>/dev/null || true
|
||||||
|
export -f configure_cloud_init_interactive 2>/dev/null || true
|
||||||
|
export -f display_cloud_init_info 2>/dev/null || true
|
||||||
|
export -f cleanup_cloud_init_credentials 2>/dev/null || true
|
||||||
|
export -f has_cloud_init 2>/dev/null || true
|
||||||
|
export -f regenerate_cloud_init 2>/dev/null || true
|
||||||
|
export -f get_vm_ip 2>/dev/null || true
|
||||||
|
export -f wait_for_cloud_init 2>/dev/null || true
|
||||||
|
export -f validate_ip_cidr 2>/dev/null || true
|
||||||
|
export -f validate_ip 2>/dev/null || true
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# SECTION 7: EXAMPLES & DOCUMENTATION
|
||||||
|
# ==============================================================================
|
||||||
|
: <<'EXAMPLES'
|
||||||
|
|
||||||
|
# Example 1: Simple DHCP setup (most common)
|
||||||
|
setup_cloud_init "$VMID" "$STORAGE" "$HN" "yes"
|
||||||
|
|
||||||
|
# Example 2: Static IP setup
|
||||||
|
setup_cloud_init "$VMID" "$STORAGE" "myserver" "yes" "root" "static" "192.168.1.100/24" "192.168.1.1"
|
||||||
|
|
||||||
|
# Example 3: Interactive configuration in advanced_settings()
|
||||||
|
configure_cloud_init_interactive "admin"
|
||||||
|
if [ "$CLOUDINIT_ENABLE" = "yes" ]; then
|
||||||
|
setup_cloud_init "$VMID" "$STORAGE" "$HN" "yes" "$CLOUDINIT_USER" \
|
||||||
|
"$CLOUDINIT_NETWORK_MODE" "$CLOUDINIT_IP" "$CLOUDINIT_GW" "$CLOUDINIT_DNS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Example 4: Display info after VM creation
|
||||||
|
display_cloud_init_info "$VMID" "$HN"
|
||||||
|
|
||||||
|
# Example 5: Check if VM has Cloud-Init
|
||||||
|
if has_cloud_init "$VMID"; then
|
||||||
|
echo "Cloud-Init is configured"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Example 6: Wait for Cloud-Init to complete after VM start
|
||||||
|
if [ "$START_VM" = "yes" ]; then
|
||||||
|
qm start "$VMID"
|
||||||
|
sleep 30
|
||||||
|
wait_for_cloud_init "$VMID" 300
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Example 7: Cleanup credentials file after user has noted password
|
||||||
|
display_cloud_init_info "$VMID" "$HN"
|
||||||
|
read -p "Have you saved the credentials? (y/N): " -r
|
||||||
|
[[ $REPLY =~ ^[Yy]$ ]] && cleanup_cloud_init_credentials
|
||||||
|
|
||||||
|
# Example 8: Validate IP before using
|
||||||
|
if validate_ip_cidr "192.168.1.100/24"; then
|
||||||
|
echo "Valid IP/CIDR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
EXAMPLES
|
||||||
@@ -1,699 +0,0 @@
|
|||||||
config_file() {
|
|
||||||
CONFIG_FILE="/opt/community-scripts/.settings"
|
|
||||||
|
|
||||||
if [[ -f "/opt/community-scripts/${NSAPP}.conf" ]]; then
|
|
||||||
CONFIG_FILE="/opt/community-scripts/${NSAPP}.conf"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if CONFIG_FILE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set absolute path to config file" 8 58 "$CONFIG_FILE" --title "CONFIG FILE" 3>&1 1>&2 2>&3); then
|
|
||||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
|
||||||
echo -e "${CROSS}${RD}Config file not found, exiting script!.${CL}"
|
|
||||||
exit
|
|
||||||
else
|
|
||||||
echo -e "${INFO}${BOLD}${DGN}Using config File: ${BGN}$CONFIG_FILE${CL}"
|
|
||||||
source "$CONFIG_FILE"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
if [[ -n "${CT_ID-}" ]]; then
|
|
||||||
if [[ "$CT_ID" =~ ^([0-9]{3,4})-([0-9]{3,4})$ ]]; then
|
|
||||||
MIN_ID=${BASH_REMATCH[1]}
|
|
||||||
MAX_ID=${BASH_REMATCH[2]}
|
|
||||||
if ((MIN_ID >= MAX_ID)); then
|
|
||||||
msg_error "Invalid Container ID range. The first number must be smaller than the second number, was ${CT_ID}"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
LIST_OF_IDS=$(pvesh get /cluster/resources --type vm --output-format json 2>/dev/null | grep -oP '"vmid":\s*\K\d+') || true
|
|
||||||
if [[ -n "$LIST_OF_IDS" ]]; then
|
|
||||||
for ((ID = MIN_ID; ID <= MAX_ID; ID++)); do
|
|
||||||
if ! grep -q "^$ID$" <<<"$LIST_OF_IDS"; then
|
|
||||||
CT_ID=$ID
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}$CT_ID${CL}"
|
|
||||||
|
|
||||||
elif [[ "$CT_ID" =~ ^[0-9]+$ ]]; then
|
|
||||||
LIST_OF_IDS=$(pvesh get /cluster/resources --type vm --output-format json 2>/dev/null | grep -oP '"vmid":\s*\K\d+') || true
|
|
||||||
if [[ -n "$LIST_OF_IDS" ]]; then
|
|
||||||
|
|
||||||
if ! grep -q "^$CT_ID$" <<<"$LIST_OF_IDS"; then
|
|
||||||
echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}$CT_ID${CL}"
|
|
||||||
else
|
|
||||||
msg_error "Container ID $CT_ID already exists"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}$CT_ID${CL}"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
msg_error "Invalid Container ID format. Needs to be 0000-9999 or 0-9999, was ${CT_ID}"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
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
|
|
||||||
|
|
||||||
fi
|
|
||||||
if [[ -n "${CT_TYPE-}" ]]; then
|
|
||||||
if [[ "$CT_TYPE" -eq 0 ]]; then
|
|
||||||
CT_TYPE_DESC="Privileged"
|
|
||||||
elif [[ "$CT_TYPE" -eq 1 ]]; then
|
|
||||||
CT_TYPE_DESC="Unprivileged"
|
|
||||||
else
|
|
||||||
msg_error "Unknown setting for CT_TYPE, should be 1 or 0, was ${CT_TYPE}"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
echo -e "${CONTAINERTYPE}${BOLD}${DGN}Container Type: ${BGN}$CT_TYPE_DESC${CL}"
|
|
||||||
else
|
|
||||||
if CT_TYPE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "CONTAINER TYPE" --radiolist "Choose Type" 10 58 2 \
|
|
||||||
"1" "Unprivileged" ON \
|
|
||||||
"0" "Privileged" OFF \
|
|
||||||
3>&1 1>&2 2>&3); then
|
|
||||||
if [ -n "$CT_TYPE" ]; then
|
|
||||||
CT_TYPE_DESC="Unprivileged"
|
|
||||||
if [ "$CT_TYPE" -eq 0 ]; then
|
|
||||||
CT_TYPE_DESC="Privileged"
|
|
||||||
fi
|
|
||||||
echo -e "${CONTAINERTYPE}${BOLD}${DGN}Container Type: ${BGN}$CT_TYPE_DESC${CL}"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
exit_script
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "${PW-}" ]]; then
|
|
||||||
if [[ "$PW" == "none" ]]; then
|
|
||||||
PW=""
|
|
||||||
else
|
|
||||||
if [[ "$PW" == *" "* ]]; then
|
|
||||||
msg_error "Password cannot be empty"
|
|
||||||
exit
|
|
||||||
elif [[ ${#PW} -lt 5 ]]; then
|
|
||||||
msg_error "Password must be at least 5 characters long"
|
|
||||||
exit
|
|
||||||
else
|
|
||||||
echo -e "${VERIFYPW}${BOLD}${DGN}Root Password: ${BGN}********${CL}"
|
|
||||||
fi
|
|
||||||
PW="-password $PW"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
while true; do
|
|
||||||
if PW1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --passwordbox "\nSet Root Password (needed for root ssh access)" 9 58 --title "PASSWORD (leave blank for automatic login)" 3>&1 1>&2 2>&3); then
|
|
||||||
if [[ -n "$PW1" ]]; then
|
|
||||||
if [[ "$PW1" == *" "* ]]; then
|
|
||||||
whiptail --msgbox "Password cannot contain spaces. Please try again." 8 58
|
|
||||||
elif [ ${#PW1} -lt 5 ]; then
|
|
||||||
whiptail --msgbox "Password must be at least 5 characters long. Please try again." 8 58
|
|
||||||
else
|
|
||||||
if PW2=$(whiptail --backtitle "Proxmox VE Helper Scripts" --passwordbox "\nVerify Root Password" 9 58 --title "PASSWORD VERIFICATION" 3>&1 1>&2 2>&3); then
|
|
||||||
if [[ "$PW1" == "$PW2" ]]; then
|
|
||||||
PW="-password $PW1"
|
|
||||||
echo -e "${VERIFYPW}${BOLD}${DGN}Root Password: ${BGN}********${CL}"
|
|
||||||
break
|
|
||||||
else
|
|
||||||
whiptail --msgbox "Passwords do not match. Please try again." 8 58
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
exit_script
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
PW1="Automatic Login"
|
|
||||||
PW=""
|
|
||||||
echo -e "${VERIFYPW}${BOLD}${DGN}Root Password: ${BGN}$PW1${CL}"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
exit_script
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "${HN-}" ]]; then
|
|
||||||
echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}"
|
|
||||||
else
|
|
||||||
if CT_NAME=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Hostname" 8 58 "$NSAPP" --title "HOSTNAME" 3>&1 1>&2 2>&3); then
|
|
||||||
if [ -z "$CT_NAME" ]; then
|
|
||||||
HN="$NSAPP"
|
|
||||||
else
|
|
||||||
HN=$(echo "${CT_NAME,,}" | tr -d ' ')
|
|
||||||
fi
|
|
||||||
echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}"
|
|
||||||
else
|
|
||||||
exit_script
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "${DISK_SIZE-}" ]]; then
|
|
||||||
if [[ "$DISK_SIZE" =~ ^-?[0-9]+$ ]]; then
|
|
||||||
echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB${CL}"
|
|
||||||
else
|
|
||||||
msg_error "DISK_SIZE must be an integer, was ${DISK_SIZE}"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if DISK_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Disk Size in GB" 8 58 "$var_disk" --title "DISK SIZE" 3>&1 1>&2 2>&3); then
|
|
||||||
if [ -z "$DISK_SIZE" ]; then
|
|
||||||
DISK_SIZE="$var_disk"
|
|
||||||
echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB${CL}"
|
|
||||||
else
|
|
||||||
if ! [[ $DISK_SIZE =~ $INTEGER ]]; then
|
|
||||||
echo -e "{INFO}${HOLD}${RD} DISK SIZE MUST BE AN INTEGER NUMBER!${CL}"
|
|
||||||
advanced_settings
|
|
||||||
fi
|
|
||||||
echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB${CL}"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
exit_script
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "${CORE_COUNT-}" ]]; then
|
|
||||||
if [[ "$CORE_COUNT" =~ ^-?[0-9]+$ ]]; then
|
|
||||||
echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}"
|
|
||||||
else
|
|
||||||
msg_error "CORE_COUNT must be an integer, was ${CORE_COUNT}"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if CORE_COUNT=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allocate CPU Cores" 8 58 "$var_cpu" --title "CORE COUNT" 3>&1 1>&2 2>&3); then
|
|
||||||
if [ -z "$CORE_COUNT" ]; then
|
|
||||||
CORE_COUNT="$var_cpu"
|
|
||||||
echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}"
|
|
||||||
else
|
|
||||||
echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
exit_script
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "${RAM_SIZE-}" ]]; then
|
|
||||||
if [[ "$RAM_SIZE" =~ ^-?[0-9]+$ ]]; then
|
|
||||||
echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}"
|
|
||||||
else
|
|
||||||
msg_error "RAM_SIZE must be an integer, was ${RAM_SIZE}"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if RAM_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allocate RAM in MiB" 8 58 "$var_ram" --title "RAM" 3>&1 1>&2 2>&3); then
|
|
||||||
if [ -z "$RAM_SIZE" ]; then
|
|
||||||
RAM_SIZE="$var_ram"
|
|
||||||
echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}"
|
|
||||||
else
|
|
||||||
echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
exit_script
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
IFACE_FILEPATH_LIST="/etc/network/interfaces"$'\n'$(find "/etc/network/interfaces.d/" -type f)
|
|
||||||
BRIDGES=""
|
|
||||||
OLD_IFS=$IFS
|
|
||||||
IFS=$'\n'
|
|
||||||
|
|
||||||
for iface_filepath in ${IFACE_FILEPATH_LIST}; do
|
|
||||||
|
|
||||||
iface_indexes_tmpfile=$(mktemp -q -u '.iface-XXXX')
|
|
||||||
(grep -Pn '^\s*iface' "${iface_filepath}" | cut -d':' -f1 && wc -l "${iface_filepath}" | cut -d' ' -f1) | awk 'FNR==1 {line=$0; next} {print line":"$0-1; line=$0}' >"${iface_indexes_tmpfile}" || true
|
|
||||||
|
|
||||||
if [ -f "${iface_indexes_tmpfile}" ]; then
|
|
||||||
|
|
||||||
while read -r pair; do
|
|
||||||
start=$(echo "${pair}" | cut -d':' -f1)
|
|
||||||
end=$(echo "${pair}" | cut -d':' -f2)
|
|
||||||
if awk "NR >= ${start} && NR <= ${end}" "${iface_filepath}" | grep -qP '^\s*(bridge[-_](ports|stp|fd|vlan-aware|vids)|ovs_type\s+OVSBridge)\b'; then
|
|
||||||
iface_name=$(sed "${start}q;d" "${iface_filepath}" | awk '{print $2}')
|
|
||||||
BRIDGES="${iface_name}"$'\n'"${BRIDGES}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
done <"${iface_indexes_tmpfile}"
|
|
||||||
rm -f "${iface_indexes_tmpfile}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
done
|
|
||||||
IFS=$OLD_IFS
|
|
||||||
BRIDGES=$(echo "$BRIDGES" | grep -v '^\s*$' | sort | uniq)
|
|
||||||
|
|
||||||
if [[ -n "${BRG-}" ]]; then
|
|
||||||
if echo "$BRIDGES" | grep -q "${BRG}"; then
|
|
||||||
echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}"
|
|
||||||
else
|
|
||||||
msg_error "Bridge '${BRG}' does not exist in /etc/network/interfaces or /etc/network/interfaces.d/sdn"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
BRG=$(whiptail --backtitle "Proxmox VE Helper Scripts" --menu "Select network bridge:" 15 40 6 $(echo "$BRIDGES" | awk '{print $0, "Bridge"}') 3>&1 1>&2 2>&3)
|
|
||||||
if [ -z "$BRG" ]; then
|
|
||||||
exit_script
|
|
||||||
else
|
|
||||||
echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
local ip_cidr_regex='^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/([0-9]{1,2})$'
|
|
||||||
local ip_regex='^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$'
|
|
||||||
|
|
||||||
if [[ -n ${NET-} ]]; then
|
|
||||||
if [ "$NET" == "dhcp" ]; then
|
|
||||||
echo -e "${NETWORK}${BOLD}${DGN}IP Address: ${BGN}DHCP${CL}"
|
|
||||||
echo -e "${GATEWAY}${BOLD}${DGN}Gateway IP Address: ${BGN}Default${CL}"
|
|
||||||
GATE=""
|
|
||||||
elif [[ "$NET" =~ $ip_cidr_regex ]]; then
|
|
||||||
echo -e "${NETWORK}${BOLD}${DGN}IP Address: ${BGN}$NET${CL}"
|
|
||||||
if [[ -n "$GATE" ]]; then
|
|
||||||
[[ "$GATE" =~ ",gw=" ]] && GATE="${GATE##,gw=}"
|
|
||||||
if [[ "$GATE" =~ $ip_regex ]]; then
|
|
||||||
echo -e "${GATEWAY}${BOLD}${DGN}Gateway IP Address: ${BGN}$GATE${CL}"
|
|
||||||
GATE=",gw=$GATE"
|
|
||||||
else
|
|
||||||
msg_error "Invalid IP Address format for Gateway. Needs to be 0.0.0.0, was ${GATE}"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
else
|
|
||||||
while true; do
|
|
||||||
GATE1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Enter gateway IP address" 8 58 --title "Gateway IP" 3>&1 1>&2 2>&3)
|
|
||||||
if [ -z "$GATE1" ]; then
|
|
||||||
whiptail --backtitle "Proxmox VE Helper Scripts" --msgbox "Gateway IP address cannot be empty" 8 58
|
|
||||||
elif [[ ! "$GATE1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
|
|
||||||
whiptail --backtitle "Proxmox VE Helper Scripts" --msgbox "Invalid IP address format" 8 58
|
|
||||||
else
|
|
||||||
GATE=",gw=$GATE1"
|
|
||||||
echo -e "${GATEWAY}${BOLD}${DGN}Gateway IP Address: ${BGN}$GATE1${CL}"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
elif [[ "$NET" == *-* ]]; then
|
|
||||||
IFS="-" read -r ip_start ip_end <<<"$NET"
|
|
||||||
|
|
||||||
if [[ ! "$ip_start" =~ $ip_cidr_regex ]] || [[ ! "$ip_end" =~ $ip_cidr_regex ]]; then
|
|
||||||
msg_error "Invalid IP range format, was $NET should be 0.0.0.0/0-0.0.0.0/0"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
ip1="${ip_start%%/*}"
|
|
||||||
ip2="${ip_end%%/*}"
|
|
||||||
cidr="${ip_start##*/}"
|
|
||||||
|
|
||||||
ip_to_int() {
|
|
||||||
local IFS=.
|
|
||||||
read -r i1 i2 i3 i4 <<<"$1"
|
|
||||||
echo $(((i1 << 24) + (i2 << 16) + (i3 << 8) + i4))
|
|
||||||
}
|
|
||||||
|
|
||||||
int_to_ip() {
|
|
||||||
local ip=$1
|
|
||||||
echo "$(((ip >> 24) & 0xFF)).$(((ip >> 16) & 0xFF)).$(((ip >> 8) & 0xFF)).$((ip & 0xFF))"
|
|
||||||
}
|
|
||||||
|
|
||||||
start_int=$(ip_to_int "$ip1")
|
|
||||||
end_int=$(ip_to_int "$ip2")
|
|
||||||
|
|
||||||
for ((ip_int = start_int; ip_int <= end_int; ip_int++)); do
|
|
||||||
ip=$(int_to_ip $ip_int)
|
|
||||||
msg_info "Checking IP: $ip"
|
|
||||||
if ! ping -c 2 -W 1 "$ip" >/dev/null 2>&1; then
|
|
||||||
NET="$ip/$cidr"
|
|
||||||
msg_ok "Using free IP Address: ${BGN}$NET${CL}"
|
|
||||||
sleep 3
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
if [[ "$NET" == *-* ]]; then
|
|
||||||
msg_error "No free IP found in range"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ -n "$GATE" ]; then
|
|
||||||
if [[ "$GATE" =~ $ip_regex ]]; then
|
|
||||||
echo -e "${GATEWAY}${BOLD}${DGN}Gateway IP Address: ${BGN}$GATE${CL}"
|
|
||||||
GATE=",gw=$GATE"
|
|
||||||
else
|
|
||||||
msg_error "Invalid IP Address format for Gateway. Needs to be 0.0.0.0, was ${GATE}"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
while true; do
|
|
||||||
GATE1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Enter gateway IP address" 8 58 --title "Gateway IP" 3>&1 1>&2 2>&3)
|
|
||||||
if [ -z "$GATE1" ]; then
|
|
||||||
whiptail --backtitle "Proxmox VE Helper Scripts" --msgbox "Gateway IP address cannot be empty" 8 58
|
|
||||||
elif [[ ! "$GATE1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
|
|
||||||
whiptail --backtitle "Proxmox VE Helper Scripts" --msgbox "Invalid IP address format" 8 58
|
|
||||||
else
|
|
||||||
GATE=",gw=$GATE1"
|
|
||||||
echo -e "${GATEWAY}${BOLD}${DGN}Gateway IP Address: ${BGN}$GATE1${CL}"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
msg_error "Invalid IP Address format. Needs to be 0.0.0.0/0 or a range like 10.0.0.1/24-10.0.0.10/24, was ${NET}"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
while true; do
|
|
||||||
NET=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a Static IPv4 CIDR Address (/24)" 8 58 dhcp --title "IP ADDRESS" 3>&1 1>&2 2>&3)
|
|
||||||
exit_status=$?
|
|
||||||
if [ $exit_status -eq 0 ]; then
|
|
||||||
if [ "$NET" = "dhcp" ]; then
|
|
||||||
echo -e "${NETWORK}${BOLD}${DGN}IP Address: ${BGN}$NET${CL}"
|
|
||||||
break
|
|
||||||
else
|
|
||||||
if [[ "$NET" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$ ]]; then
|
|
||||||
echo -e "${NETWORK}${BOLD}${DGN}IP Address: ${BGN}$NET${CL}"
|
|
||||||
break
|
|
||||||
else
|
|
||||||
whiptail --backtitle "Proxmox VE Helper Scripts" --msgbox "$NET is an invalid IPv4 CIDR address. Please enter a valid IPv4 CIDR address or 'dhcp'" 8 58
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
exit_script
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
if [ "$NET" != "dhcp" ]; then
|
|
||||||
while true; do
|
|
||||||
GATE1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Enter gateway IP address" 8 58 --title "Gateway IP" 3>&1 1>&2 2>&3)
|
|
||||||
if [ -z "$GATE1" ]; then
|
|
||||||
whiptail --backtitle "Proxmox VE Helper Scripts" --msgbox "Gateway IP address cannot be empty" 8 58
|
|
||||||
elif [[ ! "$GATE1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
|
|
||||||
whiptail --backtitle "Proxmox VE Helper Scripts" --msgbox "Invalid IP address format" 8 58
|
|
||||||
else
|
|
||||||
GATE=",gw=$GATE1"
|
|
||||||
echo -e "${GATEWAY}${BOLD}${DGN}Gateway IP Address: ${BGN}$GATE1${CL}"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
else
|
|
||||||
GATE=""
|
|
||||||
echo -e "${GATEWAY}${BOLD}${DGN}Gateway IP Address: ${BGN}Default${CL}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$var_os" == "alpine" ]; then
|
|
||||||
APT_CACHER=""
|
|
||||||
APT_CACHER_IP=""
|
|
||||||
else
|
|
||||||
if [[ -n "${APT_CACHER_IP-}" ]]; then
|
|
||||||
if [[ ! $APT_CACHER_IP == "none" ]]; then
|
|
||||||
APT_CACHER="yes"
|
|
||||||
echo -e "${NETWORK}${BOLD}${DGN}APT-CACHER IP Address: ${BGN}$APT_CACHER_IP${CL}"
|
|
||||||
else
|
|
||||||
APT_CACHER=""
|
|
||||||
echo -e "${NETWORK}${BOLD}${DGN}APT-Cacher IP Address: ${BGN}No${CL}"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if APT_CACHER_IP=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set APT-Cacher IP (leave blank for none)" 8 58 --title "APT-Cacher IP" 3>&1 1>&2 2>&3); then
|
|
||||||
APT_CACHER="${APT_CACHER_IP:+yes}"
|
|
||||||
echo -e "${NETWORK}${BOLD}${DGN}APT-Cacher IP Address: ${BGN}${APT_CACHER_IP:-Default}${CL}"
|
|
||||||
if [[ -n $APT_CACHER_IP ]]; then
|
|
||||||
APT_CACHER_IP="none"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
exit_script
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "${MTU-}" ]]; then
|
|
||||||
if [[ "$MTU" =~ ^-?[0-9]+$ ]]; then
|
|
||||||
echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU${CL}"
|
|
||||||
MTU=",mtu=$MTU"
|
|
||||||
else
|
|
||||||
msg_error "MTU must be an integer, was ${MTU}"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if MTU1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Interface MTU Size (leave blank for default [The MTU of your selected vmbr, default is 1500])" 8 58 --title "MTU SIZE" 3>&1 1>&2 2>&3); then
|
|
||||||
if [ -z "$MTU1" ]; then
|
|
||||||
MTU1="Default"
|
|
||||||
MTU=""
|
|
||||||
else
|
|
||||||
MTU=",mtu=$MTU1"
|
|
||||||
fi
|
|
||||||
echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}"
|
|
||||||
else
|
|
||||||
exit_script
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$IPV6_METHOD" == "static" ]]; then
|
|
||||||
if [[ -n "$IPV6STATIC" ]]; then
|
|
||||||
IP6=",ip6=${IPV6STATIC}"
|
|
||||||
echo -e "${NETWORK}${BOLD}${DGN}IPv6 Address: ${BGN}${IPV6STATIC}${CL}"
|
|
||||||
else
|
|
||||||
msg_error "IPV6_METHOD is set to static but IPV6STATIC is empty"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
elif [[ "$IPV6_METHOD" == "auto" ]]; then
|
|
||||||
IP6=",ip6=auto"
|
|
||||||
echo -e "${NETWORK}${BOLD}${DGN}IPv6 Address: ${BGN}auto${CL}"
|
|
||||||
else
|
|
||||||
IP6=""
|
|
||||||
echo -e "${NETWORK}${BOLD}${DGN}IPv6 Address: ${BGN}none${CL}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "${SD-}" ]]; then
|
|
||||||
if [[ "$SD" == "none" ]]; then
|
|
||||||
SD=""
|
|
||||||
echo -e "${SEARCH}${BOLD}${DGN}DNS Search Domain: ${BGN}Host${CL}"
|
|
||||||
else
|
|
||||||
# Strip prefix if present for config file storage
|
|
||||||
local SD_VALUE="$SD"
|
|
||||||
[[ "$SD" =~ ^-searchdomain= ]] && SD_VALUE="${SD#-searchdomain=}"
|
|
||||||
echo -e "${SEARCH}${BOLD}${DGN}DNS Search Domain: ${BGN}$SD_VALUE${CL}"
|
|
||||||
SD="-searchdomain=$SD_VALUE"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if SD=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a DNS Search Domain (leave blank for HOST)" 8 58 --title "DNS Search Domain" 3>&1 1>&2 2>&3); then
|
|
||||||
if [ -z "$SD" ]; then
|
|
||||||
SX=Host
|
|
||||||
SD=""
|
|
||||||
else
|
|
||||||
SX=$SD
|
|
||||||
SD="-searchdomain=$SD"
|
|
||||||
fi
|
|
||||||
echo -e "${SEARCH}${BOLD}${DGN}DNS Search Domain: ${BGN}$SX${CL}"
|
|
||||||
else
|
|
||||||
exit_script
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "${NS-}" ]]; then
|
|
||||||
if [[ $NS == "none" ]]; then
|
|
||||||
NS=""
|
|
||||||
echo -e "${NETWORK}${BOLD}${DGN}DNS Server IP Address: ${BGN}Host${CL}"
|
|
||||||
else
|
|
||||||
# Strip prefix if present for config file storage
|
|
||||||
local NS_VALUE="$NS"
|
|
||||||
[[ "$NS" =~ ^-nameserver= ]] && NS_VALUE="${NS#-nameserver=}"
|
|
||||||
if [[ "$NS_VALUE" =~ $ip_regex ]]; then
|
|
||||||
echo -e "${NETWORK}${BOLD}${DGN}DNS Server IP Address: ${BGN}$NS_VALUE${CL}"
|
|
||||||
NS="-nameserver=$NS_VALUE"
|
|
||||||
else
|
|
||||||
msg_error "Invalid IP Address format for DNS Server. Needs to be 0.0.0.0, was ${NS_VALUE}"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if NX=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a DNS Server IP (leave blank for HOST)" 8 58 --title "DNS SERVER IP" 3>&1 1>&2 2>&3); then
|
|
||||||
if [ -z "$NX" ]; then
|
|
||||||
NX=Host
|
|
||||||
NS=""
|
|
||||||
else
|
|
||||||
NS="-nameserver=$NX"
|
|
||||||
fi
|
|
||||||
echo -e "${NETWORK}${BOLD}${DGN}DNS Server IP Address: ${BGN}$NX${CL}"
|
|
||||||
else
|
|
||||||
exit_script
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "${MAC-}" ]]; then
|
|
||||||
if [[ "$MAC" == "none" ]]; then
|
|
||||||
MAC=""
|
|
||||||
echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}Host${CL}"
|
|
||||||
else
|
|
||||||
# Strip prefix if present for config file storage
|
|
||||||
local MAC_VALUE="$MAC"
|
|
||||||
[[ "$MAC" =~ ^,hwaddr= ]] && MAC_VALUE="${MAC#,hwaddr=}"
|
|
||||||
if [[ "$MAC_VALUE" =~ ^([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}$ ]]; then
|
|
||||||
echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC_VALUE${CL}"
|
|
||||||
MAC=",hwaddr=$MAC_VALUE"
|
|
||||||
else
|
|
||||||
msg_error "MAC Address must be in the format xx:xx:xx:xx:xx:xx, was ${MAC_VALUE}"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if MAC1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a MAC Address(leave blank for generated MAC)" 8 58 --title "MAC ADDRESS" 3>&1 1>&2 2>&3); then
|
|
||||||
if [ -z "$MAC1" ]; then
|
|
||||||
MAC1="Default"
|
|
||||||
MAC=""
|
|
||||||
else
|
|
||||||
MAC=",hwaddr=$MAC1"
|
|
||||||
echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC1${CL}"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
exit_script
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "${VLAN-}" ]]; then
|
|
||||||
if [[ "$VLAN" == "none" ]]; then
|
|
||||||
VLAN=""
|
|
||||||
echo -e "${VLANTAG}${BOLD}${DGN}Vlan: ${BGN}Host${CL}"
|
|
||||||
else
|
|
||||||
# Strip prefix if present for config file storage
|
|
||||||
local VLAN_VALUE="$VLAN"
|
|
||||||
[[ "$VLAN" =~ ^,tag= ]] && VLAN_VALUE="${VLAN#,tag=}"
|
|
||||||
if [[ "$VLAN_VALUE" =~ ^-?[0-9]+$ ]]; then
|
|
||||||
echo -e "${VLANTAG}${BOLD}${DGN}Vlan: ${BGN}$VLAN_VALUE${CL}"
|
|
||||||
VLAN=",tag=$VLAN_VALUE"
|
|
||||||
else
|
|
||||||
msg_error "VLAN must be an integer, was ${VLAN_VALUE}"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if VLAN1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a Vlan(leave blank for no VLAN)" 8 58 --title "VLAN" 3>&1 1>&2 2>&3); then
|
|
||||||
if [ -z "$VLAN1" ]; then
|
|
||||||
VLAN1="Default"
|
|
||||||
VLAN=""
|
|
||||||
else
|
|
||||||
VLAN=",tag=$VLAN1"
|
|
||||||
fi
|
|
||||||
echo -e "${VLANTAG}${BOLD}${DGN}Vlan: ${BGN}$VLAN1${CL}"
|
|
||||||
else
|
|
||||||
exit_script
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "${TAGS-}" ]]; then
|
|
||||||
if [[ "$TAGS" == *"DEFAULT"* ]]; then
|
|
||||||
TAGS="${TAGS//DEFAULT/}"
|
|
||||||
TAGS="${TAGS//;/}"
|
|
||||||
TAGS="$TAGS;${var_tags:-}"
|
|
||||||
echo -e "${NETWORK}${BOLD}${DGN}Tags: ${BGN}$TAGS${CL}"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
TAGS="community-scripts;"
|
|
||||||
if ADV_TAGS=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Custom Tags?[If you remove all, there will be no tags!]" 8 58 "${TAGS}" --title "Advanced Tags" 3>&1 1>&2 2>&3); then
|
|
||||||
if [ -n "${ADV_TAGS}" ]; then
|
|
||||||
ADV_TAGS=$(echo "$ADV_TAGS" | tr -d '[:space:]')
|
|
||||||
TAGS="${ADV_TAGS}"
|
|
||||||
else
|
|
||||||
TAGS=";"
|
|
||||||
fi
|
|
||||||
echo -e "${NETWORK}${BOLD}${DGN}Tags: ${BGN}$TAGS${CL}"
|
|
||||||
else
|
|
||||||
exit_script
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "${SSH-}" ]]; then
|
|
||||||
if [[ "$SSH" == "yes" ]]; then
|
|
||||||
echo -e "${ROOTSSH}${BOLD}${DGN}Root SSH Access: ${BGN}$SSH${CL}"
|
|
||||||
if [[ ! -z "$SSH_AUTHORIZED_KEY" ]]; then
|
|
||||||
echo -e "${ROOTSSH}${BOLD}${DGN}SSH Authorized Key: ${BGN}********************${CL}"
|
|
||||||
else
|
|
||||||
echo -e "${ROOTSSH}${BOLD}${DGN}SSH Authorized Key: ${BGN}None${CL}"
|
|
||||||
fi
|
|
||||||
elif [[ "$SSH" == "no" ]]; then
|
|
||||||
echo -e "${ROOTSSH}${BOLD}${DGN}Root SSH Access: ${BGN}$SSH${CL}"
|
|
||||||
else
|
|
||||||
msg_error "SSH needs to be 'yes' or 'no', was ${SSH}"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
SSH_AUTHORIZED_KEY="$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "SSH Authorized key for root (leave empty for none)" 8 58 --title "SSH Key" 3>&1 1>&2 2>&3)"
|
|
||||||
if [[ -z "${SSH_AUTHORIZED_KEY}" ]]; then
|
|
||||||
SSH_AUTHORIZED_KEY=""
|
|
||||||
fi
|
|
||||||
if [[ "$PW" == -password* || -n "$SSH_AUTHORIZED_KEY" ]]; then
|
|
||||||
if (whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "SSH ACCESS" --yesno "Enable Root SSH Access?" 10 58); then
|
|
||||||
SSH="yes"
|
|
||||||
else
|
|
||||||
SSH="no"
|
|
||||||
fi
|
|
||||||
echo -e "${ROOTSSH}${BOLD}${DGN}Root SSH Access: ${BGN}$SSH${CL}"
|
|
||||||
else
|
|
||||||
SSH="no"
|
|
||||||
echo -e "${ROOTSSH}${BOLD}${DGN}Root SSH Access: ${BGN}$SSH${CL}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "$ENABLE_FUSE" ]]; then
|
|
||||||
if [[ "$ENABLE_FUSE" == "yes" ]]; then
|
|
||||||
echo -e "${FUSE}${BOLD}${DGN}Enable FUSE: ${BGN}Yes${CL}"
|
|
||||||
elif [[ "$ENABLE_FUSE" == "no" ]]; then
|
|
||||||
echo -e "${FUSE}${BOLD}${DGN}Enable FUSE: ${BGN}No${CL}"
|
|
||||||
else
|
|
||||||
msg_error "Enable FUSE needs to be 'yes' or 'no', was ${ENABLE_FUSE}"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if (whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "FUSE" --yesno "Enable FUSE?" 10 58); then
|
|
||||||
ENABLE_FUSE="yes"
|
|
||||||
else
|
|
||||||
ENABLE_FUSE="no"
|
|
||||||
fi
|
|
||||||
echo -e "${FUSE}${BOLD}${DGN}Enable FUSE: ${BGN}$ENABLE_FUSE${CL}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "$ENABLE_TUN" ]]; then
|
|
||||||
if [[ "$ENABLE_TUN" == "yes" ]]; then
|
|
||||||
echo -e "${FUSE}${BOLD}${DGN}Enable TUN: ${BGN}Yes${CL}"
|
|
||||||
elif [[ "$ENABLE_TUN" == "no" ]]; then
|
|
||||||
echo -e "${FUSE}${BOLD}${DGN}Enable TUN: ${BGN}No${CL}"
|
|
||||||
else
|
|
||||||
msg_error "Enable TUN needs to be 'yes' or 'no', was ${ENABLE_TUN}"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if (whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "TUN" --yesno "Enable TUN?" 10 58); then
|
|
||||||
ENABLE_TUN="yes"
|
|
||||||
else
|
|
||||||
ENABLE_TUN="no"
|
|
||||||
fi
|
|
||||||
echo -e "${FUSE}${BOLD}${DGN}Enable TUN: ${BGN}$ENABLE_TUN${CL}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "${VERBOSE-}" ]]; then
|
|
||||||
if [[ "$VERBOSE" == "yes" ]]; then
|
|
||||||
echo -e "${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}$VERBOSE${CL}"
|
|
||||||
elif [[ "$VERBOSE" == "no" ]]; then
|
|
||||||
echo -e "${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}No${CL}"
|
|
||||||
else
|
|
||||||
msg_error "Verbose Mode needs to be 'yes' or 'no', was ${VERBOSE}"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if (whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "VERBOSE MODE" --yesno "Enable Verbose Mode?" 10 58); then
|
|
||||||
VERBOSE="yes"
|
|
||||||
else
|
|
||||||
VERBOSE="no"
|
|
||||||
fi
|
|
||||||
echo -e "${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}$VERBOSE${CL}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "ADVANCED SETTINGS WITH CONFIG FILE COMPLETE" --yesno "Ready to create ${APP} LXC?" 10 58); then
|
|
||||||
echo -e "${CREATING}${BOLD}${RD}Creating a ${APP} LXC using the above settings${CL}"
|
|
||||||
else
|
|
||||||
clear
|
|
||||||
header_info
|
|
||||||
echo -e "${INFO}${HOLD} ${GN}Using Config File on node $PVEHOST_NAME${CL}"
|
|
||||||
config_file
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,35 @@
|
|||||||
# Copyright (c) 2021-2025 community-scripts ORG
|
#!/usr/bin/env bash
|
||||||
|
# Copyright (c) 2021-2026 community-scripts ORG
|
||||||
# License: MIT | https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/LICENSE
|
# License: MIT | https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/LICENSE
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ==============================================================================
|
||||||
# Loads core utility groups once (colors, formatting, icons, defaults).
|
# CORE FUNCTIONS - LXC CONTAINER UTILITIES
|
||||||
# ------------------------------------------------------------------------------
|
# ==============================================================================
|
||||||
|
#
|
||||||
|
# This file provides core utility functions for LXC container management
|
||||||
|
# including colors, formatting, validation checks, message output, and
|
||||||
|
# execution helpers used throughout the Community-Scripts ecosystem.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# source <(curl -fsSL https://git.community-scripts.org/.../core.func)
|
||||||
|
# load_functions
|
||||||
|
#
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
[[ -n "${_CORE_FUNC_LOADED:-}" ]] && return
|
[[ -n "${_CORE_FUNC_LOADED:-}" ]] && return
|
||||||
_CORE_FUNC_LOADED=1
|
_CORE_FUNC_LOADED=1
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# SECTION 1: INITIALIZATION & SETUP
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# load_functions()
|
||||||
|
#
|
||||||
|
# - Initializes all core utility groups (colors, formatting, icons, defaults)
|
||||||
|
# - Ensures functions are loaded only once via __FUNCTIONS_LOADED flag
|
||||||
|
# - Must be called at start of any script using these utilities
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
load_functions() {
|
load_functions() {
|
||||||
[[ -n "${__FUNCTIONS_LOADED:-}" ]] && return
|
[[ -n "${__FUNCTIONS_LOADED:-}" ]] && return
|
||||||
__FUNCTIONS_LOADED=1
|
__FUNCTIONS_LOADED=1
|
||||||
@@ -16,58 +38,14 @@ load_functions() {
|
|||||||
icons
|
icons
|
||||||
default_vars
|
default_vars
|
||||||
set_std_mode
|
set_std_mode
|
||||||
# add more
|
|
||||||
}
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# Error & Signal Handling – robust, universal, subshell-safe
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
_tool_error_hint() {
|
|
||||||
local cmd="$1"
|
|
||||||
local code="$2"
|
|
||||||
case "$cmd" in
|
|
||||||
curl)
|
|
||||||
case "$code" in
|
|
||||||
6) echo "Curl: Could not resolve host (DNS problem)" ;;
|
|
||||||
7) echo "Curl: Failed to connect to host (connection refused)" ;;
|
|
||||||
22) echo "Curl: HTTP error (404/403 etc)" ;;
|
|
||||||
28) echo "Curl: Operation timeout" ;;
|
|
||||||
*) echo "Curl: Unknown error ($code)" ;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
wget)
|
|
||||||
echo "Wget failed – URL unreachable or permission denied"
|
|
||||||
;;
|
|
||||||
systemctl)
|
|
||||||
echo "Systemd unit failure – check service name and permissions"
|
|
||||||
;;
|
|
||||||
jq)
|
|
||||||
echo "jq parse error – malformed JSON or missing key"
|
|
||||||
;;
|
|
||||||
mariadb | mysql)
|
|
||||||
echo "MySQL/MariaDB command failed – check credentials or DB"
|
|
||||||
;;
|
|
||||||
unzip)
|
|
||||||
echo "unzip failed – corrupt file or missing permission"
|
|
||||||
;;
|
|
||||||
tar)
|
|
||||||
echo "tar failed – invalid format or missing binary"
|
|
||||||
;;
|
|
||||||
node | npm | pnpm | yarn)
|
|
||||||
echo "Node tool failed – check version compatibility or package.json"
|
|
||||||
;;
|
|
||||||
*) echo "" ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
catch_errors() {
|
|
||||||
set -Eeuo pipefail
|
|
||||||
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Sets ANSI color codes used for styled terminal output.
|
# color()
|
||||||
|
#
|
||||||
|
# - Sets ANSI color codes for styled terminal output
|
||||||
|
# - Variables: YW (yellow), YWB (yellow bright), BL (blue), RD (red)
|
||||||
|
# GN (green), DGN (dark green), BGN (background green), CL (clear)
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
color() {
|
color() {
|
||||||
YW=$(echo "\033[33m")
|
YW=$(echo "\033[33m")
|
||||||
@@ -80,7 +58,14 @@ color() {
|
|||||||
CL=$(echo "\033[m")
|
CL=$(echo "\033[m")
|
||||||
}
|
}
|
||||||
|
|
||||||
# Special for spinner and colorized output via printf
|
# ------------------------------------------------------------------------------
|
||||||
|
# color_spinner()
|
||||||
|
#
|
||||||
|
# - Sets ANSI color codes specifically for spinner animation
|
||||||
|
# - Variables: CS_YW (spinner yellow), CS_YWB (spinner yellow bright),
|
||||||
|
# CS_CL (spinner clear)
|
||||||
|
# - Used by spinner() function to avoid color conflicts
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
color_spinner() {
|
color_spinner() {
|
||||||
CS_YW=$'\033[33m'
|
CS_YW=$'\033[33m'
|
||||||
CS_YWB=$'\033[93m'
|
CS_YWB=$'\033[93m'
|
||||||
@@ -88,7 +73,12 @@ color_spinner() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Defines formatting helpers like tab, bold, and line reset sequences.
|
# formatting()
|
||||||
|
#
|
||||||
|
# - Defines formatting helpers for terminal output
|
||||||
|
# - BFR: Backspace and clear line sequence
|
||||||
|
# - BOLD: Bold text escape code
|
||||||
|
# - TAB/TAB3: Indentation spacing
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
formatting() {
|
formatting() {
|
||||||
BFR="\\r\\033[K"
|
BFR="\\r\\033[K"
|
||||||
@@ -99,7 +89,11 @@ formatting() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Sets symbolic icons used throughout user feedback and prompts.
|
# icons()
|
||||||
|
#
|
||||||
|
# - Sets symbolic emoji icons used throughout user feedback
|
||||||
|
# - Provides consistent visual indicators for success, error, info, etc.
|
||||||
|
# - Icons: CM (checkmark), CROSS (error), INFO (info), HOURGLASS (wait), etc.
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
icons() {
|
icons() {
|
||||||
CM="${TAB}✔️${TAB}"
|
CM="${TAB}✔️${TAB}"
|
||||||
@@ -129,22 +123,31 @@ icons() {
|
|||||||
CREATING="${TAB}🚀${TAB}${CL}"
|
CREATING="${TAB}🚀${TAB}${CL}"
|
||||||
ADVANCED="${TAB}🧩${TAB}${CL}"
|
ADVANCED="${TAB}🧩${TAB}${CL}"
|
||||||
FUSE="${TAB}🗂️${TAB}${CL}"
|
FUSE="${TAB}🗂️${TAB}${CL}"
|
||||||
|
GPU="${TAB}🎮${TAB}${CL}"
|
||||||
HOURGLASS="${TAB}⏳${TAB}"
|
HOURGLASS="${TAB}⏳${TAB}"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Sets default retry and wait variables used for system actions.
|
# default_vars()
|
||||||
|
#
|
||||||
|
# - Sets default retry and wait variables used for system actions
|
||||||
|
# - RETRY_NUM: Maximum number of retry attempts (default: 10)
|
||||||
|
# - RETRY_EVERY: Seconds to wait between retries (default: 3)
|
||||||
|
# - i: Counter variable initialized to RETRY_NUM
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
default_vars() {
|
default_vars() {
|
||||||
RETRY_NUM=10
|
RETRY_NUM=10
|
||||||
RETRY_EVERY=3
|
RETRY_EVERY=3
|
||||||
i=$RETRY_NUM
|
i=$RETRY_NUM
|
||||||
#[[ "${VAR_OS:-}" == "unknown" ]]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Sets default verbose mode for script and os execution.
|
# set_std_mode()
|
||||||
|
#
|
||||||
|
# - Sets default verbose mode for script and OS execution
|
||||||
|
# - If VERBOSE=yes: STD="" (show all output)
|
||||||
|
# - If VERBOSE=no: STD="silent" (suppress output via silent() wrapper)
|
||||||
|
# - If DEV_MODE_TRACE=true: Enables bash tracing (set -x)
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
set_std_mode() {
|
set_std_mode() {
|
||||||
if [ "${VERBOSE:-no}" = "yes" ]; then
|
if [ "${VERBOSE:-no}" = "yes" ]; then
|
||||||
@@ -152,138 +155,338 @@ set_std_mode() {
|
|||||||
else
|
else
|
||||||
STD="silent"
|
STD="silent"
|
||||||
fi
|
fi
|
||||||
}
|
|
||||||
|
|
||||||
# Silent execution function
|
# Enable bash tracing if trace mode active
|
||||||
silent() {
|
if [[ "${DEV_MODE_TRACE:-false}" == "true" ]]; then
|
||||||
"$@" >/dev/null 2>&1
|
set -x
|
||||||
}
|
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
|
||||||
|
|
||||||
# Function to download & save header files
|
|
||||||
get_header() {
|
|
||||||
local app_name=$(echo "${APP,,}" | tr -d ' ')
|
|
||||||
local app_type=${APP_TYPE:-ct}
|
|
||||||
local header_url="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/${app_type}/headers/${app_name}"
|
|
||||||
local local_header_path="/usr/local/community-scripts/headers/${app_type}/${app_name}"
|
|
||||||
|
|
||||||
mkdir -p "$(dirname "$local_header_path")"
|
|
||||||
|
|
||||||
if [ ! -s "$local_header_path" ]; then
|
|
||||||
if ! curl -fsSL "$header_url" -o "$local_header_path"; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
cat "$local_header_path" 2>/dev/null || true
|
|
||||||
}
|
|
||||||
|
|
||||||
header_info() {
|
|
||||||
local app_name=$(echo "${APP,,}" | tr -d ' ')
|
|
||||||
local header_content
|
|
||||||
|
|
||||||
header_content=$(get_header "$app_name") || header_content=""
|
|
||||||
|
|
||||||
clear
|
|
||||||
local term_width
|
|
||||||
term_width=$(tput cols 2>/dev/null || echo 120)
|
|
||||||
|
|
||||||
if [ -n "$header_content" ]; then
|
|
||||||
echo "$header_content"
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
ensure_tput() {
|
# ------------------------------------------------------------------------------
|
||||||
if ! command -v tput >/dev/null 2>&1; then
|
# parse_dev_mode()
|
||||||
if grep -qi 'alpine' /etc/os-release; then
|
#
|
||||||
apk add --no-cache ncurses >/dev/null 2>&1
|
# - Parses comma-separated dev_mode variable (e.g., "motd,keep,trace")
|
||||||
elif command -v apt-get >/dev/null 2>&1; then
|
# - Sets global flags for each mode:
|
||||||
apt-get update -qq >/dev/null
|
# * DEV_MODE_MOTD: Setup SSH/MOTD before installation
|
||||||
apt-get install -y -qq ncurses-bin >/dev/null 2>&1
|
# * DEV_MODE_KEEP: Never delete container on failure
|
||||||
fi
|
# * DEV_MODE_TRACE: Enable bash set -x tracing
|
||||||
fi
|
# * DEV_MODE_PAUSE: Pause after each msg_info step
|
||||||
}
|
# * DEV_MODE_BREAKPOINT: Open shell on error instead of cleanup
|
||||||
|
# * DEV_MODE_LOGS: Persist all logs to /var/log/community-scripts/
|
||||||
|
# * DEV_MODE_DRYRUN: Show commands without executing
|
||||||
|
# - Call this early in script execution
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
parse_dev_mode() {
|
||||||
|
local mode
|
||||||
|
# Initialize all flags to false
|
||||||
|
export DEV_MODE_MOTD=false
|
||||||
|
export DEV_MODE_KEEP=false
|
||||||
|
export DEV_MODE_TRACE=false
|
||||||
|
export DEV_MODE_PAUSE=false
|
||||||
|
export DEV_MODE_BREAKPOINT=false
|
||||||
|
export DEV_MODE_LOGS=false
|
||||||
|
export DEV_MODE_DRYRUN=false
|
||||||
|
|
||||||
is_alpine() {
|
# Parse comma-separated modes
|
||||||
local os_id="${var_os:-${PCT_OSTYPE:-}}"
|
if [[ -n "${dev_mode:-}" ]]; then
|
||||||
|
IFS=',' read -ra MODES <<<"$dev_mode"
|
||||||
if [[ -z "$os_id" && -f /etc/os-release ]]; then
|
for mode in "${MODES[@]}"; do
|
||||||
os_id="$(
|
mode="$(echo "$mode" | xargs)" # Trim whitespace
|
||||||
. /etc/os-release 2>/dev/null
|
case "$mode" in
|
||||||
echo "${ID:-}"
|
motd) export DEV_MODE_MOTD=true ;;
|
||||||
)"
|
keep) export DEV_MODE_KEEP=true ;;
|
||||||
fi
|
trace) export DEV_MODE_TRACE=true ;;
|
||||||
|
pause) export DEV_MODE_PAUSE=true ;;
|
||||||
[[ "$os_id" == "alpine" ]]
|
breakpoint) export DEV_MODE_BREAKPOINT=true ;;
|
||||||
}
|
logs) export DEV_MODE_LOGS=true ;;
|
||||||
|
dryrun) export DEV_MODE_DRYRUN=true ;;
|
||||||
is_verbose_mode() {
|
*)
|
||||||
local verbose="${VERBOSE:-${var_verbose:-no}}"
|
if declare -f msg_warn >/dev/null 2>&1; then
|
||||||
local tty_status
|
msg_warn "Unknown dev_mode: '$mode' (ignored)"
|
||||||
if [[ -t 2 ]]; then
|
|
||||||
tty_status="interactive"
|
|
||||||
else
|
else
|
||||||
tty_status="not-a-tty"
|
echo "[WARN] Unknown dev_mode: '$mode' (ignored)" >&2
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Show active dev modes
|
||||||
|
local active_modes=()
|
||||||
|
[[ $DEV_MODE_MOTD == true ]] && active_modes+=("motd")
|
||||||
|
[[ $DEV_MODE_KEEP == true ]] && active_modes+=("keep")
|
||||||
|
[[ $DEV_MODE_TRACE == true ]] && active_modes+=("trace")
|
||||||
|
[[ $DEV_MODE_PAUSE == true ]] && active_modes+=("pause")
|
||||||
|
[[ $DEV_MODE_BREAKPOINT == true ]] && active_modes+=("breakpoint")
|
||||||
|
[[ $DEV_MODE_LOGS == true ]] && active_modes+=("logs")
|
||||||
|
[[ $DEV_MODE_DRYRUN == true ]] && active_modes+=("dryrun")
|
||||||
|
|
||||||
|
if [[ ${#active_modes[@]} -gt 0 ]]; then
|
||||||
|
if declare -f msg_custom >/dev/null 2>&1; then
|
||||||
|
msg_custom "🔧" "${YWB}" "Dev modes active: ${active_modes[*]}"
|
||||||
|
else
|
||||||
|
echo "[DEV] Active modes: ${active_modes[*]}" >&2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# SECTION 2: VALIDATION CHECKS
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# shell_check()
|
||||||
|
#
|
||||||
|
# - Verifies that the script is running under Bash shell
|
||||||
|
# - Exits with error message if different shell is detected
|
||||||
|
# - Required because scripts use Bash-specific features
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
shell_check() {
|
||||||
|
if [[ "$(ps -p $$ -o comm=)" != "bash" ]]; then
|
||||||
|
clear
|
||||||
|
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
|
||||||
fi
|
fi
|
||||||
[[ "$verbose" != "no" || ! -t 2 ]]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Handles specific curl error codes and displays descriptive messages.
|
# root_check()
|
||||||
|
#
|
||||||
|
# - Verifies script is running with root privileges
|
||||||
|
# - Detects if executed via sudo (which can cause issues)
|
||||||
|
# - Exits with error if not running as root directly
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
__curl_err_handler() {
|
root_check() {
|
||||||
local exit_code="$1"
|
if [[ "$(id -u)" -ne 0 || $(ps -o comm= -p $PPID) == "sudo" ]]; then
|
||||||
local target="$2"
|
clear
|
||||||
local curl_msg="$3"
|
msg_error "Please run this script as root."
|
||||||
|
echo -e "\nExiting..."
|
||||||
|
sleep 2
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
case $exit_code in
|
# ------------------------------------------------------------------------------
|
||||||
1) msg_error "Unsupported protocol: $target" ;;
|
# pve_check()
|
||||||
2) msg_error "Curl init failed: $target" ;;
|
#
|
||||||
3) msg_error "Malformed URL: $target" ;;
|
# - Validates Proxmox VE version compatibility
|
||||||
5) msg_error "Proxy resolution failed: $target" ;;
|
# - Supported: PVE 8.0-8.9 and PVE 9.0-9.1
|
||||||
6) msg_error "Host resolution failed: $target" ;;
|
# - Exits with error message if unsupported version detected
|
||||||
7) msg_error "Connection failed: $target" ;;
|
# ------------------------------------------------------------------------------
|
||||||
9) msg_error "Access denied: $target" ;;
|
pve_check() {
|
||||||
18) msg_error "Partial file transfer: $target" ;;
|
local PVE_VER
|
||||||
22) msg_error "HTTP error (e.g. 400/404): $target" ;;
|
PVE_VER="$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')"
|
||||||
23) msg_error "Write error on local system: $target" ;;
|
|
||||||
26) msg_error "Read error from local file: $target" ;;
|
|
||||||
28) msg_error "Timeout: $target" ;;
|
|
||||||
35) msg_error "SSL connect error: $target" ;;
|
|
||||||
47) msg_error "Too many redirects: $target" ;;
|
|
||||||
51) msg_error "SSL cert verify failed: $target" ;;
|
|
||||||
52) msg_error "Empty server response: $target" ;;
|
|
||||||
55) msg_error "Send error: $target" ;;
|
|
||||||
56) msg_error "Receive error: $target" ;;
|
|
||||||
60) msg_error "SSL CA not trusted: $target" ;;
|
|
||||||
67) msg_error "Login denied by server: $target" ;;
|
|
||||||
78) msg_error "Remote file not found (404): $target" ;;
|
|
||||||
*) msg_error "Curl failed with code $exit_code: $target" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
[[ -n "$curl_msg" ]] && printf "%s\n" "$curl_msg" >&2
|
# Check for Proxmox VE 8.x: allow 8.0–8.9
|
||||||
|
if [[ "$PVE_VER" =~ ^8\.([0-9]+) ]]; then
|
||||||
|
local MINOR="${BASH_REMATCH[1]}"
|
||||||
|
if ((MINOR < 0 || MINOR > 9)); then
|
||||||
|
msg_error "This version of Proxmox VE is not supported."
|
||||||
|
msg_error "Supported: Proxmox VE version 8.0 – 8.9"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for Proxmox VE 9.x: allow 9.0–9.1
|
||||||
|
if [[ "$PVE_VER" =~ ^9\.([0-9]+) ]]; then
|
||||||
|
local MINOR="${BASH_REMATCH[1]}"
|
||||||
|
if ((MINOR < 0 || MINOR > 1)); then
|
||||||
|
msg_error "This version of Proxmox VE is not yet supported."
|
||||||
|
msg_error "Supported: Proxmox VE version 9.0 – 9.1"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# All other unsupported versions
|
||||||
|
msg_error "This version of Proxmox VE is not supported."
|
||||||
|
msg_error "Supported versions: Proxmox VE 8.0 – 8.9 or 9.0 – 9.1"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
fatal() {
|
# ------------------------------------------------------------------------------
|
||||||
msg_error "$1"
|
# arch_check()
|
||||||
kill -INT $$
|
#
|
||||||
|
# - Validates system architecture is amd64/x86_64
|
||||||
|
# - Exits with error message for unsupported architectures (e.g., ARM/PiMox)
|
||||||
|
# - Provides link to ARM64-compatible scripts
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
arch_check() {
|
||||||
|
if [ "$(dpkg --print-architecture)" != "amd64" ]; then
|
||||||
|
echo -e "\n ${INFO}${YWB}This script will not work with PiMox! \n"
|
||||||
|
echo -e "\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \n"
|
||||||
|
echo -e "Exiting..."
|
||||||
|
sleep 2
|
||||||
|
exit
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# ssh_check()
|
||||||
|
#
|
||||||
|
# - Detects if script is running over SSH connection
|
||||||
|
# - Warns user for external SSH connections (recommends Proxmox shell)
|
||||||
|
# - Skips warning for local/same-subnet connections
|
||||||
|
# - Does not abort execution, only warns
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
ssh_check() {
|
||||||
|
if [ -n "$SSH_CLIENT" ]; then
|
||||||
|
local client_ip=$(awk '{print $1}' <<<"$SSH_CLIENT")
|
||||||
|
local host_ip=$(hostname -I | awk '{print $1}')
|
||||||
|
|
||||||
|
# Check if connection is local (Proxmox WebUI or same machine)
|
||||||
|
# - localhost (127.0.0.1, ::1)
|
||||||
|
# - same IP as host
|
||||||
|
# - local network range (10.x, 172.16-31.x, 192.168.x)
|
||||||
|
if [[ "$client_ip" == "127.0.0.1" || "$client_ip" == "::1" || "$client_ip" == "$host_ip" ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if client is in same local network (optional, safer approach)
|
||||||
|
local host_subnet=$(echo "$host_ip" | cut -d. -f1-3)
|
||||||
|
local client_subnet=$(echo "$client_ip" | cut -d. -f1-3)
|
||||||
|
if [[ "$host_subnet" == "$client_subnet" ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Only warn for truly external connections
|
||||||
|
msg_warn "Running via external SSH (client: $client_ip)."
|
||||||
|
msg_warn "For better stability, consider using the Proxmox Shell (Console) instead."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# SECTION 3: EXECUTION HELPERS
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# get_active_logfile()
|
||||||
|
#
|
||||||
|
# - Returns the appropriate log file based on execution context
|
||||||
|
# - BUILD_LOG: Host operations (container creation)
|
||||||
|
# - INSTALL_LOG: Container operations (application installation)
|
||||||
|
# - Fallback to BUILD_LOG if neither is set
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
get_active_logfile() {
|
||||||
|
if [[ -n "${INSTALL_LOG:-}" ]]; then
|
||||||
|
echo "$INSTALL_LOG"
|
||||||
|
elif [[ -n "${BUILD_LOG:-}" ]]; then
|
||||||
|
echo "$BUILD_LOG"
|
||||||
|
else
|
||||||
|
# Fallback for legacy scripts
|
||||||
|
echo "/tmp/build-$(date +%Y%m%d_%H%M%S).log"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Legacy compatibility: SILENT_LOGFILE points to active log
|
||||||
|
SILENT_LOGFILE="$(get_active_logfile)"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# silent()
|
||||||
|
#
|
||||||
|
# - Executes command with output redirected to active log file
|
||||||
|
# - On error: displays last 10 lines of log and exits with original exit code
|
||||||
|
# - Temporarily disables error trap to capture exit code correctly
|
||||||
|
# - Sources explain_exit_code() for detailed error messages
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
silent() {
|
||||||
|
local cmd="$*"
|
||||||
|
local caller_line="${BASH_LINENO[0]:-unknown}"
|
||||||
|
local logfile="$(get_active_logfile)"
|
||||||
|
|
||||||
|
# Dryrun mode: Show command without executing
|
||||||
|
if [[ "${DEV_MODE_DRYRUN:-false}" == "true" ]]; then
|
||||||
|
if declare -f msg_custom >/dev/null 2>&1; then
|
||||||
|
msg_custom "🔍" "${BL}" "[DRYRUN] $cmd"
|
||||||
|
else
|
||||||
|
echo "[DRYRUN] $cmd" >&2
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
set +Eeuo pipefail
|
||||||
|
trap - ERR
|
||||||
|
|
||||||
|
"$@" >>"$logfile" 2>&1
|
||||||
|
local rc=$?
|
||||||
|
|
||||||
|
set -Eeuo pipefail
|
||||||
|
trap 'error_handler' ERR
|
||||||
|
|
||||||
|
if [[ $rc -ne 0 ]]; then
|
||||||
|
# Source explain_exit_code if needed
|
||||||
|
if ! declare -f explain_exit_code >/dev/null 2>&1; then
|
||||||
|
source "$(dirname "${BASH_SOURCE[0]}")/error-handler.func"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local explanation
|
||||||
|
explanation="$(explain_exit_code "$rc")"
|
||||||
|
|
||||||
|
printf "\e[?25h"
|
||||||
|
msg_error "in line ${caller_line}: exit code ${rc} (${explanation})"
|
||||||
|
msg_custom "→" "${YWB}" "${cmd}"
|
||||||
|
|
||||||
|
if [[ -s "$logfile" ]]; then
|
||||||
|
local log_lines=$(wc -l <"$logfile")
|
||||||
|
echo "--- Last 10 lines of silent log ---"
|
||||||
|
tail -n 10 "$logfile"
|
||||||
|
echo "-----------------------------------"
|
||||||
|
|
||||||
|
# Show how to view full log if there are more lines
|
||||||
|
if [[ $log_lines -gt 10 ]]; then
|
||||||
|
msg_custom "📋" "${YW}" "View full log (${log_lines} lines): ${logfile}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit "$rc"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# spinner()
|
||||||
|
#
|
||||||
|
# - Displays animated spinner with rotating characters (⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏)
|
||||||
|
# - Shows SPINNER_MSG alongside animation
|
||||||
|
# - Runs in infinite loop until killed by stop_spinner()
|
||||||
|
# - Uses color_spinner() colors for output
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
spinner() {
|
spinner() {
|
||||||
local chars=(⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏)
|
local chars=(⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏)
|
||||||
|
local msg="${SPINNER_MSG:-Processing...}"
|
||||||
local i=0
|
local i=0
|
||||||
while true; do
|
while true; do
|
||||||
local index=$((i++ % ${#chars[@]}))
|
local index=$((i++ % ${#chars[@]}))
|
||||||
printf "\r\033[2K%s %b" "${CS_YWB}${chars[$index]}${CS_CL}" "${CS_YWB}${SPINNER_MSG:-}${CS_CL}"
|
printf "\r\033[2K%s %b" "${CS_YWB}${chars[$index]}${CS_CL}" "${CS_YWB}${msg}${CS_CL}"
|
||||||
sleep 0.1
|
sleep 0.1
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# clear_line()
|
||||||
|
#
|
||||||
|
# - Clears current terminal line using tput or ANSI escape codes
|
||||||
|
# - Moves cursor to beginning of line (carriage return)
|
||||||
|
# - Erases from cursor to end of line
|
||||||
|
# - Fallback to ANSI codes if tput not available
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
clear_line() {
|
clear_line() {
|
||||||
tput cr 2>/dev/null || echo -en "\r"
|
tput cr 2>/dev/null || echo -en "\r"
|
||||||
tput el 2>/dev/null || echo -en "\033[K"
|
tput el 2>/dev/null || echo -en "\033[K"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# stop_spinner()
|
||||||
|
#
|
||||||
|
# - Stops running spinner process by PID
|
||||||
|
# - Reads PID from SPINNER_PID variable or /tmp/.spinner.pid file
|
||||||
|
# - Attempts graceful kill, then forced kill if needed
|
||||||
|
# - Cleans up temp file and resets terminal state
|
||||||
|
# - Unsets SPINNER_PID and SPINNER_MSG variables
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
stop_spinner() {
|
stop_spinner() {
|
||||||
local pid="${SPINNER_PID:-}"
|
local pid="${SPINNER_PID:-}"
|
||||||
[[ -z "$pid" && -f /tmp/.spinner.pid ]] && pid=$(</tmp/.spinner.pid)
|
[[ -z "$pid" && -f /tmp/.spinner.pid ]] && pid=$(</tmp/.spinner.pid)
|
||||||
@@ -301,6 +504,19 @@ stop_spinner() {
|
|||||||
stty sane 2>/dev/null || true
|
stty sane 2>/dev/null || true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# SECTION 4: MESSAGE OUTPUT
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# msg_info()
|
||||||
|
#
|
||||||
|
# - Displays informational message with spinner animation
|
||||||
|
# - Shows each unique message only once (tracked via MSG_INFO_SHOWN)
|
||||||
|
# - In verbose/Alpine mode: shows hourglass icon instead of spinner
|
||||||
|
# - Stops any existing spinner before starting new one
|
||||||
|
# - Backgrounds spinner process and stores PID for later cleanup
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
msg_info() {
|
msg_info() {
|
||||||
local msg="$1"
|
local msg="$1"
|
||||||
[[ -z "$msg" ]] && return
|
[[ -z "$msg" ]] && return
|
||||||
@@ -317,6 +533,12 @@ msg_info() {
|
|||||||
if is_verbose_mode || is_alpine; then
|
if is_verbose_mode || is_alpine; then
|
||||||
local HOURGLASS="${TAB}⏳${TAB}"
|
local HOURGLASS="${TAB}⏳${TAB}"
|
||||||
printf "\r\e[2K%s %b" "$HOURGLASS" "${YW}${msg}${CL}" >&2
|
printf "\r\e[2K%s %b" "$HOURGLASS" "${YW}${msg}${CL}" >&2
|
||||||
|
|
||||||
|
# Pause mode: Wait for Enter after each step
|
||||||
|
if [[ "${DEV_MODE_PAUSE:-false}" == "true" ]]; then
|
||||||
|
echo -en "\n${YWB}[PAUSE]${CL} Press Enter to continue..." >&2
|
||||||
|
read -r
|
||||||
|
fi
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -325,29 +547,68 @@ msg_info() {
|
|||||||
SPINNER_PID=$!
|
SPINNER_PID=$!
|
||||||
echo "$SPINNER_PID" >/tmp/.spinner.pid
|
echo "$SPINNER_PID" >/tmp/.spinner.pid
|
||||||
disown "$SPINNER_PID" 2>/dev/null || true
|
disown "$SPINNER_PID" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Pause mode: Stop spinner and wait
|
||||||
|
if [[ "${DEV_MODE_PAUSE:-false}" == "true" ]]; then
|
||||||
|
stop_spinner
|
||||||
|
echo -en "\n${YWB}[PAUSE]${CL} Press Enter to continue..." >&2
|
||||||
|
read -r
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# msg_ok()
|
||||||
|
#
|
||||||
|
# - Displays success message with checkmark icon
|
||||||
|
# - Stops spinner and clears line before output
|
||||||
|
# - Removes message from MSG_INFO_SHOWN to allow re-display
|
||||||
|
# - Uses green color for success indication
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
msg_ok() {
|
msg_ok() {
|
||||||
local msg="$1"
|
local msg="$1"
|
||||||
[[ -z "$msg" ]] && return
|
[[ -z "$msg" ]] && return
|
||||||
stop_spinner
|
stop_spinner
|
||||||
clear_line
|
clear_line
|
||||||
printf "%s %b\n" "$CM" "${GN}${msg}${CL}" >&2
|
echo -e "$CM ${GN}${msg}${CL}"
|
||||||
unset MSG_INFO_SHOWN["$msg"]
|
unset MSG_INFO_SHOWN["$msg"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# msg_error()
|
||||||
|
#
|
||||||
|
# - Displays error message with cross/X icon
|
||||||
|
# - Stops spinner before output
|
||||||
|
# - Uses red color for error indication
|
||||||
|
# - Outputs to stderr
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
msg_error() {
|
msg_error() {
|
||||||
stop_spinner
|
stop_spinner
|
||||||
local msg="$1"
|
local msg="$1"
|
||||||
echo -e "${BFR:-} ${CROSS:-✖️} ${RD}${msg}${CL}"
|
echo -e "${BFR:-}${CROSS:-✖️} ${RD}${msg}${CL}" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# msg_warn()
|
||||||
|
#
|
||||||
|
# - Displays warning message with info/lightbulb icon
|
||||||
|
# - Stops spinner before output
|
||||||
|
# - Uses bright yellow color for warning indication
|
||||||
|
# - Outputs to stderr
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
msg_warn() {
|
msg_warn() {
|
||||||
stop_spinner
|
stop_spinner
|
||||||
local msg="$1"
|
local msg="$1"
|
||||||
echo -e "${BFR:-} ${INFO:-ℹ️} ${YWB}${msg}${CL}"
|
echo -e "${BFR:-}${INFO:-ℹ️} ${YWB}${msg}${CL}" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# msg_custom()
|
||||||
|
#
|
||||||
|
# - Displays custom message with user-defined symbol and color
|
||||||
|
# - Arguments: symbol, color code, message text
|
||||||
|
# - Stops spinner before output
|
||||||
|
# - Useful for specialized status messages
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
msg_custom() {
|
msg_custom() {
|
||||||
local symbol="${1:-"[*]"}"
|
local symbol="${1:-"[*]"}"
|
||||||
local color="${2:-"\e[36m"}"
|
local color="${2:-"\e[36m"}"
|
||||||
@@ -357,17 +618,228 @@ msg_custom() {
|
|||||||
echo -e "${BFR:-} ${symbol} ${color}${msg}${CL:-\e[0m}"
|
echo -e "${BFR:-} ${symbol} ${color}${msg}${CL:-\e[0m}"
|
||||||
}
|
}
|
||||||
|
|
||||||
run_container_safe() {
|
# ------------------------------------------------------------------------------
|
||||||
local ct="$1"
|
# msg_debug()
|
||||||
shift
|
#
|
||||||
local cmd="$*"
|
# - Displays debug message with timestamp when var_full_verbose=1
|
||||||
|
# - Automatically enables var_verbose if not already set
|
||||||
lxc-attach -n "$ct" -- bash -euo pipefail -c "
|
# - Shows date/time prefix for log correlation
|
||||||
trap 'echo Aborted in container; exit 130' SIGINT SIGTERM
|
# - Uses bright yellow color for debug output
|
||||||
$cmd
|
# ------------------------------------------------------------------------------
|
||||||
" || __handle_general_error "lxc-attach to CT $ct"
|
msg_debug() {
|
||||||
|
if [[ "${var_full_verbose:-0}" == "1" ]]; then
|
||||||
|
[[ "${var_verbose:-0}" != "1" ]] && var_verbose=1
|
||||||
|
echo -e "${YWB}[$(date '+%F %T')] [DEBUG]${CL} $*"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# msg_dev()
|
||||||
|
#
|
||||||
|
# - Display development mode messages with 🔧 icon
|
||||||
|
# - Only shown when dev_mode is active
|
||||||
|
# - Useful for debugging and development-specific output
|
||||||
|
# - Format: [DEV] message with distinct formatting
|
||||||
|
# - Usage: msg_dev "Container ready for debugging"
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
msg_dev() {
|
||||||
|
if [[ -n "${dev_mode:-}" ]]; then
|
||||||
|
echo -e "${SEARCH}${BOLD}${DGN}🔧 [DEV]${CL} $*"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
#
|
||||||
|
# - Displays error message and immediately terminates script
|
||||||
|
# - Sends SIGINT to current process to trigger error handler
|
||||||
|
# - Use for unrecoverable errors that require immediate exit
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
fatal() {
|
||||||
|
msg_error "$1"
|
||||||
|
kill -INT $$
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# SECTION 5: UTILITY FUNCTIONS
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# exit_script()
|
||||||
|
#
|
||||||
|
# - Called when user cancels an action
|
||||||
|
# - Clears screen and displays exit message
|
||||||
|
# - Exits with default exit code
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
exit_script() {
|
||||||
|
clear
|
||||||
|
echo -e "\n${CROSS}${RD}User exited script${CL}\n"
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# get_header()
|
||||||
|
#
|
||||||
|
# - Downloads and caches application header ASCII art
|
||||||
|
# - Falls back to local cache if already downloaded
|
||||||
|
# - Determines app type (ct/vm) from APP_TYPE variable
|
||||||
|
# - Returns header content or empty string on failure
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
get_header() {
|
||||||
|
local app_name=$(echo "${APP,,}" | tr -d ' ')
|
||||||
|
local app_type=${APP_TYPE:-ct} # Default to 'ct' if not set
|
||||||
|
local header_url="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/${app_type}/headers/${app_name}"
|
||||||
|
local local_header_path="/usr/local/community-scripts/headers/${app_type}/${app_name}"
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$local_header_path")"
|
||||||
|
|
||||||
|
if [ ! -s "$local_header_path" ]; then
|
||||||
|
if ! curl -fsSL "$header_url" -o "$local_header_path"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat "$local_header_path" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# header_info()
|
||||||
|
#
|
||||||
|
# - Displays application header ASCII art at top of screen
|
||||||
|
# - Clears screen before displaying header
|
||||||
|
# - Detects terminal width for formatting
|
||||||
|
# - Returns silently if header not available
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
header_info() {
|
||||||
|
local app_name=$(echo "${APP,,}" | tr -d ' ')
|
||||||
|
local header_content
|
||||||
|
|
||||||
|
header_content=$(get_header "$app_name") || header_content=""
|
||||||
|
|
||||||
|
clear
|
||||||
|
local term_width
|
||||||
|
term_width=$(tput cols 2>/dev/null || echo 120)
|
||||||
|
|
||||||
|
if [ -n "$header_content" ]; then
|
||||||
|
echo "$header_content"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# ensure_tput()
|
||||||
|
#
|
||||||
|
# - Ensures tput command is available for terminal control
|
||||||
|
# - Installs ncurses-bin on Debian/Ubuntu or ncurses on Alpine
|
||||||
|
# - Required for clear_line() and terminal width detection
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
ensure_tput() {
|
||||||
|
if ! command -v tput >/dev/null 2>&1; then
|
||||||
|
if grep -qi 'alpine' /etc/os-release; then
|
||||||
|
apk add --no-cache ncurses >/dev/null 2>&1
|
||||||
|
elif command -v apt-get >/dev/null 2>&1; then
|
||||||
|
apt-get update -qq >/dev/null
|
||||||
|
apt-get install -y -qq ncurses-bin >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# is_alpine()
|
||||||
|
#
|
||||||
|
# - Detects if running on Alpine Linux
|
||||||
|
# - Checks var_os, PCT_OSTYPE, or /etc/os-release
|
||||||
|
# - Returns 0 if Alpine, 1 otherwise
|
||||||
|
# - Used to adjust behavior for Alpine-specific commands
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
is_alpine() {
|
||||||
|
local os_id="${var_os:-${PCT_OSTYPE:-}}"
|
||||||
|
|
||||||
|
if [[ -z "$os_id" && -f /etc/os-release ]]; then
|
||||||
|
os_id="$(
|
||||||
|
. /etc/os-release 2>/dev/null
|
||||||
|
echo "${ID:-}"
|
||||||
|
)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ "$os_id" == "alpine" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# is_verbose_mode()
|
||||||
|
#
|
||||||
|
# - Determines if script should run in verbose mode
|
||||||
|
# - Checks VERBOSE and var_verbose variables
|
||||||
|
# - Also returns true if not running in TTY (pipe/redirect scenario)
|
||||||
|
# - Used by msg_info() to decide between spinner and static output
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
is_verbose_mode() {
|
||||||
|
local verbose="${VERBOSE:-${var_verbose:-no}}"
|
||||||
|
local tty_status
|
||||||
|
if [[ -t 2 ]]; then
|
||||||
|
tty_status="interactive"
|
||||||
|
else
|
||||||
|
tty_status="not-a-tty"
|
||||||
|
fi
|
||||||
|
[[ "$verbose" != "no" || ! -t 2 ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# SECTION 6: CLEANUP & MAINTENANCE
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# cleanup_lxc()
|
||||||
|
#
|
||||||
|
# - Comprehensive cleanup of package managers, caches, and logs
|
||||||
|
# - Supports Alpine (apk), Debian/Ubuntu (apt), and language package managers
|
||||||
|
# - Cleans: Python (pip/uv), Node.js (npm/yarn/pnpm), Go, Rust, Ruby, PHP
|
||||||
|
# - Truncates log files and vacuums systemd journal
|
||||||
|
# - Run at end of container creation to minimize disk usage
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
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
|
||||||
|
|
||||||
|
# Node.js npm - directly remove cache directory
|
||||||
|
# npm cache clean/verify can fail with ENOTEMPTY errors, so we skip them
|
||||||
|
if command -v npm &>/dev/null; then
|
||||||
|
rm -rf /root/.npm/_cacache /root/.npm/_logs 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
# Node.js yarn
|
||||||
|
if command -v yarn &>/dev/null; then yarn cache clean &>/dev/null || true; fi
|
||||||
|
# Node.js pnpm
|
||||||
|
if command -v pnpm &>/dev/null; then pnpm store prune &>/dev/null || 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 COMPOSER_ALLOW_SUPERUSER=1 $STD composer clear-cache || true; fi
|
||||||
|
|
||||||
|
msg_ok "Cleaned"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# check_or_create_swap()
|
||||||
|
#
|
||||||
|
# - Checks if swap is active on system
|
||||||
|
# - Offers to create swap file if none exists
|
||||||
|
# - Prompts user for swap size in MB
|
||||||
|
# - Creates /swapfile with specified size
|
||||||
|
# - Activates swap immediately
|
||||||
|
# - Returns 0 if swap active or successfully created, 1 if declined/failed
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
check_or_create_swap() {
|
check_or_create_swap() {
|
||||||
msg_info "Checking for active swap"
|
msg_info "Checking for active swap"
|
||||||
|
|
||||||
@@ -406,7 +878,8 @@ check_or_create_swap() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
trap 'stop_spinner' EXIT INT TERM
|
# ==============================================================================
|
||||||
|
# SIGNAL TRAPS
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
# Initialize functions when core.func is sourced
|
trap 'stop_spinner' EXIT INT TERM
|
||||||
load_functions
|
|
||||||
|
|||||||
@@ -1,380 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# Copyright (c) 2021-2025 tteck
|
|
||||||
# Author: tteck (tteckster)
|
|
||||||
# Co-Author: MickLesk
|
|
||||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
|
||||||
|
|
||||||
# This sets verbose mode if the global variable is set to "yes"
|
|
||||||
# if [ "$VERBOSE" == "yes" ]; then set -x; fi
|
|
||||||
|
|
||||||
source "$(dirname "$0")/core.func"
|
|
||||||
|
|
||||||
|
|
||||||
# This sets error handling options and defines the error_handler function to handle errors
|
|
||||||
set -Eeuo pipefail
|
|
||||||
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
|
|
||||||
trap on_exit EXIT
|
|
||||||
trap on_interrupt INT
|
|
||||||
trap on_terminate TERM
|
|
||||||
|
|
||||||
function on_exit() {
|
|
||||||
local exit_code="$?"
|
|
||||||
[[ -n "${lockfile:-}" && -e "$lockfile" ]] && rm -f "$lockfile"
|
|
||||||
exit "$exit_code"
|
|
||||||
}
|
|
||||||
|
|
||||||
function error_handler() {
|
|
||||||
local exit_code="$?"
|
|
||||||
local line_number="$1"
|
|
||||||
local command="$2"
|
|
||||||
printf "\e[?25h"
|
|
||||||
echo -e "\n${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}\n"
|
|
||||||
exit "$exit_code"
|
|
||||||
}
|
|
||||||
|
|
||||||
function on_interrupt() {
|
|
||||||
echo -e "\n${RD}Interrupted by user (SIGINT)${CL}"
|
|
||||||
exit 130
|
|
||||||
}
|
|
||||||
|
|
||||||
function on_terminate() {
|
|
||||||
echo -e "\n${RD}Terminated by signal (SIGTERM)${CL}"
|
|
||||||
exit 143
|
|
||||||
}
|
|
||||||
|
|
||||||
function exit_script() {
|
|
||||||
clear
|
|
||||||
printf "\e[?25h"
|
|
||||||
echo -e "\n${CROSS}${RD}User exited script${CL}\n"
|
|
||||||
kill 0
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
function check_storage_support() {
|
|
||||||
local CONTENT="$1"
|
|
||||||
local -a VALID_STORAGES=()
|
|
||||||
while IFS= read -r line; do
|
|
||||||
local STORAGE_NAME
|
|
||||||
STORAGE_NAME=$(awk '{print $1}' <<<"$line")
|
|
||||||
[[ -z "$STORAGE_NAME" ]] && continue
|
|
||||||
VALID_STORAGES+=("$STORAGE_NAME")
|
|
||||||
done < <(pvesm status -content "$CONTENT" 2>/dev/null | awk 'NR>1')
|
|
||||||
|
|
||||||
[[ ${#VALID_STORAGES[@]} -gt 0 ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
# This function selects a storage pool for a given content type (e.g., rootdir, vztmpl).
|
|
||||||
function select_storage() {
|
|
||||||
local CLASS=$1 CONTENT CONTENT_LABEL
|
|
||||||
|
|
||||||
case $CLASS in
|
|
||||||
container)
|
|
||||||
CONTENT='rootdir'
|
|
||||||
CONTENT_LABEL='Container'
|
|
||||||
;;
|
|
||||||
template)
|
|
||||||
CONTENT='vztmpl'
|
|
||||||
CONTENT_LABEL='Container template'
|
|
||||||
;;
|
|
||||||
iso)
|
|
||||||
CONTENT='iso'
|
|
||||||
CONTENT_LABEL='ISO image'
|
|
||||||
;;
|
|
||||||
images)
|
|
||||||
CONTENT='images'
|
|
||||||
CONTENT_LABEL='VM Disk image'
|
|
||||||
;;
|
|
||||||
backup)
|
|
||||||
CONTENT='backup'
|
|
||||||
CONTENT_LABEL='Backup'
|
|
||||||
;;
|
|
||||||
snippets)
|
|
||||||
CONTENT='snippets'
|
|
||||||
CONTENT_LABEL='Snippets'
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
msg_error "Invalid storage class '$CLASS'"
|
|
||||||
return 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Check for preset STORAGE variable
|
|
||||||
if [ "$CONTENT" = "rootdir" ] && [ -n "${STORAGE:-}" ]; then
|
|
||||||
if pvesm status -content "$CONTENT" | awk 'NR>1 {print $1}' | grep -qx "$STORAGE"; then
|
|
||||||
STORAGE_RESULT="$STORAGE"
|
|
||||||
msg_info "Using preset storage: $STORAGE_RESULT for $CONTENT_LABEL"
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
msg_error "Preset storage '$STORAGE' is not valid for content type '$CONTENT'."
|
|
||||||
return 2
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
local -A STORAGE_MAP
|
|
||||||
local -a MENU
|
|
||||||
local COL_WIDTH=0
|
|
||||||
|
|
||||||
while read -r TAG TYPE _ TOTAL USED FREE _; do
|
|
||||||
[[ -n "$TAG" && -n "$TYPE" ]] || continue
|
|
||||||
local STORAGE_NAME="$TAG"
|
|
||||||
local DISPLAY="${STORAGE_NAME} (${TYPE})"
|
|
||||||
local USED_FMT=$(numfmt --to=iec --from-unit=K --format %.1f <<<"$USED")
|
|
||||||
local FREE_FMT=$(numfmt --to=iec --from-unit=K --format %.1f <<<"$FREE")
|
|
||||||
local INFO="Free: ${FREE_FMT}B Used: ${USED_FMT}B"
|
|
||||||
STORAGE_MAP["$DISPLAY"]="$STORAGE_NAME"
|
|
||||||
MENU+=("$DISPLAY" "$INFO" "OFF")
|
|
||||||
((${#DISPLAY} > COL_WIDTH)) && COL_WIDTH=${#DISPLAY}
|
|
||||||
done < <(pvesm status -content "$CONTENT" | awk 'NR>1')
|
|
||||||
|
|
||||||
if [ ${#MENU[@]} -eq 0 ]; then
|
|
||||||
msg_error "No storage found for content type '$CONTENT'."
|
|
||||||
return 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ $((${#MENU[@]} / 3)) -eq 1 ]; then
|
|
||||||
STORAGE_RESULT="${STORAGE_MAP[${MENU[0]}]}"
|
|
||||||
STORAGE_INFO="${MENU[1]}"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
local WIDTH=$((COL_WIDTH + 42))
|
|
||||||
while true; do
|
|
||||||
local DISPLAY_SELECTED
|
|
||||||
DISPLAY_SELECTED=$(whiptail --backtitle "Proxmox VE Helper Scripts" \
|
|
||||||
--title "Storage Pools" \
|
|
||||||
--radiolist "Which storage pool for ${CONTENT_LABEL,,}?\n(Spacebar to select)" \
|
|
||||||
16 "$WIDTH" 6 "${MENU[@]}" 3>&1 1>&2 2>&3)
|
|
||||||
|
|
||||||
# Cancel or ESC
|
|
||||||
[[ $? -ne 0 ]] && exit_script
|
|
||||||
|
|
||||||
# Strip trailing whitespace or newline (important for storages like "storage (dir)")
|
|
||||||
DISPLAY_SELECTED=$(sed 's/[[:space:]]*$//' <<<"$DISPLAY_SELECTED")
|
|
||||||
|
|
||||||
if [[ -z "$DISPLAY_SELECTED" || -z "${STORAGE_MAP[$DISPLAY_SELECTED]+_}" ]]; then
|
|
||||||
whiptail --msgbox "No valid storage selected. Please try again." 8 58
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
STORAGE_RESULT="${STORAGE_MAP[$DISPLAY_SELECTED]}"
|
|
||||||
for ((i = 0; i < ${#MENU[@]}; i += 3)); do
|
|
||||||
if [[ "${MENU[$i]}" == "$DISPLAY_SELECTED" ]]; then
|
|
||||||
STORAGE_INFO="${MENU[$i + 1]}"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
return 0
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# Test if required variables are set
|
|
||||||
[[ "${CTID:-}" ]] || {
|
|
||||||
msg_error "You need to set 'CTID' variable."
|
|
||||||
exit 203
|
|
||||||
}
|
|
||||||
[[ "${PCT_OSTYPE:-}" ]] || {
|
|
||||||
msg_error "You need to set 'PCT_OSTYPE' variable."
|
|
||||||
exit 204
|
|
||||||
}
|
|
||||||
|
|
||||||
# Test if ID is valid
|
|
||||||
[ "$CTID" -ge "100" ] || {
|
|
||||||
msg_error "ID cannot be less than 100."
|
|
||||||
exit 205
|
|
||||||
}
|
|
||||||
|
|
||||||
# Test if ID is in use
|
|
||||||
if qm status "$CTID" &>/dev/null || pct status "$CTID" &>/dev/null; then
|
|
||||||
echo -e "ID '$CTID' is already in use."
|
|
||||||
unset CTID
|
|
||||||
msg_error "Cannot use ID that is already in use."
|
|
||||||
exit 206
|
|
||||||
fi
|
|
||||||
|
|
||||||
# This checks for the presence of valid Container Storage and Template Storage locations
|
|
||||||
msg_info "Validating storage"
|
|
||||||
if ! check_storage_support "rootdir"; then
|
|
||||||
msg_error "No valid storage found for 'rootdir' [Container]"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if ! check_storage_support "vztmpl"; then
|
|
||||||
msg_error "No valid storage found for 'vztmpl' [Template]"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
#msg_info "Checking template storage"
|
|
||||||
while true; do
|
|
||||||
if select_storage template; then
|
|
||||||
TEMPLATE_STORAGE="$STORAGE_RESULT"
|
|
||||||
TEMPLATE_STORAGE_INFO="$STORAGE_INFO"
|
|
||||||
msg_ok "Storage ${BL}$TEMPLATE_STORAGE${CL} ($TEMPLATE_STORAGE_INFO) [Template]"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
while true; do
|
|
||||||
if select_storage container; then
|
|
||||||
CONTAINER_STORAGE="$STORAGE_RESULT"
|
|
||||||
CONTAINER_STORAGE_INFO="$STORAGE_INFO"
|
|
||||||
msg_ok "Storage ${BL}$CONTAINER_STORAGE${CL} ($CONTAINER_STORAGE_INFO) [Container]"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Check free space on selected container storage
|
|
||||||
STORAGE_FREE=$(pvesm status | awk -v s="$CONTAINER_STORAGE" '$1 == s { print $6 }')
|
|
||||||
REQUIRED_KB=$((${PCT_DISK_SIZE:-8} * 1024 * 1024))
|
|
||||||
if [ "$STORAGE_FREE" -lt "$REQUIRED_KB" ]; then
|
|
||||||
msg_error "Not enough space on '$CONTAINER_STORAGE'. Needed: ${PCT_DISK_SIZE:-8}G."
|
|
||||||
exit 214
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check Cluster Quorum if in Cluster
|
|
||||||
if [ -f /etc/pve/corosync.conf ]; then
|
|
||||||
msg_info "Checking cluster quorum"
|
|
||||||
if ! pvecm status | awk -F':' '/^Quorate/ { exit ($2 ~ /Yes/) ? 0 : 1 }'; then
|
|
||||||
|
|
||||||
msg_error "Cluster is not quorate. Start all nodes or configure quorum device (QDevice)."
|
|
||||||
exit 210
|
|
||||||
fi
|
|
||||||
msg_ok "Cluster is quorate"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Update LXC template list
|
|
||||||
TEMPLATE_SEARCH="${PCT_OSTYPE}-${PCT_OSVERSION:-}"
|
|
||||||
case "$PCT_OSTYPE" in
|
|
||||||
debian | ubuntu)
|
|
||||||
TEMPLATE_PATTERN="-standard_"
|
|
||||||
;;
|
|
||||||
alpine | fedora | rocky | centos)
|
|
||||||
TEMPLATE_PATTERN="-default_"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
TEMPLATE_PATTERN=""
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# 1. Check local templates first
|
|
||||||
msg_info "Searching for template '$TEMPLATE_SEARCH'"
|
|
||||||
mapfile -t TEMPLATES < <(
|
|
||||||
pveam list "$TEMPLATE_STORAGE" |
|
|
||||||
awk -v s="$TEMPLATE_SEARCH" -v p="$TEMPLATE_PATTERN" '$1 ~ s && $1 ~ p {print $1}' |
|
|
||||||
sed 's/.*\///' | sort -t - -k 2 -V
|
|
||||||
)
|
|
||||||
|
|
||||||
if [ ${#TEMPLATES[@]} -gt 0 ]; then
|
|
||||||
TEMPLATE_SOURCE="local"
|
|
||||||
else
|
|
||||||
msg_info "No local template found, checking online repository"
|
|
||||||
pveam update >/dev/null 2>&1
|
|
||||||
mapfile -t TEMPLATES < <(
|
|
||||||
pveam update >/dev/null 2>&1 &&
|
|
||||||
pveam available -section system |
|
|
||||||
sed -n "s/.*\($TEMPLATE_SEARCH.*$TEMPLATE_PATTERN.*\)/\1/p" |
|
|
||||||
sort -t - -k 2 -V
|
|
||||||
)
|
|
||||||
TEMPLATE_SOURCE="online"
|
|
||||||
fi
|
|
||||||
|
|
||||||
TEMPLATE="${TEMPLATES[-1]}"
|
|
||||||
TEMPLATE_PATH="$(pvesm path $TEMPLATE_STORAGE:vztmpl/$TEMPLATE 2>/dev/null ||
|
|
||||||
echo "/var/lib/vz/template/cache/$TEMPLATE")"
|
|
||||||
msg_ok "Template ${BL}$TEMPLATE${CL} [$TEMPLATE_SOURCE]"
|
|
||||||
|
|
||||||
# 4. Validate template (exists & not corrupted)
|
|
||||||
TEMPLATE_VALID=1
|
|
||||||
|
|
||||||
if [ ! -s "$TEMPLATE_PATH" ]; then
|
|
||||||
TEMPLATE_VALID=0
|
|
||||||
elif ! tar --use-compress-program=zstdcat -tf "$TEMPLATE_PATH" >/dev/null 2>&1; then
|
|
||||||
TEMPLATE_VALID=0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$TEMPLATE_VALID" -eq 0 ]; then
|
|
||||||
msg_warn "Template $TEMPLATE is missing or corrupted. Re-downloading."
|
|
||||||
[[ -f "$TEMPLATE_PATH" ]] && rm -f "$TEMPLATE_PATH"
|
|
||||||
for attempt in {1..3}; do
|
|
||||||
msg_info "Attempt $attempt: Downloading LXC template..."
|
|
||||||
if pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >/dev/null 2>&1; then
|
|
||||||
msg_ok "Template download successful."
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
if [ $attempt -eq 3 ]; then
|
|
||||||
msg_error "Failed after 3 attempts. Please check network access or manually run:\n pveam download $TEMPLATE_STORAGE $TEMPLATE"
|
|
||||||
exit 208
|
|
||||||
fi
|
|
||||||
sleep $((attempt * 5))
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
msg_info "Creating LXC Container"
|
|
||||||
# Check and fix subuid/subgid
|
|
||||||
grep -q "root:100000:65536" /etc/subuid || echo "root:100000:65536" >>/etc/subuid
|
|
||||||
grep -q "root:100000:65536" /etc/subgid || echo "root:100000:65536" >>/etc/subgid
|
|
||||||
|
|
||||||
# Combine all options
|
|
||||||
PCT_OPTIONS=(${PCT_OPTIONS[@]:-${DEFAULT_PCT_OPTIONS[@]}})
|
|
||||||
[[ " ${PCT_OPTIONS[@]} " =~ " -rootfs " ]] || PCT_OPTIONS+=(-rootfs "$CONTAINER_STORAGE:${PCT_DISK_SIZE:-8}")
|
|
||||||
|
|
||||||
# Secure creation of the LXC container with lock and template check
|
|
||||||
lockfile="/tmp/template.${TEMPLATE}.lock"
|
|
||||||
exec 9>"$lockfile" || {
|
|
||||||
msg_error "Failed to create lock file '$lockfile'."
|
|
||||||
exit 200
|
|
||||||
}
|
|
||||||
flock -w 60 9 || {
|
|
||||||
msg_error "Timeout while waiting for template lock"
|
|
||||||
exit 211
|
|
||||||
}
|
|
||||||
|
|
||||||
if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" &>/dev/null; then
|
|
||||||
msg_error "Container creation failed. Checking if template is corrupted or incomplete."
|
|
||||||
|
|
||||||
if [[ ! -s "$TEMPLATE_PATH" || "$(stat -c%s "$TEMPLATE_PATH")" -lt 1000000 ]]; then
|
|
||||||
msg_error "Template file too small or missing – re-downloading."
|
|
||||||
rm -f "$TEMPLATE_PATH"
|
|
||||||
elif ! zstdcat "$TEMPLATE_PATH" | tar -tf - &>/dev/null; then
|
|
||||||
msg_error "Template appears to be corrupted – re-downloading."
|
|
||||||
rm -f "$TEMPLATE_PATH"
|
|
||||||
else
|
|
||||||
msg_error "Template is valid, but container creation still failed."
|
|
||||||
exit 209
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Retry download
|
|
||||||
for attempt in {1..3}; do
|
|
||||||
msg_info "Attempt $attempt: Re-downloading template..."
|
|
||||||
if timeout 120 pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >/dev/null; then
|
|
||||||
msg_ok "Template re-download successful."
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
if [ "$attempt" -eq 3 ]; then
|
|
||||||
msg_error "Three failed attempts. Aborting."
|
|
||||||
exit 208
|
|
||||||
fi
|
|
||||||
sleep $((attempt * 5))
|
|
||||||
done
|
|
||||||
|
|
||||||
sleep 1 # I/O-Sync-Delay
|
|
||||||
msg_ok "Re-downloaded LXC Template"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! pct list | awk '{print $1}' | grep -qx "$CTID"; then
|
|
||||||
msg_error "Container ID $CTID not listed in 'pct list' – unexpected failure."
|
|
||||||
exit 215
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! grep -q '^rootfs:' "/etc/pve/lxc/$CTID.conf"; then
|
|
||||||
msg_error "RootFS entry missing in container config – storage not correctly assigned."
|
|
||||||
exit 216
|
|
||||||
fi
|
|
||||||
|
|
||||||
if grep -q '^hostname:' "/etc/pve/lxc/$CTID.conf"; then
|
|
||||||
CT_HOSTNAME=$(grep '^hostname:' "/etc/pve/lxc/$CTID.conf" | awk '{print $2}')
|
|
||||||
if [[ ! "$CT_HOSTNAME" =~ ^[a-z0-9-]+$ ]]; then
|
|
||||||
msg_warn "Hostname '$CT_HOSTNAME' contains invalid characters – may cause issues with networking or DNS."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
msg_ok "LXC Container ${BL}$CTID${CL} ${GN}was successfully created."
|
|
||||||
322
scripts/core/error-handler.func
Normal file
322
scripts/core/error-handler.func
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# ERROR HANDLER - ERROR & SIGNAL MANAGEMENT
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright (c) 2021-2026 community-scripts ORG
|
||||||
|
# Author: MickLesk (CanbiZ)
|
||||||
|
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# Provides comprehensive error handling and signal management for all scripts.
|
||||||
|
# Includes:
|
||||||
|
# - Exit code explanations (shell, package managers, databases, custom codes)
|
||||||
|
# - Error handler with detailed logging
|
||||||
|
# - Signal handlers (EXIT, INT, TERM)
|
||||||
|
# - Initialization function for trap setup
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# source <(curl -fsSL .../error_handler.func)
|
||||||
|
# catch_errors
|
||||||
|
#
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# SECTION 1: EXIT CODE EXPLANATIONS
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# explain_exit_code()
|
||||||
|
#
|
||||||
|
# - Maps numeric exit codes to human-readable error descriptions
|
||||||
|
# - Supports:
|
||||||
|
# * Generic/Shell errors (1, 2, 126, 127, 128, 130, 137, 139, 143)
|
||||||
|
# * Package manager errors (APT, DPKG: 100, 101, 255)
|
||||||
|
# * Node.js/npm errors (243-249, 254)
|
||||||
|
# * Python/pip/uv errors (210-212)
|
||||||
|
# * PostgreSQL errors (231-234)
|
||||||
|
# * MySQL/MariaDB errors (241-244)
|
||||||
|
# * MongoDB errors (251-254)
|
||||||
|
# * Proxmox custom codes (200-231)
|
||||||
|
# - Returns description string for given exit code
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
explain_exit_code() {
|
||||||
|
local code="$1"
|
||||||
|
case "$code" in
|
||||||
|
# --- Generic / Shell ---
|
||||||
|
1) echo "General error / Operation not permitted" ;;
|
||||||
|
2) echo "Misuse of shell builtins (e.g. syntax error)" ;;
|
||||||
|
126) echo "Command invoked cannot execute (permission problem?)" ;;
|
||||||
|
127) echo "Command not found" ;;
|
||||||
|
128) echo "Invalid argument to exit" ;;
|
||||||
|
130) echo "Terminated by Ctrl+C (SIGINT)" ;;
|
||||||
|
137) echo "Killed (SIGKILL / Out of memory?)" ;;
|
||||||
|
139) echo "Segmentation fault (core dumped)" ;;
|
||||||
|
143) echo "Terminated (SIGTERM)" ;;
|
||||||
|
|
||||||
|
# --- Package manager / APT / DPKG ---
|
||||||
|
100) echo "APT: Package manager error (broken packages / dependency problems)" ;;
|
||||||
|
101) echo "APT: Configuration error (bad sources.list, malformed config)" ;;
|
||||||
|
255) echo "DPKG: Fatal internal error" ;;
|
||||||
|
|
||||||
|
# --- Node.js / npm / pnpm / yarn ---
|
||||||
|
243) echo "Node.js: Out of memory (JavaScript heap out of memory)" ;;
|
||||||
|
245) echo "Node.js: Invalid command-line option" ;;
|
||||||
|
246) echo "Node.js: Internal JavaScript Parse Error" ;;
|
||||||
|
247) echo "Node.js: Fatal internal error" ;;
|
||||||
|
248) echo "Node.js: Invalid C++ addon / N-API failure" ;;
|
||||||
|
249) echo "Node.js: Inspector error" ;;
|
||||||
|
254) echo "npm/pnpm/yarn: Unknown fatal error" ;;
|
||||||
|
|
||||||
|
# --- Python / pip / uv ---
|
||||||
|
210) echo "Python: Virtualenv / uv environment missing or broken" ;;
|
||||||
|
211) echo "Python: Dependency resolution failed" ;;
|
||||||
|
212) echo "Python: Installation aborted (permissions or EXTERNALLY-MANAGED)" ;;
|
||||||
|
|
||||||
|
# --- PostgreSQL ---
|
||||||
|
231) echo "PostgreSQL: Connection failed (server not running / wrong socket)" ;;
|
||||||
|
232) echo "PostgreSQL: Authentication failed (bad user/password)" ;;
|
||||||
|
233) echo "PostgreSQL: Database does not exist" ;;
|
||||||
|
234) echo "PostgreSQL: Fatal error in query / syntax" ;;
|
||||||
|
|
||||||
|
# --- MySQL / MariaDB ---
|
||||||
|
241) echo "MySQL/MariaDB: Connection failed (server not running / wrong socket)" ;;
|
||||||
|
242) echo "MySQL/MariaDB: Authentication failed (bad user/password)" ;;
|
||||||
|
243) echo "MySQL/MariaDB: Database does not exist" ;;
|
||||||
|
244) echo "MySQL/MariaDB: Fatal error in query / syntax" ;;
|
||||||
|
|
||||||
|
# --- MongoDB ---
|
||||||
|
251) echo "MongoDB: Connection failed (server not running)" ;;
|
||||||
|
252) echo "MongoDB: Authentication failed (bad user/password)" ;;
|
||||||
|
253) echo "MongoDB: Database not found" ;;
|
||||||
|
254) echo "MongoDB: Fatal query error" ;;
|
||||||
|
|
||||||
|
# --- Proxmox Custom Codes ---
|
||||||
|
200) echo "Proxmox: Failed to create lock file" ;;
|
||||||
|
203) echo "Proxmox: Missing CTID variable" ;;
|
||||||
|
204) echo "Proxmox: Missing PCT_OSTYPE variable" ;;
|
||||||
|
205) echo "Proxmox: Invalid CTID (<100)" ;;
|
||||||
|
206) echo "Proxmox: CTID already in use" ;;
|
||||||
|
207) echo "Proxmox: Password contains unescaped special characters" ;;
|
||||||
|
208) echo "Proxmox: Invalid configuration (DNS/MAC/Network format)" ;;
|
||||||
|
209) echo "Proxmox: Container creation failed" ;;
|
||||||
|
210) echo "Proxmox: Cluster not quorate" ;;
|
||||||
|
211) echo "Proxmox: Timeout waiting for template lock" ;;
|
||||||
|
212) echo "Proxmox: Storage type 'iscsidirect' does not support containers (VMs only)" ;;
|
||||||
|
213) echo "Proxmox: Storage type does not support 'rootdir' content" ;;
|
||||||
|
214) echo "Proxmox: Not enough storage space" ;;
|
||||||
|
215) echo "Proxmox: Container created but not listed (ghost state)" ;;
|
||||||
|
216) echo "Proxmox: RootFS entry missing in config" ;;
|
||||||
|
217) echo "Proxmox: Storage not accessible" ;;
|
||||||
|
219) echo "Proxmox: CephFS does not support containers - use RBD" ;;
|
||||||
|
224) echo "Proxmox: PBS storage is for backups only" ;;
|
||||||
|
218) echo "Proxmox: Template file corrupted or incomplete" ;;
|
||||||
|
220) echo "Proxmox: Unable to resolve template path" ;;
|
||||||
|
221) echo "Proxmox: Template file not readable" ;;
|
||||||
|
222) echo "Proxmox: Template download failed" ;;
|
||||||
|
223) echo "Proxmox: Template not available after download" ;;
|
||||||
|
225) echo "Proxmox: No template available for OS/Version" ;;
|
||||||
|
231) echo "Proxmox: LXC stack upgrade failed" ;;
|
||||||
|
|
||||||
|
# --- Default ---
|
||||||
|
*) echo "Unknown error" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# SECTION 2: ERROR HANDLERS
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# error_handler()
|
||||||
|
#
|
||||||
|
# - Main error handler triggered by ERR trap
|
||||||
|
# - Arguments: exit_code, command, line_number
|
||||||
|
# - Behavior:
|
||||||
|
# * Returns silently if exit_code is 0 (success)
|
||||||
|
# * Sources explain_exit_code() for detailed error description
|
||||||
|
# * Displays error message with:
|
||||||
|
# - Line number where error occurred
|
||||||
|
# - Exit code with explanation
|
||||||
|
# - Command that failed
|
||||||
|
# * Shows last 20 lines of SILENT_LOGFILE if available
|
||||||
|
# * Copies log to container /root for later inspection
|
||||||
|
# * Exits with original exit code
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
error_handler() {
|
||||||
|
local exit_code=${1:-$?}
|
||||||
|
local command=${2:-${BASH_COMMAND:-unknown}}
|
||||||
|
local line_number=${BASH_LINENO[0]:-unknown}
|
||||||
|
|
||||||
|
command="${command//\$STD/}"
|
||||||
|
|
||||||
|
if [[ "$exit_code" -eq 0 ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local explanation
|
||||||
|
explanation="$(explain_exit_code "$exit_code")"
|
||||||
|
|
||||||
|
printf "\e[?25h"
|
||||||
|
|
||||||
|
# Use msg_error if available, fallback to echo
|
||||||
|
if declare -f msg_error >/dev/null 2>&1; then
|
||||||
|
msg_error "in line ${line_number}: exit code ${exit_code} (${explanation}): while executing command ${command}"
|
||||||
|
else
|
||||||
|
echo -e "\n${RD}[ERROR]${CL} in line ${RD}${line_number}${CL}: exit code ${RD}${exit_code}${CL} (${explanation}): while executing command ${YWB}${command}${CL}\n"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "${DEBUG_LOGFILE:-}" ]]; then
|
||||||
|
{
|
||||||
|
echo "------ ERROR ------"
|
||||||
|
echo "Timestamp : $(date '+%Y-%m-%d %H:%M:%S')"
|
||||||
|
echo "Exit Code : $exit_code ($explanation)"
|
||||||
|
echo "Line : $line_number"
|
||||||
|
echo "Command : $command"
|
||||||
|
echo "-------------------"
|
||||||
|
} >>"$DEBUG_LOGFILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get active log file (BUILD_LOG or INSTALL_LOG)
|
||||||
|
local active_log=""
|
||||||
|
if declare -f get_active_logfile >/dev/null 2>&1; then
|
||||||
|
active_log="$(get_active_logfile)"
|
||||||
|
elif [[ -n "${SILENT_LOGFILE:-}" ]]; then
|
||||||
|
active_log="$SILENT_LOGFILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$active_log" && -s "$active_log" ]]; then
|
||||||
|
echo "--- Last 20 lines of silent log ---"
|
||||||
|
tail -n 20 "$active_log"
|
||||||
|
echo "-----------------------------------"
|
||||||
|
|
||||||
|
# Detect context: Container (INSTALL_LOG set + /root exists) vs Host (BUILD_LOG)
|
||||||
|
if [[ -n "${INSTALL_LOG:-}" && -d /root ]]; then
|
||||||
|
# CONTAINER CONTEXT: Copy log and create flag file for host
|
||||||
|
local container_log="/root/.install-${SESSION_ID:-error}.log"
|
||||||
|
cp "$active_log" "$container_log" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Create error flag file with exit code for host detection
|
||||||
|
echo "$exit_code" >"/root/.install-${SESSION_ID:-error}.failed" 2>/dev/null || true
|
||||||
|
|
||||||
|
if declare -f msg_custom >/dev/null 2>&1; then
|
||||||
|
msg_custom "📋" "${YW}" "Log saved to: ${container_log}"
|
||||||
|
else
|
||||||
|
echo -e "${YW}Log saved to:${CL} ${BL}${container_log}${CL}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# HOST CONTEXT: Show local log path and offer container cleanup
|
||||||
|
if declare -f msg_custom >/dev/null 2>&1; then
|
||||||
|
msg_custom "📋" "${YW}" "Full log: ${active_log}"
|
||||||
|
else
|
||||||
|
echo -e "${YW}Full log:${CL} ${BL}${active_log}${CL}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Offer to remove container if it exists (build errors after container creation)
|
||||||
|
if [[ -n "${CTID:-}" ]] && command -v pct &>/dev/null && pct status "$CTID" &>/dev/null; then
|
||||||
|
echo ""
|
||||||
|
echo -en "${YW}Remove broken container ${CTID}? (Y/n) [auto-remove in 60s]: ${CL}"
|
||||||
|
|
||||||
|
if read -t 60 -r response; then
|
||||||
|
if [[ -z "$response" || "$response" =~ ^[Yy]$ ]]; then
|
||||||
|
echo -e "\n${YW}Removing container ${CTID}${CL}"
|
||||||
|
pct stop "$CTID" &>/dev/null || true
|
||||||
|
pct destroy "$CTID" &>/dev/null || true
|
||||||
|
echo -e "${GN}✔${CL} Container ${CTID} removed"
|
||||||
|
elif [[ "$response" =~ ^[Nn]$ ]]; then
|
||||||
|
echo -e "\n${YW}Container ${CTID} kept for debugging${CL}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Timeout - auto-remove
|
||||||
|
echo -e "\n${YW}No response - auto-removing container${CL}"
|
||||||
|
pct stop "$CTID" &>/dev/null || true
|
||||||
|
pct destroy "$CTID" &>/dev/null || true
|
||||||
|
echo -e "${GN}✔${CL} Container ${CTID} removed"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit "$exit_code"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# SECTION 3: SIGNAL HANDLERS
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# on_exit()
|
||||||
|
#
|
||||||
|
# - EXIT trap handler
|
||||||
|
# - Cleans up lock files if lockfile variable is set
|
||||||
|
# - Exits with captured exit code
|
||||||
|
# - Always runs on script termination (success or failure)
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
on_exit() {
|
||||||
|
local exit_code=$?
|
||||||
|
[[ -n "${lockfile:-}" && -e "$lockfile" ]] && rm -f "$lockfile"
|
||||||
|
exit "$exit_code"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# on_interrupt()
|
||||||
|
#
|
||||||
|
# - SIGINT (Ctrl+C) trap handler
|
||||||
|
# - Displays "Interrupted by user" message
|
||||||
|
# - Exits with code 130 (128 + SIGINT=2)
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
on_interrupt() {
|
||||||
|
if declare -f msg_error >/dev/null 2>&1; then
|
||||||
|
msg_error "Interrupted by user (SIGINT)"
|
||||||
|
else
|
||||||
|
echo -e "\n${RD}Interrupted by user (SIGINT)${CL}"
|
||||||
|
fi
|
||||||
|
exit 130
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# on_terminate()
|
||||||
|
#
|
||||||
|
# - SIGTERM trap handler
|
||||||
|
# - Displays "Terminated by signal" message
|
||||||
|
# - Exits with code 143 (128 + SIGTERM=15)
|
||||||
|
# - Triggered by external process termination
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
on_terminate() {
|
||||||
|
if declare -f msg_error >/dev/null 2>&1; then
|
||||||
|
msg_error "Terminated by signal (SIGTERM)"
|
||||||
|
else
|
||||||
|
echo -e "\n${RD}Terminated by signal (SIGTERM)${CL}"
|
||||||
|
fi
|
||||||
|
exit 143
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# SECTION 4: INITIALIZATION
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# catch_errors()
|
||||||
|
#
|
||||||
|
# - Initializes error handling and signal traps
|
||||||
|
# - Enables strict error handling:
|
||||||
|
# * set -Ee: Exit on error, inherit ERR trap in functions
|
||||||
|
# * set -o pipefail: Pipeline fails if any command fails
|
||||||
|
# * set -u: (optional) Exit on undefined variable (if STRICT_UNSET=1)
|
||||||
|
# - Sets up traps:
|
||||||
|
# * ERR → error_handler
|
||||||
|
# * EXIT → on_exit
|
||||||
|
# * INT → on_interrupt
|
||||||
|
# * TERM → on_terminate
|
||||||
|
# - Call this function early in every script
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
catch_errors() {
|
||||||
|
set -Ee -o pipefail
|
||||||
|
if [ "${STRICT_UNSET:-0}" = "1" ]; then
|
||||||
|
set -u
|
||||||
|
fi
|
||||||
|
|
||||||
|
trap 'error_handler' ERR
|
||||||
|
trap on_exit EXIT
|
||||||
|
trap on_interrupt INT
|
||||||
|
trap on_terminate TERM
|
||||||
|
}
|
||||||
@@ -1,48 +1,79 @@
|
|||||||
# Copyright (c) 2021-2025 michelroegl-brunner
|
# Copyright (c) 2021-2026 community-scripts ORG
|
||||||
# Author: michelroegl-brunner
|
# Author: tteck (tteckster)
|
||||||
# License: MIT
|
# Co-Author: MickLesk
|
||||||
# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# INSTALL.FUNC - CONTAINER INSTALLATION & SETUP
|
||||||
|
# ==============================================================================
|
||||||
|
#
|
||||||
|
# This file provides installation functions executed inside LXC containers
|
||||||
|
# after creation. Handles:
|
||||||
|
#
|
||||||
|
# - Network connectivity verification (IPv4/IPv6)
|
||||||
|
# - OS updates and package installation
|
||||||
|
# - DNS resolution checks
|
||||||
|
# - MOTD and SSH configuration
|
||||||
|
# - Container customization and auto-login
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# - Sourced by <app>-install.sh scripts
|
||||||
|
# - Executes via pct exec inside container
|
||||||
|
# - Requires internet connectivity
|
||||||
|
#
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# SECTION 1: INITIALIZATION
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
if ! command -v curl >/dev/null 2>&1; then
|
if ! command -v curl >/dev/null 2>&1; then
|
||||||
printf "\r\e[2K%b" '\033[93m Setup Source \033[m' >&2
|
printf "\r\e[2K%b" '\033[93m Setup Source \033[m' >&2
|
||||||
apt-get update >/dev/null 2>&1
|
apt update >/dev/null 2>&1
|
||||||
apt-get install -y curl >/dev/null 2>&1
|
apt install -y curl >/dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
# core.func is included in FUNCTIONS_FILE_PATH
|
source "$(dirname "${BASH_SOURCE[0]}")/core.func"
|
||||||
|
source "$(dirname "${BASH_SOURCE[0]}")/error-handler.func"
|
||||||
load_functions
|
load_functions
|
||||||
# This function enables IPv6 if it's not disabled and sets verbose mode
|
catch_errors
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# SECTION 2: NETWORK & CONNECTIVITY
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# verb_ip6()
|
||||||
|
#
|
||||||
|
# - Configures IPv6 based on DISABLEIPV6 variable
|
||||||
|
# - If DISABLEIPV6=yes: disables IPv6 via sysctl
|
||||||
|
# - Sets verbose mode via set_std_mode()
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
verb_ip6() {
|
verb_ip6() {
|
||||||
set_std_mode # Set STD mode based on VERBOSE
|
set_std_mode # Set STD mode based on VERBOSE
|
||||||
|
|
||||||
if [ "$DISABLEIPV6" == "yes" ]; then
|
if [ "${IPV6_METHOD:-}" = "disable" ]; then
|
||||||
echo "net.ipv6.conf.all.disable_ipv6 = 1" >>/etc/sysctl.conf
|
msg_info "Disabling IPv6 (this may affect some services)"
|
||||||
$STD sysctl -p
|
mkdir -p /etc/sysctl.d
|
||||||
|
$STD tee /etc/sysctl.d/99-disable-ipv6.conf >/dev/null <<EOF
|
||||||
|
# Disable IPv6 (set by community-scripts)
|
||||||
|
net.ipv6.conf.all.disable_ipv6 = 1
|
||||||
|
net.ipv6.conf.default.disable_ipv6 = 1
|
||||||
|
net.ipv6.conf.lo.disable_ipv6 = 1
|
||||||
|
EOF
|
||||||
|
$STD sysctl -p /etc/sysctl.d/99-disable-ipv6.conf
|
||||||
|
msg_ok "Disabled IPv6"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# This function sets error handling options and defines the error_handler function to handle errors
|
# ------------------------------------------------------------------------------
|
||||||
catch_errors() {
|
# setting_up_container()
|
||||||
set -Eeuo pipefail
|
#
|
||||||
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
|
# - Verifies network connectivity via hostname -I
|
||||||
}
|
# - Retries up to RETRY_NUM times with RETRY_EVERY seconds delay
|
||||||
|
# - Removes Python EXTERNALLY-MANAGED restrictions
|
||||||
# This function handles errors
|
# - Disables systemd-networkd-wait-online.service for faster boot
|
||||||
error_handler() {
|
# - Exits with error if network unavailable after retries
|
||||||
printf "\e[?25h"
|
# ------------------------------------------------------------------------------
|
||||||
local exit_code="$?"
|
|
||||||
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}"
|
|
||||||
echo -e "\n$error_message"
|
|
||||||
if [[ "$line_number" -eq 51 ]]; then
|
|
||||||
echo -e "The silent function has suppressed the error, run the script with verbose mode enabled, which will provide more detailed output.\n"
|
|
||||||
post_update_to_api "failed" "No error message, script ran in silent mode"
|
|
||||||
else
|
|
||||||
post_update_to_api "failed" "${command}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# This function sets up the Container OS by generating the locale, setting the timezone, and checking the network connection
|
|
||||||
setting_up_container() {
|
setting_up_container() {
|
||||||
msg_info "Setting up Container OS"
|
msg_info "Setting up Container OS"
|
||||||
for ((i = RETRY_NUM; i > 0; i--)); do
|
for ((i = RETRY_NUM; i > 0; i--)); do
|
||||||
@@ -64,8 +95,17 @@ setting_up_container() {
|
|||||||
msg_ok "Network Connected: ${BL}$(hostname -I)"
|
msg_ok "Network Connected: ${BL}$(hostname -I)"
|
||||||
}
|
}
|
||||||
|
|
||||||
# This function checks the network connection by pinging a known IP address and prompts the user to continue if the internet is not connected
|
# ------------------------------------------------------------------------------
|
||||||
# This function checks the network connection by pinging a known IP address and prompts the user to continue if the internet is not connected
|
# network_check()
|
||||||
|
#
|
||||||
|
# - Comprehensive network connectivity check for IPv4 and IPv6
|
||||||
|
# - Tests connectivity to multiple DNS servers:
|
||||||
|
# * IPv4: 1.1.1.1 (Cloudflare), 8.8.8.8 (Google), 9.9.9.9 (Quad9)
|
||||||
|
# * IPv6: 2606:4700:4700::1111, 2001:4860:4860::8888, 2620:fe::fe
|
||||||
|
# - Verifies DNS resolution for GitHub and Community-Scripts domains
|
||||||
|
# - Prompts user to continue if no internet detected
|
||||||
|
# - Uses fatal() on DNS resolution failure for critical hosts
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
network_check() {
|
network_check() {
|
||||||
set +e
|
set +e
|
||||||
trap - ERR
|
trap - ERR
|
||||||
@@ -125,7 +165,19 @@ network_check() {
|
|||||||
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
|
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
|
||||||
}
|
}
|
||||||
|
|
||||||
# This function updates the Container OS by running apt-get update and upgrade
|
# ==============================================================================
|
||||||
|
# SECTION 3: OS UPDATE & PACKAGE MANAGEMENT
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# update_os()
|
||||||
|
#
|
||||||
|
# - Updates container OS via apt-get update and dist-upgrade
|
||||||
|
# - Configures APT cacher proxy if CACHER=yes (accelerates package downloads)
|
||||||
|
# - Removes Python EXTERNALLY-MANAGED restrictions for pip
|
||||||
|
# - Sources tools.func for additional setup functions after update
|
||||||
|
# - Uses $STD wrapper to suppress output unless VERBOSE=yes
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
update_os() {
|
update_os() {
|
||||||
msg_info "Updating Container OS"
|
msg_info "Updating Container OS"
|
||||||
if [[ "$CACHER" == "yes" ]]; then
|
if [[ "$CACHER" == "yes" ]]; then
|
||||||
@@ -145,29 +197,37 @@ EOF
|
|||||||
rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED
|
rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED
|
||||||
msg_ok "Updated Container OS"
|
msg_ok "Updated Container OS"
|
||||||
|
|
||||||
# tools.func is included in FUNCTIONS_FILE_PATH
|
source "$(dirname "${BASH_SOURCE[0]}")/tools.func"
|
||||||
}
|
}
|
||||||
|
|
||||||
# This function modifies the message of the day (motd) and SSH settings
|
# ==============================================================================
|
||||||
|
# SECTION 4: MOTD & SSH CONFIGURATION
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# motd_ssh()
|
||||||
|
#
|
||||||
|
# - Configures Message of the Day (MOTD) with container information
|
||||||
|
# - Creates /etc/profile.d/00_lxc-details.sh with:
|
||||||
|
# * Application name
|
||||||
|
# * Warning banner (DEV repository)
|
||||||
|
# * OS name and version
|
||||||
|
# * Hostname and IP address
|
||||||
|
# * GitHub repository link
|
||||||
|
# - Disables executable flag on /etc/update-motd.d/* scripts
|
||||||
|
# - Enables root SSH access if SSH_ROOT=yes
|
||||||
|
# - Configures TERM environment variable for better terminal support
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
motd_ssh() {
|
motd_ssh() {
|
||||||
# Set terminal to 256-color mode
|
# Set terminal to 256-color mode
|
||||||
grep -qxF "export TERM='xterm-256color'" /root/.bashrc || echo "export TERM='xterm-256color'" >>/root/.bashrc
|
grep -qxF "export TERM='xterm-256color'" /root/.bashrc || echo "export TERM='xterm-256color'" >>/root/.bashrc
|
||||||
|
|
||||||
# Get OS information (Debian / Ubuntu)
|
|
||||||
if [ -f "/etc/os-release" ]; then
|
|
||||||
OS_NAME=$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '"')
|
|
||||||
OS_VERSION=$(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '"')
|
|
||||||
elif [ -f "/etc/debian_version" ]; then
|
|
||||||
OS_NAME="Debian"
|
|
||||||
OS_VERSION=$(cat /etc/debian_version)
|
|
||||||
fi
|
|
||||||
|
|
||||||
PROFILE_FILE="/etc/profile.d/00_lxc-details.sh"
|
PROFILE_FILE="/etc/profile.d/00_lxc-details.sh"
|
||||||
echo "echo -e \"\"" >"$PROFILE_FILE"
|
echo "echo -e \"\"" >"$PROFILE_FILE"
|
||||||
echo -e "echo -e \"${BOLD}${APPLICATION} LXC Container${CL}"\" >>"$PROFILE_FILE"
|
echo -e "echo -e \"${BOLD}${APPLICATION} LXC Container${CL}"\" >>"$PROFILE_FILE"
|
||||||
echo -e "echo -e \"${TAB}${GATEWAY}${YW} Provided by: ${GN}community-scripts ORG ${YW}| GitHub: ${GN}https://github.com/community-scripts/ProxmoxVE${CL}\"" >>"$PROFILE_FILE"
|
echo -e "echo -e \"${TAB}${GATEWAY}${YW} Provided by: ${GN}community-scripts ORG ${YW}| GitHub: ${GN}https://github.com/community-scripts/ProxmoxVE${CL}\"" >>"$PROFILE_FILE"
|
||||||
echo "echo \"\"" >>"$PROFILE_FILE"
|
echo "echo \"\"" >>"$PROFILE_FILE"
|
||||||
echo -e "echo -e \"${TAB}${OS}${YW} OS: ${GN}${OS_NAME} - Version: ${OS_VERSION}${CL}\"" >>"$PROFILE_FILE"
|
echo -e "echo -e \"${TAB}${OS}${YW} OS: ${GN}\$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '\"') - Version: \$(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '\"')${CL}\"" >>"$PROFILE_FILE"
|
||||||
echo -e "echo -e \"${TAB}${HOSTNAME}${YW} Hostname: ${GN}\$(hostname)${CL}\"" >>"$PROFILE_FILE"
|
echo -e "echo -e \"${TAB}${HOSTNAME}${YW} Hostname: ${GN}\$(hostname)${CL}\"" >>"$PROFILE_FILE"
|
||||||
echo -e "echo -e \"${TAB}${INFO}${YW} IP Address: ${GN}\$(hostname -I | awk '{print \$1}')${CL}\"" >>"$PROFILE_FILE"
|
echo -e "echo -e \"${TAB}${INFO}${YW} IP Address: ${GN}\$(hostname -I | awk '{print \$1}')${CL}\"" >>"$PROFILE_FILE"
|
||||||
|
|
||||||
@@ -180,7 +240,19 @@ motd_ssh() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# This function customizes the container by modifying the getty service and enabling auto-login for the root user
|
# ==============================================================================
|
||||||
|
# SECTION 5: CONTAINER CUSTOMIZATION
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# customize()
|
||||||
|
#
|
||||||
|
# - Customizes container for passwordless root login if PASSWORD is empty
|
||||||
|
# - Configures getty for auto-login via /etc/systemd/system/container-getty@1.service.d/override.conf
|
||||||
|
# - Creates /usr/bin/update script for easy application updates
|
||||||
|
# - Injects SSH authorized keys if SSH_AUTHORIZED_KEY variable is set
|
||||||
|
# - Sets proper permissions on SSH directories and key files
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
customize() {
|
customize() {
|
||||||
if [[ "$PASSWORD" == "" ]]; then
|
if [[ "$PASSWORD" == "" ]]; then
|
||||||
msg_info "Customizing Container"
|
msg_info "Customizing Container"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
44
scripts/ct/debian.sh
Normal file
44
scripts/ct/debian.sh
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
SCRIPT_DIR="$(dirname "$0")"
|
||||||
|
source "$SCRIPT_DIR/../core/build.func"
|
||||||
|
# Copyright (c) 2021-2025 tteck
|
||||||
|
# Author: tteck (tteckster)
|
||||||
|
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||||
|
# Source: https://www.debian.org/
|
||||||
|
|
||||||
|
APP="Debian"
|
||||||
|
var_tags="${var_tags:-os}"
|
||||||
|
var_cpu="${var_cpu:-1}"
|
||||||
|
var_ram="${var_ram:-512}"
|
||||||
|
var_disk="${var_disk:-2}"
|
||||||
|
var_os="${var_os:-debian}"
|
||||||
|
var_version="${var_version:-13}"
|
||||||
|
var_unprivileged="${var_unprivileged:-1}"
|
||||||
|
|
||||||
|
header_info "$APP"
|
||||||
|
variables
|
||||||
|
color
|
||||||
|
catch_errors
|
||||||
|
|
||||||
|
function update_script() {
|
||||||
|
header_info
|
||||||
|
check_container_storage
|
||||||
|
check_container_resources
|
||||||
|
if [[ ! -d /var ]]; then
|
||||||
|
msg_error "No ${APP} Installation Found!"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
msg_info "Updating $APP LXC"
|
||||||
|
$STD apt update
|
||||||
|
$STD apt -y upgrade
|
||||||
|
msg_ok "Updated $APP LXC"
|
||||||
|
msg_ok "Updated successfully!"
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
start
|
||||||
|
build_container
|
||||||
|
description
|
||||||
|
|
||||||
|
msg_ok "Completed Successfully!\n"
|
||||||
|
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
|
||||||
18
scripts/install/debian-install.sh
Normal file
18
scripts/install/debian-install.sh
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Copyright (c) 2021-2025 tteck
|
||||||
|
# Author: tteck (tteckster)
|
||||||
|
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||||
|
# Source: https://www.debian.org/
|
||||||
|
|
||||||
|
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
|
||||||
|
color
|
||||||
|
verb_ip6
|
||||||
|
catch_errors
|
||||||
|
setting_up_container
|
||||||
|
network_check
|
||||||
|
update_os
|
||||||
|
|
||||||
|
motd_ssh
|
||||||
|
customize
|
||||||
|
cleanup_lxc
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "2FAuth",
|
|
||||||
"slug": "2fauth",
|
|
||||||
"categories": [
|
|
||||||
6
|
|
||||||
],
|
|
||||||
"date_created": "2024-12-20",
|
|
||||||
"type": "ct",
|
|
||||||
"updateable": true,
|
|
||||||
"privileged": false,
|
|
||||||
"interface_port": 80,
|
|
||||||
"documentation": "https://docs.2fauth.app/",
|
|
||||||
"website": "https://2fauth.app/",
|
|
||||||
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/webp/2fauth.webp",
|
|
||||||
"config_path": "cat /opt/2fauth/.env",
|
|
||||||
"description": "2FAuth is a web based self-hosted alternative to One Time Passcode (OTP) generators like Google Authenticator, designed for both mobile and desktop. It aims to ease you perform your 2FA authentication steps whatever the device you handle, with a clean and suitable interface.",
|
|
||||||
"install_methods": [
|
|
||||||
{
|
|
||||||
"type": "default",
|
|
||||||
"script": "ct/2fauth.sh",
|
|
||||||
"resources": {
|
|
||||||
"cpu": 1,
|
|
||||||
"ram": 512,
|
|
||||||
"hdd": 2,
|
|
||||||
"os": "debian",
|
|
||||||
"version": "13"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"default_credentials": {
|
|
||||||
"username": null,
|
|
||||||
"password": null
|
|
||||||
},
|
|
||||||
"notes": [
|
|
||||||
{
|
|
||||||
"text": "Database credentials: `cat ~/2FAuth.creds`",
|
|
||||||
"type": "info"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"text": "The very first account created is automatically set up as an administrator account.",
|
|
||||||
"type": "info"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
"username": null,
|
"username": null,
|
||||||
"password": null
|
"password": null
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -48,5 +48,6 @@
|
|||||||
"text": "You can execute the ip tool manually with `iptag-run`",
|
"text": "You can execute the ip tool manually with `iptag-run`",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -44,5 +44,6 @@
|
|||||||
"text": "The script only works in Debian/Ubuntu, not in Alpine!",
|
"text": "The script only works in Debian/Ubuntu, not in Alpine!",
|
||||||
"type": "warning"
|
"type": "warning"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -40,5 +40,6 @@
|
|||||||
"text": "Execute within the Proxmox host shell",
|
"text": "Execute within the Proxmox host shell",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -47,5 +47,6 @@
|
|||||||
"text": "AdGuard Home can only be updated via the user interface.",
|
"text": "AdGuard Home can only be updated via the user interface.",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -40,5 +40,6 @@
|
|||||||
"text": "Use `cat ~/adventurelog.creds` to see login credentials.",
|
"text": "Use `cat ~/adventurelog.creds` to see login credentials.",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
"username": null,
|
"username": null,
|
||||||
"password": null
|
"password": null
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "Resource and network settings are adjustable post LXC creation.",
|
"text": "Resource and network settings are adjustable post LXC creation.",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -42,5 +42,6 @@
|
|||||||
"username": null,
|
"username": null,
|
||||||
"password": null
|
"password": null
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
"username": null,
|
"username": null,
|
||||||
"password": null
|
"password": null
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -47,5 +47,6 @@
|
|||||||
"text": "The default credentials are located in `/opt/tinyauth/credentials.txt`.",
|
"text": "The default credentials are located in `/opt/tinyauth/credentials.txt`.",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "To Update Alpine: `apk -U upgrade`",
|
"text": "To Update Alpine: `apk -U upgrade`",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
"username": null,
|
"username": null,
|
||||||
"password": null
|
"password": null
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "Show Login Credentials: `cat CouchDB.creds`",
|
"text": "Show Login Credentials: `cat CouchDB.creds`",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
"username": "guacadmin",
|
"username": "guacadmin",
|
||||||
"password": "guacadmin"
|
"password": "guacadmin"
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "Configuration file is not created at install time. Example is at: `https://cwiki.apache.org/confluence/display/TIKA/TikaServer+in+Tika+2.x`",
|
"text": "Configuration file is not created at install time. Example is at: `https://cwiki.apache.org/confluence/display/TIKA/TikaServer+in+Tika+2.x`",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "User can select which Adoptium JDK should be used for the selected Tomcat version (9, 10.1 or 11). ",
|
"text": "User can select which Adoptium JDK should be used for the selected Tomcat version (9, 10.1 or 11). ",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
"username": null,
|
"username": null,
|
||||||
"password": null
|
"password": null
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
"username": "archivebox",
|
"username": "archivebox",
|
||||||
"password": "helper-scripts.com"
|
"password": "helper-scripts.com"
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "doesnt work with lvm and lvmthin disks!",
|
"text": "doesnt work with lvm and lvmthin disks!",
|
||||||
"type": "warning"
|
"type": "warning"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
"username": null,
|
"username": null,
|
||||||
"password": null
|
"password": null
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "Within the LXC console, run `cat rpc.secret` to display the rpc-secret. Copy this token and paste it into the Aria2 RPC Secret Token box within the AriaNG Settings. Then, click the reload AriaNG button.",
|
"text": "Within the LXC console, run `cat rpc.secret` to display the rpc-secret. Copy this token and paste it into the Aria2 RPC Secret Token box within the AriaNG Settings. Then, click the reload AriaNG button.",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
"username": null,
|
"username": null,
|
||||||
"password": null
|
"password": null
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
"username": null,
|
"username": null,
|
||||||
"password": null
|
"password": null
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "During installation, you will have to input your domain (ex. domain.com). Authelia will use auth.domain.com",
|
"text": "During installation, you will have to input your domain (ex. domain.com). Authelia will use auth.domain.com",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
"username": null,
|
"username": null,
|
||||||
"password": null
|
"password": null
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
"username": "admin",
|
"username": "admin",
|
||||||
"password": "admin123"
|
"password": "admin123"
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "for private SSL setup visit: `https://github.com/babybuddy/babybuddy/blob/master/docs/setup/ssl.md`",
|
"text": "for private SSL setup visit: `https://github.com/babybuddy/babybuddy/blob/master/docs/setup/ssl.md`",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"type": "info",
|
"type": "info",
|
||||||
"text": "`cat ~/.ssh/id_ed25519.pub` to view ssh public key. This key is used to authenticate with sftp targets. You can add this key on the sftp server."
|
"text": "`cat ~/.ssh/id_ed25519.pub` to view ssh public key. This key is used to authenticate with sftp targets. You can add this key on the sftp server."
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
"username": "Admin",
|
"username": "Admin",
|
||||||
"password": null
|
"password": null
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
"username": null,
|
"username": null,
|
||||||
"password": null
|
"password": null
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "After install enable the option \"Use Redis cache\" on the settings page.",
|
"text": "After install enable the option \"Use Redis cache\" on the settings page.",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
"ram": 1024,
|
"ram": 1024,
|
||||||
"hdd": 4,
|
"hdd": 4,
|
||||||
"os": "debian",
|
"os": "debian",
|
||||||
"version": "12"
|
"version": "13"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
"username": null,
|
"username": null,
|
||||||
"password": null
|
"password": null
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
"username": null,
|
"username": null,
|
||||||
"password": null
|
"password": null
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -47,5 +47,6 @@
|
|||||||
"text": "During installation you will be asked to enter your TMDB API key, if you wanna use it. Make sure you have it ready.",
|
"text": "During installation you will be asked to enter your TMDB API key, if you wanna use it. Make sure you have it ready.",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
"username": null,
|
"username": null,
|
||||||
"password": null
|
"password": null
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "Starting Booklore (Web UI) may take up to 2 minutes after a restart or fresh installation.",
|
"text": "Starting Booklore (Web UI) may take up to 2 minutes after a restart or fresh installation.",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "Bookstack works only with static ip. If you Change the IP of your LXC, you Need to edit the .env File `nano /opt/bookstack/.env`",
|
"text": "Bookstack works only with static ip. If you Change the IP of your LXC, you Need to edit the .env File `nano /opt/bookstack/.env`",
|
||||||
"type": "warning"
|
"type": "warning"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "WARNING: Installation sources scripts outside of Community Scripts repo. Please check the source before installing.",
|
"text": "WARNING: Installation sources scripts outside of Community Scripts repo. Please check the source before installing.",
|
||||||
"type": "warning"
|
"type": "warning"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
"username": null,
|
"username": null,
|
||||||
"password": null
|
"password": null
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -55,5 +55,6 @@
|
|||||||
"text": "if you need an external module run: `xcaddy build --with github.com/caddy-dns/cloudflare`",
|
"text": "if you need an external module run: `xcaddy build --with github.com/caddy-dns/cloudflare`",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -40,5 +40,6 @@
|
|||||||
"text": "WARNING: Installation sources scripts outside of Community Scripts repo. Please check the source before installing.",
|
"text": "WARNING: Installation sources scripts outside of Community Scripts repo. Please check the source before installing.",
|
||||||
"type": "warning"
|
"type": "warning"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
"username": null,
|
"username": null,
|
||||||
"password": null
|
"password": null
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "WARNING: Installation sources scripts outside of Community Scripts repo. Please check the source before installing.",
|
"text": "WARNING: Installation sources scripts outside of Community Scripts repo. Please check the source before installing.",
|
||||||
"type": "warning"
|
"type": "warning"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "Login Credentials : `cat ~/checkmk.creds`",
|
"text": "Login Credentials : `cat ~/checkmk.creds`",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "Execute within the Proxmox shell",
|
"text": "Execute within the Proxmox shell",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "Execute within the Proxmox shell",
|
"text": "Execute within the Proxmox shell",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
"username": null,
|
"username": null,
|
||||||
"password": null
|
"password": null
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -40,5 +40,6 @@
|
|||||||
"text": "To update the configuration edit `/etc/systemd/system/cloudflare-ddns.service`. After edit please restart with `systemctl restart cloudflare-ddns`",
|
"text": "To update the configuration edit `/etc/systemd/system/cloudflare-ddns.service`. After edit please restart with `systemctl restart cloudflare-ddns`",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "With an option to configure cloudflared as a DNS-over-HTTPS (DoH) proxy",
|
"text": "With an option to configure cloudflared as a DNS-over-HTTPS (DoH) proxy",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "After Installation: Register your user -> Login -> Dashboard -> Accept Primary URL.",
|
"text": "After Installation: Register your user -> Login -> Dashboard -> Accept Primary URL.",
|
||||||
"type": "warn"
|
"type": "warn"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -33,8 +33,13 @@
|
|||||||
},
|
},
|
||||||
"notes": [
|
"notes": [
|
||||||
{
|
{
|
||||||
"text": "Set a root password if using autologin. This will be the Cockpit password.`sudo passwd root`",
|
"text": "Set a root password if using autologin. This will be the Cockpit password. To set root password run `sudo passwd root`",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "If you plan on using 45Drives extension with NFS, you must setup LXC as privileged. Some features of 45Drives don't work on Debian 13, so Debian 12 must be used.",
|
||||||
|
"type": "warning"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -38,5 +38,6 @@
|
|||||||
"text": "Execute within an existing LXC Console",
|
"text": "Execute within an existing LXC Console",
|
||||||
"type": "warning"
|
"type": "warning"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
"username": "admin",
|
"username": "admin",
|
||||||
"password": "admin"
|
"password": "admin"
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "change secrets file /opt/configarr/secrets.yml",
|
"text": "change secrets file /opt/configarr/secrets.yml",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "Complete setup via the web interface at http://<container-ip>:3000. Create and secure the admin account immediately.",
|
"text": "Complete setup via the web interface at http://<container-ip>:3000. Create and secure the admin account immediately.",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "Execute within the Proxmox shell or in LXC",
|
"text": "Execute within the Proxmox shell or in LXC",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -37,5 +37,6 @@
|
|||||||
"type": "info",
|
"type": "info",
|
||||||
"text": "The file `/etc/sysconfig/CosmosCloud` is optional. If you need custom settings, you can create it yourself."
|
"text": "The file `/etc/sysconfig/CosmosCloud` is optional. If you need custom settings, you can create it yourself."
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "Show password: `cat ~/crafty-controller.creds`",
|
"text": "Show password: `cat ~/crafty-controller.creds`",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -40,5 +40,6 @@
|
|||||||
"text": "To exclude LXCs from updating, edit the crontab using `crontab -e` and add CTID as shown in the example below:\n\n\n\n`0 0 * * 0 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin /bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/update-lxcs-cron.sh)\" -s 103 111 >>/var/log/update-lxcs-cron.log 2>/dev/null`",
|
"text": "To exclude LXCs from updating, edit the crontab using `crontab -e` and add CTID as shown in the example below:\n\n\n\n`0 0 * * 0 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin /bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/update-lxcs-cron.sh)\" -s 103 111 >>/var/log/update-lxcs-cron.log 2>/dev/null`",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -28,13 +28,14 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"default_credentials": {
|
"default_credentials": {
|
||||||
"username": null,
|
"username": "admin",
|
||||||
"password": null
|
"password": "admin"
|
||||||
},
|
},
|
||||||
"notes": [
|
"notes": [
|
||||||
{
|
{
|
||||||
"text": "Primary and Worker Private Keys Must Match in the config file",
|
"text": "Primary and Worker Private Keys Must Match in the config file",
|
||||||
"type": "warning"
|
"type": "warning"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "After the installation cross-seed will fail to start with an empty configuration. To fix this, edit the config file to properly configure cross-seed, then restart by running `systemctl restart cross-seed`.",
|
"text": "After the installation cross-seed will fail to start with an empty configuration. To fix this, edit the config file to properly configure cross-seed, then restart by running `systemctl restart cross-seed`.",
|
||||||
"type": "warning"
|
"type": "warning"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "Execute within an existing LXC Console. Debian only!",
|
"text": "Execute within an existing LXC Console. Debian only!",
|
||||||
"type": "warning"
|
"type": "warning"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "After installation finishes, `systemctl status cryptpad.service` to get token URL which you can use to create admin account",
|
"text": "After installation finishes, `systemctl status cryptpad.service` to get token URL which you can use to create admin account",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
"username": null,
|
"username": null,
|
||||||
"password": null
|
"password": null
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
"username": null,
|
"username": null,
|
||||||
"password": null
|
"password": null
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -44,5 +44,6 @@
|
|||||||
"text": "If you use Cloud-init, checkout after installation: ´https://github.com/community-scripts/ProxmoxVE/discussions/272´",
|
"text": "If you use Cloud-init, checkout after installation: ´https://github.com/community-scripts/ProxmoxVE/discussions/272´",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -40,5 +40,6 @@
|
|||||||
"text": "After installation, checkout: ´https://github.com/community-scripts/ProxmoxVE/discussions/836´ for useful Debian commands",
|
"text": "After installation, checkout: ´https://github.com/community-scripts/ProxmoxVE/discussions/836´ for useful Debian commands",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
"username": null,
|
"username": null,
|
||||||
"password": null
|
"password": null
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
"username": null,
|
"username": null,
|
||||||
"password": null
|
"password": null
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
"username": null,
|
"username": null,
|
||||||
"password": "deluge"
|
"password": "deluge"
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -41,5 +41,6 @@
|
|||||||
"text": "This Script works on amd64 and arm64 Architecture.",
|
"text": "This Script works on amd64 and arm64 Architecture.",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -55,5 +55,6 @@
|
|||||||
"text": "Options to Install Portainer and/or Docker Compose V2",
|
"text": "Options to Install Portainer and/or Docker Compose V2",
|
||||||
"type": "warning"
|
"type": "warning"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user