Compare commits
148 Commits
v0.4.10.2
...
feat/core_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4aa8661c4 | ||
|
|
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 |
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/
|
||||||
|
|
||||||
|
|||||||
@@ -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,15 +44,18 @@ 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;
|
||||||
},
|
},
|
||||||
// Ignore ESLint errors during build (they can be fixed separately)
|
// TypeScript errors will fail the build
|
||||||
eslint: {
|
|
||||||
ignoreDuringBuilds: true,
|
|
||||||
},
|
|
||||||
// Ignore TypeScript errors during build (they can be fixed separately)
|
|
||||||
typescript: {
|
typescript: {
|
||||||
ignoreBuildErrors: true,
|
ignoreBuildErrors: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
4262
package-lock.json
generated
4262
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
101
package.json
101
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",
|
"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.0.1",
|
||||||
|
"@prisma/client": "^7.0.1",
|
||||||
"@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.8",
|
||||||
"@tailwindcss/typography": "^0.5.19",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@tanstack/react-query": "^5.90.5",
|
"@tanstack/react-query": "^5.90.11",
|
||||||
"@trpc/client": "^11.6.0",
|
"@trpc/client": "^11.7.2",
|
||||||
"@trpc/react-query": "^11.6.0",
|
"@trpc/react-query": "^11.7.2",
|
||||||
"@trpc/server": "^11.6.0",
|
"@trpc/server": "^11.7.2",
|
||||||
"@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.10.0",
|
||||||
"@xterm/addon-web-links": "^0.11.0",
|
"@xterm/addon-web-links": "^0.11.0",
|
||||||
"@xterm/xterm": "^5.5.0",
|
"@xterm/xterm": "^5.5.0",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.13.2",
|
||||||
"bcryptjs": "^3.0.2",
|
"bcryptjs": "^3.0.3",
|
||||||
|
"better-sqlite3": "^12.5.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.2",
|
||||||
"lucide-react": "^0.553.0",
|
"lucide-react": "^0.555.0",
|
||||||
"next": "^15.1.6",
|
"next": "^16.0.6",
|
||||||
"node-cron": "^3.0.3",
|
"node-cron": "^4.2.1",
|
||||||
"node-pty": "^1.0.0",
|
"node-pty": "^1.0.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.2.0",
|
||||||
"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.18.3",
|
||||||
"zod": "^4.1.12"
|
"zod": "^4.1.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.3.1",
|
"@tailwindcss/postcss": "^4.1.17",
|
||||||
"@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.0",
|
||||||
"@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.1",
|
||||||
"@types/node-cron": "^3.0.11",
|
"@types/node-cron": "^3.0.11",
|
||||||
"@types/react": "^19.0.0",
|
"@types/react": "^19.2.7",
|
||||||
"@types/react-dom": "^19.2.2",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^5.1.0",
|
"@vitejs/plugin-react": "^5.1.1",
|
||||||
"@vitest/coverage-v8": "^3.2.4",
|
"@vitest/coverage-v8": "^4.0.15",
|
||||||
"@vitest/ui": "^3.2.4",
|
"@vitest/ui": "^4.0.14",
|
||||||
"eslint": "^9.38.0",
|
"baseline-browser-mapping": "^2.8.32",
|
||||||
"eslint-config-next": "^15.1.6",
|
"eslint": "^9.39.1",
|
||||||
"jsdom": "^27.0.1",
|
"eslint-config-next": "^16.0.6",
|
||||||
"postcss": "^8.5.3",
|
"jsdom": "^27.2.0",
|
||||||
"prettier": "^3.5.3",
|
"postcss": "^8.5.6",
|
||||||
"prettier-plugin-tailwindcss": "^0.7.1",
|
"prettier": "^3.7.3",
|
||||||
"prisma": "^6.19.0",
|
"prettier-plugin-tailwindcss": "^0.7.2",
|
||||||
"tailwindcss": "^4.1.16",
|
"prisma": "^7.0.1",
|
||||||
"typescript": "^5.8.2",
|
"tailwindcss": "^4.1.17",
|
||||||
"typescript-eslint": "^8.46.2",
|
"tsx": "^4.21.0",
|
||||||
"vitest": "^3.2.4"
|
"typescript": "^5.9.3",
|
||||||
|
"typescript-eslint": "^8.48.1",
|
||||||
|
"vitest": "^4.0.14"
|
||||||
},
|
},
|
||||||
"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
|
||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,153 @@
|
|||||||
# Author: michelroegl-brunner
|
# Author: michelroegl-brunner
|
||||||
# License: MIT | https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/LICENSE
|
# 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() {
|
post_to_api() {
|
||||||
|
|
||||||
if ! command -v curl &>/dev/null; then
|
if ! command -v curl &>/dev/null; then
|
||||||
@@ -30,7 +177,6 @@ post_to_api() {
|
|||||||
"ram_size": $RAM_SIZE,
|
"ram_size": $RAM_SIZE,
|
||||||
"os_type": "$var_os",
|
"os_type": "$var_os",
|
||||||
"os_version": "$var_version",
|
"os_version": "$var_version",
|
||||||
"disableip6": "",
|
|
||||||
"nsapp": "$NSAPP",
|
"nsapp": "$NSAPP",
|
||||||
"method": "$METHOD(PVE-Local)",
|
"method": "$METHOD(PVE-Local)",
|
||||||
"pve_version": "$pve_version",
|
"pve_version": "$pve_version",
|
||||||
@@ -39,14 +185,26 @@ post_to_api() {
|
|||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
|
|
||||||
if [[ "$DIAGNOSTICS" == "yes" ]]; then
|
if [[ "$DIAGNOSTICS" == "yes" ]]; then
|
||||||
RESPONSE=$(curl -s -w "%{http_code}" -L -X POST "$API_URL" --post301 --post302 \
|
RESPONSE=$(curl -s -w "%{http_code}" -L -X POST "$API_URL" --post301 --post302 \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "$JSON_PAYLOAD") || true
|
-d "$JSON_PAYLOAD") || true
|
||||||
fi
|
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() {
|
post_to_api_vm() {
|
||||||
|
|
||||||
if [[ ! -f /usr/local/community-scripts/diagnostics ]]; then
|
if [[ ! -f /usr/local/community-scripts/diagnostics ]]; then
|
||||||
@@ -81,7 +239,6 @@ post_to_api_vm() {
|
|||||||
"ram_size": $RAM_SIZE,
|
"ram_size": $RAM_SIZE,
|
||||||
"os_type": "$var_os",
|
"os_type": "$var_os",
|
||||||
"os_version": "$var_version",
|
"os_version": "$var_version",
|
||||||
"disableip6": "",
|
|
||||||
"nsapp": "$NSAPP",
|
"nsapp": "$NSAPP",
|
||||||
"method": "$METHOD(PVE-Local)",
|
"method": "$METHOD(PVE-Local)",
|
||||||
"pve_version": "$pve_version",
|
"pve_version": "$pve_version",
|
||||||
@@ -90,7 +247,6 @@ post_to_api_vm() {
|
|||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
|
|
||||||
if [[ "$DIAGNOSTICS" == "yes" ]]; then
|
if [[ "$DIAGNOSTICS" == "yes" ]]; then
|
||||||
RESPONSE=$(curl -s -w "%{http_code}" -L -X POST "$API_URL" --post301 --post302 \
|
RESPONSE=$(curl -s -w "%{http_code}" -L -X POST "$API_URL" --post301 --post302 \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
@@ -98,19 +254,54 @@ EOF
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
POST_UPDATE_DONE=false
|
# ------------------------------------------------------------------------------
|
||||||
|
# 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() {
|
post_update_to_api() {
|
||||||
|
|
||||||
if ! command -v curl &>/dev/null; then
|
if ! command -v curl &>/dev/null; then
|
||||||
return
|
return
|
||||||
fi
|
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
|
if [ "$POST_UPDATE_DONE" = true ]; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
exit_code=${2:-1}
|
||||||
local API_URL="http://api.community-scripts.org/upload/updatestatus"
|
local API_URL="http://api.community-scripts.org/upload/updatestatus"
|
||||||
local status="${1:-failed}"
|
local status="${1:-failed}"
|
||||||
local error="${2:-No error message}"
|
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=$(
|
JSON_PAYLOAD=$(
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
@@ -121,7 +312,6 @@ post_update_to_api() {
|
|||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
|
|
||||||
if [[ "$DIAGNOSTICS" == "yes" ]]; then
|
if [[ "$DIAGNOSTICS" == "yes" ]]; then
|
||||||
RESPONSE=$(curl -s -w "%{http_code}" -L -X POST "$API_URL" --post301 --post302 \
|
RESPONSE=$(curl -s -w "%{http_code}" -L -X POST "$API_URL" --post301 --post302 \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
# Copyright (c) 2021-2025 community-scripts ORG
|
# Copyright (c) 2021-2025 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}"
|
||||||
@@ -130,21 +124,29 @@ icons() {
|
|||||||
ADVANCED="${TAB}🧩${TAB}${CL}"
|
ADVANCED="${TAB}🧩${TAB}${CL}"
|
||||||
FUSE="${TAB}🗂️${TAB}${CL}"
|
FUSE="${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 +154,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 +503,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 +532,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 +546,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 +617,234 @@ 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
|
||||||
|
|
||||||
|
# Truncate writable log files silently (permission errors ignored)
|
||||||
|
if command -v truncate >/dev/null 2>&1; then
|
||||||
|
find /var/log -type f -writable -print0 2>/dev/null |
|
||||||
|
xargs -0 -n1 truncate -s 0 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Node.js npm
|
||||||
|
if command -v npm &>/dev/null; then $STD npm cache clean --force || true; fi
|
||||||
|
# Node.js yarn
|
||||||
|
if command -v yarn &>/dev/null; then $STD yarn cache clean || true; fi
|
||||||
|
# Node.js pnpm
|
||||||
|
if command -v pnpm &>/dev/null; then $STD pnpm store prune || true; fi
|
||||||
|
# Go
|
||||||
|
if command -v go &>/dev/null; then $STD go clean -cache -modcache || true; fi
|
||||||
|
# Rust cargo
|
||||||
|
if command -v cargo &>/dev/null; then $STD cargo clean || true; fi
|
||||||
|
# Ruby gem
|
||||||
|
if command -v gem &>/dev/null; then $STD gem cleanup || true; fi
|
||||||
|
# Composer (PHP)
|
||||||
|
if command -v composer &>/dev/null; then $STD composer clear-cache || true; fi
|
||||||
|
|
||||||
|
if command -v journalctl &>/dev/null; then
|
||||||
|
$STD journalctl --vacuum-time=10m || 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 +883,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."
|
|
||||||
@@ -1,48 +1,79 @@
|
|||||||
# Copyright (c) 2021-2025 michelroegl-brunner
|
# Copyright (c) 2021-2025 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,10 +197,27 @@ 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
|
||||||
@@ -180,7 +249,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"
|
||||||
|
|||||||
@@ -73,14 +73,18 @@ stop_all_services() {
|
|||||||
|
|
||||||
for pattern in "${service_patterns[@]}"; do
|
for pattern in "${service_patterns[@]}"; do
|
||||||
# Find all matching services
|
# Find all matching services
|
||||||
|
|
||||||
systemctl list-units --type=service --all 2>/dev/null |
|
systemctl list-units --type=service --all 2>/dev/null |
|
||||||
grep -oE "${pattern}[^ ]*\.service" |
|
grep -oE "${pattern}[^ ]*\.service" |
|
||||||
sort -u |
|
sort -u |
|
||||||
while read -r service; do
|
while read -r service; do
|
||||||
|
|
||||||
$STD systemctl stop "$service" 2>/dev/null || true
|
$STD systemctl stop "$service" 2>/dev/null || true
|
||||||
$STD systemctl disable "$service" 2>/dev/null || true
|
$STD systemctl disable "$service" 2>/dev/null || true
|
||||||
done
|
done
|
||||||
|
|
||||||
done
|
done
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
@@ -1198,8 +1202,8 @@ ensure_apt_working() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Standardized deb822 repository setup
|
# Standardized deb822 repository setup (with optional Architectures)
|
||||||
# Validates all parameters and fails safely if any are empty
|
# Always runs apt update after repo creation to ensure package availability
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
setup_deb822_repo() {
|
setup_deb822_repo() {
|
||||||
local name="$1"
|
local name="$1"
|
||||||
@@ -1207,56 +1211,40 @@ setup_deb822_repo() {
|
|||||||
local repo_url="$3"
|
local repo_url="$3"
|
||||||
local suite="$4"
|
local suite="$4"
|
||||||
local component="${5:-main}"
|
local component="${5:-main}"
|
||||||
local architectures="${6:-$(dpkg --print-architecture)}"
|
local architectures="${6-}" # optional
|
||||||
|
|
||||||
# Validate required parameters
|
# Validate required parameters
|
||||||
if [[ -z "$name" || -z "$gpg_url" || -z "$repo_url" || -z "$suite" ]]; then
|
if [[ -z "$name" || -z "$gpg_url" || -z "$repo_url" || -z "$suite" ]]; then
|
||||||
msg_error "setup_deb822_repo: missing required parameters (name=$name, gpg=$gpg_url, repo=$repo_url, suite=$suite)"
|
msg_error "setup_deb822_repo: missing required parameters (name=$name repo=$repo_url suite=$suite)"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Cleanup old configs for this app
|
# Cleanup
|
||||||
cleanup_old_repo_files "$name"
|
cleanup_old_repo_files "$name"
|
||||||
|
|
||||||
# Cleanup any orphaned .sources files from other apps
|
|
||||||
cleanup_orphaned_sources
|
cleanup_orphaned_sources
|
||||||
|
|
||||||
# Ensure keyring directory exists
|
|
||||||
mkdir -p /etc/apt/keyrings || {
|
mkdir -p /etc/apt/keyrings || {
|
||||||
msg_error "Failed to create /etc/apt/keyrings directory"
|
msg_error "Failed to create /etc/apt/keyrings"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Download GPG key (with --yes to avoid interactive prompts)
|
# Import GPG
|
||||||
curl -fsSL "$gpg_url" | gpg --dearmor --yes -o "/etc/apt/keyrings/${name}.gpg" 2>/dev/null || {
|
curl -fsSL "$gpg_url" | gpg --dearmor --yes -o "/etc/apt/keyrings/${name}.gpg" || {
|
||||||
msg_error "Failed to download or import GPG key for ${name} from $gpg_url"
|
msg_error "Failed to import GPG key for ${name}"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create deb822 sources file
|
# Write deb822
|
||||||
cat <<EOF >/etc/apt/sources.list.d/${name}.sources
|
{
|
||||||
Types: deb
|
echo "Types: deb"
|
||||||
URIs: $repo_url
|
echo "URIs: $repo_url"
|
||||||
Suites: $suite
|
echo "Suites: $suite"
|
||||||
Components: $component
|
echo "Components: $component"
|
||||||
Architectures: $architectures
|
[[ -n "$architectures" ]] && echo "Architectures: $architectures"
|
||||||
Signed-By: /etc/apt/keyrings/${name}.gpg
|
echo "Signed-By: /etc/apt/keyrings/${name}.gpg"
|
||||||
EOF
|
} >/etc/apt/sources.list.d/${name}.sources
|
||||||
|
|
||||||
# Use cached apt update
|
|
||||||
local apt_cache_file="/var/cache/apt-update-timestamp"
|
|
||||||
local current_time=$(date +%s)
|
|
||||||
local last_update=0
|
|
||||||
|
|
||||||
if [[ -f "$apt_cache_file" ]]; then
|
|
||||||
last_update=$(cat "$apt_cache_file" 2>/dev/null || echo 0)
|
|
||||||
fi
|
|
||||||
|
|
||||||
# For repo changes, always update but respect short-term cache (30s)
|
|
||||||
if ((current_time - last_update > 30)); then
|
|
||||||
$STD apt update
|
$STD apt update
|
||||||
echo "$current_time" >"$apt_cache_file"
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
@@ -1415,7 +1403,7 @@ verify_gpg_fingerprint() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# EXISTING FUNCTIONS
|
# INSTALL FUNCTIONS
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
@@ -1517,7 +1505,7 @@ check_for_gh_release() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
msg_error "No update available: ${app} is not installed!"
|
msg_ok "No update available: ${app} is already on pinned version (${current})"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -1543,9 +1531,10 @@ check_for_gh_release() {
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
create_self_signed_cert() {
|
create_self_signed_cert() {
|
||||||
local APP_NAME="${1:-${APPLICATION}}"
|
local APP_NAME="${1:-${APPLICATION}}"
|
||||||
local CERT_DIR="/etc/ssl/${APP_NAME}"
|
local APP_NAME_LC=$(echo "${APP_NAME,,}" | tr -d ' ')
|
||||||
local CERT_KEY="${CERT_DIR}/${APP_NAME}.key"
|
local CERT_DIR="/etc/ssl/${APP_NAME_LC}"
|
||||||
local CERT_CRT="${CERT_DIR}/${APP_NAME}.crt"
|
local CERT_KEY="${CERT_DIR}/${APP_NAME_LC}.key"
|
||||||
|
local CERT_CRT="${CERT_DIR}/${APP_NAME_LC}.crt"
|
||||||
|
|
||||||
if [[ -f "$CERT_CRT" && -f "$CERT_KEY" ]]; then
|
if [[ -f "$CERT_CRT" && -f "$CERT_KEY" ]]; then
|
||||||
return 0
|
return 0
|
||||||
@@ -1559,7 +1548,8 @@ create_self_signed_cert() {
|
|||||||
|
|
||||||
mkdir -p "$CERT_DIR"
|
mkdir -p "$CERT_DIR"
|
||||||
$STD openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 \
|
$STD openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 \
|
||||||
-subj "/C=US/ST=State/L=City/O=Organization/CN=${APP_NAME}" \
|
-subj "/CN=${APP_NAME}" \
|
||||||
|
-addext "subjectAltName=DNS:${APP_NAME}" \
|
||||||
-keyout "$CERT_KEY" \
|
-keyout "$CERT_KEY" \
|
||||||
-out "$CERT_CRT" || {
|
-out "$CERT_CRT" || {
|
||||||
msg_error "Failed to create self-signed certificate"
|
msg_error "Failed to create self-signed certificate"
|
||||||
@@ -2794,11 +2784,19 @@ function setup_java() {
|
|||||||
INSTALLED_VERSION=$(dpkg -l 2>/dev/null | awk '/temurin-.*-jdk/{print $2}' | grep -oP 'temurin-\K[0-9]+' | head -n1 || echo "")
|
INSTALLED_VERSION=$(dpkg -l 2>/dev/null | awk '/temurin-.*-jdk/{print $2}' | grep -oP 'temurin-\K[0-9]+' | head -n1 || echo "")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Validate INSTALLED_VERSION is not empty if matched
|
# Validate INSTALLED_VERSION is not empty if JDK package found
|
||||||
local JDK_COUNT=$(dpkg -l 2>/dev/null | grep -c "temurin-.*-jdk" || echo "0")
|
local JDK_COUNT=0
|
||||||
if [[ -z "$INSTALLED_VERSION" && "$JDK_COUNT" -gt 0 ]]; then
|
JDK_COUNT=$(dpkg -l 2>/dev/null | grep -c "temurin-.*-jdk" || true)
|
||||||
msg_warn "Found Temurin JDK but cannot determine version"
|
if [[ -z "$INSTALLED_VERSION" && "${JDK_COUNT:-0}" -gt 0 ]]; then
|
||||||
INSTALLED_VERSION="0"
|
msg_warn "Found Temurin JDK but cannot determine version - attempting reinstall"
|
||||||
|
# Try to get actual package name for purge
|
||||||
|
local OLD_PACKAGE
|
||||||
|
OLD_PACKAGE=$(dpkg -l 2>/dev/null | awk '/temurin-.*-jdk/{print $2}' | head -n1 || echo "")
|
||||||
|
if [[ -n "$OLD_PACKAGE" ]]; then
|
||||||
|
msg_info "Removing existing package: $OLD_PACKAGE"
|
||||||
|
$STD apt purge -y "$OLD_PACKAGE" || true
|
||||||
|
fi
|
||||||
|
INSTALLED_VERSION="" # Reset to trigger fresh install
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Scenario 1: Already at correct version
|
# Scenario 1: Already at correct version
|
||||||
@@ -2946,9 +2944,16 @@ setup_mariadb() {
|
|||||||
# Resolve "latest" to actual version
|
# Resolve "latest" to actual version
|
||||||
if [[ "$MARIADB_VERSION" == "latest" ]]; then
|
if [[ "$MARIADB_VERSION" == "latest" ]]; then
|
||||||
if ! curl -fsI --max-time 10 http://mirror.mariadb.org/repo/ >/dev/null 2>&1; then
|
if ! curl -fsI --max-time 10 http://mirror.mariadb.org/repo/ >/dev/null 2>&1; then
|
||||||
msg_warn "MariaDB mirror not reachable - trying cached package list fallback"
|
msg_warn "MariaDB mirror not reachable - trying mariadb_repo_setup fallback"
|
||||||
# Fallback: try to use a known stable version
|
# Try using official mariadb_repo_setup script as fallback
|
||||||
MARIADB_VERSION="12.0"
|
if curl -fsSL --max-time 15 https://r.mariadb.com/downloads/mariadb_repo_setup 2>/dev/null | bash -s -- --skip-verify >/dev/null 2>&1; then
|
||||||
|
msg_ok "MariaDB repository configured via mariadb_repo_setup"
|
||||||
|
# Extract version from configured repo
|
||||||
|
MARIADB_VERSION=$(grep -oP 'repo/\K[0-9]+\.[0-9]+\.[0-9]+' /etc/apt/sources.list.d/mariadb.list 2>/dev/null | head -n1 || echo "12.2")
|
||||||
|
else
|
||||||
|
msg_warn "mariadb_repo_setup failed - using hardcoded fallback version"
|
||||||
|
MARIADB_VERSION="12.2"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
MARIADB_VERSION=$(curl -fsSL --max-time 15 http://mirror.mariadb.org/repo/ 2>/dev/null |
|
MARIADB_VERSION=$(curl -fsSL --max-time 15 http://mirror.mariadb.org/repo/ 2>/dev/null |
|
||||||
grep -Eo '[0-9]+\.[0-9]+\.[0-9]+/' |
|
grep -Eo '[0-9]+\.[0-9]+\.[0-9]+/' |
|
||||||
@@ -2958,8 +2963,14 @@ setup_mariadb() {
|
|||||||
head -n1 || echo "")
|
head -n1 || echo "")
|
||||||
|
|
||||||
if [[ -z "$MARIADB_VERSION" ]]; then
|
if [[ -z "$MARIADB_VERSION" ]]; then
|
||||||
msg_warn "Could not parse latest GA MariaDB version from mirror - using fallback"
|
msg_warn "Could not parse latest GA MariaDB version from mirror - trying mariadb_repo_setup"
|
||||||
MARIADB_VERSION="12.0"
|
if curl -fsSL --max-time 15 https://r.mariadb.com/downloads/mariadb_repo_setup 2>/dev/null | bash -s -- --skip-verify >/dev/null 2>&1; then
|
||||||
|
msg_ok "MariaDB repository configured via mariadb_repo_setup"
|
||||||
|
MARIADB_VERSION=$(grep -oP 'repo/\K[0-9]+\.[0-9]+\.[0-9]+' /etc/apt/sources.list.d/mariadb.list 2>/dev/null | head -n1 || echo "12.2")
|
||||||
|
else
|
||||||
|
msg_warn "mariadb_repo_setup failed - using hardcoded fallback version"
|
||||||
|
MARIADB_VERSION="12.2"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -3060,6 +3071,86 @@ setup_mariadb() {
|
|||||||
msg_ok "Setup MariaDB $MARIADB_VERSION"
|
msg_ok "Setup MariaDB $MARIADB_VERSION"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Creates MariaDB database with user, charset and optional extra grants/modes
|
||||||
|
#
|
||||||
|
# Description:
|
||||||
|
# - Generates password if empty
|
||||||
|
# - Creates database with utf8mb4_unicode_ci
|
||||||
|
# - Creates local user with password
|
||||||
|
# - Grants full access to this DB
|
||||||
|
# - Optional: apply extra GRANT statements (comma-separated)
|
||||||
|
# - Optional: apply custom GLOBAL sql_mode
|
||||||
|
# - Saves credentials to file
|
||||||
|
# - Exports variables for use in calling script
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# MARIADB_DB_NAME="myapp_db" MARIADB_DB_USER="myapp_user" setup_mariadb_db
|
||||||
|
# MARIADB_DB_NAME="domain_monitor" MARIADB_DB_USER="domainmonitor" setup_mariadb_db
|
||||||
|
# MARIADB_DB_NAME="myapp" MARIADB_DB_USER="myapp" MARIADB_DB_EXTRA_GRANTS="GRANT SELECT ON \`mysql\`.\`time_zone_name\`" setup_mariadb_db
|
||||||
|
# MARIADB_DB_NAME="ghostfolio" MARIADB_DB_USER="ghostfolio" MARIADB_DB_SQL_MODE="" setup_mariadb_db
|
||||||
|
#
|
||||||
|
# Variables:
|
||||||
|
# MARIADB_DB_NAME - Database name (required)
|
||||||
|
# MARIADB_DB_USER - Database user (required)
|
||||||
|
# MARIADB_DB_PASS - User password (optional, auto-generated if empty)
|
||||||
|
# MARIADB_DB_EXTRA_GRANTS - Comma-separated GRANT statements (optional)
|
||||||
|
# Example: "GRANT SELECT ON \`mysql\`.\`time_zone_name\`"
|
||||||
|
# MARIADB_DB_SQL_MODE - Optional global sql_mode override (e.g. "", "STRICT_TRANS_TABLES")
|
||||||
|
# MARIADB_DB_CREDS_FILE - Credentials file path (optional, default: ~/${APPLICATION}.creds)
|
||||||
|
#
|
||||||
|
# Exports:
|
||||||
|
# MARIADB_DB_NAME, MARIADB_DB_USER, MARIADB_DB_PASS
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function setup_mariadb_db() {
|
||||||
|
if [[ -z "${MARIADB_DB_NAME:-}" || -z "${MARIADB_DB_USER:-}" ]]; then
|
||||||
|
msg_error "MARIADB_DB_NAME and MARIADB_DB_USER must be set before calling setup_mariadb_db"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${MARIADB_DB_PASS:-}" ]]; then
|
||||||
|
MARIADB_DB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg_info "Setting up MariaDB Database"
|
||||||
|
|
||||||
|
$STD mariadb -u root -e "CREATE DATABASE \`$MARIADB_DB_NAME\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
|
||||||
|
$STD mariadb -u root -e "CREATE USER '$MARIADB_DB_USER'@'localhost' IDENTIFIED BY '$MARIADB_DB_PASS';"
|
||||||
|
$STD mariadb -u root -e "GRANT ALL ON \`$MARIADB_DB_NAME\`.* TO '$MARIADB_DB_USER'@'localhost';"
|
||||||
|
|
||||||
|
# Optional extra grants
|
||||||
|
if [[ -n "${MARIADB_DB_EXTRA_GRANTS:-}" ]]; then
|
||||||
|
IFS=',' read -ra G_LIST <<<"${MARIADB_DB_EXTRA_GRANTS:-}"
|
||||||
|
for g in "${G_LIST[@]}"; do
|
||||||
|
g=$(echo "$g" | xargs)
|
||||||
|
$STD mariadb -u root -e "$g TO '$MARIADB_DB_USER'@'localhost';"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Optional sql_mode override
|
||||||
|
if [[ -n "${MARIADB_DB_SQL_MODE:-}" ]]; then
|
||||||
|
$STD mariadb -u root -e "SET GLOBAL sql_mode='${MARIADB_DB_SQL_MODE:-}';"
|
||||||
|
fi
|
||||||
|
|
||||||
|
$STD mariadb -u root -e "FLUSH PRIVILEGES;"
|
||||||
|
|
||||||
|
local app_name="${APPLICATION,,}"
|
||||||
|
local CREDS_FILE="${MARIADB_DB_CREDS_FILE:-${HOME}/${app_name}.creds}"
|
||||||
|
{
|
||||||
|
echo "MariaDB Credentials"
|
||||||
|
echo "Database: $MARIADB_DB_NAME"
|
||||||
|
echo "User: $MARIADB_DB_USER"
|
||||||
|
echo "Password: $MARIADB_DB_PASS"
|
||||||
|
} >>"$CREDS_FILE"
|
||||||
|
|
||||||
|
msg_ok "Set up MariaDB Database"
|
||||||
|
|
||||||
|
export MARIADB_DB_NAME
|
||||||
|
export MARIADB_DB_USER
|
||||||
|
export MARIADB_DB_PASS
|
||||||
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Installs or updates MongoDB to specified major version.
|
# Installs or updates MongoDB to specified major version.
|
||||||
#
|
#
|
||||||
@@ -3154,7 +3245,6 @@ function setup_mongodb() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Verify MongoDB was installed correctly
|
|
||||||
if ! command -v mongod >/dev/null 2>&1; then
|
if ! command -v mongod >/dev/null 2>&1; then
|
||||||
msg_error "MongoDB binary not found after installation"
|
msg_error "MongoDB binary not found after installation"
|
||||||
return 1
|
return 1
|
||||||
@@ -3330,12 +3420,12 @@ EOF
|
|||||||
# - Optionally installs or updates global npm modules
|
# - Optionally installs or updates global npm modules
|
||||||
#
|
#
|
||||||
# Variables:
|
# Variables:
|
||||||
# NODE_VERSION - Node.js version to install (default: 22)
|
# NODE_VERSION - Node.js version to install (default: 24 LTS)
|
||||||
# NODE_MODULE - Comma-separated list of global modules (e.g. "yarn,@vue/cli@5.0.0")
|
# NODE_MODULE - Comma-separated list of global modules (e.g. "yarn,@vue/cli@5.0.0")
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
function setup_nodejs() {
|
function setup_nodejs() {
|
||||||
local NODE_VERSION="${NODE_VERSION:-22}"
|
local NODE_VERSION="${NODE_VERSION:-24}"
|
||||||
local NODE_MODULE="${NODE_MODULE:-}"
|
local NODE_MODULE="${NODE_MODULE:-}"
|
||||||
|
|
||||||
# ALWAYS clean up legacy installations first (nvm, etc.) to prevent conflicts
|
# ALWAYS clean up legacy installations first (nvm, etc.) to prevent conflicts
|
||||||
@@ -3397,14 +3487,11 @@ function setup_nodejs() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# CRITICAL: Force APT cache refresh AFTER repository setup
|
# Force APT cache refresh after repository setup
|
||||||
# This ensures NodeSource is the only nodejs source in APT cache
|
|
||||||
$STD apt update
|
$STD apt update
|
||||||
|
|
||||||
# Install dependencies (NodeSource is now the only nodejs source)
|
|
||||||
ensure_dependencies curl ca-certificates gnupg
|
ensure_dependencies curl ca-certificates gnupg
|
||||||
|
|
||||||
# Install Node.js from NodeSource
|
|
||||||
install_packages_with_retry "nodejs" || {
|
install_packages_with_retry "nodejs" || {
|
||||||
msg_error "Failed to install Node.js ${NODE_VERSION} from NodeSource"
|
msg_error "Failed to install Node.js ${NODE_VERSION} from NodeSource"
|
||||||
return 1
|
return 1
|
||||||
@@ -3555,59 +3642,57 @@ function setup_php() {
|
|||||||
local CURRENT_PHP=""
|
local CURRENT_PHP=""
|
||||||
CURRENT_PHP=$(is_tool_installed "php" 2>/dev/null) || true
|
CURRENT_PHP=$(is_tool_installed "php" 2>/dev/null) || true
|
||||||
|
|
||||||
# Scenario 1: Already at target version - just update packages
|
# Remove conflicting PHP version before pinning
|
||||||
if [[ -n "$CURRENT_PHP" && "$CURRENT_PHP" == "$PHP_VERSION" ]]; then
|
|
||||||
msg_info "Update PHP $PHP_VERSION"
|
|
||||||
|
|
||||||
# Ensure Sury repo is available
|
|
||||||
if [[ ! -f /etc/apt/sources.list.d/php.sources ]]; then
|
|
||||||
manage_tool_repository "php" "$PHP_VERSION" "" "https://packages.sury.org/debsuryorg-archive-keyring.deb" || {
|
|
||||||
msg_error "Failed to setup PHP repository"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
fi
|
|
||||||
|
|
||||||
ensure_apt_working || return 1
|
|
||||||
|
|
||||||
# Perform upgrade with retry logic (non-fatal if fails)
|
|
||||||
upgrade_packages_with_retry "php${PHP_VERSION}" || true
|
|
||||||
|
|
||||||
cache_installed_version "php" "$PHP_VERSION"
|
|
||||||
msg_ok "Update PHP $PHP_VERSION"
|
|
||||||
else
|
|
||||||
# Scenario 2: Different version installed - clean upgrade
|
|
||||||
if [[ -n "$CURRENT_PHP" && "$CURRENT_PHP" != "$PHP_VERSION" ]]; then
|
if [[ -n "$CURRENT_PHP" && "$CURRENT_PHP" != "$PHP_VERSION" ]]; then
|
||||||
msg_info "Upgrade PHP from $CURRENT_PHP to $PHP_VERSION"
|
msg_info "Removing conflicting PHP ${CURRENT_PHP} (need ${PHP_VERSION})"
|
||||||
# Stop and disable ALL PHP-FPM versions
|
|
||||||
stop_all_services "php.*-fpm"
|
stop_all_services "php.*-fpm"
|
||||||
remove_old_tool_version "php"
|
$STD apt purge -y "php*" 2>/dev/null || true
|
||||||
else
|
$STD apt autoremove -y 2>/dev/null || true
|
||||||
msg_info "Setup PHP $PHP_VERSION"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Prepare repository (cleanup + validation)
|
# NOW create pinning for the desired version
|
||||||
|
mkdir -p /etc/apt/preferences.d
|
||||||
|
cat <<EOF >/etc/apt/preferences.d/php-pin
|
||||||
|
Package: php${PHP_VERSION}*
|
||||||
|
Pin: version ${PHP_VERSION}.*
|
||||||
|
Pin-Priority: 1001
|
||||||
|
|
||||||
|
Package: php[0-9].*
|
||||||
|
Pin: release o=packages.sury.org-php
|
||||||
|
Pin-Priority: -1
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Setup repository
|
||||||
prepare_repository_setup "php" "deb.sury.org-php" || {
|
prepare_repository_setup "php" "deb.sury.org-php" || {
|
||||||
msg_error "Failed to prepare PHP repository"
|
msg_error "Failed to prepare PHP repository"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Setup Sury repository
|
|
||||||
manage_tool_repository "php" "$PHP_VERSION" "" "https://packages.sury.org/debsuryorg-archive-keyring.deb" || {
|
manage_tool_repository "php" "$PHP_VERSION" "" "https://packages.sury.org/debsuryorg-archive-keyring.deb" || {
|
||||||
msg_error "Failed to setup PHP repository"
|
msg_error "Failed to setup PHP repository"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
ensure_apt_working || return 1
|
ensure_apt_working || return 1
|
||||||
|
$STD apt update
|
||||||
|
|
||||||
|
# Get available PHP version from repository
|
||||||
|
local AVAILABLE_PHP_VERSION=""
|
||||||
|
AVAILABLE_PHP_VERSION=$(apt-cache show "php${PHP_VERSION}" 2>/dev/null | grep -m1 "^Version:" | awk '{print $2}' | cut -d- -f1) || true
|
||||||
|
|
||||||
|
if [[ -z "$AVAILABLE_PHP_VERSION" ]]; then
|
||||||
|
msg_error "PHP ${PHP_VERSION} not found in configured repositories"
|
||||||
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Build module list
|
# Build module list - without version pinning (preferences.d handles it)
|
||||||
local MODULE_LIST="php${PHP_VERSION}"
|
local MODULE_LIST="php${PHP_VERSION}"
|
||||||
|
|
||||||
IFS=',' read -ra MODULES <<<"$COMBINED_MODULES"
|
IFS=',' read -ra MODULES <<<"$COMBINED_MODULES"
|
||||||
for mod in "${MODULES[@]}"; do
|
for mod in "${MODULES[@]}"; do
|
||||||
if apt-cache show "php${PHP_VERSION}-${mod}" >/dev/null 2>&1; then
|
|
||||||
MODULE_LIST+=" php${PHP_VERSION}-${mod}"
|
MODULE_LIST+=" php${PHP_VERSION}-${mod}"
|
||||||
fi
|
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ "$PHP_FPM" == "YES" ]]; then
|
if [[ "$PHP_FPM" == "YES" ]]; then
|
||||||
MODULE_LIST+=" php${PHP_VERSION}-fpm"
|
MODULE_LIST+=" php${PHP_VERSION}-fpm"
|
||||||
fi
|
fi
|
||||||
@@ -3615,18 +3700,52 @@ function setup_php() {
|
|||||||
# install apache2 with PHP support if requested
|
# install apache2 with PHP support if requested
|
||||||
if [[ "$PHP_APACHE" == "YES" ]]; then
|
if [[ "$PHP_APACHE" == "YES" ]]; then
|
||||||
if ! dpkg -l 2>/dev/null | grep -q "libapache2-mod-php${PHP_VERSION}"; then
|
if ! dpkg -l 2>/dev/null | grep -q "libapache2-mod-php${PHP_VERSION}"; then
|
||||||
install_packages_with_retry "apache2" "libapache2-mod-php${PHP_VERSION}" || {
|
msg_info "Installing Apache with PHP ${PHP_VERSION} module"
|
||||||
msg_error "Failed to install Apache with PHP module"
|
install_packages_with_retry "apache2" || {
|
||||||
|
msg_error "Failed to install Apache"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
install_packages_with_retry "libapache2-mod-php${PHP_VERSION}" || {
|
||||||
|
msg_warn "Failed to install libapache2-mod-php${PHP_VERSION}, continuing without Apache module"
|
||||||
|
}
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Install PHP packages with retry logic
|
# Install PHP packages (pinning via preferences.d ensures correct version)
|
||||||
install_packages_with_retry $MODULE_LIST || {
|
msg_info "Installing PHP ${PHP_VERSION} packages"
|
||||||
msg_error "Failed to install PHP packages"
|
if ! install_packages_with_retry $MODULE_LIST; then
|
||||||
|
msg_warn "Failed to install PHP packages, attempting individual installation"
|
||||||
|
|
||||||
|
# Install main package first (critical)
|
||||||
|
install_packages_with_retry "php${PHP_VERSION}" || {
|
||||||
|
msg_error "Failed to install php${PHP_VERSION}"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Try to install Apache module individually if requested
|
||||||
|
if [[ "$PHP_APACHE" == "YES" ]]; then
|
||||||
|
install_packages_with_retry "libapache2-mod-php${PHP_VERSION}" || {
|
||||||
|
msg_warn "Could not install libapache2-mod-php${PHP_VERSION}"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Try to install modules individually - skip those that don't exist
|
||||||
|
for pkg in "${MODULES[@]}"; do
|
||||||
|
if apt-cache search "^php${PHP_VERSION}-${pkg}\$" 2>/dev/null | grep -q "^php${PHP_VERSION}-${pkg}"; then
|
||||||
|
install_packages_with_retry "php${PHP_VERSION}-${pkg}" || {
|
||||||
|
msg_warn "Could not install php${PHP_VERSION}-${pkg}"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$PHP_FPM" == "YES" ]]; then
|
||||||
|
if apt-cache search "^php${PHP_VERSION}-fpm\$" 2>/dev/null | grep -q "^php${PHP_VERSION}-fpm"; then
|
||||||
|
install_packages_with_retry "php${PHP_VERSION}-fpm" || {
|
||||||
|
msg_warn "Could not install php${PHP_VERSION}-fpm"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
cache_installed_version "php" "$PHP_VERSION"
|
cache_installed_version "php" "$PHP_VERSION"
|
||||||
|
|
||||||
# Patch all relevant php.ini files
|
# Patch all relevant php.ini files
|
||||||
@@ -3662,7 +3781,23 @@ function setup_php() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
msg_ok "Setup PHP $PHP_VERSION"
|
# Verify PHP installation - critical check
|
||||||
|
if ! command -v php >/dev/null 2>&1; then
|
||||||
|
msg_error "PHP installation verification failed - php command not found"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local INSTALLED_VERSION=$(php -v 2>/dev/null | awk '/^PHP/{print $2}' | cut -d. -f1,2)
|
||||||
|
|
||||||
|
if [[ "$INSTALLED_VERSION" != "$PHP_VERSION" ]]; then
|
||||||
|
msg_error "PHP version mismatch: requested ${PHP_VERSION} but got ${INSTALLED_VERSION}"
|
||||||
|
msg_error "This indicates a critical package installation issue"
|
||||||
|
# Don't cache wrong version
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cache_installed_version "php" "$INSTALLED_VERSION"
|
||||||
|
msg_ok "Setup PHP ${INSTALLED_VERSION}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
@@ -3733,11 +3868,14 @@ function setup_postgresql() {
|
|||||||
local SUITE
|
local SUITE
|
||||||
case "$DISTRO_CODENAME" in
|
case "$DISTRO_CODENAME" in
|
||||||
trixie | forky | sid)
|
trixie | forky | sid)
|
||||||
|
|
||||||
if verify_repo_available "https://apt.postgresql.org/pub/repos/apt" "trixie-pgdg"; then
|
if verify_repo_available "https://apt.postgresql.org/pub/repos/apt" "trixie-pgdg"; then
|
||||||
SUITE="trixie-pgdg"
|
SUITE="trixie-pgdg"
|
||||||
|
|
||||||
else
|
else
|
||||||
SUITE="bookworm-pgdg"
|
SUITE="bookworm-pgdg"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
SUITE=$(get_fallback_suite "$DISTRO_ID" "$DISTRO_CODENAME" "https://apt.postgresql.org/pub/repos/apt")
|
SUITE=$(get_fallback_suite "$DISTRO_ID" "$DISTRO_CODENAME" "https://apt.postgresql.org/pub/repos/apt")
|
||||||
@@ -3819,6 +3957,104 @@ function setup_postgresql() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Creates PostgreSQL database with user and optional extensions
|
||||||
|
#
|
||||||
|
# Description:
|
||||||
|
# - Creates PostgreSQL role with login and password
|
||||||
|
# - Creates database with UTF8 encoding and template0
|
||||||
|
# - Installs optional extensions (postgis, pgvector, etc.)
|
||||||
|
# - Configures ALTER ROLE settings for Django/Rails compatibility
|
||||||
|
# - Saves credentials to file
|
||||||
|
# - Exports variables for use in calling script
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# PG_DB_NAME="myapp_db" PG_DB_USER="myapp_user" setup_postgresql_db
|
||||||
|
# PG_DB_NAME="immich" PG_DB_USER="immich" PG_DB_EXTENSIONS="pgvector" setup_postgresql_db
|
||||||
|
# PG_DB_NAME="ghostfolio" PG_DB_USER="ghostfolio" PG_DB_GRANT_SUPERUSER="true" setup_postgresql_db
|
||||||
|
# PG_DB_NAME="adventurelog" PG_DB_USER="adventurelog" PG_DB_EXTENSIONS="postgis" setup_postgresql_db
|
||||||
|
#
|
||||||
|
# Variables:
|
||||||
|
# PG_DB_NAME - Database name (required)
|
||||||
|
# PG_DB_USER - Database user (required)
|
||||||
|
# PG_DB_PASS - Database password (optional, auto-generated if empty)
|
||||||
|
# PG_DB_EXTENSIONS - Comma-separated list of extensions (optional, e.g. "postgis,pgvector")
|
||||||
|
# PG_DB_GRANT_SUPERUSER - Grant SUPERUSER privilege (optional, "true" to enable, security risk!)
|
||||||
|
# PG_DB_SCHEMA_PERMS - Grant schema-level permissions (optional, "true" to enable)
|
||||||
|
# PG_DB_SKIP_ALTER_ROLE - Skip ALTER ROLE settings (optional, "true" to skip)
|
||||||
|
# PG_DB_CREDS_FILE - Credentials file path (optional, default: ~/${APPLICATION}.creds)
|
||||||
|
#
|
||||||
|
# Exports:
|
||||||
|
# PG_DB_NAME, PG_DB_USER, PG_DB_PASS - For use in calling script
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function setup_postgresql_db() {
|
||||||
|
# Validation
|
||||||
|
if [[ -z "${PG_DB_NAME:-}" || -z "${PG_DB_USER:-}" ]]; then
|
||||||
|
msg_error "PG_DB_NAME and PG_DB_USER must be set before calling setup_postgresql_db"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate password if not provided
|
||||||
|
if [[ -z "${PG_DB_PASS:-}" ]]; then
|
||||||
|
PG_DB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg_info "Setting up PostgreSQL Database"
|
||||||
|
$STD sudo -u postgres psql -c "CREATE ROLE $PG_DB_USER WITH LOGIN PASSWORD '$PG_DB_PASS';"
|
||||||
|
$STD sudo -u postgres psql -c "CREATE DATABASE $PG_DB_NAME WITH OWNER $PG_DB_USER ENCODING 'UTF8' TEMPLATE template0;"
|
||||||
|
|
||||||
|
# Install extensions (comma-separated)
|
||||||
|
if [[ -n "${PG_DB_EXTENSIONS:-}" ]]; then
|
||||||
|
IFS=',' read -ra EXT_LIST <<<"${PG_DB_EXTENSIONS:-}"
|
||||||
|
for ext in "${EXT_LIST[@]}"; do
|
||||||
|
ext=$(echo "$ext" | xargs) # Trim whitespace
|
||||||
|
$STD sudo -u postgres psql -d "$PG_DB_NAME" -c "CREATE EXTENSION IF NOT EXISTS $ext;"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ALTER ROLE settings for Django/Rails compatibility (unless skipped)
|
||||||
|
if [[ "${PG_DB_SKIP_ALTER_ROLE:-}" != "true" ]]; then
|
||||||
|
$STD sudo -u postgres psql -c "ALTER ROLE $PG_DB_USER SET client_encoding TO 'utf8';"
|
||||||
|
$STD sudo -u postgres psql -c "ALTER ROLE $PG_DB_USER SET default_transaction_isolation TO 'read committed';"
|
||||||
|
$STD sudo -u postgres psql -c "ALTER ROLE $PG_DB_USER SET timezone TO 'UTC';"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Schema permissions (if requested)
|
||||||
|
if [[ "${PG_DB_SCHEMA_PERMS:-}" == "true" ]]; then
|
||||||
|
$STD sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE $PG_DB_NAME TO $PG_DB_USER;"
|
||||||
|
$STD sudo -u postgres psql -c "ALTER USER $PG_DB_USER CREATEDB;"
|
||||||
|
$STD sudo -u postgres psql -d "$PG_DB_NAME" -c "GRANT ALL ON SCHEMA public TO $PG_DB_USER;"
|
||||||
|
$STD sudo -u postgres psql -d "$PG_DB_NAME" -c "GRANT CREATE ON SCHEMA public TO $PG_DB_USER;"
|
||||||
|
$STD sudo -u postgres psql -d "$PG_DB_NAME" -c "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO $PG_DB_USER;"
|
||||||
|
$STD sudo -u postgres psql -d "$PG_DB_NAME" -c "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO $PG_DB_USER;"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Superuser grant (if requested - WARNING!)
|
||||||
|
if [[ "${PG_DB_GRANT_SUPERUSER:-}" == "true" ]]; then
|
||||||
|
msg_warn "Granting SUPERUSER privilege (security risk!)"
|
||||||
|
$STD sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE $PG_DB_NAME to $PG_DB_USER;"
|
||||||
|
$STD sudo -u postgres psql -c "ALTER USER $PG_DB_USER WITH SUPERUSER;"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Save credentials
|
||||||
|
local app_name="${APPLICATION,,}"
|
||||||
|
local CREDS_FILE="${PG_DB_CREDS_FILE:-${HOME}/${app_name}.creds}"
|
||||||
|
{
|
||||||
|
echo "PostgreSQL Credentials"
|
||||||
|
echo "Database: $PG_DB_NAME"
|
||||||
|
echo "User: $PG_DB_USER"
|
||||||
|
echo "Password: $PG_DB_PASS"
|
||||||
|
} >>"$CREDS_FILE"
|
||||||
|
|
||||||
|
msg_ok "Set up PostgreSQL Database"
|
||||||
|
|
||||||
|
# Export for use in calling script
|
||||||
|
export PG_DB_NAME
|
||||||
|
export PG_DB_USER
|
||||||
|
export PG_DB_PASS
|
||||||
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Installs rbenv and ruby-build, installs Ruby and optionally Rails.
|
# Installs rbenv and ruby-build, installs Ruby and optionally Rails.
|
||||||
#
|
#
|
||||||
@@ -4172,12 +4408,28 @@ function setup_rust() {
|
|||||||
}
|
}
|
||||||
export PATH="$CARGO_BIN:$PATH"
|
export PATH="$CARGO_BIN:$PATH"
|
||||||
echo 'export PATH="$HOME/.cargo/bin:$PATH"' >>"$HOME/.profile"
|
echo 'export PATH="$HOME/.cargo/bin:$PATH"' >>"$HOME/.profile"
|
||||||
|
|
||||||
|
# Verify installation
|
||||||
|
if ! command -v rustc >/dev/null 2>&1; then
|
||||||
|
msg_error "Rust binary not found after installation"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
local RUST_VERSION=$(rustc --version 2>/dev/null | awk '{print $2}')
|
local RUST_VERSION=$(rustc --version 2>/dev/null | awk '{print $2}')
|
||||||
|
if [[ -z "$RUST_VERSION" ]]; then
|
||||||
|
msg_error "Failed to determine Rust version"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
cache_installed_version "rust" "$RUST_VERSION"
|
cache_installed_version "rust" "$RUST_VERSION"
|
||||||
msg_ok "Setup Rust $RUST_VERSION"
|
msg_ok "Setup Rust $RUST_VERSION"
|
||||||
else
|
else
|
||||||
# Scenario 2: Rustup already installed - update/maintain
|
# Scenario 2: Rustup already installed - update/maintain
|
||||||
msg_info "Update Rust ($RUST_TOOLCHAIN)"
|
msg_info "Update Rust ($RUST_TOOLCHAIN)"
|
||||||
|
|
||||||
|
# Ensure default toolchain is set
|
||||||
|
$STD rustup default "$RUST_TOOLCHAIN" 2>/dev/null || {
|
||||||
|
# If default fails, install the toolchain first
|
||||||
$STD rustup install "$RUST_TOOLCHAIN" || {
|
$STD rustup install "$RUST_TOOLCHAIN" || {
|
||||||
msg_error "Failed to install Rust toolchain $RUST_TOOLCHAIN"
|
msg_error "Failed to install Rust toolchain $RUST_TOOLCHAIN"
|
||||||
return 1
|
return 1
|
||||||
@@ -4186,17 +4438,33 @@ function setup_rust() {
|
|||||||
msg_error "Failed to set default Rust toolchain"
|
msg_error "Failed to set default Rust toolchain"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
$STD rustup update "$RUST_TOOLCHAIN" || true
|
}
|
||||||
|
|
||||||
|
# Update to latest patch version
|
||||||
|
$STD rustup update "$RUST_TOOLCHAIN" </dev/null || true
|
||||||
|
|
||||||
|
# Ensure PATH is updated for current shell session
|
||||||
|
export PATH="$CARGO_BIN:$PATH"
|
||||||
|
|
||||||
local RUST_VERSION=$(rustc --version 2>/dev/null | awk '{print $2}')
|
local RUST_VERSION=$(rustc --version 2>/dev/null | awk '{print $2}')
|
||||||
|
if [[ -z "$RUST_VERSION" ]]; then
|
||||||
|
msg_error "Failed to determine Rust version after update"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
cache_installed_version "rust" "$RUST_VERSION"
|
cache_installed_version "rust" "$RUST_VERSION"
|
||||||
msg_ok "Update Rust $RUST_VERSION"
|
msg_ok "Update Rust $RUST_VERSION"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Install global crates
|
# Install global crates
|
||||||
if [[ -n "$RUST_CRATES" ]]; then
|
if [[ -n "$RUST_CRATES" ]]; then
|
||||||
|
msg_info "Processing Rust crates: $RUST_CRATES"
|
||||||
IFS=',' read -ra CRATES <<<"$RUST_CRATES"
|
IFS=',' read -ra CRATES <<<"$RUST_CRATES"
|
||||||
for crate in "${CRATES[@]}"; do
|
for crate in "${CRATES[@]}"; do
|
||||||
local NAME VER INSTALLED_VER
|
crate=$(echo "$crate" | xargs) # trim whitespace
|
||||||
|
[[ -z "$crate" ]] && continue # skip empty entries
|
||||||
|
|
||||||
|
local NAME VER INSTALLED_VER CRATE_LIST
|
||||||
if [[ "$crate" == *"@"* ]]; then
|
if [[ "$crate" == *"@"* ]]; then
|
||||||
NAME="${crate%@*}"
|
NAME="${crate%@*}"
|
||||||
VER="${crate##*@}"
|
VER="${crate##*@}"
|
||||||
@@ -4205,18 +4473,50 @@ function setup_rust() {
|
|||||||
VER=""
|
VER=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
INSTALLED_VER=$(cargo install --list 2>/dev/null | awk "/^$NAME v[0-9]/ {print \$2}" | tr -d 'v')
|
# Get list of installed crates once
|
||||||
|
CRATE_LIST=$(cargo install --list 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
# Check if already installed
|
||||||
|
if echo "$CRATE_LIST" | grep -q "^${NAME} "; then
|
||||||
|
INSTALLED_VER=$(echo "$CRATE_LIST" | grep "^${NAME} " | head -1 | awk '{print $2}' | tr -d 'v:')
|
||||||
|
|
||||||
if [[ -n "$INSTALLED_VER" ]]; then
|
|
||||||
if [[ -n "$VER" && "$VER" != "$INSTALLED_VER" ]]; then
|
if [[ -n "$VER" && "$VER" != "$INSTALLED_VER" ]]; then
|
||||||
$STD cargo install "$NAME" --version "$VER" --force
|
msg_info "Upgrading $NAME from v$INSTALLED_VER to v$VER"
|
||||||
|
$STD cargo install "$NAME" --version "$VER" --force || {
|
||||||
|
msg_error "Failed to install $NAME@$VER"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
msg_ok "Upgraded $NAME to v$VER"
|
||||||
elif [[ -z "$VER" ]]; then
|
elif [[ -z "$VER" ]]; then
|
||||||
$STD cargo install "$NAME" --force
|
msg_info "Upgrading $NAME to latest"
|
||||||
|
$STD cargo install "$NAME" --force || {
|
||||||
|
msg_error "Failed to upgrade $NAME"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
local NEW_VER=$(cargo install --list 2>/dev/null | grep "^${NAME} " | head -1 | awk '{print $2}' | tr -d 'v:')
|
||||||
|
msg_ok "Upgraded $NAME to v$NEW_VER"
|
||||||
|
else
|
||||||
|
msg_ok "$NAME v$INSTALLED_VER already installed"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
$STD cargo install "$NAME" ${VER:+--version "$VER"}
|
msg_info "Installing $NAME${VER:+@$VER}"
|
||||||
|
if [[ -n "$VER" ]]; then
|
||||||
|
$STD cargo install "$NAME" --version "$VER" || {
|
||||||
|
msg_error "Failed to install $NAME@$VER"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
msg_ok "Installed $NAME v$VER"
|
||||||
|
else
|
||||||
|
$STD cargo install "$NAME" || {
|
||||||
|
msg_error "Failed to install $NAME"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
local NEW_VER=$(cargo install --list 2>/dev/null | grep "^${NAME} " | head -1 | awk '{print $2}' | tr -d 'v:')
|
||||||
|
msg_ok "Installed $NAME v$NEW_VER"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
msg_ok "Processed Rust crates"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4353,7 +4653,9 @@ function setup_uv() {
|
|||||||
|
|
||||||
# Optional: Generate shell completions
|
# Optional: Generate shell completions
|
||||||
$STD uv generate-shell-completion bash >/etc/bash_completion.d/uv 2>/dev/null || true
|
$STD uv generate-shell-completion bash >/etc/bash_completion.d/uv 2>/dev/null || true
|
||||||
|
if [[ -d /usr/share/zsh/site-functions ]]; then
|
||||||
$STD uv generate-shell-completion zsh >/usr/share/zsh/site-functions/_uv 2>/dev/null || true
|
$STD uv generate-shell-completion zsh >/usr/share/zsh/site-functions/_uv 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
# Optional: Install specific Python version if requested
|
# Optional: Install specific Python version if requested
|
||||||
if [[ -n "${PYTHON_VERSION:-}" ]]; then
|
if [[ -n "${PYTHON_VERSION:-}" ]]; then
|
||||||
@@ -4467,3 +4769,214 @@ function setup_yq() {
|
|||||||
cache_installed_version "yq" "$FINAL_VERSION"
|
cache_installed_version "yq" "$FINAL_VERSION"
|
||||||
msg_ok "Setup yq $FINAL_VERSION"
|
msg_ok "Setup yq $FINAL_VERSION"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Docker Engine Installation and Management (All-In-One)
|
||||||
|
#
|
||||||
|
# Description:
|
||||||
|
# - Detects and migrates old Docker installations
|
||||||
|
# - Installs/Updates Docker Engine via official repository
|
||||||
|
# - Optional: Installs/Updates Portainer CE
|
||||||
|
# - Updates running containers interactively
|
||||||
|
# - Cleans up legacy repository files
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# setup_docker
|
||||||
|
# DOCKER_PORTAINER="true" setup_docker
|
||||||
|
# DOCKER_LOG_DRIVER="json-file" setup_docker
|
||||||
|
#
|
||||||
|
# Variables:
|
||||||
|
# DOCKER_PORTAINER - Install Portainer CE (optional, "true" to enable)
|
||||||
|
# DOCKER_LOG_DRIVER - Log driver (optional, default: "journald")
|
||||||
|
# DOCKER_SKIP_UPDATES - Skip container update check (optional, "true" to skip)
|
||||||
|
#
|
||||||
|
# Features:
|
||||||
|
# - Migrates from get.docker.com to repository-based installation
|
||||||
|
# - Updates Docker Engine if newer version available
|
||||||
|
# - Interactive container update with multi-select
|
||||||
|
# - Portainer installation and update support
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
function setup_docker() {
|
||||||
|
local docker_installed=false
|
||||||
|
local portainer_installed=false
|
||||||
|
|
||||||
|
# Check if Docker is already installed
|
||||||
|
if command -v docker &>/dev/null; then
|
||||||
|
docker_installed=true
|
||||||
|
DOCKER_CURRENT_VERSION=$(docker --version | grep -oP '\d+\.\d+\.\d+' | head -1)
|
||||||
|
msg_info "Docker $DOCKER_CURRENT_VERSION detected"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if Portainer is running
|
||||||
|
if docker ps --format '{{.Names}}' 2>/dev/null | grep -q '^portainer$'; then
|
||||||
|
portainer_installed=true
|
||||||
|
msg_info "Portainer container detected"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Cleanup old repository configurations
|
||||||
|
if [ -f /etc/apt/sources.list.d/docker.list ]; then
|
||||||
|
msg_info "Migrating from old Docker repository format"
|
||||||
|
rm -f /etc/apt/sources.list.d/docker.list
|
||||||
|
rm -f /etc/apt/keyrings/docker.asc
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Setup/Update Docker repository
|
||||||
|
msg_info "Setting up Docker Repository"
|
||||||
|
setup_deb822_repo \
|
||||||
|
"docker" \
|
||||||
|
"https://download.docker.com/linux/$(get_os_info id)/gpg" \
|
||||||
|
"https://download.docker.com/linux/$(get_os_info id)" \
|
||||||
|
"$(get_os_info codename)" \
|
||||||
|
"stable" \
|
||||||
|
"$(dpkg --print-architecture)"
|
||||||
|
|
||||||
|
# Install or upgrade Docker
|
||||||
|
if [ "$docker_installed" = true ]; then
|
||||||
|
msg_info "Checking for Docker updates"
|
||||||
|
DOCKER_LATEST_VERSION=$(apt-cache policy docker-ce | grep Candidate | awk '{print $2}' | cut -d':' -f2 | cut -d'-' -f1)
|
||||||
|
|
||||||
|
if [ "$DOCKER_CURRENT_VERSION" != "$DOCKER_LATEST_VERSION" ]; then
|
||||||
|
msg_info "Updating Docker $DOCKER_CURRENT_VERSION → $DOCKER_LATEST_VERSION"
|
||||||
|
$STD apt install -y --only-upgrade \
|
||||||
|
docker-ce \
|
||||||
|
docker-ce-cli \
|
||||||
|
containerd.io \
|
||||||
|
docker-buildx-plugin \
|
||||||
|
docker-compose-plugin
|
||||||
|
msg_ok "Updated Docker to $DOCKER_LATEST_VERSION"
|
||||||
|
else
|
||||||
|
msg_ok "Docker is up-to-date ($DOCKER_CURRENT_VERSION)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
msg_info "Installing Docker"
|
||||||
|
$STD apt install -y \
|
||||||
|
docker-ce \
|
||||||
|
docker-ce-cli \
|
||||||
|
containerd.io \
|
||||||
|
docker-buildx-plugin \
|
||||||
|
docker-compose-plugin
|
||||||
|
|
||||||
|
DOCKER_CURRENT_VERSION=$(docker --version | grep -oP '\d+\.\d+\.\d+' | head -1)
|
||||||
|
msg_ok "Installed Docker $DOCKER_CURRENT_VERSION"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Configure daemon.json
|
||||||
|
local log_driver="${DOCKER_LOG_DRIVER:-journald}"
|
||||||
|
mkdir -p /etc/docker
|
||||||
|
if [ ! -f /etc/docker/daemon.json ]; then
|
||||||
|
cat <<EOF >/etc/docker/daemon.json
|
||||||
|
{
|
||||||
|
"log-driver": "$log_driver"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Enable and start Docker
|
||||||
|
systemctl enable -q --now docker
|
||||||
|
|
||||||
|
# Portainer Management
|
||||||
|
if [[ "${DOCKER_PORTAINER:-}" == "true" ]]; then
|
||||||
|
if [ "$portainer_installed" = true ]; then
|
||||||
|
msg_info "Checking for Portainer updates"
|
||||||
|
PORTAINER_CURRENT=$(docker inspect portainer --format='{{.Config.Image}}' 2>/dev/null | cut -d':' -f2)
|
||||||
|
PORTAINER_LATEST=$(curl -fsSL https://registry.hub.docker.com/v2/repositories/portainer/portainer-ce/tags?page_size=100 | grep -oP '"name":"\K[0-9]+\.[0-9]+\.[0-9]+"' | head -1 | tr -d '"')
|
||||||
|
|
||||||
|
if [ "$PORTAINER_CURRENT" != "$PORTAINER_LATEST" ]; then
|
||||||
|
read -r -p "${TAB3}Update Portainer $PORTAINER_CURRENT → $PORTAINER_LATEST? <y/N> " prompt
|
||||||
|
if [[ ${prompt,,} =~ ^(y|yes)$ ]]; then
|
||||||
|
msg_info "Updating Portainer"
|
||||||
|
docker stop portainer
|
||||||
|
docker rm portainer
|
||||||
|
docker pull portainer/portainer-ce:latest
|
||||||
|
docker run -d \
|
||||||
|
-p 9000:9000 \
|
||||||
|
-p 9443:9443 \
|
||||||
|
--name=portainer \
|
||||||
|
--restart=always \
|
||||||
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
-v portainer_data:/data \
|
||||||
|
portainer/portainer-ce:latest
|
||||||
|
msg_ok "Updated Portainer to $PORTAINER_LATEST"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
msg_ok "Portainer is up-to-date ($PORTAINER_CURRENT)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
msg_info "Installing Portainer"
|
||||||
|
docker volume create portainer_data
|
||||||
|
docker run -d \
|
||||||
|
-p 9000:9000 \
|
||||||
|
-p 9443:9443 \
|
||||||
|
--name=portainer \
|
||||||
|
--restart=always \
|
||||||
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
-v portainer_data:/data \
|
||||||
|
portainer/portainer-ce:latest
|
||||||
|
|
||||||
|
LOCAL_IP=$(hostname -I | awk '{print $1}')
|
||||||
|
msg_ok "Installed Portainer (http://${LOCAL_IP}:9000)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Interactive Container Update Check
|
||||||
|
if [[ "${DOCKER_SKIP_UPDATES:-}" != "true" ]] && [ "$docker_installed" = true ]; then
|
||||||
|
msg_info "Checking for container updates"
|
||||||
|
|
||||||
|
# Get list of running containers with update status
|
||||||
|
local containers_with_updates=()
|
||||||
|
local container_info=()
|
||||||
|
local index=1
|
||||||
|
|
||||||
|
while IFS= read -r container; do
|
||||||
|
local name=$(echo "$container" | awk '{print $1}')
|
||||||
|
local image=$(echo "$container" | awk '{print $2}')
|
||||||
|
local current_digest=$(docker inspect "$name" --format='{{.Image}}' 2>/dev/null | cut -d':' -f2 | cut -c1-12)
|
||||||
|
|
||||||
|
# Pull latest image digest
|
||||||
|
docker pull "$image" >/dev/null 2>&1
|
||||||
|
local latest_digest=$(docker inspect "$image" --format='{{.Id}}' 2>/dev/null | cut -d':' -f2 | cut -c1-12)
|
||||||
|
|
||||||
|
if [ "$current_digest" != "$latest_digest" ]; then
|
||||||
|
containers_with_updates+=("$name")
|
||||||
|
container_info+=("${index}) ${name} (${image})")
|
||||||
|
((index++))
|
||||||
|
fi
|
||||||
|
done < <(docker ps --format '{{.Names}} {{.Image}}')
|
||||||
|
|
||||||
|
if [ ${#containers_with_updates[@]} -gt 0 ]; then
|
||||||
|
echo ""
|
||||||
|
echo "${TAB3}Container updates available:"
|
||||||
|
for info in "${container_info[@]}"; do
|
||||||
|
echo "${TAB3} $info"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
read -r -p "${TAB3}Select containers to update (e.g., 1,3,5 or 'all' or 'none'): " selection
|
||||||
|
|
||||||
|
if [[ ${selection,,} == "all" ]]; then
|
||||||
|
for container in "${containers_with_updates[@]}"; do
|
||||||
|
msg_info "Updating container: $container"
|
||||||
|
docker stop "$container"
|
||||||
|
docker rm "$container"
|
||||||
|
# Note: This requires the original docker run command - best to recreate via compose
|
||||||
|
msg_ok "Stopped and removed $container (please recreate with updated image)"
|
||||||
|
done
|
||||||
|
elif [[ ${selection,,} != "none" ]]; then
|
||||||
|
IFS=',' read -ra SELECTED <<<"$selection"
|
||||||
|
for num in "${SELECTED[@]}"; do
|
||||||
|
num=$(echo "$num" | xargs) # trim whitespace
|
||||||
|
if [[ "$num" =~ ^[0-9]+$ ]] && [ "$num" -ge 1 ] && [ "$num" -le "${#containers_with_updates[@]}" ]; then
|
||||||
|
container="${containers_with_updates[$((num - 1))]}"
|
||||||
|
msg_info "Updating container: $container"
|
||||||
|
docker stop "$container"
|
||||||
|
docker rm "$container"
|
||||||
|
msg_ok "Stopped and removed $container (please recreate with updated image)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
msg_ok "All containers are up-to-date"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg_ok "Docker setup completed"
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -40,5 +40,6 @@
|
|||||||
"text": "If the LXC is created Privileged, the script will automatically set up USB passthrough.",
|
"text": "If the LXC is created Privileged, the script will automatically set up USB passthrough.",
|
||||||
"type": "warning"
|
"type": "warning"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "Use `cat ~/docmost.creds` to see database credentials.",
|
"text": "Use `cat ~/docmost.creds` to see database credentials.",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -31,5 +31,6 @@
|
|||||||
"username": "helper-scripts@local.com",
|
"username": "helper-scripts@local.com",
|
||||||
"password": "helper-scripts"
|
"password": "helper-scripts"
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "Database credentials: `cat ~/dolibarr.creds`",
|
"text": "Database credentials: `cat ~/dolibarr.creds`",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "FTP server credentials: `cat ~/ftp.creds`",
|
"text": "FTP server credentials: `cat ~/ftp.creds`",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repository_url": "https://github.com/community-scripts/ProxmoxVE"
|
||||||
}
|
}
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
"text": "Admin password and database encryption key: `cat ~/duplicati.creds`",
|
"text": "Admin password and database encryption key: `cat ~/duplicati.creds`",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"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