mirror of
https://github.com/community-scripts/ProxmoxVE.git
synced 2026-03-03 22:35:53 +00:00
Compare commits
67 Commits
exit_code_
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbfffb97a5 | ||
|
|
4306b4018d | ||
|
|
3722533431 | ||
|
|
f773af17b2 | ||
|
|
56b4490554 | ||
|
|
b45842d76a | ||
|
|
ea279ace89 | ||
|
|
034061e744 | ||
|
|
dd07ba4453 | ||
|
|
380aa4bc0f | ||
|
|
aca721e9ee | ||
|
|
42e546904f | ||
|
|
4045824bf1 | ||
|
|
738cbfd1ae | ||
|
|
278c3cc2d8 | ||
|
|
14a7ac2618 | ||
|
|
a7699361c1 | ||
|
|
82a0893036 | ||
|
|
f9b59d7634 | ||
|
|
f279536eb7 | ||
|
|
8f34c7cd2e | ||
|
|
f922f669a8 | ||
|
|
2694d60faf | ||
|
|
a326994459 | ||
|
|
4c8d1ef010 | ||
|
|
86a9a0aac6 | ||
|
|
5059ed2320 | ||
|
|
df65c60fe4 | ||
|
|
cff2c90041 | ||
|
|
88d1494e46 | ||
|
|
8b62b8f3c5 | ||
|
|
cd38bc3a65 | ||
|
|
46d25645c2 | ||
|
|
3701737eff | ||
|
|
b39e296684 | ||
|
|
d8a7620c64 | ||
|
|
7d5900de18 | ||
|
|
e8b3b936df | ||
|
|
5c246310f4 | ||
|
|
bdad2cc941 | ||
|
|
f23c33fee7 | ||
|
|
5b207cf5bd | ||
|
|
f20c9e4ec9 | ||
|
|
1398ff8397 | ||
|
|
ebc3512f50 | ||
|
|
564a8136a5 | ||
|
|
00047c95b8 | ||
|
|
9849ce79a7 | ||
|
|
cea9858193 | ||
|
|
ee8ea672ef | ||
|
|
fc59910bd2 | ||
|
|
20ab7bc005 | ||
|
|
17de8e761b | ||
|
|
ab1e1cc66e | ||
|
|
256143af42 | ||
|
|
5dc244a8c1 | ||
|
|
9f15ca6242 | ||
|
|
750b904abc | ||
|
|
96b5411d1d | ||
|
|
a3404102ce | ||
|
|
374c4492d9 | ||
|
|
3cfe86512d | ||
|
|
47e3e415b9 | ||
|
|
3373533725 | ||
|
|
62c7c329d7 | ||
|
|
da04899558 | ||
|
|
e49f09b5ec |
255
.github/workflows/push-json-to-pocketbase.yml
generated
vendored
Normal file
255
.github/workflows/push-json-to-pocketbase.yml
generated
vendored
Normal file
@@ -0,0 +1,255 @@
|
||||
name: Push JSON changes to PocketBase
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "frontend/public/json/**"
|
||||
|
||||
jobs:
|
||||
push-json:
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get changed JSON files with slug
|
||||
id: changed
|
||||
run: |
|
||||
changed=$(git diff --name-only "${{ github.event.before }}" "${{ github.event.after }}" -- frontend/public/json/ | grep '\.json$' || true)
|
||||
with_slug=""
|
||||
for f in $changed; do
|
||||
[[ -f "$f" ]] || continue
|
||||
jq -e '.slug' "$f" >/dev/null 2>&1 && with_slug="$with_slug $f"
|
||||
done
|
||||
with_slug=$(echo $with_slug | xargs -n1)
|
||||
if [[ -z "$with_slug" ]]; then
|
||||
echo "No app JSON files changed (or no files with slug)."
|
||||
echo "count=0" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
echo "$with_slug" > changed_app_jsons.txt
|
||||
echo "count=$(echo "$with_slug" | wc -w)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Push to PocketBase
|
||||
if: steps.changed.outputs.count != '0'
|
||||
env:
|
||||
POCKETBASE_URL: ${{ secrets.POCKETBASE_URL }}
|
||||
POCKETBASE_COLLECTION: ${{ secrets.POCKETBASE_COLLECTION }}
|
||||
POCKETBASE_ADMIN_EMAIL: ${{ secrets.POCKETBASE_ADMIN_EMAIL }}
|
||||
POCKETBASE_ADMIN_PASSWORD: ${{ secrets.POCKETBASE_ADMIN_PASSWORD }}
|
||||
run: |
|
||||
node << 'ENDSCRIPT'
|
||||
(async function() {
|
||||
const fs = require('fs');
|
||||
const https = require('https');
|
||||
const http = require('http');
|
||||
const url = require('url');
|
||||
function request(fullUrl, opts) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
const u = url.parse(fullUrl);
|
||||
const isHttps = u.protocol === 'https:';
|
||||
const body = opts.body;
|
||||
const options = {
|
||||
hostname: u.hostname,
|
||||
port: u.port || (isHttps ? 443 : 80),
|
||||
path: u.path,
|
||||
method: opts.method || 'GET',
|
||||
headers: opts.headers || {}
|
||||
};
|
||||
if (body) options.headers['Content-Length'] = Buffer.byteLength(body);
|
||||
const lib = isHttps ? https : http;
|
||||
const req = lib.request(options, function(res) {
|
||||
let data = '';
|
||||
res.on('data', function(chunk) { data += chunk; });
|
||||
res.on('end', function() {
|
||||
resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, statusCode: res.statusCode, body: data });
|
||||
});
|
||||
});
|
||||
req.on('error', reject);
|
||||
if (body) req.write(body);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
const raw = process.env.POCKETBASE_URL.replace(/\/$/, '');
|
||||
const apiBase = /\/api$/i.test(raw) ? raw : raw + '/api';
|
||||
const coll = process.env.POCKETBASE_COLLECTION;
|
||||
const files = fs.readFileSync('changed_app_jsons.txt', 'utf8').trim().split(/\s+/).filter(Boolean);
|
||||
const authUrl = apiBase + '/collections/users/auth-with-password';
|
||||
console.log('Auth URL: ' + authUrl);
|
||||
const authBody = JSON.stringify({
|
||||
identity: process.env.POCKETBASE_ADMIN_EMAIL,
|
||||
password: process.env.POCKETBASE_ADMIN_PASSWORD
|
||||
});
|
||||
const authRes = await request(authUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: authBody
|
||||
});
|
||||
if (!authRes.ok) {
|
||||
throw new Error('Auth failed. Tried: ' + authUrl + ' - Verify POST to that URL with body {"identity":"...","password":"..."} works. Response: ' + authRes.body);
|
||||
}
|
||||
const token = JSON.parse(authRes.body).token;
|
||||
const recordsUrl = apiBase + '/collections/' + encodeURIComponent(coll) + '/records';
|
||||
let categoryIdToName = {};
|
||||
try {
|
||||
const metadata = JSON.parse(fs.readFileSync('frontend/public/json/metadata.json', 'utf8'));
|
||||
(metadata.categories || []).forEach(function(cat) { categoryIdToName[cat.id] = cat.name; });
|
||||
} catch (e) { console.warn('Could not load metadata.json:', e.message); }
|
||||
let typeValueToId = {};
|
||||
let categoryNameToPbId = {};
|
||||
try {
|
||||
const typesRes = await request(apiBase + '/collections/z_ref_script_types/records?perPage=500', { headers: { 'Authorization': token } });
|
||||
if (typesRes.ok) {
|
||||
const typesData = JSON.parse(typesRes.body);
|
||||
(typesData.items || []).forEach(function(item) {
|
||||
if (item.type != null) typeValueToId[item.type] = item.id;
|
||||
if (item.name != null) typeValueToId[item.name] = item.id;
|
||||
if (item.value != null) typeValueToId[item.value] = item.id;
|
||||
});
|
||||
}
|
||||
} catch (e) { console.warn('Could not fetch z_ref_script_types:', e.message); }
|
||||
try {
|
||||
const catRes = await request(apiBase + '/collections/script_categories/records?perPage=500', { headers: { 'Authorization': token } });
|
||||
if (catRes.ok) {
|
||||
const catData = JSON.parse(catRes.body);
|
||||
(catData.items || []).forEach(function(item) { if (item.name) categoryNameToPbId[item.name] = item.id; });
|
||||
}
|
||||
} catch (e) { console.warn('Could not fetch script_categories:', e.message); }
|
||||
var noteTypeToId = {};
|
||||
var installMethodTypeToId = {};
|
||||
var osToId = {};
|
||||
var osVersionToId = {};
|
||||
try {
|
||||
const res = await request(apiBase + '/collections/z_ref_note_types/records?perPage=500', { headers: { 'Authorization': token } });
|
||||
if (res.ok) JSON.parse(res.body).items?.forEach(function(item) { if (item.type != null) noteTypeToId[item.type] = item.id; });
|
||||
} catch (e) { console.warn('z_ref_note_types:', e.message); }
|
||||
try {
|
||||
const res = await request(apiBase + '/collections/z_ref_install_method_types/records?perPage=500', { headers: { 'Authorization': token } });
|
||||
if (res.ok) JSON.parse(res.body).items?.forEach(function(item) { if (item.type != null) installMethodTypeToId[item.type] = item.id; });
|
||||
} catch (e) { console.warn('z_ref_install_method_types:', e.message); }
|
||||
try {
|
||||
const res = await request(apiBase + '/collections/z_ref_os/records?perPage=500', { headers: { 'Authorization': token } });
|
||||
if (res.ok) JSON.parse(res.body).items?.forEach(function(item) { if (item.os != null) osToId[item.os] = item.id; });
|
||||
} catch (e) { console.warn('z_ref_os:', e.message); }
|
||||
try {
|
||||
const res = await request(apiBase + '/collections/z_ref_os_version/records?perPage=500&expand=os', { headers: { 'Authorization': token } });
|
||||
if (res.ok) {
|
||||
(JSON.parse(res.body).items || []).forEach(function(item) {
|
||||
var osName = item.expand && item.expand.os && item.expand.os.os != null ? item.expand.os.os : null;
|
||||
if (osName != null && item.version != null) osVersionToId[osName + '|' + item.version] = item.id;
|
||||
});
|
||||
}
|
||||
} catch (e) { console.warn('z_ref_os_version:', e.message); }
|
||||
var notesCollUrl = apiBase + '/collections/script_notes/records';
|
||||
var installMethodsCollUrl = apiBase + '/collections/script_install_methods/records';
|
||||
for (const file of files) {
|
||||
if (!fs.existsSync(file)) continue;
|
||||
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||
if (!data.slug) { console.log('Skipping', file, '(no slug)'); continue; }
|
||||
var payload = {
|
||||
name: data.name,
|
||||
slug: data.slug,
|
||||
script_created: data.date_created || data.script_created,
|
||||
script_updated: data.date_created || data.script_updated,
|
||||
updateable: data.updateable,
|
||||
privileged: data.privileged,
|
||||
port: data.interface_port != null ? data.interface_port : data.port,
|
||||
documentation: data.documentation,
|
||||
website: data.website,
|
||||
logo: data.logo,
|
||||
description: data.description,
|
||||
config_path: data.config_path,
|
||||
default_user: (data.default_credentials && data.default_credentials.username) || data.default_user,
|
||||
default_passwd: (data.default_credentials && data.default_credentials.password) || data.default_passwd,
|
||||
is_dev: false
|
||||
};
|
||||
var resolvedType = typeValueToId[data.type];
|
||||
if (resolvedType == null && data.type === 'ct') resolvedType = typeValueToId['lxc'];
|
||||
if (resolvedType) payload.type = resolvedType;
|
||||
var resolvedCats = (data.categories || []).map(function(n) { return categoryNameToPbId[categoryIdToName[n]]; }).filter(Boolean);
|
||||
if (resolvedCats.length) payload.categories = resolvedCats;
|
||||
if (data.version !== undefined) payload.version = data.version;
|
||||
if (data.changelog !== undefined) payload.changelog = data.changelog;
|
||||
if (data.screenshots !== undefined) payload.screenshots = data.screenshots;
|
||||
const filter = "(slug='" + data.slug + "')";
|
||||
const listRes = await request(recordsUrl + '?filter=' + encodeURIComponent(filter) + '&perPage=1', {
|
||||
headers: { 'Authorization': token }
|
||||
});
|
||||
const list = JSON.parse(listRes.body);
|
||||
const existingId = list.items && list.items[0] && list.items[0].id;
|
||||
async function resolveNotesAndInstallMethods(scriptId) {
|
||||
var noteIds = [];
|
||||
for (var i = 0; i < (data.notes || []).length; i++) {
|
||||
var note = data.notes[i];
|
||||
var typeId = noteTypeToId[note.type];
|
||||
if (typeId == null) continue;
|
||||
var postRes = await request(notesCollUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text: note.text || '', type: typeId })
|
||||
});
|
||||
if (postRes.ok) noteIds.push(JSON.parse(postRes.body).id);
|
||||
}
|
||||
var installMethodIds = [];
|
||||
for (var j = 0; j < (data.install_methods || []).length; j++) {
|
||||
var im = data.install_methods[j];
|
||||
var typeId = installMethodTypeToId[im.type];
|
||||
var res = im.resources || {};
|
||||
var osId = osToId[res.os];
|
||||
var osVersionKey = (res.os != null && res.version != null) ? res.os + '|' + res.version : null;
|
||||
var osVersionId = osVersionKey ? osVersionToId[osVersionKey] : null;
|
||||
var imBody = {
|
||||
script: scriptId,
|
||||
resources_cpu: res.cpu != null ? res.cpu : 0,
|
||||
resources_ram: res.ram != null ? res.ram : 0,
|
||||
resources_hdd: res.hdd != null ? res.hdd : 0
|
||||
};
|
||||
if (typeId) imBody.type = typeId;
|
||||
if (osId) imBody.os = osId;
|
||||
if (osVersionId) imBody.os_version = osVersionId;
|
||||
var imPostRes = await request(installMethodsCollUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(imBody)
|
||||
});
|
||||
if (imPostRes.ok) installMethodIds.push(JSON.parse(imPostRes.body).id);
|
||||
}
|
||||
return { noteIds: noteIds, installMethodIds: installMethodIds };
|
||||
}
|
||||
if (existingId) {
|
||||
var resolved = await resolveNotesAndInstallMethods(existingId);
|
||||
payload.notes = resolved.noteIds;
|
||||
payload.install_methods = resolved.installMethodIds;
|
||||
console.log('Updating', file, '(slug=' + data.slug + ')');
|
||||
const r = await request(recordsUrl + '/' + existingId, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
if (!r.ok) throw new Error('PATCH failed: ' + r.body);
|
||||
} else {
|
||||
console.log('Creating', file, '(slug=' + data.slug + ')');
|
||||
const r = await request(recordsUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
if (!r.ok) throw new Error('POST failed: ' + r.body);
|
||||
var scriptId = JSON.parse(r.body).id;
|
||||
var resolved = await resolveNotesAndInstallMethods(scriptId);
|
||||
var patchRes = await request(recordsUrl + '/' + scriptId, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ install_methods: resolved.installMethodIds, notes: resolved.noteIds })
|
||||
});
|
||||
if (!patchRes.ok) throw new Error('PATCH relations failed: ' + patchRes.body);
|
||||
}
|
||||
}
|
||||
console.log('Done.');
|
||||
})().catch(e => { console.error(e); process.exit(1); });
|
||||
ENDSCRIPT
|
||||
shell: bash
|
||||
56
CHANGELOG.md
56
CHANGELOG.md
@@ -410,20 +410,74 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
|
||||
|
||||
</details>
|
||||
|
||||
## 2026-03-02
|
||||
## 2026-03-03
|
||||
|
||||
### 🆕 New Scripts
|
||||
|
||||
- Tinyauth: v5 Support & add Debian Version [@MickLesk](https://github.com/MickLesk) ([#12501](https://github.com/community-scripts/ProxmoxVE/pull/12501))
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- cross-seed: install build-essential to resolve missing `make` error [@Copilot](https://github.com/Copilot) ([#12522](https://github.com/community-scripts/ProxmoxVE/pull/12522))
|
||||
- meshcentral: increased disk space to 4GB [@MickLesk](https://github.com/MickLesk) ([#12509](https://github.com/community-scripts/ProxmoxVE/pull/12509))
|
||||
|
||||
- #### 🔧 Refactor
|
||||
|
||||
- opnsense-vm: harden temp dir, bridge detection and network selection [@MickLesk](https://github.com/MickLesk) ([#12513](https://github.com/community-scripts/ProxmoxVE/pull/12513))
|
||||
|
||||
### 🗑️ Deleted Scripts
|
||||
|
||||
- Remove Unifi Network Server scripts (dead APT repo) [@Copilot](https://github.com/Copilot) ([#12500](https://github.com/community-scripts/ProxmoxVE/pull/12500))
|
||||
|
||||
### 💾 Core
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- core: recovery - add ENOSPC disk-full detection with auto-retry using * 2 hdd [@MickLesk](https://github.com/MickLesk) ([#12511](https://github.com/community-scripts/ProxmoxVE/pull/12511))
|
||||
|
||||
### 🌐 Website
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- Revert #11534 PR that messed up search [@BramSuurdje](https://github.com/BramSuurdje) ([#12492](https://github.com/community-scripts/ProxmoxVE/pull/12492))
|
||||
|
||||
## 2026-03-02
|
||||
|
||||
### 🆕 New Scripts
|
||||
|
||||
- PowerDNS ([#12481](https://github.com/community-scripts/ProxmoxVE/pull/12481))
|
||||
- Profilarr ([#12441](https://github.com/community-scripts/ProxmoxVE/pull/12441))
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- Tracearr: prepare for imminent v1.4.19 release [@durzo](https://github.com/durzo) ([#12413](https://github.com/community-scripts/ProxmoxVE/pull/12413))
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- Frigate: Bump to v0.17 [@MickLesk](https://github.com/MickLesk) ([#12474](https://github.com/community-scripts/ProxmoxVE/pull/12474))
|
||||
|
||||
- #### 💥 Breaking Changes
|
||||
|
||||
- Migrate: DokPloy, Komodo, Coolify, Dockge, Runtipi to Addons [@MickLesk](https://github.com/MickLesk) ([#12275](https://github.com/community-scripts/ProxmoxVE/pull/12275))
|
||||
|
||||
- #### 🔧 Refactor
|
||||
|
||||
- ref: replace generic exit 1 with specific exit codes in ct & install [@MickLesk](https://github.com/MickLesk) ([#12475](https://github.com/community-scripts/ProxmoxVE/pull/12475))
|
||||
|
||||
### 💾 Core
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- tools.func: Improve stability with retry logic, caching, and debug mode [@MickLesk](https://github.com/MickLesk) ([#10351](https://github.com/community-scripts/ProxmoxVE/pull/10351))
|
||||
|
||||
- #### 🔧 Refactor
|
||||
|
||||
- core: standardize exit codes and add mappings [@MickLesk](https://github.com/MickLesk) ([#12467](https://github.com/community-scripts/ProxmoxVE/pull/12467))
|
||||
|
||||
### 🌐 Website
|
||||
|
||||
- frontend: improve detail view badges, addon texts, and HTML title [@MickLesk](https://github.com/MickLesk) ([#12461](https://github.com/community-scripts/ProxmoxVE/pull/12461))
|
||||
|
||||
@@ -39,7 +39,7 @@ function update_script() {
|
||||
COMPOSE_FILE=$(find /opt/komodo -maxdepth 1 -type f -name '*.compose.yaml' ! -name 'compose.env' | head -n1)
|
||||
if [[ -z "$COMPOSE_FILE" ]]; then
|
||||
msg_error "No valid compose file found in /opt/komodo!"
|
||||
exit 1
|
||||
exit 252
|
||||
fi
|
||||
$STD docker compose -p komodo -f "$COMPOSE_FILE" --env-file /opt/komodo/compose.env pull
|
||||
$STD docker compose -p komodo -f "$COMPOSE_FILE" --env-file /opt/komodo/compose.env up -d
|
||||
|
||||
@@ -35,6 +35,20 @@ function update_script() {
|
||||
$STD service tinyauth stop
|
||||
msg_ok "Service Stopped"
|
||||
|
||||
if [[ -f /opt/tinyauth/.env ]] && ! grep -q "^TINYAUTH_" /opt/tinyauth/.env; then
|
||||
msg_info "Migrating .env to v5 format"
|
||||
sed -i \
|
||||
-e 's/^DATABASE_PATH=/TINYAUTH_DATABASE_PATH=/' \
|
||||
-e 's/^USERS=/TINYAUTH_AUTH_USERS=/' \
|
||||
-e "s/^USERS='/TINYAUTH_AUTH_USERS='/" \
|
||||
-e 's/^APP_URL=/TINYAUTH_APPURL=/' \
|
||||
-e 's/^SECRET=/TINYAUTH_AUTH_SECRET=/' \
|
||||
-e 's/^PORT=/TINYAUTH_SERVER_PORT=/' \
|
||||
-e 's/^ADDRESS=/TINYAUTH_SERVER_ADDRESS=/' \
|
||||
/opt/tinyauth/.env
|
||||
msg_ok "Migrated .env to v5 format"
|
||||
fi
|
||||
|
||||
msg_info "Updating Tinyauth"
|
||||
rm -f /opt/tinyauth/tinyauth
|
||||
curl -fsSL "https://github.com/steveiliop56/tinyauth/releases/download/v${RELEASE}/tinyauth-amd64" -o /opt/tinyauth/tinyauth
|
||||
|
||||
@@ -25,6 +25,7 @@ function update_script() {
|
||||
check_container_resources
|
||||
|
||||
NODE_VERSION="24" setup_nodejs
|
||||
ensure_dependencies build-essential
|
||||
|
||||
if command -v cross-seed &>/dev/null; then
|
||||
current_version=$(cross-seed --version)
|
||||
|
||||
@@ -26,7 +26,7 @@ function update_script() {
|
||||
|
||||
if [[ ! -d /opt/endurain ]]; then
|
||||
msg_error "No ${APP} installation found!"
|
||||
exit 1
|
||||
exit 233
|
||||
fi
|
||||
if check_for_gh_release "endurain" "endurain-project/endurain"; then
|
||||
msg_info "Stopping Service"
|
||||
|
||||
@@ -25,7 +25,7 @@ function update_script() {
|
||||
check_container_resources
|
||||
if ! command -v evcc >/dev/null 2>&1; then
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit 1
|
||||
exit 233
|
||||
fi
|
||||
|
||||
if [[ -f /etc/apt/sources.list.d/evcc-stable.list ]]; then
|
||||
|
||||
@@ -26,7 +26,7 @@ function update_script() {
|
||||
|
||||
if ! dpkg -s grafana >/dev/null 2>&1; then
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit 1
|
||||
exit 233
|
||||
fi
|
||||
|
||||
if [[ -f /etc/apt/sources.list.d/grafana.list ]] || [[ ! -f /etc/apt/sources.list.d/grafana.sources ]]; then
|
||||
|
||||
6
ct/headers/powerdns
Normal file
6
ct/headers/powerdns
Normal file
@@ -0,0 +1,6 @@
|
||||
____ ____ _ _______
|
||||
/ __ \____ _ _____ _____/ __ \/ | / / ___/
|
||||
/ /_/ / __ \ | /| / / _ \/ ___/ / / / |/ /\__ \
|
||||
/ ____/ /_/ / |/ |/ / __/ / / /_/ / /| /___/ /
|
||||
/_/ \____/|__/|__/\___/_/ /_____/_/ |_//____/
|
||||
|
||||
6
ct/headers/profilarr
Normal file
6
ct/headers/profilarr
Normal file
@@ -0,0 +1,6 @@
|
||||
____ _____ __
|
||||
/ __ \_________ / __(_) /___ ___________
|
||||
/ /_/ / ___/ __ \/ /_/ / / __ `/ ___/ ___/
|
||||
/ ____/ / / /_/ / __/ / / /_/ / / / /
|
||||
/_/ /_/ \____/_/ /_/_/\__,_/_/ /_/
|
||||
|
||||
6
ct/headers/tinyauth
Normal file
6
ct/headers/tinyauth
Normal file
@@ -0,0 +1,6 @@
|
||||
_______ __ __
|
||||
/_ __(_)___ __ ______ ___ __/ /_/ /_
|
||||
/ / / / __ \/ / / / __ `/ / / / __/ __ \
|
||||
/ / / / / / / /_/ / /_/ / /_/ / /_/ / / /
|
||||
/_/ /_/_/ /_/\__, /\__,_/\__,_/\__/_/ /_/
|
||||
/____/
|
||||
@@ -1,6 +0,0 @@
|
||||
__ __ _ _____
|
||||
/ / / /___ (_) __(_)
|
||||
/ / / / __ \/ / /_/ /
|
||||
/ /_/ / / / / / __/ /
|
||||
\____/_/ /_/_/_/ /_/
|
||||
|
||||
@@ -26,7 +26,7 @@ function update_script() {
|
||||
|
||||
if [[ ! -f /etc/itsm-ng/config_db.php ]]; then
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit 1
|
||||
exit 233
|
||||
fi
|
||||
setup_mariadb
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ function update_script() {
|
||||
|
||||
if [[ -z "$KASM_URL" ]] || [[ -z "$KASM_VERSION" ]]; then
|
||||
msg_error "Unable to detect latest Kasm release URL."
|
||||
exit 1
|
||||
exit 250
|
||||
fi
|
||||
msg_info "Checked for new version"
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ function update_script() {
|
||||
COMPOSE_FILE=$(find /opt/komodo -maxdepth 1 -type f -name '*.compose.yaml' ! -name 'compose.env' | head -n1)
|
||||
if [[ -z "$COMPOSE_FILE" ]]; then
|
||||
msg_error "No valid compose file found in /opt/komodo!"
|
||||
exit 1
|
||||
exit 252
|
||||
fi
|
||||
$STD docker compose -p komodo -f "$COMPOSE_FILE" --env-file /opt/komodo/compose.env pull
|
||||
$STD docker compose -p komodo -f "$COMPOSE_FILE" --env-file /opt/komodo/compose.env up -d
|
||||
|
||||
@@ -26,7 +26,7 @@ function update_script() {
|
||||
|
||||
if ! dpkg -s loki >/dev/null 2>&1; then
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit 1
|
||||
exit 233
|
||||
fi
|
||||
|
||||
CHOICE=$(msg_menu "Loki Update Options" \
|
||||
|
||||
@@ -9,7 +9,7 @@ APP="MeshCentral"
|
||||
var_tags="${var_tags:-remote-management}"
|
||||
var_cpu="${var_cpu:-1}"
|
||||
var_ram="${var_ram:-512}"
|
||||
var_disk="${var_disk:-2}"
|
||||
var_disk="${var_disk:-4}"
|
||||
var_os="${var_os:-debian}"
|
||||
var_version="${var_version:-13}"
|
||||
var_unprivileged="${var_unprivileged:-1}"
|
||||
|
||||
@@ -44,7 +44,7 @@ function update_script() {
|
||||
echo -e "${TAB}${GATEWAY}${BGN}https://github.com/community-scripts/ProxmoxVE/discussions/9223${CL}"
|
||||
echo -e ""
|
||||
msg_custom "⚠️" "Update aborted. Please migrate your data first."
|
||||
exit 1
|
||||
exit 253
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
68
ct/powerdns.sh
Normal file
68
ct/powerdns.sh
Normal file
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: Slaviša Arežina (tremor021)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://www.powerdns.com/
|
||||
|
||||
APP="PowerDNS"
|
||||
var_tags="${var_tags:-dns}"
|
||||
var_cpu="${var_cpu:-1}"
|
||||
var_ram="${var_ram:-1024}"
|
||||
var_disk="${var_disk:-4}"
|
||||
var_os="${var_os:-debian}"
|
||||
var_version="${var_version:-13}"
|
||||
var_unprivileged="${var_unprivileged:-1}"
|
||||
|
||||
header_info "$APP"
|
||||
variables
|
||||
color
|
||||
catch_errors
|
||||
|
||||
function update_script() {
|
||||
header_info
|
||||
check_container_storage
|
||||
check_container_resources
|
||||
if [[ ! -d /opt/poweradmin ]]; then
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit
|
||||
fi
|
||||
|
||||
msg_info "Updating PowerDNS"
|
||||
$STD apt update
|
||||
$STD apt install -y --only-upgrade pdns-server pdns-backend-sqlite3
|
||||
msg_ok "Updated PowerDNS"
|
||||
|
||||
if check_for_gh_release "poweradmin" "poweradmin/poweradmin"; then
|
||||
msg_info "Backing up Configuration"
|
||||
cp /opt/poweradmin/config/settings.php /opt/poweradmin_settings.php.bak
|
||||
cp /opt/poweradmin/powerdns.db /opt/poweradmin_powerdns.db.bak
|
||||
msg_ok "Backed up Configuration"
|
||||
|
||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "poweradmin" "poweradmin/poweradmin" "tarball"
|
||||
|
||||
msg_info "Updating Poweradmin"
|
||||
cp /opt/poweradmin_settings.php.bak /opt/poweradmin/config/settings.php
|
||||
cp /opt/poweradmin_powerdns.db.bak /opt/poweradmin/powerdns.db
|
||||
rm -rf /opt/poweradmin/install
|
||||
rm -f /opt/poweradmin_settings.php.bak /opt/poweradmin_powerdns.db.bak
|
||||
chown -R www-data:www-data /opt/poweradmin
|
||||
msg_ok "Updated Poweradmin"
|
||||
|
||||
msg_info "Restarting Services"
|
||||
systemctl restart pdns apache2
|
||||
msg_ok "Restarted Services"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
}
|
||||
|
||||
start
|
||||
build_container
|
||||
description
|
||||
|
||||
msg_ok "Completed Successfully!\n"
|
||||
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
|
||||
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
|
||||
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}${CL}"
|
||||
85
ct/profilarr.sh
Normal file
85
ct/profilarr.sh
Normal file
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env bash
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: michelroegl-brunner
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://github.com/Dictionarry-Hub/profilarr
|
||||
|
||||
APP="Profilarr"
|
||||
var_tags="${var_tags:-arr;radarr;sonarr;config}"
|
||||
var_cpu="${var_cpu:-2}"
|
||||
var_ram="${var_ram:-2048}"
|
||||
var_disk="${var_disk:-8}"
|
||||
var_os="${var_os:-debian}"
|
||||
var_version="${var_version:-13}"
|
||||
var_unprivileged="${var_unprivileged:-1}"
|
||||
|
||||
header_info "$APP"
|
||||
variables
|
||||
color
|
||||
catch_errors
|
||||
|
||||
function update_script() {
|
||||
header_info
|
||||
check_container_storage
|
||||
check_container_resources
|
||||
|
||||
if [[ ! -d /opt/profilarr ]]; then
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit
|
||||
fi
|
||||
|
||||
if check_for_gh_release "profilarr" "Dictionarry-Hub/profilarr"; then
|
||||
msg_info "Stopping Service"
|
||||
systemctl stop profilarr
|
||||
msg_ok "Stopped Service"
|
||||
|
||||
msg_info "Backing up Data"
|
||||
if [[ -d /config ]]; then
|
||||
cp -r /config /opt/profilarr_config_backup
|
||||
fi
|
||||
msg_ok "Backed up Data"
|
||||
|
||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "profilarr" "Dictionarry-Hub/profilarr" "tarball"
|
||||
|
||||
msg_info "Installing Python Dependencies"
|
||||
cd /opt/profilarr/backend
|
||||
$STD uv venv /opt/profilarr/backend/.venv
|
||||
sed 's/==/>=/g' requirements.txt >requirements-relaxed.txt
|
||||
$STD uv pip install --python /opt/profilarr/backend/.venv/bin/python -r requirements-relaxed.txt
|
||||
rm -f requirements-relaxed.txt
|
||||
msg_ok "Installed Python Dependencies"
|
||||
|
||||
msg_info "Building Frontend"
|
||||
if [[ -d /opt/profilarr/frontend ]]; then
|
||||
cd /opt/profilarr/frontend
|
||||
$STD npm install
|
||||
$STD npm run build
|
||||
cp -r dist /opt/profilarr/backend/app/static
|
||||
fi
|
||||
msg_ok "Built Frontend"
|
||||
|
||||
msg_info "Restoring Data"
|
||||
if [[ -d /opt/profilarr_config_backup ]]; then
|
||||
mkdir -p /config
|
||||
cp -r /opt/profilarr_config_backup/. /config/
|
||||
rm -rf /opt/profilarr_config_backup
|
||||
fi
|
||||
msg_ok "Restored Data"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start profilarr
|
||||
msg_ok "Started Service"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
}
|
||||
|
||||
start
|
||||
build_container
|
||||
description
|
||||
|
||||
msg_ok "Completed Successfully!\n"
|
||||
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
|
||||
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
|
||||
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:6868${CL}"
|
||||
53
ct/tinyauth.sh
Normal file
53
ct/tinyauth.sh
Normal file
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env bash
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: MickLesk (CanbiZ)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://github.com/steveiliop56/tinyauth
|
||||
|
||||
APP="Tinyauth"
|
||||
var_tags="${var_tags:-auth}"
|
||||
var_cpu="${var_cpu:-1}"
|
||||
var_ram="${var_ram:-512}"
|
||||
var_disk="${var_disk:-4}"
|
||||
var_os="${var_os:-debian}"
|
||||
var_version="${var_version:-13}"
|
||||
var_unprivileged="${var_unprivileged:-1}"
|
||||
|
||||
header_info "$APP"
|
||||
variables
|
||||
color
|
||||
catch_errors
|
||||
|
||||
function update_script() {
|
||||
header_info
|
||||
check_container_storage
|
||||
check_container_resources
|
||||
if [[ ! -d /opt/tinyauth ]]; then
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit
|
||||
fi
|
||||
|
||||
if check_for_gh_release "tinyauth" "steveiliop56/tinyauth"; then
|
||||
msg_info "Stopping Service"
|
||||
systemctl stop tinyauth
|
||||
msg_ok "Stopped Service"
|
||||
|
||||
fetch_and_deploy_gh_release "tinyauth" "steveiliop56/tinyauth" "singlefile" "latest" "/opt/tinyauth" "tinyauth-amd64"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start tinyauth
|
||||
msg_ok "Started Service"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
}
|
||||
|
||||
start
|
||||
build_container
|
||||
description
|
||||
|
||||
msg_ok "Completed successfully!\n"
|
||||
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
|
||||
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
|
||||
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}"
|
||||
@@ -75,10 +75,31 @@ if [ -f \$pg_config_file ]; then
|
||||
fi
|
||||
fi
|
||||
systemctl restart postgresql
|
||||
sudo -u postgres psql -c "ALTER USER tracearr WITH SUPERUSER;"
|
||||
EOF
|
||||
chmod +x /data/tracearr/prestart.sh
|
||||
msg_ok "Updated prestart script"
|
||||
|
||||
# check if tailscale is installed
|
||||
if command -v tailscale >/dev/null 2>&1; then
|
||||
# Tracearr runs tailscaled in user mode, disable the service.
|
||||
$STD systemctl disable --now tailscaled
|
||||
$STD systemctl stop tailscaled
|
||||
msg_ok "Tailscale already installed"
|
||||
else
|
||||
msg_info "Installing tailscale"
|
||||
setup_deb822_repo \
|
||||
"tailscale" \
|
||||
"https://pkgs.tailscale.com/stable/$(get_os_info id)/$(get_os_info codename).noarmor.gpg" \
|
||||
"https://pkgs.tailscale.com/stable/$(get_os_info id)/" \
|
||||
"$(get_os_info codename)"
|
||||
$STD apt install -y tailscale
|
||||
# Tracearr runs tailscaled in user mode, disable the service.
|
||||
$STD systemctl disable --now tailscaled
|
||||
$STD systemctl stop tailscaled
|
||||
msg_ok "Installed tailscale"
|
||||
fi
|
||||
|
||||
if check_for_gh_release "tracearr" "connorgallopo/Tracearr"; then
|
||||
msg_info "Stopping Services"
|
||||
systemctl stop tracearr postgresql redis
|
||||
@@ -122,6 +143,8 @@ EOF
|
||||
sed -i "s/^APP_VERSION=.*/APP_VERSION=$(cat /root/.tracearr)/" /data/tracearr/.env
|
||||
chmod 600 /data/tracearr/.env
|
||||
chown -R tracearr:tracearr /data/tracearr
|
||||
mkdir -p /data/backup
|
||||
chown -R tracearr:tracearr /data/backup
|
||||
msg_ok "Configured Tracearr"
|
||||
|
||||
msg_info "Starting services"
|
||||
|
||||
47
ct/unifi.sh
47
ct/unifi.sh
@@ -1,47 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
|
||||
# Copyright (c) 2021-2026 tteck
|
||||
# Author: tteck (tteckster)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://ui.com/download/unifi
|
||||
|
||||
APP="Unifi"
|
||||
var_tags="${var_tags:-network;unifi}"
|
||||
var_cpu="${var_cpu:-2}"
|
||||
var_ram="${var_ram:-2048}"
|
||||
var_disk="${var_disk:-8}"
|
||||
var_os="${var_os:-debian}"
|
||||
var_version="${var_version:-12}"
|
||||
var_unprivileged="${var_unprivileged:-1}"
|
||||
|
||||
header_info "$APP"
|
||||
variables
|
||||
color
|
||||
catch_errors
|
||||
|
||||
function update_script() {
|
||||
header_info
|
||||
check_container_storage
|
||||
check_container_resources
|
||||
if [[ ! -d /usr/lib/unifi ]]; then
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit
|
||||
fi
|
||||
|
||||
JAVA_VERSION="21" setup_java
|
||||
|
||||
msg_info "Updating ${APP}"
|
||||
$STD apt update --allow-releaseinfo-change
|
||||
ensure_dependencies unifi
|
||||
msg_ok "Updated successfully!"
|
||||
exit
|
||||
}
|
||||
|
||||
start
|
||||
build_container
|
||||
description
|
||||
|
||||
msg_ok "Completed successfully!\n"
|
||||
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
|
||||
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
|
||||
echo -e "${TAB}${GATEWAY}${BGN}https://${IP}:8443${CL}"
|
||||
@@ -34,14 +34,14 @@ function update_script() {
|
||||
msg_warn "This requires MANUAL config changes in /etc/vikunja/config.yml."
|
||||
msg_warn "See: https://vikunja.io/changelog/whats-new-in-vikunja-1.0.0/#config-changes"
|
||||
|
||||
read -rp "Continue with update? (y to proceed): " -t 30 CONFIRM1 || exit 1
|
||||
read -rp "Continue with update? (y to proceed): " -t 30 CONFIRM1 || exit 254
|
||||
[[ "$CONFIRM1" =~ ^[yY]$ ]] || exit 0
|
||||
|
||||
echo
|
||||
msg_warn "Vikunja may not start after the update until you manually adjust the config."
|
||||
msg_warn "Details: https://vikunja.io/changelog/whats-new-in-vikunja-1.0.0/#config-changes"
|
||||
|
||||
read -rp "Acknowledge and continue? (y): " -t 30 CONFIRM2 || exit 1
|
||||
read -rp "Acknowledge and continue? (y): " -t 30 CONFIRM2 || exit 254
|
||||
[[ "$CONFIRM2" =~ ^[yY]$ ]] || exit 0
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generated": "2026-03-02T06:20:15Z",
|
||||
"generated": "2026-03-03T18:16:19Z",
|
||||
"versions": [
|
||||
{
|
||||
"slug": "2fauth",
|
||||
@@ -200,9 +200,9 @@
|
||||
{
|
||||
"slug": "cleanuparr",
|
||||
"repo": "Cleanuparr/Cleanuparr",
|
||||
"version": "v2.7.6",
|
||||
"version": "v2.7.7",
|
||||
"pinned": false,
|
||||
"date": "2026-02-27T19:32:02Z"
|
||||
"date": "2026-03-02T13:08:32Z"
|
||||
},
|
||||
{
|
||||
"slug": "cloudreve",
|
||||
@@ -291,9 +291,9 @@
|
||||
{
|
||||
"slug": "dispatcharr",
|
||||
"repo": "Dispatcharr/Dispatcharr",
|
||||
"version": "v0.20.1",
|
||||
"version": "v0.20.2",
|
||||
"pinned": false,
|
||||
"date": "2026-02-26T21:38:19Z"
|
||||
"date": "2026-03-03T01:40:33Z"
|
||||
},
|
||||
{
|
||||
"slug": "docmost",
|
||||
@@ -312,9 +312,9 @@
|
||||
{
|
||||
"slug": "domain-monitor",
|
||||
"repo": "Hosteroid/domain-monitor",
|
||||
"version": "v1.1.3",
|
||||
"version": "v1.1.4",
|
||||
"pinned": false,
|
||||
"date": "2026-02-11T15:48:18Z"
|
||||
"date": "2026-03-02T09:25:01Z"
|
||||
},
|
||||
{
|
||||
"slug": "donetick",
|
||||
@@ -382,9 +382,9 @@
|
||||
{
|
||||
"slug": "firefly",
|
||||
"repo": "firefly-iii/firefly-iii",
|
||||
"version": "v6.5.1",
|
||||
"version": "v6.5.2",
|
||||
"pinned": false,
|
||||
"date": "2026-02-27T20:55:55Z"
|
||||
"date": "2026-03-03T05:42:27Z"
|
||||
},
|
||||
{
|
||||
"slug": "fladder",
|
||||
@@ -424,9 +424,9 @@
|
||||
{
|
||||
"slug": "frigate",
|
||||
"repo": "blakeblackshear/frigate",
|
||||
"version": "v0.16.4",
|
||||
"version": "v0.17.0",
|
||||
"pinned": true,
|
||||
"date": "2026-01-29T00:42:14Z"
|
||||
"date": "2026-02-27T03:03:01Z"
|
||||
},
|
||||
{
|
||||
"slug": "gatus",
|
||||
@@ -452,9 +452,9 @@
|
||||
{
|
||||
"slug": "gitea-mirror",
|
||||
"repo": "RayLabsHQ/gitea-mirror",
|
||||
"version": "v3.10.1",
|
||||
"version": "v3.11.0",
|
||||
"pinned": false,
|
||||
"date": "2026-03-01T03:08:07Z"
|
||||
"date": "2026-03-02T10:19:59Z"
|
||||
},
|
||||
{
|
||||
"slug": "glance",
|
||||
@@ -557,9 +557,9 @@
|
||||
{
|
||||
"slug": "homebox",
|
||||
"repo": "sysadminsmedia/homebox",
|
||||
"version": "v0.23.1",
|
||||
"version": "v0.24.0",
|
||||
"pinned": false,
|
||||
"date": "2026-02-01T22:53:32Z"
|
||||
"date": "2026-03-03T16:09:55Z"
|
||||
},
|
||||
{
|
||||
"slug": "homepage",
|
||||
@@ -585,9 +585,9 @@
|
||||
{
|
||||
"slug": "immich-public-proxy",
|
||||
"repo": "alangrainger/immich-public-proxy",
|
||||
"version": "v1.15.3",
|
||||
"version": "v1.15.4",
|
||||
"pinned": false,
|
||||
"date": "2026-02-16T22:54:27Z"
|
||||
"date": "2026-03-02T21:28:06Z"
|
||||
},
|
||||
{
|
||||
"slug": "inspircd",
|
||||
@@ -613,9 +613,9 @@
|
||||
{
|
||||
"slug": "jackett",
|
||||
"repo": "Jackett/Jackett",
|
||||
"version": "v0.24.1247",
|
||||
"version": "v0.24.1261",
|
||||
"pinned": false,
|
||||
"date": "2026-03-02T05:56:37Z"
|
||||
"date": "2026-03-03T05:54:20Z"
|
||||
},
|
||||
{
|
||||
"slug": "jellystat",
|
||||
@@ -634,9 +634,9 @@
|
||||
{
|
||||
"slug": "jotty",
|
||||
"repo": "fccview/jotty",
|
||||
"version": "1.20.0",
|
||||
"version": "1.21.0",
|
||||
"pinned": false,
|
||||
"date": "2026-02-12T09:23:30Z"
|
||||
"date": "2026-03-02T11:08:54Z"
|
||||
},
|
||||
{
|
||||
"slug": "kapowarr",
|
||||
@@ -669,9 +669,9 @@
|
||||
{
|
||||
"slug": "kima-hub",
|
||||
"repo": "Chevron7Locked/kima-hub",
|
||||
"version": "v1.6.0",
|
||||
"version": "v1.6.1",
|
||||
"pinned": false,
|
||||
"date": "2026-03-02T05:43:31Z"
|
||||
"date": "2026-03-03T16:13:53Z"
|
||||
},
|
||||
{
|
||||
"slug": "kimai",
|
||||
@@ -872,9 +872,9 @@
|
||||
{
|
||||
"slug": "metube",
|
||||
"repo": "alexta69/metube",
|
||||
"version": "2026.02.27",
|
||||
"version": "2026.03.02",
|
||||
"pinned": false,
|
||||
"date": "2026-02-27T11:47:02Z"
|
||||
"date": "2026-03-02T19:19:10Z"
|
||||
},
|
||||
{
|
||||
"slug": "miniflux",
|
||||
@@ -1149,6 +1149,13 @@
|
||||
"pinned": false,
|
||||
"date": "2026-02-23T19:50:48Z"
|
||||
},
|
||||
{
|
||||
"slug": "powerdns",
|
||||
"repo": "poweradmin/poweradmin",
|
||||
"version": "v4.0.7",
|
||||
"pinned": false,
|
||||
"date": "2026-02-15T20:09:48Z"
|
||||
},
|
||||
{
|
||||
"slug": "privatebin",
|
||||
"repo": "PrivateBin/PrivateBin",
|
||||
@@ -1156,6 +1163,13 @@
|
||||
"pinned": false,
|
||||
"date": "2025-11-12T07:10:14Z"
|
||||
},
|
||||
{
|
||||
"slug": "profilarr",
|
||||
"repo": "Dictionarry-Hub/profilarr",
|
||||
"version": "v1.1.4",
|
||||
"pinned": false,
|
||||
"date": "2026-01-29T14:57:25Z"
|
||||
},
|
||||
{
|
||||
"slug": "projectsend",
|
||||
"repo": "projectsend/projectsend",
|
||||
@@ -1215,9 +1229,9 @@
|
||||
{
|
||||
"slug": "pulse",
|
||||
"repo": "rcourtman/Pulse",
|
||||
"version": "v5.1.16",
|
||||
"version": "v5.1.17",
|
||||
"pinned": false,
|
||||
"date": "2026-03-01T23:13:09Z"
|
||||
"date": "2026-03-02T20:15:31Z"
|
||||
},
|
||||
{
|
||||
"slug": "pve-scripts-local",
|
||||
@@ -1341,9 +1355,9 @@
|
||||
{
|
||||
"slug": "scanopy",
|
||||
"repo": "scanopy/scanopy",
|
||||
"version": "v0.14.10",
|
||||
"version": "v0.14.11",
|
||||
"pinned": false,
|
||||
"date": "2026-02-28T21:05:12Z"
|
||||
"date": "2026-03-02T08:48:42Z"
|
||||
},
|
||||
{
|
||||
"slug": "scraparr",
|
||||
@@ -1418,9 +1432,9 @@
|
||||
{
|
||||
"slug": "snowshare",
|
||||
"repo": "TuroYT/snowshare",
|
||||
"version": "v1.3.7",
|
||||
"version": "v1.3.8",
|
||||
"pinned": false,
|
||||
"date": "2026-02-23T15:51:39Z"
|
||||
"date": "2026-03-02T07:43:42Z"
|
||||
},
|
||||
{
|
||||
"slug": "sonarr",
|
||||
@@ -1432,9 +1446,9 @@
|
||||
{
|
||||
"slug": "sonobarr",
|
||||
"repo": "Dodelidoo-Labs/sonobarr",
|
||||
"version": "0.11.0",
|
||||
"version": "0.12.1",
|
||||
"pinned": false,
|
||||
"date": "2026-01-21T19:07:21Z"
|
||||
"date": "2026-03-03T13:43:02Z"
|
||||
},
|
||||
{
|
||||
"slug": "speedtest-tracker",
|
||||
@@ -1548,6 +1562,13 @@
|
||||
"pinned": false,
|
||||
"date": "2026-02-13T16:30:09Z"
|
||||
},
|
||||
{
|
||||
"slug": "tinyauth",
|
||||
"repo": "steveiliop56/tinyauth",
|
||||
"version": "v5.0.0",
|
||||
"pinned": false,
|
||||
"date": "2026-03-02T18:43:57Z"
|
||||
},
|
||||
{
|
||||
"slug": "traccar",
|
||||
"repo": "traccar/traccar",
|
||||
@@ -1558,9 +1579,9 @@
|
||||
{
|
||||
"slug": "tracearr",
|
||||
"repo": "connorgallopo/Tracearr",
|
||||
"version": "v1.4.19",
|
||||
"version": "v1.4.20",
|
||||
"pinned": false,
|
||||
"date": "2026-02-28T21:25:47Z"
|
||||
"date": "2026-03-03T13:49:01Z"
|
||||
},
|
||||
{
|
||||
"slug": "tracktor",
|
||||
@@ -1628,9 +1649,9 @@
|
||||
{
|
||||
"slug": "upgopher",
|
||||
"repo": "wanetty/upgopher",
|
||||
"version": "v1.14.0",
|
||||
"version": "v1.15.2",
|
||||
"pinned": false,
|
||||
"date": "2026-02-24T22:43:34Z"
|
||||
"date": "2026-03-03T13:40:45Z"
|
||||
},
|
||||
{
|
||||
"slug": "upsnap",
|
||||
@@ -1656,9 +1677,9 @@
|
||||
{
|
||||
"slug": "victoriametrics",
|
||||
"repo": "VictoriaMetrics/VictoriaMetrics",
|
||||
"version": "v1.136.0",
|
||||
"version": "v1.137.0",
|
||||
"pinned": false,
|
||||
"date": "2026-02-16T13:17:50Z"
|
||||
"date": "2026-03-02T10:09:29Z"
|
||||
},
|
||||
{
|
||||
"slug": "vikunja",
|
||||
@@ -1684,9 +1705,9 @@
|
||||
{
|
||||
"slug": "wanderer",
|
||||
"repo": "meilisearch/meilisearch",
|
||||
"version": "v1.36.0",
|
||||
"version": "v1.37.0",
|
||||
"pinned": false,
|
||||
"date": "2026-02-23T08:13:32Z"
|
||||
"date": "2026-03-02T09:16:36Z"
|
||||
},
|
||||
{
|
||||
"slug": "warracker",
|
||||
@@ -1719,9 +1740,9 @@
|
||||
{
|
||||
"slug": "wealthfolio",
|
||||
"repo": "afadil/wealthfolio",
|
||||
"version": "v3.0.0",
|
||||
"version": "v3.0.2",
|
||||
"pinned": false,
|
||||
"date": "2026-02-24T22:37:05Z"
|
||||
"date": "2026-03-03T05:01:49Z"
|
||||
},
|
||||
{
|
||||
"slug": "web-check",
|
||||
@@ -1789,9 +1810,9 @@
|
||||
{
|
||||
"slug": "zigbee2mqtt",
|
||||
"repo": "Koenkk/zigbee2mqtt",
|
||||
"version": "2.9.0",
|
||||
"version": "2.9.1",
|
||||
"pinned": false,
|
||||
"date": "2026-03-01T13:58:14Z"
|
||||
"date": "2026-03-02T11:16:46Z"
|
||||
},
|
||||
{
|
||||
"slug": "zipline",
|
||||
@@ -1803,9 +1824,9 @@
|
||||
{
|
||||
"slug": "zitadel",
|
||||
"repo": "zitadel/zitadel",
|
||||
"version": "v4.11.1",
|
||||
"version": "v4.12.0",
|
||||
"pinned": false,
|
||||
"date": "2026-02-25T06:13:13Z"
|
||||
"date": "2026-03-02T08:16:10Z"
|
||||
},
|
||||
{
|
||||
"slug": "zoraxy",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"resources": {
|
||||
"cpu": 1,
|
||||
"ram": 512,
|
||||
"hdd": 2,
|
||||
"hdd": 4,
|
||||
"os": "debian",
|
||||
"version": "13"
|
||||
}
|
||||
|
||||
40
frontend/public/json/powerdns.json
Normal file
40
frontend/public/json/powerdns.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "PowerDNS",
|
||||
"slug": "powerdns",
|
||||
"categories": [
|
||||
5
|
||||
],
|
||||
"date_created": "2026-03-02",
|
||||
"type": "ct",
|
||||
"updateable": true,
|
||||
"privileged": false,
|
||||
"interface_port": 80,
|
||||
"documentation": "https://doc.powerdns.com/index.html",
|
||||
"config_path": "/opt/poweradmin/config/settings.php",
|
||||
"website": "https://www.powerdns.com/",
|
||||
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/powerdns.webp",
|
||||
"description": "The PowerDNS Authoritative Server is a versatile nameserver which supports a large number of backends. These backends can either be plain zone files or be more dynamic in nature. PowerDNS has the concepts of ‘backends’. A backend is a datastore that the server will consult that contains DNS records (and some metadata). The backends range from database backends (MySQL, PostgreSQL) and BIND zone files to co-processes and JSON API’s.",
|
||||
"install_methods": [
|
||||
{
|
||||
"type": "default",
|
||||
"script": "ct/powerdns.sh",
|
||||
"resources": {
|
||||
"cpu": 1,
|
||||
"ram": 1024,
|
||||
"hdd": 4,
|
||||
"os": "Debian",
|
||||
"version": "13"
|
||||
}
|
||||
}
|
||||
],
|
||||
"default_credentials": {
|
||||
"username": null,
|
||||
"password": null
|
||||
},
|
||||
"notes": [
|
||||
{
|
||||
"text": "For administrator credentials type: `cat ~/poweradmin.creds` inside LXC.",
|
||||
"type": "info"
|
||||
}
|
||||
]
|
||||
}
|
||||
35
frontend/public/json/profilarr.json
Normal file
35
frontend/public/json/profilarr.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "Profilarr",
|
||||
"slug": "profilarr",
|
||||
"categories": [
|
||||
14
|
||||
],
|
||||
"date_created": "2026-03-02",
|
||||
"type": "ct",
|
||||
"updateable": true,
|
||||
"privileged": false,
|
||||
"interface_port": 6868,
|
||||
"documentation": "https://github.com/Dictionarry-Hub/profilarr#readme",
|
||||
"website": "https://github.com/Dictionarry-Hub/profilarr",
|
||||
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/profilarr.webp",
|
||||
"config_path": "/config",
|
||||
"description": "Profilarr is a configuration management platform for Radarr and Sonarr that simplifies importing, syncing, and managing quality profiles, custom formats, and release profiles.",
|
||||
"install_methods": [
|
||||
{
|
||||
"type": "default",
|
||||
"script": "ct/profilarr.sh",
|
||||
"resources": {
|
||||
"cpu": 2,
|
||||
"ram": 2048,
|
||||
"hdd": 8,
|
||||
"os": "Debian",
|
||||
"version": "13"
|
||||
}
|
||||
}
|
||||
],
|
||||
"default_credentials": {
|
||||
"username": null,
|
||||
"password": null
|
||||
},
|
||||
"notes": []
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "Alpine-Tinyauth",
|
||||
"slug": "alpine-tinyauth",
|
||||
"name": "Tinyauth",
|
||||
"slug": "tinyauth",
|
||||
"categories": [
|
||||
6
|
||||
],
|
||||
"date_created": "2025-05-06",
|
||||
"date_created": "2026-03-03",
|
||||
"type": "ct",
|
||||
"updateable": true,
|
||||
"privileged": false,
|
||||
@@ -17,13 +17,13 @@
|
||||
"install_methods": [
|
||||
{
|
||||
"type": "default",
|
||||
"script": "ct/alpine-tinyauth.sh",
|
||||
"script": "ct/tinyauth.sh",
|
||||
"resources": {
|
||||
"cpu": 1,
|
||||
"ram": 256,
|
||||
"hdd": 2,
|
||||
"os": "alpine",
|
||||
"version": "3.23"
|
||||
"ram": 512,
|
||||
"hdd": 4,
|
||||
"os": "debian",
|
||||
"version": "13"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"name": "UniFi Network Server",
|
||||
"slug": "unifi",
|
||||
"categories": [
|
||||
4
|
||||
],
|
||||
"date_created": "2024-05-02",
|
||||
"type": "ct",
|
||||
"updateable": true,
|
||||
"privileged": false,
|
||||
"interface_port": 8443,
|
||||
"documentation": "https://help.ui.com/hc/en-us/articles/360012282453-Self-Hosting-a-UniFi-Network-Server",
|
||||
"website": "https://www.ui.com/",
|
||||
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/ubiquiti-unifi.webp",
|
||||
"config_path": "",
|
||||
"description": "UniFi Network Server is a software that helps manage and monitor UniFi networks (Wi-Fi, Ethernet, etc.) by providing an intuitive user interface and advanced features. It allows network administrators to configure, monitor, and upgrade network devices, as well as view network statistics, client devices, and historical events. The aim of the application is to make the management of UniFi networks easier and more efficient.",
|
||||
"disable": true,
|
||||
"disable_description": "This script is disabled because UniFi no longer delivers APT packages for Debian systems. The installation relies on APT repositories that are no longer maintained or available. For more details, see: https://github.com/community-scripts/ProxmoxVE/issues/11876",
|
||||
"install_methods": [
|
||||
{
|
||||
"type": "default",
|
||||
"script": "ct/unifi.sh",
|
||||
"resources": {
|
||||
"cpu": 2,
|
||||
"ram": 2048,
|
||||
"hdd": 8,
|
||||
"os": "debian",
|
||||
"version": "12"
|
||||
}
|
||||
}
|
||||
],
|
||||
"default_credentials": {
|
||||
"username": null,
|
||||
"password": null
|
||||
},
|
||||
"notes": [
|
||||
{
|
||||
"text": "For non-AVX CPUs, MongoDB 4.4 is installed. Please note this is a legacy solution that may present security risks and could become unsupported in future updates.",
|
||||
"type": "warning"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -2,42 +2,68 @@
|
||||
|
||||
import type { z } from "zod";
|
||||
|
||||
import { githubGist, nord } from "react-syntax-highlighter/dist/esm/styles/hljs";
|
||||
import { CalendarIcon, Check, Clipboard, Download } from "lucide-react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import SyntaxHighlighter from "react-syntax-highlighter";
|
||||
import { useTheme } from "next-themes";
|
||||
import { format } from "date-fns";
|
||||
import { toast } from "sonner";
|
||||
import Image from "next/image";
|
||||
|
||||
import type { Category } from "@/lib/types";
|
||||
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { basePath } from "@/config/site-config";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { fetchCategories } from "@/lib/data";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import type { Script } from "./_schemas/schemas";
|
||||
|
||||
import { ScriptItem } from "../scripts/_components/script-item";
|
||||
import InstallMethod from "./_components/install-method";
|
||||
import { ScriptSchema } from "./_schemas/schemas";
|
||||
import Categories from "./_components/categories";
|
||||
import Note from "./_components/note";
|
||||
|
||||
import { githubGist, nord } from "react-syntax-highlighter/dist/esm/styles/hljs";
|
||||
import SyntaxHighlighter from "react-syntax-highlighter";
|
||||
import { ScriptItem } from "../scripts/_components/script-item";
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||
import { search } from "@/components/command-menu";
|
||||
import { basePath } from "@/config/site-config";
|
||||
import Image from "next/image";
|
||||
import { useTheme } from "next-themes";
|
||||
function search(scripts: Script[], query: string): Script[] {
|
||||
const queryLower = query.toLowerCase().trim();
|
||||
const searchWords = queryLower.split(/\s+/).filter(Boolean);
|
||||
|
||||
return scripts
|
||||
.map((script) => {
|
||||
const nameLower = script.name.toLowerCase();
|
||||
const descriptionLower = (script.description || "").toLowerCase();
|
||||
|
||||
let score = 0;
|
||||
|
||||
for (const word of searchWords) {
|
||||
if (nameLower.includes(word)) {
|
||||
score += 10;
|
||||
}
|
||||
if (descriptionLower.includes(word)) {
|
||||
score += 5;
|
||||
}
|
||||
}
|
||||
|
||||
return { script, score };
|
||||
})
|
||||
.filter(({ score }) => score > 0)
|
||||
.sort((a, b) => b.score - a.score)
|
||||
.slice(0, 20)
|
||||
.map(({ script }) => script);
|
||||
}
|
||||
|
||||
const initialScript: Script = {
|
||||
name: "",
|
||||
@@ -77,32 +103,32 @@ export default function JSONGenerator() {
|
||||
|
||||
const selectedCategoryObj = useMemo(
|
||||
() => categories.find(cat => cat.id.toString() === selectedCategory),
|
||||
[categories, selectedCategory]
|
||||
[categories, selectedCategory],
|
||||
);
|
||||
|
||||
const allScripts = useMemo(
|
||||
() => categories.flatMap(cat => cat.scripts || []),
|
||||
[categories]
|
||||
[categories],
|
||||
);
|
||||
|
||||
const scripts = useMemo(() => {
|
||||
const query = searchQuery.trim()
|
||||
const query = searchQuery.trim();
|
||||
|
||||
if (query) {
|
||||
return search(allScripts, query)
|
||||
return search(allScripts, query);
|
||||
}
|
||||
|
||||
if (selectedCategoryObj) {
|
||||
return selectedCategoryObj.scripts || []
|
||||
return selectedCategoryObj.scripts || [];
|
||||
}
|
||||
|
||||
return []
|
||||
return [];
|
||||
}, [allScripts, selectedCategoryObj, searchQuery]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchCategories()
|
||||
.then(setCategories)
|
||||
.catch((error) => console.error("Error fetching categories:", error));
|
||||
.catch(error => console.error("Error fetching categories:", error));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -122,11 +148,14 @@ export default function JSONGenerator() {
|
||||
|
||||
if (updated.type === "pve") {
|
||||
scriptPath = `tools/pve/${updated.slug}.sh`;
|
||||
} else if (updated.type === "addon") {
|
||||
}
|
||||
else if (updated.type === "addon") {
|
||||
scriptPath = `tools/addon/${updated.slug}.sh`;
|
||||
} else if (method.type === "alpine") {
|
||||
}
|
||||
else if (method.type === "alpine") {
|
||||
scriptPath = `${updated.type}/alpine-${updated.slug}.sh`;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
scriptPath = `${updated.type}/${updated.slug}.sh`;
|
||||
}
|
||||
|
||||
@@ -145,11 +174,13 @@ export default function JSONGenerator() {
|
||||
}, []);
|
||||
|
||||
const handleCopy = useCallback(() => {
|
||||
if (!isValid) toast.warning("JSON schema is invalid. Copying anyway.");
|
||||
if (!isValid)
|
||||
toast.warning("JSON schema is invalid. Copying anyway.");
|
||||
navigator.clipboard.writeText(JSON.stringify(script, null, 2));
|
||||
setIsCopied(true);
|
||||
setTimeout(() => setIsCopied(false), 2000);
|
||||
if (isValid) toast.success("Copied metadata to clipboard");
|
||||
if (isValid)
|
||||
toast.success("Copied metadata to clipboard");
|
||||
}, [script]);
|
||||
|
||||
const importScript = (script: Script) => {
|
||||
@@ -166,11 +197,11 @@ export default function JSONGenerator() {
|
||||
setIsValid(true);
|
||||
setZodErrors(null);
|
||||
toast.success("Imported JSON successfully");
|
||||
} catch (error) {
|
||||
}
|
||||
catch (error) {
|
||||
toast.error("Failed to read or parse the JSON file.");
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileImport = useCallback(() => {
|
||||
const input = document.createElement("input");
|
||||
@@ -180,7 +211,8 @@ export default function JSONGenerator() {
|
||||
input.onchange = (e: Event) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
const file = target.files?.[0];
|
||||
if (!file) return;
|
||||
if (!file)
|
||||
return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
@@ -189,7 +221,8 @@ export default function JSONGenerator() {
|
||||
const parsed = JSON.parse(content);
|
||||
importScript(parsed);
|
||||
toast.success("Imported JSON successfully");
|
||||
} catch (error) {
|
||||
}
|
||||
catch (error) {
|
||||
toast.error("Failed to read the JSON file.");
|
||||
}
|
||||
};
|
||||
@@ -243,7 +276,10 @@ export default function JSONGenerator() {
|
||||
<div className="mt-2 space-y-1">
|
||||
{zodErrors.issues.map((error, index) => (
|
||||
<AlertDescription key={index} className="p-1 text-red-500">
|
||||
{error.path.join(".")} -{error.message}
|
||||
{error.path.join(".")}
|
||||
{" "}
|
||||
-
|
||||
{error.message}
|
||||
</AlertDescription>
|
||||
))}
|
||||
</div>
|
||||
@@ -270,7 +306,7 @@ export default function JSONGenerator() {
|
||||
onOpenChange={setIsImportDialogOpen}
|
||||
>
|
||||
<DialogTrigger asChild>
|
||||
<DropdownMenuItem onSelect={(e) => e.preventDefault()}>
|
||||
<DropdownMenuItem onSelect={e => e.preventDefault()}>
|
||||
Import existing script
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
@@ -292,7 +328,7 @@ export default function JSONGenerator() {
|
||||
<SelectValue placeholder="Category" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{categories.map((category) => (
|
||||
{categories.map(category => (
|
||||
<SelectItem key={category.id} value={category.id.toString()}>
|
||||
{category.name}
|
||||
</SelectItem>
|
||||
@@ -302,40 +338,44 @@ export default function JSONGenerator() {
|
||||
<Input
|
||||
placeholder="Search for a script..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
onChange={e => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
{!selectedCategory && !searchQuery ? (
|
||||
<p className="text-muted-foreground text-sm text-center">
|
||||
Select a category or search for a script
|
||||
</p>
|
||||
) : scripts.length === 0 ? (
|
||||
<p className="text-muted-foreground text-sm text-center">
|
||||
No scripts found
|
||||
</p>
|
||||
) : (
|
||||
<div className="grid grid-cols-3 auto-rows-min h-64 overflow-y-auto gap-4">
|
||||
{scripts.map(script => (
|
||||
<div
|
||||
key={script.slug}
|
||||
className="p-2 border rounded cursor-pointer hover:bg-accent hover:text-accent-foreground"
|
||||
onClick={() => {
|
||||
importScript(script);
|
||||
setIsImportDialogOpen(false);
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={script.logo || `/${basePath}/logo.png`}
|
||||
alt={script.name}
|
||||
className="w-full h-12 object-contain mb-2"
|
||||
width={16}
|
||||
height={16}
|
||||
unoptimized
|
||||
/>
|
||||
<p className="text-sm text-center">{script.name}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{!selectedCategory && !searchQuery
|
||||
? (
|
||||
<p className="text-muted-foreground text-sm text-center">
|
||||
Select a category or search for a script
|
||||
</p>
|
||||
)
|
||||
: scripts.length === 0
|
||||
? (
|
||||
<p className="text-muted-foreground text-sm text-center">
|
||||
No scripts found
|
||||
</p>
|
||||
)
|
||||
: (
|
||||
<div className="grid grid-cols-3 auto-rows-min h-64 overflow-y-auto gap-4">
|
||||
{scripts.map(script => (
|
||||
<div
|
||||
key={script.slug}
|
||||
className="p-2 border rounded cursor-pointer hover:bg-accent hover:text-accent-foreground"
|
||||
onClick={() => {
|
||||
importScript(script);
|
||||
setIsImportDialogOpen(false);
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={script.logo || `/${basePath}/logo.png`}
|
||||
alt={script.name}
|
||||
className="w-full h-12 object-contain mb-2"
|
||||
width={16}
|
||||
height={16}
|
||||
unoptimized
|
||||
/>
|
||||
<p className="text-sm text-center">{script.name}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
@@ -348,15 +388,19 @@ export default function JSONGenerator() {
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label>
|
||||
Name <span className="text-red-500">*</span>
|
||||
Name
|
||||
{" "}
|
||||
<span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input placeholder="Example" value={script.name} onChange={(e) => updateScript("name", e.target.value)} />
|
||||
<Input placeholder="Example" value={script.name} onChange={e => updateScript("name", e.target.value)} />
|
||||
</div>
|
||||
<div>
|
||||
<Label>
|
||||
Slug <span className="text-red-500">*</span>
|
||||
Slug
|
||||
{" "}
|
||||
<span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input placeholder="example" value={script.slug} onChange={(e) => updateScript("slug", e.target.value)} />
|
||||
<Input placeholder="example" value={script.slug} onChange={e => updateScript("slug", e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@@ -366,7 +410,7 @@ export default function JSONGenerator() {
|
||||
<Input
|
||||
placeholder="Full logo URL"
|
||||
value={script.logo || ""}
|
||||
onChange={(e) => updateScript("logo", e.target.value || null)}
|
||||
onChange={e => updateScript("logo", e.target.value || null)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -374,24 +418,28 @@ export default function JSONGenerator() {
|
||||
<Input
|
||||
placeholder="Path to config file"
|
||||
value={script.config_path || ""}
|
||||
onChange={(e) => updateScript("config_path", e.target.value || "")}
|
||||
onChange={e => updateScript("config_path", e.target.value || "")}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>
|
||||
Description <span className="text-red-500">*</span>
|
||||
Description
|
||||
{" "}
|
||||
<span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Textarea
|
||||
placeholder="Example"
|
||||
value={script.description}
|
||||
onChange={(e) => updateScript("description", e.target.value)}
|
||||
onChange={e => updateScript("description", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<Categories script={script} setScript={setScript} categories={categories} />
|
||||
<div className="flex gap-2">
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<Label>
|
||||
Date Created <span className="text-red-500">*</span>
|
||||
Date Created
|
||||
{" "}
|
||||
<span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild className="flex-1">
|
||||
@@ -415,7 +463,7 @@ export default function JSONGenerator() {
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<Label>Type</Label>
|
||||
<Select value={script.type} onValueChange={(value) => updateScript("type", value)}>
|
||||
<Select value={script.type} onValueChange={value => updateScript("type", value)}>
|
||||
<SelectTrigger className="flex-1">
|
||||
<SelectValue placeholder="Type" />
|
||||
</SelectTrigger>
|
||||
@@ -430,17 +478,17 @@ export default function JSONGenerator() {
|
||||
</div>
|
||||
<div className="w-full flex gap-5">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch checked={script.updateable} onCheckedChange={(checked) => updateScript("updateable", checked)} />
|
||||
<Switch checked={script.updateable} onCheckedChange={checked => updateScript("updateable", checked)} />
|
||||
<label>Updateable</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch checked={script.privileged} onCheckedChange={(checked) => updateScript("privileged", checked)} />
|
||||
<Switch checked={script.privileged} onCheckedChange={checked => updateScript("privileged", checked)} />
|
||||
<label>Privileged</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch
|
||||
checked={script.disable || false}
|
||||
onCheckedChange={(checked) => updateScript("disable", checked)}
|
||||
onCheckedChange={checked => updateScript("disable", checked)}
|
||||
/>
|
||||
<label>Disabled</label>
|
||||
</div>
|
||||
@@ -448,12 +496,14 @@ export default function JSONGenerator() {
|
||||
{script.disable && (
|
||||
<div>
|
||||
<Label>
|
||||
Disable Description <span className="text-red-500">*</span>
|
||||
Disable Description
|
||||
{" "}
|
||||
<span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Textarea
|
||||
placeholder="Explain why this script is disabled..."
|
||||
value={script.disable_description || ""}
|
||||
onChange={(e) => updateScript("disable_description", e.target.value)}
|
||||
onChange={e => updateScript("disable_description", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -461,18 +511,18 @@ export default function JSONGenerator() {
|
||||
placeholder="Interface Port"
|
||||
type="number"
|
||||
value={script.interface_port || ""}
|
||||
onChange={(e) => updateScript("interface_port", e.target.value ? Number(e.target.value) : null)}
|
||||
onChange={e => updateScript("interface_port", e.target.value ? Number(e.target.value) : null)}
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
placeholder="Website URL"
|
||||
value={script.website || ""}
|
||||
onChange={(e) => updateScript("website", e.target.value || null)}
|
||||
onChange={e => updateScript("website", e.target.value || null)}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Documentation URL"
|
||||
value={script.documentation || ""}
|
||||
onChange={(e) => updateScript("documentation", e.target.value || null)}
|
||||
onChange={e => updateScript("documentation", e.target.value || null)}
|
||||
/>
|
||||
</div>
|
||||
<InstallMethod script={script} setScript={setScript} setIsValid={setIsValid} setZodErrors={setZodErrors} />
|
||||
@@ -480,22 +530,20 @@ export default function JSONGenerator() {
|
||||
<Input
|
||||
placeholder="Username"
|
||||
value={script.default_credentials.username || ""}
|
||||
onChange={(e) =>
|
||||
onChange={e =>
|
||||
updateScript("default_credentials", {
|
||||
...script.default_credentials,
|
||||
username: e.target.value || null,
|
||||
})
|
||||
}
|
||||
})}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Password"
|
||||
value={script.default_credentials.password || ""}
|
||||
onChange={(e) =>
|
||||
onChange={e =>
|
||||
updateScript("default_credentials", {
|
||||
...script.default_credentials,
|
||||
password: e.target.value || null,
|
||||
})
|
||||
}
|
||||
})}
|
||||
/>
|
||||
<Note script={script} setScript={setScript} setIsValid={setIsValid} setZodErrors={setZodErrors} />
|
||||
</form>
|
||||
@@ -504,7 +552,7 @@ export default function JSONGenerator() {
|
||||
<Tabs
|
||||
defaultValue="json"
|
||||
className="w-full"
|
||||
onValueChange={(value) => setCurrentTab(value as "json" | "preview")}
|
||||
onValueChange={value => setCurrentTab(value as "json" | "preview")}
|
||||
value={currentTab}
|
||||
>
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useRouter } from "next/navigation";
|
||||
import { ArrowRightIcon, Sparkles } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import React from "react";
|
||||
|
||||
import type { Category, Script } from "@/lib/types";
|
||||
@@ -21,35 +22,6 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./ui/t
|
||||
import { DialogTitle } from "./ui/dialog";
|
||||
import { Button } from "./ui/button";
|
||||
import { Badge } from "./ui/badge";
|
||||
import Link from "next/link";
|
||||
|
||||
export function search(scripts: Script[], query: string): Script[] {
|
||||
const queryLower = query.toLowerCase().trim();
|
||||
const searchWords = queryLower.split(/\s+/).filter(Boolean);
|
||||
|
||||
return scripts
|
||||
.map(script => {
|
||||
const nameLower = script.name.toLowerCase();
|
||||
const descriptionLower = (script.description || "").toLowerCase();
|
||||
|
||||
let score = 0;
|
||||
|
||||
for (const word of searchWords) {
|
||||
if (nameLower.includes(word)) {
|
||||
score += 10;
|
||||
}
|
||||
if (descriptionLower.includes(word)) {
|
||||
score += 5;
|
||||
}
|
||||
}
|
||||
|
||||
return { script, score };
|
||||
})
|
||||
.filter(({ score }) => score > 0)
|
||||
.sort((a, b) => b.score - a.score)
|
||||
.slice(0, 20)
|
||||
.map(({ script }) => script);
|
||||
}
|
||||
|
||||
export function formattedBadge(type: string) {
|
||||
switch (type) {
|
||||
@@ -79,11 +51,9 @@ function getRandomScript(categories: Category[], previouslySelected: Set<string>
|
||||
}
|
||||
|
||||
function CommandMenu() {
|
||||
const [query, setQuery] = React.useState("");
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [links, setLinks] = React.useState<Category[]>([]);
|
||||
const [isLoading, setIsLoading] = React.useState(false);
|
||||
const [results, setResults] = React.useState<Script[]>([]);
|
||||
const [selectedScripts, setSelectedScripts] = React.useState<Set<string>>(new Set());
|
||||
const router = useRouter();
|
||||
|
||||
@@ -100,27 +70,6 @@ function CommandMenu() {
|
||||
});
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (query.trim() === "") {
|
||||
fetchSortedCategories();
|
||||
}
|
||||
else {
|
||||
const scriptMap = new Map<string, Script>();
|
||||
|
||||
for (const category of links) {
|
||||
for (const script of category.scripts || []) {
|
||||
if (!scriptMap.has(script.slug)) {
|
||||
scriptMap.set(script.slug, script);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const uniqueScripts = Array.from(scriptMap.values());
|
||||
const filteredResults = search(uniqueScripts, query);
|
||||
setResults(filteredResults);
|
||||
}
|
||||
}, [query]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
|
||||
@@ -248,46 +197,49 @@ function CommandMenu() {
|
||||
|
||||
<CommandDialog
|
||||
open={open}
|
||||
onOpenChange={(open) => {
|
||||
setOpen(open);
|
||||
if (open) {
|
||||
setQuery("");
|
||||
setResults([]);
|
||||
}
|
||||
onOpenChange={setOpen}
|
||||
filter={(value: string, search: string) => {
|
||||
const searchLower = search.toLowerCase().trim();
|
||||
if (!searchLower)
|
||||
return 1;
|
||||
const valueLower = value.toLowerCase();
|
||||
const searchWords = searchLower.split(/\s+/).filter(Boolean);
|
||||
// All search words must appear somewhere in the value (name + description)
|
||||
const allWordsMatch = searchWords.every((word: string) => valueLower.includes(word));
|
||||
return allWordsMatch ? 1 : 0;
|
||||
}}
|
||||
>
|
||||
<DialogTitle className="sr-only">Search scripts</DialogTitle>
|
||||
<CommandInput
|
||||
placeholder="Search for a script..."
|
||||
onValueChange={setQuery}
|
||||
value={query}
|
||||
/>
|
||||
<CommandInput placeholder="Search for a script..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>
|
||||
{isLoading ? (
|
||||
"Searching..."
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center py-6 text-center">
|
||||
<p className="text-sm text-muted-foreground">No scripts match your search.</p>
|
||||
<div className="mt-4">
|
||||
<p className="text-xs text-muted-foreground mb-2">Want to add a new script?</p>
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<Link
|
||||
href={`https://github.com/community-scripts/${basePath}/tree/main/docs/contribution/GUIDE.md`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Documentation <ArrowRightIcon className="ml-2 h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isLoading
|
||||
? (
|
||||
"Searching..."
|
||||
)
|
||||
: (
|
||||
<div className="flex flex-col items-center justify-center py-6 text-center">
|
||||
<p className="text-sm text-muted-foreground">No scripts match your search.</p>
|
||||
<div className="mt-4">
|
||||
<p className="text-xs text-muted-foreground mb-2">Want to add a new script?</p>
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<Link
|
||||
href={`https://github.com/community-scripts/${basePath}/tree/main/docs/contribution/GUIDE.md`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Documentation
|
||||
{" "}
|
||||
<ArrowRightIcon className="ml-2 h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CommandEmpty>
|
||||
|
||||
{results.length > 0 ? (
|
||||
<CommandGroup heading="Search Results">
|
||||
{results.map(script => (
|
||||
{Object.entries(uniqueScriptsByCategory).map(([categoryName, scripts]) => (
|
||||
<CommandGroup key={`category:${categoryName}`} heading={categoryName}>
|
||||
{scripts.map(script => (
|
||||
<CommandItem
|
||||
key={`script:${script.slug}`}
|
||||
value={`${script.name} ${script.type} ${script.description || ""}`}
|
||||
@@ -320,44 +272,7 @@ function CommandMenu() {
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
) : ( // When no search results, show all scripts grouped by category
|
||||
Object.entries(uniqueScriptsByCategory).map(([categoryName, scripts]) => (
|
||||
<CommandGroup key={`category:${categoryName}`} heading={categoryName}>
|
||||
{scripts.map(script => (
|
||||
<CommandItem
|
||||
key={`script:${script.slug}`}
|
||||
value={`${script.name} ${script.type} ${script.description || ""}`}
|
||||
onSelect={() => {
|
||||
setOpen(false);
|
||||
router.push(`/scripts?id=${script.slug}`);
|
||||
}}
|
||||
tabIndex={0}
|
||||
aria-label={`Open script ${script.name}`}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
setOpen(false);
|
||||
router.push(`/scripts?id=${script.slug}`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex gap-2" onClick={() => setOpen(false)}>
|
||||
<Image
|
||||
src={script.logo || `/${basePath}/logo.png`}
|
||||
onError={e => ((e.currentTarget as HTMLImageElement).src = `/${basePath}/logo.png`)}
|
||||
unoptimized
|
||||
width={16}
|
||||
height={16}
|
||||
alt=""
|
||||
className="h-5 w-5"
|
||||
/>
|
||||
<span>{script.name}</span>
|
||||
<span>{formattedBadge(script.type)}</span>
|
||||
</div>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
))
|
||||
)}
|
||||
))}
|
||||
</CommandList>
|
||||
</CommandDialog>
|
||||
</>
|
||||
|
||||
@@ -14,7 +14,7 @@ network_check
|
||||
update_os
|
||||
|
||||
read -r -p "${TAB3}Enter PostgreSQL version (15/16/17): " ver
|
||||
[[ $ver =~ ^(15|16|17)$ ]] || { echo "Invalid version"; exit 1; }
|
||||
[[ $ver =~ ^(15|16|17)$ ]] || { echo "Invalid version"; exit 64; }
|
||||
|
||||
msg_info "Installing PostgreSQL ${ver}"
|
||||
$STD apk add --no-cache postgresql${ver} postgresql${ver}-contrib postgresql${ver}-openrc sudo
|
||||
|
||||
@@ -36,9 +36,9 @@ msg_ok "Installed Tinyauth"
|
||||
read -r -p "${TAB3}Enter your Tinyauth subdomain (e.g. https://tinyauth.example.com): " app_url
|
||||
|
||||
cat <<EOF >/opt/tinyauth/.env
|
||||
DATABASE_PATH=/opt/tinyauth/database.db
|
||||
USERS='${USER}'
|
||||
APP_URL=${app_url}
|
||||
TINYAUTH_DATABASE_PATH=/opt/tinyauth/database.db
|
||||
TINYAUTH_AUTH_USERS='${USER}'
|
||||
TINYAUTH_APPURL=${app_url}
|
||||
EOF
|
||||
|
||||
msg_info "Creating Service"
|
||||
|
||||
@@ -25,7 +25,7 @@ case $version in
|
||||
;;
|
||||
*)
|
||||
msg_error "Invalid JDK version selected. Please enter 8, 11, 17 or 21."
|
||||
exit 1
|
||||
exit 64
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
@@ -39,7 +39,7 @@ case $version in
|
||||
;;
|
||||
*)
|
||||
msg_error "Invalid JDK version selected. Please enter 11, 17 or 21."
|
||||
exit 1
|
||||
exit 64
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
@@ -53,13 +53,13 @@ case $version in
|
||||
;;
|
||||
*)
|
||||
msg_error "Invalid JDK version selected. Please enter 17 or 21."
|
||||
exit 1
|
||||
exit 64
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
msg_error "Invalid Tomcat version selected. Please enter 9, 10.1 or 11."
|
||||
exit 1
|
||||
exit 64
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ mkdir -p /opt/booklore/dist
|
||||
JAR_PATH=$(find /opt/booklore/booklore-api/build/libs -maxdepth 1 -type f -name "booklore-api-*.jar" ! -name "*plain*" | head -n1)
|
||||
if [[ -z "$JAR_PATH" ]]; then
|
||||
msg_error "Backend JAR not found"
|
||||
exit 1
|
||||
exit 153
|
||||
fi
|
||||
cp "$JAR_PATH" /opt/booklore/dist/app.jar
|
||||
msg_ok "Built Backend"
|
||||
|
||||
@@ -13,6 +13,10 @@ setting_up_container
|
||||
network_check
|
||||
update_os
|
||||
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apt install -y build-essential
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
NODE_VERSION="24" setup_nodejs
|
||||
|
||||
msg_info "Setup Cross-Seed"
|
||||
|
||||
@@ -86,7 +86,7 @@ EOF
|
||||
msg_ok "Docker TCP socket available on $socket"
|
||||
else
|
||||
msg_error "Docker failed to restart. Check journalctl -xeu docker.service"
|
||||
exit 1
|
||||
exit 150
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ msg_info "Fetching latest EMQX Enterprise version"
|
||||
LATEST_VERSION=$(curl -fsSL https://www.emqx.com/en/downloads/enterprise | grep -oP '/en/downloads/enterprise/v\K[0-9]+\.[0-9]+\.[0-9]+' | sort -V | tail -n1)
|
||||
if [[ -z "$LATEST_VERSION" ]]; then
|
||||
msg_error "Failed to determine latest EMQX version"
|
||||
exit 1
|
||||
exit 250
|
||||
fi
|
||||
msg_ok "Latest version: v$LATEST_VERSION"
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Authors: MickLesk (CanbiZ)
|
||||
# Co-Authors: remz1337
|
||||
# Authors: MickLesk (CanbiZ) | Co-Authors: remz1337
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://frigate.video/ | Github: https://github.com/blakeblackshear/frigate
|
||||
|
||||
@@ -17,7 +16,7 @@ update_os
|
||||
source /etc/os-release
|
||||
if [[ "$VERSION_ID" != "12" ]]; then
|
||||
msg_error "Frigate requires Debian 12 (Bookworm) due to Python 3.11 dependencies"
|
||||
exit 1
|
||||
exit 238
|
||||
fi
|
||||
|
||||
msg_info "Converting APT sources to DEB822 format"
|
||||
@@ -85,6 +84,7 @@ $STD apt install -y \
|
||||
tclsh \
|
||||
libopenblas-dev \
|
||||
liblapack-dev \
|
||||
libgomp1 \
|
||||
make \
|
||||
moreutils
|
||||
msg_ok "Installed Dependencies"
|
||||
@@ -101,9 +101,16 @@ export NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
|
||||
export TOKENIZERS_PARALLELISM=true
|
||||
export TRANSFORMERS_NO_ADVISORY_WARNINGS=1
|
||||
export OPENCV_FFMPEG_LOGLEVEL=8
|
||||
export PYTHONWARNINGS="ignore:::numpy.core.getlimits"
|
||||
export HAILORT_LOGGER_PATH=NONE
|
||||
export TF_CPP_MIN_LOG_LEVEL=3
|
||||
export TF_CPP_MIN_VLOG_LEVEL=3
|
||||
export TF_ENABLE_ONEDNN_OPTS=0
|
||||
export AUTOGRAPH_VERBOSITY=0
|
||||
export GLOG_minloglevel=3
|
||||
export GLOG_logtostderr=0
|
||||
|
||||
fetch_and_deploy_gh_release "frigate" "blakeblackshear/frigate" "tarball" "v0.16.4" "/opt/frigate"
|
||||
fetch_and_deploy_gh_release "frigate" "blakeblackshear/frigate" "tarball" "v0.17.0" "/opt/frigate"
|
||||
|
||||
msg_info "Building Nginx"
|
||||
$STD bash /opt/frigate/docker/main/build_nginx.sh
|
||||
@@ -138,13 +145,19 @@ install -c -m 644 libusb-1.0.pc /usr/local/lib/pkgconfig
|
||||
ldconfig
|
||||
msg_ok "Built libUSB"
|
||||
|
||||
msg_info "Bootstrapping pip"
|
||||
wget -q https://bootstrap.pypa.io/get-pip.py -O /tmp/get-pip.py
|
||||
sed -i 's/args.append("setuptools")/args.append("setuptools==77.0.3")/' /tmp/get-pip.py
|
||||
$STD python3 /tmp/get-pip.py "pip"
|
||||
rm -f /tmp/get-pip.py
|
||||
msg_ok "Bootstrapped pip"
|
||||
|
||||
msg_info "Installing Python Dependencies"
|
||||
$STD pip3 install -r /opt/frigate/docker/main/requirements.txt
|
||||
msg_ok "Installed Python Dependencies"
|
||||
|
||||
msg_info "Building Python Wheels (Patience)"
|
||||
mkdir -p /wheels
|
||||
sed -i 's|^SQLITE3_VERSION=.*|SQLITE3_VERSION="version-3.46.0"|g' /opt/frigate/docker/main/build_pysqlite3.sh
|
||||
$STD bash /opt/frigate/docker/main/build_pysqlite3.sh
|
||||
for i in {1..3}; do
|
||||
$STD pip3 wheel --wheel-dir=/wheels -r /opt/frigate/docker/main/requirements-wheels.txt --default-timeout=300 --retries=3 && break
|
||||
@@ -152,7 +165,7 @@ for i in {1..3}; do
|
||||
done
|
||||
msg_ok "Built Python Wheels"
|
||||
|
||||
NODE_VERSION="22" NODE_MODULE="yarn" setup_nodejs
|
||||
NODE_VERSION="20" setup_nodejs
|
||||
|
||||
msg_info "Downloading Inference Models"
|
||||
mkdir -p /models /openvino-model
|
||||
@@ -183,6 +196,10 @@ $STD pip3 install -U /wheels/*.whl
|
||||
ldconfig
|
||||
msg_ok "Installed HailoRT Runtime"
|
||||
|
||||
msg_info "Installing MemryX Runtime"
|
||||
$STD bash /opt/frigate/docker/main/install_memryx.sh
|
||||
msg_ok "Installed MemryX Runtime"
|
||||
|
||||
msg_info "Installing OpenVino"
|
||||
$STD pip3 install -r /opt/frigate/docker/main/requirements-ov.txt
|
||||
msg_ok "Installed OpenVino"
|
||||
@@ -209,6 +226,8 @@ $STD make version
|
||||
cd /opt/frigate/web
|
||||
$STD npm install
|
||||
$STD npm run build
|
||||
mv /opt/frigate/web/dist/BASE_PATH/monacoeditorwork/* /opt/frigate/web/dist/assets/
|
||||
rm -rf /opt/frigate/web/dist/BASE_PATH
|
||||
cp -r /opt/frigate/web/dist/* /opt/frigate/web/
|
||||
sed -i '/^s6-svc -O \.$/s/^/#/' /opt/frigate/docker/main/rootfs/etc/s6-overlay/s6-rc.d/frigate/run
|
||||
msg_ok "Built Frigate Application"
|
||||
@@ -224,6 +243,19 @@ echo "tmpfs /tmp/cache tmpfs defaults 0 0" >>/etc/fstab
|
||||
cat <<EOF >/etc/frigate.env
|
||||
DEFAULT_FFMPEG_VERSION="7.0"
|
||||
INCLUDED_FFMPEG_VERSIONS="7.0:5.0"
|
||||
NVIDIA_VISIBLE_DEVICES=all
|
||||
NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
|
||||
TOKENIZERS_PARALLELISM=true
|
||||
TRANSFORMERS_NO_ADVISORY_WARNINGS=1
|
||||
OPENCV_FFMPEG_LOGLEVEL=8
|
||||
PYTHONWARNINGS="ignore:::numpy.core.getlimits"
|
||||
HAILORT_LOGGER_PATH=NONE
|
||||
TF_CPP_MIN_LOG_LEVEL=3
|
||||
TF_CPP_MIN_VLOG_LEVEL=3
|
||||
TF_ENABLE_ONEDNN_OPTS=0
|
||||
AUTOGRAPH_VERBOSITY=0
|
||||
GLOG_minloglevel=3
|
||||
GLOG_logtostderr=0
|
||||
EOF
|
||||
|
||||
cat <<EOF >/config/config.yml
|
||||
@@ -237,7 +269,6 @@ cameras:
|
||||
input_args: -re -stream_loop -1 -fflags +genpts
|
||||
roles:
|
||||
- detect
|
||||
- rtmp
|
||||
detect:
|
||||
height: 1080
|
||||
width: 1920
|
||||
@@ -255,6 +286,7 @@ ffmpeg:
|
||||
detectors:
|
||||
detector01:
|
||||
type: openvino
|
||||
device: AUTO
|
||||
model:
|
||||
width: 300
|
||||
height: 300
|
||||
|
||||
@@ -31,7 +31,7 @@ fi
|
||||
|
||||
if [[ -z "$KASM_URL" ]] || [[ -z "$KASM_VERSION" ]]; then
|
||||
msg_error "Unable to detect latest Kasm release URL."
|
||||
exit 1
|
||||
exit 250
|
||||
fi
|
||||
msg_ok "Detected Kasm Workspaces version $KASM_VERSION"
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ $STD apt install -y \
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
PYTHON_VERSION="3.12" setup_uv
|
||||
POSTGRES_VERSION="16" setup_postgresql
|
||||
PG_VERSION="16" setup_postgresql
|
||||
NODE_MODULE="yarn" NODE_VERSION="24" setup_nodejs
|
||||
fetch_and_deploy_gh_release "mealie" "mealie-recipes/mealie" "tarball" "latest" "/opt/mealie"
|
||||
PG_DB_NAME="mealie_db" PG_DB_USER="mealie_user" PG_DB_GRANT_SUPERUSER="true" setup_postgresql_db
|
||||
|
||||
@@ -51,7 +51,7 @@ while true; do
|
||||
attempts=$((attempts + 1))
|
||||
if [[ "$attempts" -ge 3 ]]; then
|
||||
msg_error "Maximum attempts reached. Exiting."
|
||||
exit 1
|
||||
exit 254
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -76,11 +76,11 @@ for i in {1..60}; do
|
||||
elif [[ "$STATUS" == "unhealthy" ]]; then
|
||||
msg_error "NPMplus container is unhealthy! Check logs."
|
||||
docker logs "$CONTAINER_ID"
|
||||
exit 1
|
||||
exit 150
|
||||
fi
|
||||
fi
|
||||
sleep 2
|
||||
[[ $i -eq 60 ]] && msg_error "NPMplus container did not become healthy within 120s." && docker logs "$CONTAINER_ID" && exit 1
|
||||
[[ $i -eq 60 ]] && msg_error "NPMplus container did not become healthy within 120s." && docker logs "$CONTAINER_ID" && exit 150
|
||||
done
|
||||
msg_ok "Builded and started NPMplus"
|
||||
|
||||
|
||||
@@ -78,11 +78,11 @@ if curl -fL# -C - -o "$TMP_TAR" "$OLLAMA_URL"; then
|
||||
msg_ok "Installed Ollama ${RELEASE}"
|
||||
else
|
||||
msg_error "Extraction failed – archive corrupt or incomplete"
|
||||
exit 1
|
||||
exit 251
|
||||
fi
|
||||
else
|
||||
msg_error "Download failed – $OLLAMA_URL not reachable"
|
||||
exit 1
|
||||
exit 250
|
||||
fi
|
||||
|
||||
msg_info "Creating ollama User and Group"
|
||||
|
||||
@@ -59,7 +59,7 @@ EOF
|
||||
else
|
||||
msg_error "Failed to download or verify GPG key from $KEY_URL"
|
||||
[[ -f "$TMP_KEY_CONTENT" ]] && rm -f "$TMP_KEY_CONTENT"
|
||||
exit 1
|
||||
exit 250
|
||||
fi
|
||||
rm -f "$TMP_KEY_CONTENT"
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ for server in "${servers[@]}"; do
|
||||
done
|
||||
if ((attempt >= MAX_ATTEMPTS)); then
|
||||
msg_error "No more attempts - aborting script!"
|
||||
exit 1
|
||||
exit 254
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ update_os
|
||||
read -r -p "${TAB3}Enter PostgreSQL version (15/16/17/18): " ver
|
||||
[[ $ver =~ ^(15|16|17|18)$ ]] || {
|
||||
echo "Invalid version"
|
||||
exit 1
|
||||
exit 64
|
||||
}
|
||||
PG_VERSION=$ver setup_postgresql
|
||||
|
||||
|
||||
134
install/powerdns-install.sh
Normal file
134
install/powerdns-install.sh
Normal file
@@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: Slaviša Arežina (tremor021)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://www.powerdns.com/
|
||||
|
||||
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
|
||||
color
|
||||
verb_ip6
|
||||
catch_errors
|
||||
setting_up_container
|
||||
network_check
|
||||
update_os
|
||||
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apt install -y sqlite3
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
PHP_VERSION="8.3" PHP_APACHE="YES" PHP_FPM="YES" PHP_MODULE="gettext,tokenizer,sqlite3,ldap" setup_php
|
||||
setup_deb822_repo \
|
||||
"pdns" \
|
||||
"https://repo.powerdns.com/FD380FBB-pub.asc" \
|
||||
"http://repo.powerdns.com/debian" \
|
||||
"trixie-auth-50"
|
||||
|
||||
cat <<EOF >/etc/apt/preferences.d/auth-50
|
||||
Package: pdns-*
|
||||
Pin: origin repo.powerdns.com
|
||||
Pin-Priority: 600
|
||||
EOF
|
||||
|
||||
escape_sql() {
|
||||
printf '%s' "$1" | sed "s/'/''/g"
|
||||
}
|
||||
|
||||
msg_info "Setting up PowerDNS"
|
||||
$STD apt install -y \
|
||||
pdns-server \
|
||||
pdns-backend-sqlite3
|
||||
msg_ok "Setup PowerDNS"
|
||||
|
||||
fetch_and_deploy_gh_release "poweradmin" "poweradmin/poweradmin" "tarball"
|
||||
|
||||
msg_info "Setting up Poweradmin"
|
||||
sqlite3 /opt/poweradmin/powerdns.db </opt/poweradmin/sql/poweradmin-sqlite-db-structure.sql
|
||||
sqlite3 /opt/poweradmin/powerdns.db </opt/poweradmin/sql/pdns/49/schema.sqlite3.sql
|
||||
PA_ADMIN_USERNAME="admin"
|
||||
PA_ADMIN_EMAIL="admin@example.com"
|
||||
PA_ADMIN_FULLNAME="Administrator"
|
||||
PA_ADMIN_PASSWORD=$(openssl rand -base64 16 | tr -d "=+/" | cut -c1-16)
|
||||
PA_SESSION_KEY=$(openssl rand -base64 75 | tr -dc 'A-Za-z0-9^@#!(){}[]%_\-+=~' | head -c 50)
|
||||
PASSWORD_HASH=$(php -r "echo password_hash(\$argv[1], PASSWORD_DEFAULT);" -- "${PA_ADMIN_PASSWORD}" 2>/dev/null)
|
||||
sqlite3 /opt/poweradmin/powerdns.db "INSERT INTO users (username, password, fullname, email, description, perm_templ, active, use_ldap) \
|
||||
VALUES ('$(escape_sql "${PA_ADMIN_USERNAME}")', '$(escape_sql "${PASSWORD_HASH}")', '$(escape_sql "${PA_ADMIN_FULLNAME}")', \
|
||||
'$(escape_sql "${PA_ADMIN_EMAIL}")', 'System Administrator', 1, 1, 0);"
|
||||
|
||||
cat <<EOF >~/poweradmin.creds
|
||||
Admin Username: ${PA_ADMIN_USERNAME}
|
||||
Admin Password: ${PA_ADMIN_PASSWORD}
|
||||
EOF
|
||||
|
||||
cat <<EOF >/opt/poweradmin/config/settings.php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Poweradmin Settings Configuration File
|
||||
*
|
||||
* Generated by the installer on 2026-02-02 21:01:40
|
||||
*/
|
||||
|
||||
return [
|
||||
/**
|
||||
* Database Settings
|
||||
*/
|
||||
'database' => [
|
||||
'type' => 'sqlite',
|
||||
'file' => '/opt/poweradmin/powerdns.db',
|
||||
],
|
||||
|
||||
/**
|
||||
* Security Settings
|
||||
*/
|
||||
'security' => [
|
||||
'session_key' => '${PA_SESSION_KEY}',
|
||||
],
|
||||
|
||||
/**
|
||||
* Interface Settings
|
||||
*/
|
||||
'interface' => [
|
||||
'language' => 'en_EN',
|
||||
],
|
||||
|
||||
/**
|
||||
* DNS Settings
|
||||
*/
|
||||
'dns' => [
|
||||
'hostmaster' => 'localhost.lan',
|
||||
'ns1' => '8.8.8.8',
|
||||
'ns2' => '9.9.9.9',
|
||||
]
|
||||
];
|
||||
EOF
|
||||
rm -rf /opt/poweradmin/install
|
||||
msg_ok "Setup Poweradmin"
|
||||
|
||||
msg_info "Creating Service"
|
||||
rm /etc/apache2/sites-enabled/000-default.conf
|
||||
cat <<EOF >/etc/apache2/sites-enabled/poweradmin.conf
|
||||
<VirtualHost *:80>
|
||||
ServerName localhost
|
||||
DocumentRoot /opt/poweradmin
|
||||
|
||||
<Directory /opt/poweradmin>
|
||||
Options -Indexes +FollowSymLinks
|
||||
AllowOverride All
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
# For DDNS update functionality
|
||||
RewriteEngine On
|
||||
RewriteRule ^/update(.*)\$ /dynamic_update.php [L]
|
||||
RewriteRule ^/nic/update(.*)\$ /dynamic_update.php [L]
|
||||
</VirtualHost>
|
||||
EOF
|
||||
$STD a2enmod rewrite headers
|
||||
chown -R www-data:www-data /opt/poweradmin
|
||||
$STD systemctl restart apache2
|
||||
msg_info "Created Service"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
cleanup_lxc
|
||||
74
install/profilarr-install.sh
Normal file
74
install/profilarr-install.sh
Normal file
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: michelroegl-brunner
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://github.com/Dictionarry-Hub/profilarr
|
||||
|
||||
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
|
||||
color
|
||||
verb_ip6
|
||||
catch_errors
|
||||
setting_up_container
|
||||
network_check
|
||||
update_os
|
||||
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apt install -y \
|
||||
build-essential \
|
||||
python3-dev \
|
||||
libffi-dev \
|
||||
libssl-dev \
|
||||
git
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
PYTHON_VERSION="3.12" setup_uv
|
||||
NODE_VERSION="22" setup_nodejs
|
||||
|
||||
msg_info "Creating directories"
|
||||
mkdir -p /opt/profilarr \
|
||||
/config
|
||||
msg_ok "Created directories"
|
||||
|
||||
fetch_and_deploy_gh_release "profilarr" "Dictionarry-Hub/profilarr" "tarball"
|
||||
|
||||
msg_info "Installing Python Dependencies"
|
||||
cd /opt/profilarr/backend
|
||||
$STD uv venv /opt/profilarr/backend/.venv
|
||||
sed 's/==/>=/g' requirements.txt >requirements-relaxed.txt
|
||||
$STD uv pip install --python /opt/profilarr/backend/.venv/bin/python -r requirements-relaxed.txt
|
||||
rm -f requirements-relaxed.txt
|
||||
msg_ok "Installed Python Dependencies"
|
||||
|
||||
msg_info "Building Frontend"
|
||||
cd /opt/profilarr/frontend
|
||||
$STD npm install
|
||||
$STD npm run build
|
||||
cp -r dist /opt/profilarr/backend/app/static
|
||||
msg_ok "Built Frontend"
|
||||
|
||||
msg_info "Creating Service"
|
||||
cat <<EOF >/etc/systemd/system/profilarr.service
|
||||
[Unit]
|
||||
Description=Profilarr - Configuration Management Platform for Radarr/Sonarr
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=/opt/profilarr/backend
|
||||
Environment="PATH=/opt/profilarr/backend/.venv/bin:/usr/local/bin:/usr/bin:/bin"
|
||||
Environment="PYTHONPATH=/opt/profilarr/backend"
|
||||
ExecStart=/opt/profilarr/backend/.venv/bin/gunicorn --bind 0.0.0.0:6868 --timeout 600 app.main:create_app()
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl enable -q --now profilarr
|
||||
msg_ok "Created Service"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
cleanup_lxc
|
||||
@@ -25,7 +25,7 @@ if useradd -r -m -d /opt/pulse-home -s /usr/sbin/nologin pulse; then
|
||||
msg_ok "Created User"
|
||||
else
|
||||
msg_error "User creation failed"
|
||||
exit 1
|
||||
exit 71
|
||||
fi
|
||||
|
||||
mkdir -p /etc/pulse
|
||||
|
||||
@@ -34,7 +34,7 @@ while true; do
|
||||
[Nn]|[Nn][Oo]|"")
|
||||
msg_error "Terms not accepted. Installation cannot proceed."
|
||||
msg_error "Please review the terms and run the script again if you wish to proceed."
|
||||
exit 1
|
||||
exit 254
|
||||
;;
|
||||
*)
|
||||
msg_error "Invalid response. Please enter 'y' for yes or 'n' for no."
|
||||
@@ -47,7 +47,7 @@ DOWNLOAD_URL=$(curl -s "https://www.splunk.com/en_us/download/splunk-enterprise.
|
||||
RELEASE=$(echo "$DOWNLOAD_URL" | sed 's|.*/releases/\([^/]*\)/.*|\1|')
|
||||
$STD curl -fsSL -o "splunk-enterprise.tgz" "$DOWNLOAD_URL" || {
|
||||
msg_error "Failed to download Splunk Enterprise from the provided link."
|
||||
exit 1
|
||||
exit 250
|
||||
}
|
||||
$STD tar -xzf "splunk-enterprise.tgz" -C /opt
|
||||
rm -f "splunk-enterprise.tgz"
|
||||
|
||||
62
install/tinyauth-install.sh
Normal file
62
install/tinyauth-install.sh
Normal file
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: MickLesk (CanbiZ)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://github.com/steveiliop56/tinyauth
|
||||
|
||||
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
|
||||
color
|
||||
verb_ip6
|
||||
catch_errors
|
||||
setting_up_container
|
||||
network_check
|
||||
update_os
|
||||
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apt install -y \
|
||||
openssl \
|
||||
apache2-utils
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
fetch_and_deploy_gh_release "tinyauth" "steveiliop56/tinyauth" "singlefile" "latest" "/opt/tinyauth" "tinyauth-amd64"
|
||||
|
||||
msg_info "Setting up Tinyauth"
|
||||
PASS=$(openssl rand -base64 8 | tr -dc 'a-zA-Z0-9' | head -c 8)
|
||||
USER=$(htpasswd -Bbn "tinyauth" "${PASS}")
|
||||
cat <<EOF >/opt/tinyauth/credentials.txt
|
||||
Tinyauth Credentials
|
||||
Username: tinyauth
|
||||
Password: ${PASS}
|
||||
EOF
|
||||
msg_ok "Set up Tinyauth"
|
||||
|
||||
read -r -p "${TAB3}Enter your Tinyauth subdomain (e.g. https://tinyauth.example.com): " app_url
|
||||
|
||||
msg_info "Creating Service"
|
||||
cat <<EOF >/opt/tinyauth/.env
|
||||
TINYAUTH_DATABASE_PATH=/opt/tinyauth/database.db
|
||||
TINYAUTH_AUTH_USERS='${USER}'
|
||||
TINYAUTH_APPURL=${app_url}
|
||||
EOF
|
||||
cat <<EOF >/etc/systemd/system/tinyauth.service
|
||||
[Unit]
|
||||
Description=Tinyauth Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
EnvironmentFile=/opt/tinyauth/.env
|
||||
ExecStart=/opt/tinyauth/tinyauth
|
||||
WorkingDirectory=/opt/tinyauth
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl enable -q --now tinyauth
|
||||
msg_ok "Created Service"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
cleanup_lxc
|
||||
@@ -44,7 +44,20 @@ $STD timescaledb-tune -yes -memory "$ram_for_tsdb"MB
|
||||
$STD systemctl restart postgresql
|
||||
msg_ok "Installed TimescaleDB"
|
||||
|
||||
PG_DB_NAME="tracearr_db" PG_DB_USER="tracearr" PG_DB_EXTENSIONS="timescaledb,timescaledb_toolkit" setup_postgresql_db
|
||||
PG_DB_NAME="tracearr_db" PG_DB_USER="tracearr" PG_DB_EXTENSIONS="timescaledb,timescaledb_toolkit" PG_DB_GRANT_SUPERUSER="true" setup_postgresql_db
|
||||
|
||||
msg_info "Installing tailscale"
|
||||
setup_deb822_repo \
|
||||
"tailscale" \
|
||||
"https://pkgs.tailscale.com/stable/$(get_os_info id)/$(get_os_info codename).noarmor.gpg" \
|
||||
"https://pkgs.tailscale.com/stable/$(get_os_info id)/" \
|
||||
"$(get_os_info codename)"
|
||||
$STD apt install -y tailscale
|
||||
# Tracearr runs tailscaled in user mode, disable the service.
|
||||
$STD systemctl disable --now tailscaled
|
||||
$STD systemctl stop tailscaled
|
||||
msg_ok "Installed tailscale"
|
||||
|
||||
fetch_and_deploy_gh_release "tracearr" "connorgallopo/Tracearr" "tarball" "latest" "/opt/tracearr.build"
|
||||
|
||||
msg_info "Building Tracearr"
|
||||
@@ -75,6 +88,7 @@ msg_info "Configuring Tracearr"
|
||||
$STD useradd -r -s /bin/false -U tracearr
|
||||
$STD chown -R tracearr:tracearr /opt/tracearr
|
||||
install -d -m 750 -o tracearr -g tracearr /data/tracearr
|
||||
install -d -m 750 -o tracearr -g tracearr /data/backup
|
||||
export JWT_SECRET=$(openssl rand -hex 32)
|
||||
export COOKIE_SECRET=$(openssl rand -hex 32)
|
||||
cat <<EOF >/data/tracearr/.env
|
||||
@@ -89,7 +103,6 @@ JWT_SECRET=$JWT_SECRET
|
||||
COOKIE_SECRET=$COOKIE_SECRET
|
||||
APP_VERSION=$(cat /root/.tracearr)
|
||||
#CORS_ORIGIN=http://localhost:5173
|
||||
#MOBILE_BETA_MODE=true
|
||||
EOF
|
||||
chmod 600 /data/tracearr/.env
|
||||
chown -R tracearr:tracearr /data/tracearr
|
||||
@@ -140,6 +153,7 @@ if [ -f \$pg_config_file ]; then
|
||||
fi
|
||||
fi
|
||||
systemctl restart postgresql
|
||||
sudo -u postgres psql -c "ALTER USER tracearr WITH SUPERUSER;"
|
||||
EOF
|
||||
chmod +x /data/tracearr/prestart.sh
|
||||
cat <<EOF >/lib/systemd/system/tracearr.service
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 tteck
|
||||
# Author: tteck (tteckster)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://ui.com/download/unifi
|
||||
|
||||
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
|
||||
color
|
||||
verb_ip6
|
||||
catch_errors
|
||||
setting_up_container
|
||||
network_check
|
||||
update_os
|
||||
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apt install -y apt-transport-https
|
||||
curl -fsSL "https://dl.ui.com/unifi/unifi-repo.gpg" -o "/usr/share/keyrings/unifi-repo.gpg"
|
||||
cat <<EOF | sudo tee /etc/apt/sources.list.d/100-ubnt-unifi.sources >/dev/null
|
||||
Types: deb
|
||||
URIs: https://www.ui.com/downloads/unifi/debian
|
||||
Suites: stable
|
||||
Components: ubiquiti
|
||||
Architectures: amd64
|
||||
Signed-By: /usr/share/keyrings/unifi-repo.gpg
|
||||
EOF
|
||||
$STD apt update
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
JAVA_VERSION="21" setup_java
|
||||
|
||||
if lscpu | grep -q 'avx'; then
|
||||
MONGO_VERSION="8.0" setup_mongodb
|
||||
else
|
||||
msg_error "No AVX detected (CPU-Flag)! We have discontinued support for this. You are welcome to try it manually with a Debian LXC, but due to the many issues with Unifi, we currently only support AVX CPUs."
|
||||
exit 10
|
||||
fi
|
||||
|
||||
if ! dpkg -l | grep -q 'libssl1.1'; then
|
||||
msg_info "Installing libssl (if needed)"
|
||||
curl -fsSL "https://security.debian.org/debian-security/pool/updates/main/o/openssl/libssl1.1_1.1.1w-0+deb11u4_amd64.deb" -o "/tmp/libssl.deb"
|
||||
$STD dpkg -i /tmp/libssl.deb
|
||||
rm -f /tmp/libssl.deb
|
||||
msg_ok "Installed libssl1.1"
|
||||
fi
|
||||
|
||||
msg_info "Installing UniFi Network Server"
|
||||
$STD apt install -y unifi
|
||||
msg_ok "Installed UniFi Network Server"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
cleanup_lxc
|
||||
@@ -16,13 +16,13 @@ update_os
|
||||
if [[ "${CTTYPE:-1}" != "0" ]]; then
|
||||
msg_error "UniFi OS Server requires a privileged LXC container."
|
||||
msg_error "Recreate the container with unprivileged=0."
|
||||
exit 1
|
||||
exit 10
|
||||
fi
|
||||
|
||||
if [[ ! -e /dev/net/tun ]]; then
|
||||
msg_error "Missing /dev/net/tun in container."
|
||||
msg_error "Enable TUN/TAP (var_tun=yes) or add /dev/net/tun passthrough."
|
||||
exit 1
|
||||
exit 236
|
||||
fi
|
||||
|
||||
msg_info "Installing dependencies"
|
||||
@@ -48,7 +48,7 @@ TEMP_JSON="$(mktemp)"
|
||||
if ! curl -fsSL "$API_URL" -o "$TEMP_JSON"; then
|
||||
rm -f "$TEMP_JSON"
|
||||
msg_error "Failed to fetch data from Ubiquiti API"
|
||||
exit 1
|
||||
exit 250
|
||||
fi
|
||||
LATEST=$(jq -r '
|
||||
._embedded.firmware
|
||||
@@ -62,7 +62,7 @@ UOS_URL=$(echo "$LATEST" | jq -r '._links.data.href')
|
||||
rm -f "$TEMP_JSON"
|
||||
if [[ -z "$UOS_URL" || -z "$UOS_VERSION" || "$UOS_URL" == "null" ]]; then
|
||||
msg_error "Failed to parse UniFi OS Server version or download URL"
|
||||
exit 1
|
||||
exit 250
|
||||
fi
|
||||
msg_ok "Found UniFi OS Server ${UOS_VERSION}"
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ if gpg --verify /tmp/zerotier-install.sh >/dev/null 2>&1; then
|
||||
$STD bash /tmp/zerotier-install.sh
|
||||
else
|
||||
msg_warn "Could not verify signature of Zerotier-One install script. Exiting..."
|
||||
exit 1
|
||||
exit 250
|
||||
fi
|
||||
msg_ok "Setup Zerotier-One"
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ setting_up_container() {
|
||||
if [ "$(ip addr show | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}' | cut -d'/' -f1)" = "" ]; then
|
||||
echo 1>&2 -e "\n${CROSS}${RD} No Network After $RETRY_NUM Tries${CL}"
|
||||
echo -e "${NETWORK}Check Network Settings"
|
||||
exit 1
|
||||
exit 121
|
||||
fi
|
||||
msg_ok "Set up Container OS"
|
||||
msg_ok "Network Connected: ${BL}$(ip addr show | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1 | tail -n1)${CL}"
|
||||
@@ -99,7 +99,7 @@ network_check() {
|
||||
echo -e "${INFO}${RD}Expect Issues Without Internet${CL}"
|
||||
else
|
||||
echo -e "${NETWORK}Check Network Settings"
|
||||
exit 1
|
||||
exit 122
|
||||
fi
|
||||
fi
|
||||
RESOLVEDIP=$(getent hosts github.com | awk '{ print $1 }')
|
||||
@@ -119,12 +119,12 @@ update_os() {
|
||||
local tools_content
|
||||
tools_content=$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func) || {
|
||||
msg_error "Failed to download tools.func"
|
||||
exit 6
|
||||
exit 115
|
||||
}
|
||||
source /dev/stdin <<<"$tools_content"
|
||||
if ! declare -f fetch_and_deploy_gh_release >/dev/null 2>&1; then
|
||||
msg_error "tools.func loaded but incomplete — missing expected functions"
|
||||
exit 6
|
||||
exit 115
|
||||
fi
|
||||
msg_ok "Updated Container OS"
|
||||
post_progress_to_api
|
||||
|
||||
154
misc/api.func
154
misc/api.func
@@ -124,6 +124,7 @@ detect_repo_source
|
||||
# * Generic/Shell errors (1-3, 10, 124-132, 134, 137, 139, 141, 143-146)
|
||||
# * curl/wget errors (4-8, 16, 18, 22-28, 30, 32-36, 39, 44-48, 51-52, 55-57, 59, 61, 63, 75, 78-79, 92, 95)
|
||||
# * Package manager errors (APT, DPKG: 100-102, 255)
|
||||
# * Script Validation & Setup (103-123)
|
||||
# * BSD sysexits (64-78)
|
||||
# * Systemd/Service errors (150-154)
|
||||
# * Python/pip/uv errors (160-162)
|
||||
@@ -131,7 +132,9 @@ detect_repo_source
|
||||
# * MySQL/MariaDB errors (180-183)
|
||||
# * MongoDB errors (190-193)
|
||||
# * Proxmox custom codes (200-231)
|
||||
# * Tools & Addon Scripts (232-238)
|
||||
# * Node.js/npm errors (239, 243, 245-249)
|
||||
# * Application Install/Update errors (250-254)
|
||||
# - Returns description string for given exit code
|
||||
# ------------------------------------------------------------------------------
|
||||
explain_exit_code() {
|
||||
@@ -189,6 +192,29 @@ explain_exit_code() {
|
||||
101) echo "APT: Configuration error (bad sources.list, malformed config)" ;;
|
||||
102) echo "APT: Lock held by another process (dpkg/apt still running)" ;;
|
||||
|
||||
# --- Script Validation & Setup (103-123) ---
|
||||
103) echo "Validation: Shell is not Bash" ;;
|
||||
104) echo "Validation: Not running as root (or invoked via sudo)" ;;
|
||||
105) echo "Validation: Proxmox VE version not supported" ;;
|
||||
106) echo "Validation: Architecture not supported (ARM / PiMox)" ;;
|
||||
107) echo "Validation: Kernel key parameters unreadable" ;;
|
||||
108) echo "Validation: Kernel key limits exceeded" ;;
|
||||
109) echo "Proxmox: No available container ID after max attempts" ;;
|
||||
110) echo "Proxmox: Failed to apply default.vars" ;;
|
||||
111) echo "Proxmox: App defaults file not available" ;;
|
||||
112) echo "Proxmox: Invalid install menu option" ;;
|
||||
113) echo "LXC: Under-provisioned — user aborted update" ;;
|
||||
114) echo "LXC: Storage too low — user aborted update" ;;
|
||||
115) echo "Download: install.func download failed or incomplete" ;;
|
||||
116) echo "Proxmox: Default bridge vmbr0 not found" ;;
|
||||
117) echo "LXC: Container did not reach running state" ;;
|
||||
118) echo "LXC: No IP assigned to container after timeout" ;;
|
||||
119) echo "Proxmox: No valid storage for rootdir content" ;;
|
||||
120) echo "Proxmox: No valid storage for vztmpl content" ;;
|
||||
121) echo "LXC: Container network not ready (no IP after retries)" ;;
|
||||
122) echo "LXC: No internet connectivity — user declined to continue" ;;
|
||||
123) echo "LXC: Local IP detection failed" ;;
|
||||
|
||||
# --- BSD sysexits.h (64-78) ---
|
||||
64) echo "Usage error (wrong arguments)" ;;
|
||||
65) echo "Data format error (bad input data)" ;;
|
||||
@@ -276,8 +302,18 @@ explain_exit_code() {
|
||||
223) echo "Proxmox: Template not available after download" ;;
|
||||
224) echo "Proxmox: PBS storage is for backups only" ;;
|
||||
225) echo "Proxmox: No template available for OS/Version" ;;
|
||||
226) echo "Proxmox: VM disk import or post-creation setup failed" ;;
|
||||
231) echo "Proxmox: LXC stack upgrade failed" ;;
|
||||
|
||||
# --- Tools & Addon Scripts (232-238) ---
|
||||
232) echo "Tools: Wrong execution environment (run on PVE host, not inside LXC)" ;;
|
||||
233) echo "Tools: Application not installed (update prerequisite missing)" ;;
|
||||
234) echo "Tools: No LXC containers found or available" ;;
|
||||
235) echo "Tools: Backup or restore operation failed" ;;
|
||||
236) echo "Tools: Required hardware not detected" ;;
|
||||
237) echo "Tools: Dependency package installation failed" ;;
|
||||
238) echo "Tools: OS or distribution not supported for this addon" ;;
|
||||
|
||||
# --- Node.js / npm / pnpm / yarn (239-249) ---
|
||||
239) echo "npm/Node.js: Unexpected runtime error or dependency failure" ;;
|
||||
243) echo "Node.js: Out of memory (JavaScript heap out of memory)" ;;
|
||||
@@ -287,6 +323,13 @@ explain_exit_code() {
|
||||
248) echo "Node.js: Invalid C++ addon / N-API failure" ;;
|
||||
249) echo "npm/pnpm/yarn: Unknown fatal error" ;;
|
||||
|
||||
# --- Application Install/Update Errors (250-254) ---
|
||||
250) echo "App: Download failed or version not determined" ;;
|
||||
251) echo "App: File extraction failed (corrupt or incomplete archive)" ;;
|
||||
252) echo "App: Required file or resource not found" ;;
|
||||
253) echo "App: Data migration required — update aborted" ;;
|
||||
254) echo "App: User declined prompt or input timed out" ;;
|
||||
|
||||
# --- DPKG ---
|
||||
255) echo "DPKG: Fatal internal error" ;;
|
||||
|
||||
@@ -303,18 +346,20 @@ explain_exit_code() {
|
||||
# - Handles backslashes, quotes, newlines, tabs, and carriage returns
|
||||
# ------------------------------------------------------------------------------
|
||||
json_escape() {
|
||||
local s="$1"
|
||||
# Strip ANSI escape sequences (color codes etc.)
|
||||
s=$(printf '%s' "$s" | sed 's/\x1b\[[0-9;]*[a-zA-Z]//g')
|
||||
s=${s//\\/\\\\}
|
||||
s=${s//"/\\"/}
|
||||
s=${s//$'\n'/\\n}
|
||||
s=${s//$'\r'/}
|
||||
s=${s//$'\t'/\\t}
|
||||
# Remove any remaining control characters (0x00-0x1F except those already handled)
|
||||
# Also remove DEL (0x7F) and invalid high bytes that break JSON parsers
|
||||
s=$(printf '%s' "$s" | tr -d '\000-\010\013\014\016-\037\177')
|
||||
printf '%s' "$s"
|
||||
# Escape a string for safe JSON embedding using awk (handles any input size).
|
||||
# Pipeline: strip ANSI → remove control chars → escape \ " TAB → join lines with \n
|
||||
printf '%s' "$1" \
|
||||
| sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' \
|
||||
| tr -d '\000-\010\013\014\016-\037\177\r' \
|
||||
| awk '
|
||||
BEGIN { ORS = "" }
|
||||
{
|
||||
gsub(/\\/, "\\\\") # backslash → \\
|
||||
gsub(/"/, "\\\"") # double quote → \"
|
||||
gsub(/\t/, "\\t") # tab → \t
|
||||
if (NR > 1) printf "\\n"
|
||||
printf "%s", $0
|
||||
}'
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
@@ -350,6 +395,11 @@ get_error_text() {
|
||||
logfile="$BUILD_LOG"
|
||||
fi
|
||||
|
||||
# Try SILENT_LOGFILE as last resort (captures $STD command output)
|
||||
if [[ -z "$logfile" || ! -s "$logfile" ]] && [[ -n "${SILENT_LOGFILE:-}" && -s "${SILENT_LOGFILE}" ]]; then
|
||||
logfile="$SILENT_LOGFILE"
|
||||
fi
|
||||
|
||||
if [[ -n "$logfile" && -s "$logfile" ]]; then
|
||||
tail -n 20 "$logfile" 2>/dev/null | sed 's/\r$//' | sed 's/\x1b\[[0-9;]*[a-zA-Z]//g'
|
||||
fi
|
||||
@@ -395,6 +445,13 @@ get_full_log() {
|
||||
fi
|
||||
fi
|
||||
|
||||
# Fall back to SILENT_LOGFILE (captures $STD command output)
|
||||
if [[ -z "$logfile" || ! -s "$logfile" ]]; then
|
||||
if [[ -n "${SILENT_LOGFILE:-}" && -s "${SILENT_LOGFILE}" ]]; then
|
||||
logfile="$SILENT_LOGFILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "$logfile" && -s "$logfile" ]]; then
|
||||
# Strip ANSI codes, carriage returns, and anonymize IP addresses (GDPR)
|
||||
sed 's/\r$//' "$logfile" 2>/dev/null |
|
||||
@@ -632,18 +689,23 @@ EOF
|
||||
[[ "${DEV_MODE:-}" == "true" ]] && echo "[DEBUG] Sending to: $TELEMETRY_URL" >&2
|
||||
[[ "${DEV_MODE:-}" == "true" ]] && echo "[DEBUG] Payload: $JSON_PAYLOAD" >&2
|
||||
|
||||
# Fire-and-forget: never block, never fail
|
||||
local http_code
|
||||
if [[ "${DEV_MODE:-}" == "true" ]]; then
|
||||
http_code=$(curl -sS -w "%{http_code}" -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$JSON_PAYLOAD" -o /dev/stderr 2>&1) || true
|
||||
echo "[DEBUG] HTTP response code: $http_code" >&2
|
||||
else
|
||||
curl -fsS -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$JSON_PAYLOAD" &>/dev/null || true
|
||||
fi
|
||||
# Send initial "installing" record with retry.
|
||||
# This record MUST exist for all subsequent updates to succeed.
|
||||
local http_code="" attempt
|
||||
for attempt in 1 2 3; do
|
||||
if [[ "${DEV_MODE:-}" == "true" ]]; then
|
||||
http_code=$(curl -sS -w "%{http_code}" -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$JSON_PAYLOAD" -o /dev/stderr 2>&1) || http_code="000"
|
||||
echo "[DEBUG] post_to_api attempt $attempt HTTP=$http_code" >&2
|
||||
else
|
||||
http_code=$(curl -sS -w "%{http_code}" -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$JSON_PAYLOAD" -o /dev/null 2>/dev/null) || http_code="000"
|
||||
fi
|
||||
[[ "$http_code" =~ ^2[0-9]{2}$ ]] && break
|
||||
[[ "$attempt" -lt 3 ]] && sleep 1
|
||||
done
|
||||
|
||||
POST_TO_API_DONE=true
|
||||
}
|
||||
@@ -734,10 +796,15 @@ post_to_api_vm() {
|
||||
EOF
|
||||
)
|
||||
|
||||
# Fire-and-forget: never block, never fail
|
||||
curl -fsS -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$JSON_PAYLOAD" &>/dev/null || true
|
||||
# Send initial "installing" record with retry (must succeed for updates to work)
|
||||
local http_code="" attempt
|
||||
for attempt in 1 2 3; do
|
||||
http_code=$(curl -sS -w "%{http_code}" -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$JSON_PAYLOAD" -o /dev/null 2>/dev/null) || http_code="000"
|
||||
[[ "$http_code" =~ ^2[0-9]{2}$ ]] && break
|
||||
[[ "$attempt" -lt 3 ]] && sleep 1
|
||||
done
|
||||
|
||||
POST_TO_API_DONE=true
|
||||
}
|
||||
@@ -833,7 +900,7 @@ post_update_to_api() {
|
||||
esac
|
||||
|
||||
# For failed/unknown status, resolve exit code and error description
|
||||
local short_error=""
|
||||
local short_error="" medium_error=""
|
||||
if [[ "$pb_status" == "failed" ]] || [[ "$pb_status" == "unknown" ]]; then
|
||||
if [[ "$raw_exit_code" =~ ^[0-9]+$ ]]; then
|
||||
exit_code="$raw_exit_code"
|
||||
@@ -853,6 +920,18 @@ post_update_to_api() {
|
||||
short_error=$(json_escape "$(explain_exit_code "$exit_code")")
|
||||
error_category=$(categorize_error "$exit_code")
|
||||
[[ -z "$error" ]] && error="Unknown error"
|
||||
|
||||
# Build medium error for attempt 2: explanation + last 100 log lines (≤16KB)
|
||||
# This is the critical middle ground between full 120KB log and generic-only description
|
||||
local medium_log=""
|
||||
medium_log=$(get_full_log 16384) || true # 16KB max
|
||||
if [[ -z "$medium_log" ]]; then
|
||||
medium_log=$(get_error_text) || true
|
||||
fi
|
||||
local medium_full
|
||||
medium_full=$(build_error_string "$exit_code" "$medium_log")
|
||||
medium_error=$(json_escape "$medium_full")
|
||||
[[ -z "$medium_error" ]] && medium_error="$short_error"
|
||||
fi
|
||||
|
||||
# Calculate duration if timer was started
|
||||
@@ -869,6 +948,11 @@ post_update_to_api() {
|
||||
|
||||
local http_code=""
|
||||
|
||||
# Strip 'G' suffix from disk size (VMs set DISK_SIZE=32G)
|
||||
local DISK_SIZE_API="${DISK_SIZE:-0}"
|
||||
DISK_SIZE_API="${DISK_SIZE_API%G}"
|
||||
[[ ! "$DISK_SIZE_API" =~ ^[0-9]+$ ]] && DISK_SIZE_API=0
|
||||
|
||||
# ── Attempt 1: Full payload with complete error text (includes full log) ──
|
||||
local JSON_PAYLOAD
|
||||
JSON_PAYLOAD=$(
|
||||
@@ -880,7 +964,7 @@ post_update_to_api() {
|
||||
"nsapp": "${NSAPP:-unknown}",
|
||||
"status": "${pb_status}",
|
||||
"ct_type": ${CT_TYPE:-1},
|
||||
"disk_size": ${DISK_SIZE:-0},
|
||||
"disk_size": ${DISK_SIZE_API},
|
||||
"core_count": ${CORE_COUNT:-0},
|
||||
"ram_size": ${RAM_SIZE:-0},
|
||||
"os_type": "${var_os:-}",
|
||||
@@ -911,7 +995,7 @@ EOF
|
||||
return 0
|
||||
fi
|
||||
|
||||
# ── Attempt 2: Short error text (no full log) ──
|
||||
# ── Attempt 2: Medium error text (truncated log ≤16KB instead of full 120KB) ──
|
||||
sleep 1
|
||||
local RETRY_PAYLOAD
|
||||
RETRY_PAYLOAD=$(
|
||||
@@ -923,7 +1007,7 @@ EOF
|
||||
"nsapp": "${NSAPP:-unknown}",
|
||||
"status": "${pb_status}",
|
||||
"ct_type": ${CT_TYPE:-1},
|
||||
"disk_size": ${DISK_SIZE:-0},
|
||||
"disk_size": ${DISK_SIZE_API},
|
||||
"core_count": ${CORE_COUNT:-0},
|
||||
"ram_size": ${RAM_SIZE:-0},
|
||||
"os_type": "${var_os:-}",
|
||||
@@ -931,7 +1015,7 @@ EOF
|
||||
"pve_version": "${pve_version}",
|
||||
"method": "${METHOD:-default}",
|
||||
"exit_code": ${exit_code},
|
||||
"error": "${short_error}",
|
||||
"error": "${medium_error}",
|
||||
"error_category": "${error_category}",
|
||||
"install_duration": ${duration},
|
||||
"cpu_vendor": "${cpu_vendor}",
|
||||
@@ -954,7 +1038,7 @@ EOF
|
||||
return 0
|
||||
fi
|
||||
|
||||
# ── Attempt 3: Minimal payload (bare minimum to set status) ──
|
||||
# ── Attempt 3: Minimal payload with medium error (bare minimum to set status) ──
|
||||
sleep 2
|
||||
local MINIMAL_PAYLOAD
|
||||
MINIMAL_PAYLOAD=$(
|
||||
@@ -966,7 +1050,7 @@ EOF
|
||||
"nsapp": "${NSAPP:-unknown}",
|
||||
"status": "${pb_status}",
|
||||
"exit_code": ${exit_code},
|
||||
"error": "${short_error}",
|
||||
"error": "${medium_error}",
|
||||
"error_category": "${error_category}",
|
||||
"install_duration": ${duration}
|
||||
}
|
||||
|
||||
157
misc/build.func
157
misc/build.func
@@ -119,7 +119,7 @@ maxkeys_check() {
|
||||
# Exit if kernel parameters are unavailable
|
||||
if [[ "$per_user_maxkeys" -eq 0 || "$per_user_maxbytes" -eq 0 ]]; then
|
||||
msg_error "Unable to read kernel key parameters. Ensure proper permissions."
|
||||
exit 1
|
||||
exit 107
|
||||
fi
|
||||
|
||||
# Fetch key usage for user ID 100000 (typical for containers)
|
||||
@@ -148,7 +148,7 @@ maxkeys_check() {
|
||||
# Provide next steps if issues are detected
|
||||
if [[ "$failure" -eq 1 ]]; then
|
||||
msg_error "Kernel key limits exceeded - see suggestions above"
|
||||
exit 1
|
||||
exit 108
|
||||
fi
|
||||
|
||||
# Silent success - only show errors if they exist
|
||||
@@ -355,7 +355,7 @@ get_valid_container_id() {
|
||||
attempts=$((attempts + 1))
|
||||
if [[ $attempts -ge $max_attempts ]]; then
|
||||
msg_error "Could not find available container ID after $max_attempts attempts"
|
||||
exit 1
|
||||
exit 109
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -2035,7 +2035,7 @@ advanced_settings() {
|
||||
else
|
||||
whiptail --msgbox "Default bridge 'vmbr0' not found!\n\nPlease configure a network bridge in Proxmox first." 10 58
|
||||
msg_error "Default bridge 'vmbr0' not found"
|
||||
exit 1
|
||||
exit 116
|
||||
fi
|
||||
else
|
||||
if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
|
||||
@@ -3023,7 +3023,7 @@ install_script() {
|
||||
3 | mydefaults | MYDEFAULTS | userdefaults | USERDEFAULTS)
|
||||
default_var_settings || {
|
||||
msg_error "Failed to apply default.vars"
|
||||
exit 1
|
||||
exit 110
|
||||
}
|
||||
defaults_target="/usr/local/community-scripts/default.vars"
|
||||
break
|
||||
@@ -3040,7 +3040,7 @@ install_script() {
|
||||
break
|
||||
else
|
||||
msg_error "No App Defaults available for ${APP}"
|
||||
exit 1
|
||||
exit 111
|
||||
fi
|
||||
;;
|
||||
"$SETTINGS_OPTION" | settings | SETTINGS)
|
||||
@@ -3051,7 +3051,7 @@ install_script() {
|
||||
;;
|
||||
*)
|
||||
msg_error "Invalid option: $CHOICE"
|
||||
exit 1
|
||||
exit 112
|
||||
;;
|
||||
esac
|
||||
done
|
||||
@@ -3135,7 +3135,7 @@ check_container_resources() {
|
||||
read -r prompt </dev/tty
|
||||
if [[ ! ${prompt,,} =~ ^(yes)$ ]]; then
|
||||
msg_error "Aborted: under-provisioned LXC (${current_cpu} CPU/${current_ram}MB RAM < ${var_cpu} CPU/${var_ram}MB RAM)"
|
||||
exit 1
|
||||
exit 113
|
||||
fi
|
||||
else
|
||||
echo -e ""
|
||||
@@ -3158,7 +3158,7 @@ check_container_storage() {
|
||||
read -r prompt </dev/tty
|
||||
if [[ ! ${prompt,,} =~ ^(y|yes)$ ]]; then
|
||||
msg_error "Aborted: storage too low (${usage}% used)"
|
||||
exit 1
|
||||
exit 114
|
||||
fi
|
||||
fi
|
||||
}
|
||||
@@ -3446,7 +3446,7 @@ start() {
|
||||
3)
|
||||
clear
|
||||
exit_script
|
||||
exit
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
ensure_profile_loaded
|
||||
@@ -3556,7 +3556,7 @@ build_container() {
|
||||
export FUNCTIONS_FILE_PATH="$(curl -fsSL "$_func_url")"
|
||||
if [[ -z "$FUNCTIONS_FILE_PATH" || ${#FUNCTIONS_FILE_PATH} -lt 100 ]]; then
|
||||
msg_error "Failed to download install functions from: $_func_url"
|
||||
exit 1
|
||||
exit 115
|
||||
fi
|
||||
|
||||
# Core exports for install.func
|
||||
@@ -3938,7 +3938,7 @@ EOF
|
||||
local ct_status
|
||||
ct_status=$(pct status "$CTID" 2>/dev/null || echo "unknown")
|
||||
msg_error "LXC Container did not reach running state (status: ${ct_status})"
|
||||
exit 1
|
||||
exit 117
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -3967,7 +3967,7 @@ EOF
|
||||
echo " • Verify static IP configuration (if using static IP)"
|
||||
echo " • Check Proxmox firewall rules"
|
||||
echo " • If using Tailscale: Disable MagicDNS temporarily"
|
||||
exit 1
|
||||
exit 118
|
||||
fi
|
||||
|
||||
# Verify basic connectivity (ping test)
|
||||
@@ -4094,21 +4094,8 @@ EOF'
|
||||
# that sends "configuring" status AFTER the host already reported "failed"
|
||||
export CONTAINER_INSTALLING=true
|
||||
|
||||
# Capture lxc-attach terminal output to host-side log via tee.
|
||||
# This is the ONLY reliable way to get install output when:
|
||||
# - install.func fails to load (DNS error) → no container-side logging
|
||||
# - install script crashes before logging starts
|
||||
# - $STD/silent() not used for some commands
|
||||
# PIPESTATUS[0] gets the real exit code from lxc-attach (not from tee).
|
||||
# Note: stdin is redirected from /dev/null so the pipeline does not consume
|
||||
# the host's stdin/tty. This keeps /dev/tty usable for the recovery menu
|
||||
# after SIGINT or failure (prevents "read: read error: Input/output error").
|
||||
local _LXC_CAPTURE_LOG="/tmp/.install-capture-${SESSION_ID}.log"
|
||||
lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/install/${var_install}.sh)" </dev/null 2>&1 | tee "$_LXC_CAPTURE_LOG"
|
||||
local lxc_exit=${PIPESTATUS[0]}
|
||||
|
||||
# Restore terminal state after pipeline (tee/lxc-attach may leave it broken)
|
||||
stty sane 2>/dev/null || true
|
||||
lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/install/${var_install}.sh)"
|
||||
local lxc_exit=$?
|
||||
|
||||
unset CONTAINER_INSTALLING
|
||||
|
||||
@@ -4170,19 +4157,9 @@ EOF'
|
||||
build_log_copied=true
|
||||
fi
|
||||
|
||||
# Copy and append INSTALL_LOG from container (with timeout to prevent hangs)
|
||||
# Copy and append INSTALL_LOG from container
|
||||
local temp_install_log="/tmp/.install-temp-${SESSION_ID}.log"
|
||||
local container_log_ok=false
|
||||
if timeout 8 pct pull "$CTID" "/root/.install-${SESSION_ID}.log" "$temp_install_log" 2>/dev/null; then
|
||||
# Only use container log if it has meaningful content (>100 bytes)
|
||||
if [[ -s "$temp_install_log" ]] && [[ $(stat -c%s "$temp_install_log" 2>/dev/null || echo 0) -gt 100 ]]; then
|
||||
container_log_ok=true
|
||||
fi
|
||||
fi
|
||||
|
||||
# PHASE 2: Use container-side log if available, otherwise use host-captured tee output
|
||||
local _LXC_CAPTURE_LOG="/tmp/.install-capture-${SESSION_ID}.log"
|
||||
if [[ "$container_log_ok" == true ]]; then
|
||||
if pct pull "$CTID" "/root/.install-${SESSION_ID}.log" "$temp_install_log" 2>/dev/null; then
|
||||
{
|
||||
echo "================================================================================"
|
||||
echo "PHASE 2: APPLICATION INSTALLATION (Container)"
|
||||
@@ -4190,24 +4167,8 @@ EOF'
|
||||
cat "$temp_install_log"
|
||||
echo ""
|
||||
} >>"$combined_log"
|
||||
rm -f "$temp_install_log"
|
||||
install_log_copied=true
|
||||
elif [[ -s "$_LXC_CAPTURE_LOG" ]]; then
|
||||
# Fallback: host-captured terminal output from lxc-attach
|
||||
# This captures everything the user saw, including errors when install.func
|
||||
# failed to load (DNS issues, etc.) and no container-side logging was set up.
|
||||
{
|
||||
echo "================================================================================"
|
||||
echo "PHASE 2: APPLICATION INSTALLATION (Container - captured from terminal)"
|
||||
echo "================================================================================"
|
||||
# Strip ANSI escape codes from terminal capture
|
||||
sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' "$_LXC_CAPTURE_LOG" | sed 's/\r$//'
|
||||
echo ""
|
||||
} >>"$combined_log"
|
||||
install_log_copied=true
|
||||
fi
|
||||
rm -f "$temp_install_log"
|
||||
|
||||
if [[ "$install_log_copied" == true ]]; then
|
||||
# Point INSTALL_LOG to combined log so get_full_log() finds it
|
||||
INSTALL_LOG="$combined_log"
|
||||
fi
|
||||
@@ -4261,6 +4222,7 @@ EOF'
|
||||
local is_network_issue=false
|
||||
local is_apt_issue=false
|
||||
local is_cmd_not_found=false
|
||||
local is_disk_full=false
|
||||
local error_explanation=""
|
||||
if declare -f explain_exit_code >/dev/null 2>&1; then
|
||||
error_explanation="$(explain_exit_code "$install_exit_code")"
|
||||
@@ -4281,6 +4243,14 @@ EOF'
|
||||
;;
|
||||
esac
|
||||
|
||||
# Disk full / ENOSPC detection: errno -28 (ENOSPC), exit 228 (custom handler), exit 23 (curl write error)
|
||||
if [[ $install_exit_code -eq 228 || $install_exit_code -eq 23 ]]; then
|
||||
is_disk_full=true
|
||||
fi
|
||||
if [[ -f "$combined_log" ]] && grep -qiE 'ENOSPC|no space left on device|No space left on device|Disk quota exceeded|errno -28' "$combined_log"; then
|
||||
is_disk_full=true
|
||||
fi
|
||||
|
||||
# Command not found detection
|
||||
if [[ $install_exit_code -eq 127 ]]; then
|
||||
is_cmd_not_found=true
|
||||
@@ -4317,6 +4287,9 @@ EOF'
|
||||
if grep -qiE ': command not found|No such file or directory.*/s?bin/' "$combined_log"; then
|
||||
is_cmd_not_found=true
|
||||
fi
|
||||
if grep -qiE 'ENOSPC|no space left on device|Disk quota exceeded|errno -28' "$combined_log"; then
|
||||
is_disk_full=true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Show error explanation if available
|
||||
@@ -4338,6 +4311,12 @@ EOF'
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [[ "$is_disk_full" == true ]]; then
|
||||
echo -e "${TAB}${INFO} The container ran out of disk space during installation (${GN}ENOSPC${CL})."
|
||||
echo -e "${TAB}${INFO} Current disk size: ${GN}${DISK_SIZE} GB${CL}. A rebuild with doubled disk may resolve this."
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [[ "$is_cmd_not_found" == true ]]; then
|
||||
local missing_cmd=""
|
||||
if [[ -f "$combined_log" ]]; then
|
||||
@@ -4357,7 +4336,7 @@ EOF'
|
||||
echo -e " ${GN}3)${CL} Retry with verbose mode (full rebuild)"
|
||||
|
||||
local next_option=4
|
||||
local APT_OPTION="" OOM_OPTION="" DNS_OPTION=""
|
||||
local APT_OPTION="" OOM_OPTION="" DNS_OPTION="" DISK_OPTION=""
|
||||
|
||||
if [[ "$is_apt_issue" == true ]]; then
|
||||
if [[ "$var_os" == "alpine" ]]; then
|
||||
@@ -4382,6 +4361,18 @@ EOF'
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$is_disk_full" == true ]]; then
|
||||
local disk_recovery_attempt="${DISK_RECOVERY_ATTEMPT:-0}"
|
||||
if [[ $disk_recovery_attempt -lt 2 ]]; then
|
||||
local new_disk=$((DISK_SIZE * 2))
|
||||
echo -e " ${GN}${next_option})${CL} Retry with more disk space (Disk: ${DISK_SIZE}→${new_disk} GB)"
|
||||
DISK_OPTION=$next_option
|
||||
next_option=$((next_option + 1))
|
||||
else
|
||||
echo -e " ${DGN}-)${CL} ${DGN}Disk resize retry exhausted (already retried ${disk_recovery_attempt}x)${CL}"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$is_network_issue" == true ]]; then
|
||||
echo -e " ${GN}${next_option})${CL} Retry with DNS override in LXC (8.8.8.8 / 1.1.1.1)"
|
||||
DNS_OPTION=$next_option
|
||||
@@ -4393,10 +4384,8 @@ EOF'
|
||||
echo ""
|
||||
echo -en "${YW}Select option [1-${max_option}] (default: 1, auto-remove in 60s): ${CL}"
|
||||
|
||||
# Ensure terminal is sane before reading input (lxc-attach|tee may corrupt tty state)
|
||||
stty sane 2>/dev/null || true
|
||||
|
||||
if read -t 60 -r response </dev/tty; then
|
||||
local response=""
|
||||
if read -t 60 -r response; then
|
||||
case "${response:-1}" in
|
||||
1)
|
||||
# Remove container
|
||||
@@ -4483,10 +4472,8 @@ EOF'
|
||||
# Re-run install script in existing container (don't destroy/recreate)
|
||||
set +Eeuo pipefail
|
||||
trap - ERR
|
||||
local _LXC_CAPTURE_LOG="/tmp/.install-capture-${SESSION_ID}.log"
|
||||
lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/install/${var_install}.sh)" </dev/null 2>&1 | tee "$_LXC_CAPTURE_LOG"
|
||||
local apt_retry_exit=${PIPESTATUS[0]}
|
||||
stty sane 2>/dev/null || true
|
||||
lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/install/${var_install}.sh)"
|
||||
local apt_retry_exit=$?
|
||||
set -Eeuo pipefail
|
||||
trap 'error_handler' ERR
|
||||
|
||||
@@ -4546,6 +4533,35 @@ EOF'
|
||||
return $?
|
||||
fi
|
||||
|
||||
if [[ -n "${DISK_OPTION}" && "${response}" == "${DISK_OPTION}" ]]; then
|
||||
# Retry with doubled disk size
|
||||
handled=true
|
||||
echo -e "\n${TAB}${HOLD}${YW}Removing container ${CTID} for rebuild with more disk space...${CL}"
|
||||
pct stop "$CTID" &>/dev/null || true
|
||||
pct destroy "$CTID" &>/dev/null || true
|
||||
echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}"
|
||||
echo ""
|
||||
local old_ctid="$CTID"
|
||||
local old_disk="$DISK_SIZE"
|
||||
export CTID=$(get_valid_container_id "$CTID")
|
||||
export DISK_SIZE=$((DISK_SIZE * 2))
|
||||
export var_disk="$DISK_SIZE"
|
||||
export VERBOSE="yes"
|
||||
export var_verbose="yes"
|
||||
export DISK_RECOVERY_ATTEMPT=$((${DISK_RECOVERY_ATTEMPT:-0} + 1))
|
||||
|
||||
echo -e "${YW}Rebuilding with increased disk space (attempt ${DISK_RECOVERY_ATTEMPT}/2):${CL}"
|
||||
echo -e " Container ID: ${old_ctid} → ${CTID}"
|
||||
echo -e " Disk: ${old_disk} → ${GN}${DISK_SIZE}${CL} GB (x2)"
|
||||
echo -e " RAM: ${RAM_SIZE} MiB | CPU: ${CORE_COUNT} cores"
|
||||
echo -e " Network: ${NET:-dhcp} | Bridge: ${BRG:-vmbr0}"
|
||||
echo -e " Verbose: ${GN}enabled${CL}"
|
||||
echo ""
|
||||
msg_info "Restarting installation..."
|
||||
build_container
|
||||
return $?
|
||||
fi
|
||||
|
||||
if [[ -n "${DNS_OPTION}" && "${response}" == "${DNS_OPTION}" ]]; then
|
||||
# Retry with DNS override in LXC
|
||||
handled=true
|
||||
@@ -4596,9 +4612,6 @@ EOF'
|
||||
exit $install_exit_code
|
||||
fi
|
||||
|
||||
# Clean up host-side capture log (not needed on success, already in combined_log on failure)
|
||||
rm -f "/tmp/.install-capture-${SESSION_ID}.log" 2>/dev/null
|
||||
|
||||
# Re-enable error handling after successful install or recovery menu completion
|
||||
set -Eeuo pipefail
|
||||
trap 'error_handler' ERR
|
||||
@@ -5014,11 +5027,11 @@ create_lxc_container() {
|
||||
# Storage capability check
|
||||
check_storage_support "rootdir" || {
|
||||
msg_error "No valid storage found for 'rootdir' [Container]"
|
||||
exit 1
|
||||
exit 119
|
||||
}
|
||||
check_storage_support "vztmpl" || {
|
||||
msg_error "No valid storage found for 'vztmpl' [Template]"
|
||||
exit 1
|
||||
exit 120
|
||||
}
|
||||
|
||||
# Template storage selection
|
||||
@@ -5296,7 +5309,7 @@ create_lxc_container() {
|
||||
}
|
||||
else
|
||||
msg_custom "🚫" "${YW}" "Installation cancelled"
|
||||
exit 1
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
msg_error "No ${PCT_OSTYPE} templates available"
|
||||
|
||||
@@ -276,7 +276,7 @@ shell_check() {
|
||||
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 1
|
||||
exit 103
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -293,7 +293,7 @@ root_check() {
|
||||
msg_error "Please run this script as root."
|
||||
echo -e "\nExiting..."
|
||||
sleep 2
|
||||
exit 1
|
||||
exit 104
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -314,7 +314,7 @@ pve_check() {
|
||||
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
|
||||
exit 105
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
@@ -325,7 +325,7 @@ pve_check() {
|
||||
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
|
||||
exit 105
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
@@ -333,7 +333,7 @@ pve_check() {
|
||||
# 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 105
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
@@ -348,7 +348,7 @@ arch_check() {
|
||||
msg_error "This script will not work with PiMox (ARM architecture detected)."
|
||||
msg_warn "Visit https://github.com/asylumexp/Proxmox for ARM64 support."
|
||||
sleep 2
|
||||
exit 1
|
||||
exit 106
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -94,6 +94,29 @@ if ! declare -f explain_exit_code &>/dev/null; then
|
||||
100) echo "APT: Package manager error (broken packages / dependency problems)" ;;
|
||||
101) echo "APT: Configuration error (bad sources.list, malformed config)" ;;
|
||||
102) echo "APT: Lock held by another process (dpkg/apt still running)" ;;
|
||||
|
||||
# --- Script Validation & Setup (103-123) ---
|
||||
103) echo "Validation: Shell is not Bash" ;;
|
||||
104) echo "Validation: Not running as root (or invoked via sudo)" ;;
|
||||
105) echo "Validation: Proxmox VE version not supported" ;;
|
||||
106) echo "Validation: Architecture not supported (ARM / PiMox)" ;;
|
||||
107) echo "Validation: Kernel key parameters unreadable" ;;
|
||||
108) echo "Validation: Kernel key limits exceeded" ;;
|
||||
109) echo "Proxmox: No available container ID after max attempts" ;;
|
||||
110) echo "Proxmox: Failed to apply default.vars" ;;
|
||||
111) echo "Proxmox: App defaults file not available" ;;
|
||||
112) echo "Proxmox: Invalid install menu option" ;;
|
||||
113) echo "LXC: Under-provisioned — user aborted update" ;;
|
||||
114) echo "LXC: Storage too low — user aborted update" ;;
|
||||
115) echo "Download: install.func download failed or incomplete" ;;
|
||||
116) echo "Proxmox: Default bridge vmbr0 not found" ;;
|
||||
117) echo "LXC: Container did not reach running state" ;;
|
||||
118) echo "LXC: No IP assigned to container after timeout" ;;
|
||||
119) echo "Proxmox: No valid storage for rootdir content" ;;
|
||||
120) echo "Proxmox: No valid storage for vztmpl content" ;;
|
||||
121) echo "LXC: Container network not ready (no IP after retries)" ;;
|
||||
122) echo "LXC: No internet connectivity — user declined to continue" ;;
|
||||
123) echo "LXC: Local IP detection failed" ;;
|
||||
124) echo "Command timed out (timeout command)" ;;
|
||||
125) echo "Command failed to start (Docker daemon or execution error)" ;;
|
||||
126) echo "Command invoked cannot execute (permission problem?)" ;;
|
||||
@@ -155,6 +178,16 @@ if ! declare -f explain_exit_code &>/dev/null; then
|
||||
224) echo "Proxmox: PBS storage is for backups only" ;;
|
||||
225) echo "Proxmox: No template available for OS/Version" ;;
|
||||
231) echo "Proxmox: LXC stack upgrade failed" ;;
|
||||
|
||||
# --- Tools & Addon Scripts (232-238) ---
|
||||
232) echo "Tools: Wrong execution environment (run on PVE host, not inside LXC)" ;;
|
||||
233) echo "Tools: Application not installed (update prerequisite missing)" ;;
|
||||
234) echo "Tools: No LXC containers found or available" ;;
|
||||
235) echo "Tools: Backup or restore operation failed" ;;
|
||||
236) echo "Tools: Required hardware not detected" ;;
|
||||
237) echo "Tools: Dependency package installation failed" ;;
|
||||
238) echo "Tools: OS or distribution not supported for this addon" ;;
|
||||
|
||||
239) echo "npm/Node.js: Unexpected runtime error or dependency failure" ;;
|
||||
243) echo "Node.js: Out of memory (JavaScript heap out of memory)" ;;
|
||||
245) echo "Node.js: Invalid command-line option" ;;
|
||||
@@ -162,6 +195,14 @@ if ! declare -f explain_exit_code &>/dev/null; then
|
||||
247) echo "Node.js: Fatal internal error" ;;
|
||||
248) echo "Node.js: Invalid C++ addon / N-API failure" ;;
|
||||
249) echo "npm/pnpm/yarn: Unknown fatal error" ;;
|
||||
|
||||
# --- Application Install/Update Errors (250-254) ---
|
||||
250) echo "App: Download failed or version not determined" ;;
|
||||
251) echo "App: File extraction failed (corrupt or incomplete archive)" ;;
|
||||
252) echo "App: Required file or resource not found" ;;
|
||||
253) echo "App: Data migration required — update aborted" ;;
|
||||
254) echo "App: User declined prompt or input timed out" ;;
|
||||
|
||||
255) echo "DPKG: Fatal internal error" ;;
|
||||
*) echo "Unknown error" ;;
|
||||
esac
|
||||
@@ -286,7 +327,9 @@ error_handler() {
|
||||
echo -en "${YW}Remove broken container ${CTID}? (Y/n) [auto-remove in 60s]: ${CL}"
|
||||
fi
|
||||
|
||||
if read -t 60 -r response </dev/tty; then
|
||||
# Read user response
|
||||
local response=""
|
||||
if read -t 60 -r response; then
|
||||
if [[ -z "$response" || "$response" =~ ^[Yy]$ ]]; then
|
||||
echo ""
|
||||
if declare -f msg_info >/dev/null 2>&1; then
|
||||
@@ -365,10 +408,29 @@ _send_abort_telemetry() {
|
||||
[[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0
|
||||
[[ -z "${RANDOM_UUID:-}" ]] && return 0
|
||||
|
||||
# Collect last 20 log lines for error diagnosis (best-effort)
|
||||
# Collect last 200 log lines for error diagnosis (best-effort)
|
||||
# Container context has no get_full_log(), so we gather as much as possible
|
||||
local error_text=""
|
||||
local logfile=""
|
||||
if [[ -n "${INSTALL_LOG:-}" && -s "${INSTALL_LOG}" ]]; then
|
||||
error_text=$(tail -n 20 "$INSTALL_LOG" 2>/dev/null | sed 's/\x1b\[[0-9;]*[a-zA-Z]//g; s/\\/\\\\/g; s/"/\\"/g; s/\r//g' | tr '\n' '|' | sed 's/|$//' | tr -d '\000-\010\013\014\016-\037\177') || true
|
||||
logfile="${INSTALL_LOG}"
|
||||
elif [[ -n "${SILENT_LOGFILE:-}" && -s "${SILENT_LOGFILE}" ]]; then
|
||||
logfile="${SILENT_LOGFILE}"
|
||||
fi
|
||||
|
||||
if [[ -n "$logfile" ]]; then
|
||||
error_text=$(tail -n 200 "$logfile" 2>/dev/null | sed 's/\x1b\[[0-9;]*[a-zA-Z]//g; s/\\/\\\\/g; s/"/\\"/g; s/\r//g' | tr '\n' '|' | sed 's/|$//' | head -c 16384 | tr -d '\000-\010\013\014\016-\037\177') || true
|
||||
fi
|
||||
|
||||
# Prepend exit code explanation header (like build_error_string does on host)
|
||||
local explanation=""
|
||||
if declare -f explain_exit_code &>/dev/null; then
|
||||
explanation=$(explain_exit_code "$exit_code" 2>/dev/null) || true
|
||||
fi
|
||||
if [[ -n "$explanation" && -n "$error_text" ]]; then
|
||||
error_text="exit_code=${exit_code} | ${explanation}|---|${error_text}"
|
||||
elif [[ -n "$explanation" && -z "$error_text" ]]; then
|
||||
error_text="exit_code=${exit_code} | ${explanation}"
|
||||
fi
|
||||
|
||||
# Calculate duration if start time is available
|
||||
@@ -377,10 +439,17 @@ _send_abort_telemetry() {
|
||||
duration=$(($(date +%s) - DIAGNOSTICS_START_TIME))
|
||||
fi
|
||||
|
||||
# Categorize error if function is available (may not be in minimal container context)
|
||||
local error_category=""
|
||||
if declare -f categorize_error &>/dev/null; then
|
||||
error_category=$(categorize_error "$exit_code" 2>/dev/null) || true
|
||||
fi
|
||||
|
||||
# Build JSON payload with error context
|
||||
local payload
|
||||
payload="{\"random_id\":\"${RANDOM_UUID}\",\"execution_id\":\"${EXECUTION_ID:-${RANDOM_UUID}}\",\"type\":\"${TELEMETRY_TYPE:-lxc}\",\"nsapp\":\"${NSAPP:-${app:-unknown}}\",\"status\":\"failed\",\"exit_code\":${exit_code}"
|
||||
[[ -n "$error_text" ]] && payload="${payload},\"error\":\"${error_text}\""
|
||||
[[ -n "$error_category" ]] && payload="${payload},\"error_category\":\"${error_category}\""
|
||||
[[ -n "$duration" ]] && payload="${payload},\"duration\":${duration}"
|
||||
payload="${payload}}"
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ setting_up_container() {
|
||||
if [ "$(hostname -I)" = "" ]; then
|
||||
echo 1>&2 -e "\n${CROSS}${RD} No Network After $RETRY_NUM Tries${CL}"
|
||||
echo -e "${NETWORK}Check Network Settings"
|
||||
exit 1
|
||||
exit 121
|
||||
fi
|
||||
rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED
|
||||
systemctl disable -q --now systemd-networkd-wait-online.service
|
||||
@@ -177,7 +177,7 @@ network_check() {
|
||||
echo -e "${INFO}${RD}Expect Issues Without Internet${CL}"
|
||||
else
|
||||
echo -e "${NETWORK}Check Network Settings"
|
||||
exit 1
|
||||
exit 122
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -242,12 +242,12 @@ EOF
|
||||
local tools_content
|
||||
tools_content=$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func) || {
|
||||
msg_error "Failed to download tools.func"
|
||||
exit 6
|
||||
exit 115
|
||||
}
|
||||
source /dev/stdin <<<"$tools_content"
|
||||
if ! declare -f fetch_and_deploy_gh_release >/dev/null 2>&1; then
|
||||
msg_error "tools.func loaded but incomplete — missing expected functions"
|
||||
exit 6
|
||||
exit 115
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -934,7 +934,11 @@ upgrade_package() {
|
||||
# ------------------------------------------------------------------------------
|
||||
# Repository availability check with caching
|
||||
# ------------------------------------------------------------------------------
|
||||
declare -A _REPO_CACHE 2>/dev/null || true
|
||||
# Note: Must use -gA (global) because tools.func is sourced inside update_os()
|
||||
# function scope. Plain 'declare -A' would create a local variable that gets
|
||||
# destroyed when update_os() returns, causing "unbound variable" errors later
|
||||
# when setup_postgresql/verify_repo_available tries to access the cache key.
|
||||
declare -gA _REPO_CACHE 2>/dev/null || declare -A _REPO_CACHE 2>/dev/null || true
|
||||
|
||||
verify_repo_available() {
|
||||
local repo_url="$1"
|
||||
@@ -1732,6 +1736,13 @@ setup_deb822_repo() {
|
||||
rm -f "$tmp_gpg"
|
||||
return 1
|
||||
}
|
||||
else
|
||||
# Already binary — copy directly
|
||||
cp -f "$tmp_gpg" "/etc/apt/keyrings/${name}.gpg" || {
|
||||
msg_error "Failed to install GPG key for ${name}"
|
||||
rm -f "$tmp_gpg"
|
||||
return 1
|
||||
}
|
||||
fi
|
||||
rm -f "$tmp_gpg"
|
||||
chmod 644 "/etc/apt/keyrings/${name}.gpg"
|
||||
@@ -5142,7 +5153,7 @@ current_ip="$(get_current_ip)"
|
||||
|
||||
if [[ -z "$current_ip" ]]; then
|
||||
echo "[ERROR] Could not detect local IP" >&2
|
||||
exit 1
|
||||
exit 123
|
||||
fi
|
||||
|
||||
if [[ -f "$IP_FILE" ]]; then
|
||||
@@ -5643,20 +5654,20 @@ function setup_mongodb() {
|
||||
# - Handles Debian Trixie libaio1t64 transition
|
||||
#
|
||||
# Variables:
|
||||
# USE_MYSQL_REPO - Set to "true" to use official MySQL repository
|
||||
# (default: false, uses distro packages)
|
||||
# USE_MYSQL_REPO - Use official MySQL repository (default: true)
|
||||
# Set to "false" to use distro packages instead
|
||||
# MYSQL_VERSION - MySQL version to install when using official repo
|
||||
# (e.g. 8.0, 8.4) (default: 8.0)
|
||||
#
|
||||
# Examples:
|
||||
# setup_mysql # Uses distro package (recommended)
|
||||
# USE_MYSQL_REPO=true setup_mysql # Uses official MySQL repo
|
||||
# USE_MYSQL_REPO=true MYSQL_VERSION="8.4" setup_mysql # Specific version
|
||||
# setup_mysql # Uses official MySQL repo, 8.0
|
||||
# MYSQL_VERSION="8.4" setup_mysql # Specific version from MySQL repo
|
||||
# USE_MYSQL_REPO=false setup_mysql # Uses distro package instead
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
function setup_mysql() {
|
||||
local MYSQL_VERSION="${MYSQL_VERSION:-8.0}"
|
||||
local USE_MYSQL_REPO="${USE_MYSQL_REPO:-false}"
|
||||
local USE_MYSQL_REPO="${USE_MYSQL_REPO:-true}"
|
||||
local DISTRO_ID DISTRO_CODENAME
|
||||
DISTRO_ID=$(awk -F= '/^ID=/{print $2}' /etc/os-release | tr -d '"')
|
||||
DISTRO_CODENAME=$(awk -F= '/^VERSION_CODENAME=/{print $2}' /etc/os-release)
|
||||
@@ -6357,21 +6368,21 @@ EOF
|
||||
# - Restores dumped data post-upgrade
|
||||
#
|
||||
# Variables:
|
||||
# USE_PGDG_REPO - Set to "true" to use official PGDG repository
|
||||
# (default: false, uses distro packages)
|
||||
# USE_PGDG_REPO - Use official PGDG repository (default: true)
|
||||
# Set to "false" to use distro packages instead
|
||||
# PG_VERSION - Major PostgreSQL version (e.g. 15, 16) (default: 16)
|
||||
# PG_MODULES - Comma-separated list of modules (e.g. "postgis,contrib")
|
||||
#
|
||||
# Examples:
|
||||
# setup_postgresql # Uses distro package (recommended)
|
||||
# USE_PGDG_REPO=true setup_postgresql # Uses official PGDG repo
|
||||
# USE_PGDG_REPO=true PG_VERSION="17" setup_postgresql # Specific version from PGDG
|
||||
# setup_postgresql # Uses PGDG repo, PG 16
|
||||
# PG_VERSION="17" setup_postgresql # Specific version from PGDG
|
||||
# USE_PGDG_REPO=false setup_postgresql # Uses distro package instead
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
function setup_postgresql() {
|
||||
local PG_VERSION="${PG_VERSION:-16}"
|
||||
local PG_MODULES="${PG_MODULES:-}"
|
||||
local USE_PGDG_REPO="${USE_PGDG_REPO:-false}"
|
||||
local USE_PGDG_REPO="${USE_PGDG_REPO:-true}"
|
||||
local DISTRO_ID DISTRO_CODENAME
|
||||
DISTRO_ID=$(awk -F= '/^ID=/{print $2}' /etc/os-release | tr -d '"')
|
||||
DISTRO_CODENAME=$(awk -F= '/^VERSION_CODENAME=/{print $2}' /etc/os-release)
|
||||
|
||||
@@ -244,7 +244,7 @@ curl_handler() {
|
||||
|
||||
if [[ -z "$url" ]]; then
|
||||
msg_error "no valid url or option entered for curl_handler"
|
||||
exit 1
|
||||
exit 64
|
||||
fi
|
||||
|
||||
$STD msg_info "Fetching: $url"
|
||||
@@ -273,7 +273,7 @@ curl_handler() {
|
||||
rm -f /tmp/curl_error.log
|
||||
fi
|
||||
__curl_err_handler "$exit_code" "$url" "$curl_stderr"
|
||||
exit 1 # hard exit if exit_code is not 0
|
||||
exit "$exit_code"
|
||||
fi
|
||||
|
||||
$STD printf "\r\033[K${INFO}${YW}Retry $attempt/$max_retries in ${delay}s...${CL}" >&2
|
||||
@@ -316,7 +316,7 @@ __curl_err_handler() {
|
||||
esac
|
||||
|
||||
[[ -n "$curl_msg" ]] && printf "%s\n" "$curl_msg" >&2
|
||||
exit 1
|
||||
exit "$exit_code"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
@@ -331,7 +331,7 @@ shell_check() {
|
||||
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
|
||||
exit 103
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -552,7 +552,7 @@ check_root() {
|
||||
msg_error "Please run this script as root."
|
||||
echo -e "\nExiting..."
|
||||
sleep 2
|
||||
exit
|
||||
exit 104
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -562,7 +562,7 @@ pve_check() {
|
||||
echo -e "Requires Proxmox Virtual Environment Version 8.1 - 8.4 or 9.0 - 9.1."
|
||||
echo -e "Exiting..."
|
||||
sleep 2
|
||||
exit
|
||||
exit 105
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -572,21 +572,21 @@ arch_check() {
|
||||
echo -e "\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \n"
|
||||
echo -e "Exiting..."
|
||||
sleep 2
|
||||
exit
|
||||
exit 106
|
||||
fi
|
||||
}
|
||||
|
||||
exit_script() {
|
||||
clear
|
||||
echo -e "\n${CROSS}${RD}User exited script${CL}\n"
|
||||
exit
|
||||
exit 0
|
||||
}
|
||||
|
||||
check_hostname_conflict() {
|
||||
local hostname="$1"
|
||||
if qm list | awk '{print $2}' | grep -qx "$hostname"; then
|
||||
msg_error "Hostname $hostname already in use by another VM."
|
||||
exit 1
|
||||
exit 206
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ fi
|
||||
DISTRO=$(pct exec "$CTID" -- cat /etc/os-release | grep -w "ID" | cut -d'=' -f2 | tr -d '"')
|
||||
if [[ "$DISTRO" != "debian" && "$DISTRO" != "ubuntu" ]]; then
|
||||
msg "\e[1;31m Error: This script only supports Debian or Ubuntu LXC containers. Detected: $DISTRO. Aborting...\e[0m"
|
||||
exit 1
|
||||
exit 238
|
||||
fi
|
||||
|
||||
CTID_CONFIG_PATH=/etc/pve/lxc/${CTID}.conf
|
||||
|
||||
@@ -32,7 +32,7 @@ header_info
|
||||
|
||||
if ! command -v pveversion &>/dev/null; then
|
||||
msg_error "This script must be run on the Proxmox VE host (not inside an LXC container)"
|
||||
exit 1
|
||||
exit 232
|
||||
fi
|
||||
|
||||
while true; do
|
||||
@@ -64,7 +64,7 @@ while [[ -z "${CTID}" ]]; do
|
||||
CTID=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Containers on $NODE" --radiolist \
|
||||
"\nSelect a container to add Tailscale to:\n" \
|
||||
16 $((MSG_MAX_LENGTH + 23)) 6 \
|
||||
"${CTID_MENU[@]}" 3>&1 1>&2 2>&3) || exit 1
|
||||
"${CTID_MENU[@]}" 3>&1 1>&2 2>&3) || exit 0
|
||||
done
|
||||
|
||||
CTID_CONFIG_PATH="/etc/pve/lxc/${CTID}.conf"
|
||||
|
||||
@@ -69,7 +69,7 @@ elif [[ -f "/etc/debian_version" ]]; then
|
||||
SERVICE_PATH="/etc/systemd/system/adguardhome-sync.service"
|
||||
else
|
||||
msg_error "Unsupported OS detected. Exiting."
|
||||
exit 1
|
||||
exit 238
|
||||
fi
|
||||
|
||||
# ==============================================================================
|
||||
@@ -312,7 +312,7 @@ if [[ "${type:-}" == "update" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "${APP} is not installed. Nothing to update."
|
||||
exit 1
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -87,11 +87,11 @@ function update() {
|
||||
function check_docker() {
|
||||
if ! command -v docker &>/dev/null; then
|
||||
msg_error "Docker is not installed. This script requires an existing Docker LXC. Exiting."
|
||||
exit 1
|
||||
exit 10
|
||||
fi
|
||||
if ! docker compose version &>/dev/null; then
|
||||
msg_error "Docker Compose plugin is not available. Please install it before running this script. Exiting."
|
||||
exit 1
|
||||
exit 10
|
||||
fi
|
||||
msg_ok "Docker $(docker --version | cut -d' ' -f3 | tr -d ',') and Docker Compose are available"
|
||||
}
|
||||
@@ -171,7 +171,7 @@ if [[ "${type:-}" == "update" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "${APP} is not installed. Nothing to update."
|
||||
exit 1
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -93,7 +93,7 @@ function check_or_install_docker() {
|
||||
msg_ok "Docker Compose is available"
|
||||
else
|
||||
msg_error "Docker Compose plugin is not available. Please install it."
|
||||
exit 1
|
||||
exit 10
|
||||
fi
|
||||
return
|
||||
fi
|
||||
@@ -103,7 +103,7 @@ function check_or_install_docker() {
|
||||
read -r install_docker_prompt
|
||||
if [[ ! "${install_docker_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_error "Docker is required for ${APP}. Exiting."
|
||||
exit 1
|
||||
exit 10
|
||||
fi
|
||||
|
||||
msg_info "Installing Docker"
|
||||
@@ -165,7 +165,7 @@ if [[ "${type:-}" == "update" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "${APP} is not installed. Nothing to update."
|
||||
exit 1
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -49,7 +49,7 @@ elif grep -qE 'ID=debian|ID=ubuntu' /etc/os-release; then
|
||||
SERVICE_PATH="/etc/systemd/system/copyparty.service"
|
||||
else
|
||||
msg_error "Unsupported OS detected. Exiting."
|
||||
exit 1
|
||||
exit 238
|
||||
fi
|
||||
|
||||
# ==============================================================================
|
||||
@@ -318,7 +318,7 @@ if [[ "${type:-}" == "update" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "${APP} is not installed. Nothing to update."
|
||||
exit 1
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -51,7 +51,7 @@ EOF
|
||||
# ==============================================================================
|
||||
if ! grep -qE 'ID=debian|ID=ubuntu' /etc/os-release 2>/dev/null; then
|
||||
echo -e "${CROSS} Unsupported OS detected. This script only supports Debian and Ubuntu."
|
||||
exit 1
|
||||
exit 238
|
||||
fi
|
||||
|
||||
# ==============================================================================
|
||||
@@ -183,7 +183,7 @@ if [[ "${type:-}" == "update" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "${APP} is not installed. Nothing to update."
|
||||
exit 1
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -99,7 +99,7 @@ function check_or_install_docker() {
|
||||
msg_ok "Docker Compose is available"
|
||||
else
|
||||
msg_error "Docker Compose plugin is not available. Please install it."
|
||||
exit 1
|
||||
exit 10
|
||||
fi
|
||||
return
|
||||
fi
|
||||
@@ -109,7 +109,7 @@ function check_or_install_docker() {
|
||||
read -r install_docker_prompt
|
||||
if [[ ! "${install_docker_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_error "Docker is required for ${APP}. Exiting."
|
||||
exit 1
|
||||
exit 10
|
||||
fi
|
||||
|
||||
msg_info "Installing Docker"
|
||||
@@ -160,7 +160,7 @@ if [[ "${type:-}" == "update" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "${APP} is not installed. Nothing to update."
|
||||
exit 1
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -92,7 +92,7 @@ function check_or_install_docker() {
|
||||
msg_ok "Docker Compose is available"
|
||||
else
|
||||
msg_error "Docker Compose plugin is not available. Please install it."
|
||||
exit 1
|
||||
exit 10
|
||||
fi
|
||||
return
|
||||
fi
|
||||
@@ -102,7 +102,7 @@ function check_or_install_docker() {
|
||||
read -r install_docker_prompt
|
||||
if [[ ! "${install_docker_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_error "Docker is required for ${APP}. Exiting."
|
||||
exit 1
|
||||
exit 10
|
||||
fi
|
||||
|
||||
msg_info "Installing Docker"
|
||||
@@ -163,7 +163,7 @@ if [[ "${type:-}" == "update" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "${APP} is not installed. Nothing to update."
|
||||
exit 1
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -54,7 +54,7 @@ elif [[ -f "/etc/debian_version" ]]; then
|
||||
PKG_MANAGER="apt-get install -y"
|
||||
else
|
||||
echo -e "${CROSS} Unsupported OS detected. Exiting."
|
||||
exit 1
|
||||
exit 238
|
||||
fi
|
||||
|
||||
header_info
|
||||
|
||||
@@ -52,7 +52,7 @@ elif [[ -f "/etc/debian_version" ]]; then
|
||||
PKG_MANAGER="apt-get install -y"
|
||||
else
|
||||
echo -e "${CROSS} Unsupported OS detected. Exiting."
|
||||
exit 1
|
||||
exit 238
|
||||
fi
|
||||
|
||||
header_info
|
||||
|
||||
@@ -93,7 +93,7 @@ EOF
|
||||
update_glances_debian() {
|
||||
if [[ ! -d /opt/glances/.venv ]]; then
|
||||
msg_error "$APP is not installed"
|
||||
exit 1
|
||||
exit 233
|
||||
fi
|
||||
msg_info "Updating $APP"
|
||||
cd /opt/glances
|
||||
@@ -160,7 +160,7 @@ EOF
|
||||
update_glances_alpine() {
|
||||
if [[ ! -d /opt/glances/.venv ]]; then
|
||||
msg_error "$APP is not installed"
|
||||
exit 1
|
||||
exit 233
|
||||
fi
|
||||
msg_info "Updating $APP"
|
||||
cd /opt/glances
|
||||
|
||||
@@ -52,13 +52,13 @@ EOF
|
||||
# ==============================================================================
|
||||
if [[ -f "/etc/alpine-release" ]]; then
|
||||
msg_error "Alpine is not supported for ${APP}. Use Debian."
|
||||
exit 1
|
||||
exit 238
|
||||
elif [[ -f "/etc/debian_version" ]]; then
|
||||
OS="Debian"
|
||||
SERVICE_PATH="/etc/systemd/system/immich-proxy.service"
|
||||
else
|
||||
echo -e "${CROSS} Unsupported OS detected. Exiting."
|
||||
exit 1
|
||||
exit 238
|
||||
fi
|
||||
|
||||
# ==============================================================================
|
||||
@@ -231,7 +231,7 @@ if [[ "${type:-}" == "update" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "${APP} is not installed. Nothing to update."
|
||||
exit 1
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -52,13 +52,13 @@ EOF
|
||||
# ==============================================================================
|
||||
if [[ -f "/etc/alpine-release" ]]; then
|
||||
msg_error "Alpine is not supported for ${APP}. Use Debian/Ubuntu."
|
||||
exit 1
|
||||
exit 238
|
||||
elif [[ -f "/etc/debian_version" ]]; then
|
||||
OS="Debian"
|
||||
SERVICE_PATH="/etc/systemd/system/jellystat.service"
|
||||
else
|
||||
echo -e "${CROSS} Unsupported OS detected. Exiting."
|
||||
exit 1
|
||||
exit 238
|
||||
fi
|
||||
|
||||
# ==============================================================================
|
||||
@@ -326,7 +326,7 @@ if [[ "${type:-}" == "update" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "${APP} is not installed. Nothing to update."
|
||||
exit 1
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -37,7 +37,7 @@ function find_compose_file() {
|
||||
COMPOSE_FILE=$(find "$INSTALL_PATH" -maxdepth 1 -type f -name '*.compose.yaml' ! -name 'compose.env' | head -n1)
|
||||
if [[ -z "${COMPOSE_FILE:-}" ]]; then
|
||||
msg_error "No valid compose file found in ${INSTALL_PATH}!"
|
||||
exit 1
|
||||
exit 233
|
||||
fi
|
||||
COMPOSE_BASENAME=$(basename "$COMPOSE_FILE")
|
||||
}
|
||||
@@ -48,7 +48,7 @@ function check_legacy_db() {
|
||||
echo -e "${YW}This configuration is no longer supported since Komodo v1.18.0.${CL}"
|
||||
echo -e "${YW}Please follow the migration guide:${CL}"
|
||||
echo -e "${BGN}https://github.com/community-scripts/ProxmoxVE/discussions/5689${CL}\n"
|
||||
exit 1
|
||||
exit 238
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -79,14 +79,14 @@ function update() {
|
||||
BACKUP_FILE="${INSTALL_PATH}/${COMPOSE_BASENAME}.bak_$(date +%Y%m%d_%H%M%S)"
|
||||
cp "$COMPOSE_FILE" "$BACKUP_FILE" || {
|
||||
msg_error "Failed to create backup of ${COMPOSE_BASENAME}!"
|
||||
exit 1
|
||||
exit 235
|
||||
}
|
||||
|
||||
GITHUB_URL="https://raw.githubusercontent.com/moghtech/komodo/main/compose/${COMPOSE_BASENAME}"
|
||||
if ! curl -fsSL "$GITHUB_URL" -o "$COMPOSE_FILE"; then
|
||||
msg_error "Failed to download ${COMPOSE_BASENAME} from GitHub!"
|
||||
mv "$BACKUP_FILE" "$COMPOSE_FILE"
|
||||
exit 1
|
||||
exit 115
|
||||
fi
|
||||
|
||||
if ! grep -qxF 'COMPOSE_KOMODO_BACKUPS_PATH=/etc/komodo/backups' "$COMPOSE_ENV"; then
|
||||
@@ -129,7 +129,7 @@ function check_or_install_docker() {
|
||||
msg_ok "Docker Compose is available"
|
||||
else
|
||||
msg_error "Docker Compose plugin is not available. Please install it."
|
||||
exit 1
|
||||
exit 10
|
||||
fi
|
||||
return
|
||||
fi
|
||||
@@ -139,7 +139,7 @@ function check_or_install_docker() {
|
||||
read -r install_docker_prompt
|
||||
if [[ ! "${install_docker_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_error "Docker is required for ${APP}. Exiting."
|
||||
exit 1
|
||||
exit 10
|
||||
fi
|
||||
|
||||
msg_info "Installing Docker"
|
||||
@@ -239,7 +239,7 @@ if [[ "${type:-}" == "update" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "${APP} is not installed. Nothing to update."
|
||||
exit 1
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -58,7 +58,7 @@ pve_check() {
|
||||
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
|
||||
exit 105
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
@@ -69,7 +69,7 @@ pve_check() {
|
||||
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.x"
|
||||
exit 1
|
||||
exit 105
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
@@ -77,19 +77,19 @@ pve_check() {
|
||||
# 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.x"
|
||||
exit 1
|
||||
exit 105
|
||||
}
|
||||
|
||||
detect_codename() {
|
||||
source /etc/os-release
|
||||
if [[ "$ID" != "debian" ]]; then
|
||||
msg_error "Unsupported base OS: $ID (only Proxmox VE / Debian supported)."
|
||||
exit 1
|
||||
exit 238
|
||||
fi
|
||||
CODENAME="${VERSION_CODENAME:-}"
|
||||
if [[ -z "$CODENAME" ]]; then
|
||||
msg_error "Could not detect Debian codename."
|
||||
exit 1
|
||||
exit 71
|
||||
fi
|
||||
echo "$CODENAME"
|
||||
}
|
||||
@@ -124,7 +124,7 @@ install() {
|
||||
PKG=$(get_latest_repo_pkg "$REPO_URL")
|
||||
if [[ -z "$PKG" ]]; then
|
||||
msg_error "Could not find netdata-repo package for Debian $CODENAME"
|
||||
exit 1
|
||||
exit 237
|
||||
fi
|
||||
curl -fsSL "${REPO_URL}${PKG}" -o "$PKG"
|
||||
$STD dpkg -i "$PKG"
|
||||
|
||||
@@ -36,7 +36,7 @@ SERVICE_PATH="/etc/systemd/system/nextcloud-exporter.service"
|
||||
# ==============================================================================
|
||||
if ! grep -qE 'ID=debian|ID=ubuntu' /etc/os-release 2>/dev/null; then
|
||||
echo -e "${CROSS} Unsupported OS detected. This script only supports Debian and Ubuntu."
|
||||
exit 1
|
||||
exit 238
|
||||
fi
|
||||
|
||||
# ==============================================================================
|
||||
@@ -170,7 +170,7 @@ if [[ "${type:-}" == "update" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "Nextcloud-Exporter is not installed. Nothing to update."
|
||||
exit 1
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -52,7 +52,7 @@ elif [[ -f "/etc/debian_version" ]]; then
|
||||
INSTALL_DIR="$INSTALL_DIR_DEBIAN"
|
||||
else
|
||||
echo -e "${CROSS} Unsupported OS detected. Exiting."
|
||||
exit 1
|
||||
exit 238
|
||||
fi
|
||||
|
||||
header_info
|
||||
@@ -72,7 +72,7 @@ function check_internet() {
|
||||
msg_ok "Internet connectivity OK"
|
||||
else
|
||||
msg_error "Internet connectivity or GitHub unreachable (Status $HTTP_CODE). Exiting."
|
||||
exit 1
|
||||
exit 115
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ function install_php_and_modules() {
|
||||
msg_info "Installing missing PHP packages: ${MISSING_PACKAGES[*]}"
|
||||
if ! apt-get update &>/dev/null || ! apt-get install -y "${MISSING_PACKAGES[@]}" &>/dev/null; then
|
||||
msg_error "Failed to install required PHP modules. Exiting."
|
||||
exit 1
|
||||
exit 237
|
||||
fi
|
||||
msg_ok "Installed missing PHP packages"
|
||||
else
|
||||
@@ -132,7 +132,7 @@ function install_phpmyadmin() {
|
||||
msg_info "Downloading ${TARBALL_URL}"
|
||||
if ! curl -fsSL "$TARBALL_URL" -o /tmp/phpmyadmin.tar.gz; then
|
||||
msg_error "Download failed: $TARBALL_URL"
|
||||
exit 1
|
||||
exit 115
|
||||
fi
|
||||
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
@@ -188,7 +188,7 @@ EOF
|
||||
msg_ok "Started PHP-FPM service: $PHP_FPM_SERVICE"
|
||||
else
|
||||
msg_error "Failed to start PHP-FPM service: $PHP_FPM_SERVICE"
|
||||
exit 1
|
||||
exit 150
|
||||
fi
|
||||
|
||||
$STD rc-service lighttpd start
|
||||
@@ -237,7 +237,7 @@ function update_phpmyadmin() {
|
||||
|
||||
if ! curl -fsSL "$TARBALL_URL" -o /tmp/phpmyadmin.tar.gz; then
|
||||
msg_error "Download failed: $TARBALL_URL"
|
||||
exit 1
|
||||
exit 115
|
||||
fi
|
||||
|
||||
BACKUP_DIR="/tmp/phpmyadmin-backup-$(date +%Y%m%d-%H%M%S)"
|
||||
@@ -280,7 +280,7 @@ if is_phpmyadmin_installed; then
|
||||
;;
|
||||
*)
|
||||
echo -e "${YW}⚠️ Invalid input. Exiting.${CL}"
|
||||
exit 1
|
||||
exit 112
|
||||
;;
|
||||
esac
|
||||
else
|
||||
|
||||
@@ -41,7 +41,7 @@ elif grep -qE 'ID=debian|ID=ubuntu' /etc/os-release; then
|
||||
SERVICE_PATH="/etc/systemd/system/pihole-exporter.service"
|
||||
else
|
||||
echo -e "${CROSS} Unsupported OS detected. Exiting."
|
||||
exit 1
|
||||
exit 238
|
||||
fi
|
||||
|
||||
# ==============================================================================
|
||||
@@ -207,7 +207,7 @@ if [[ "${type:-}" == "update" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "Pihole-Exporter is not installed. Nothing to update."
|
||||
exit 1
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -32,7 +32,7 @@ AUTH_TOKEN_FILE="/etc/prometheus-paperless-ngx-exporter/paperless_auth_token_fil
|
||||
# ==============================================================================
|
||||
if ! grep -qE 'ID=debian|ID=ubuntu' /etc/os-release 2>/dev/null; then
|
||||
echo -e "${CROSS} Unsupported OS detected. This script only supports Debian and Ubuntu."
|
||||
exit 1
|
||||
exit 238
|
||||
fi
|
||||
|
||||
# ==============================================================================
|
||||
@@ -144,7 +144,7 @@ if [[ "${type:-}" == "update" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "Prometheus-Paperless-NGX-Exporter is not installed. Nothing to update."
|
||||
exit 1
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -41,7 +41,7 @@ elif grep -qE 'ID=debian|ID=ubuntu' /etc/os-release; then
|
||||
SERVICE_PATH="/etc/systemd/system/qbittorrent-exporter.service"
|
||||
else
|
||||
echo -e "${CROSS} Unsupported OS detected. Exiting."
|
||||
exit 1
|
||||
exit 238
|
||||
fi
|
||||
|
||||
# ==============================================================================
|
||||
@@ -200,7 +200,7 @@ if [[ "${type:-}" == "update" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "qBittorrent-Exporter is not installed. Nothing to update."
|
||||
exit 1
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -62,7 +62,7 @@ function check_or_install_docker() {
|
||||
msg_ok "Docker Compose is available"
|
||||
else
|
||||
msg_error "Docker Compose plugin is not available. Please install it."
|
||||
exit 1
|
||||
exit 10
|
||||
fi
|
||||
return
|
||||
fi
|
||||
@@ -72,7 +72,7 @@ function check_or_install_docker() {
|
||||
read -r install_docker_prompt
|
||||
if [[ ! "${install_docker_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_error "Docker is required for ${APP}. Exiting."
|
||||
exit 1
|
||||
exit 10
|
||||
fi
|
||||
|
||||
msg_info "Installing Docker"
|
||||
@@ -168,14 +168,14 @@ if [[ "${type:-}" == "update" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "${APP} is not installed. Nothing to update."
|
||||
exit 1
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ -f /etc/alpine-release ]]; then
|
||||
msg_error "${APP} does not support Alpine Linux. Please use a Debian or Ubuntu based LXC."
|
||||
exit 1
|
||||
exit 238
|
||||
fi
|
||||
|
||||
header_info
|
||||
|
||||
@@ -62,7 +62,7 @@ function msg_error() {
|
||||
}
|
||||
if [ -z "$(ls -A /var/lib/docker/volumes/hass_config/_data/backups/)" ]; then
|
||||
msg_error "No backups found! \n"
|
||||
exit 1
|
||||
exit 235
|
||||
fi
|
||||
DIR=/var/lib/docker/volumes/hass_config/_data/restore
|
||||
if [ -d "$DIR" ]; then
|
||||
|
||||
@@ -62,7 +62,7 @@ function msg_error() {
|
||||
}
|
||||
if [ -z "$(ls -A /root/.homeassistant/backups/)" ]; then
|
||||
msg_error "No backups found! \n"
|
||||
exit 1
|
||||
exit 235
|
||||
fi
|
||||
DIR=/root/.homeassistant/restore
|
||||
if [ -d "$DIR" ]; then
|
||||
|
||||
@@ -39,7 +39,7 @@ ROOT_FS=$(df -Th "/" | awk 'NR==2 {print $2}')
|
||||
if [ "$ROOT_FS" != "ext4" ]; then
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" \
|
||||
--title "Warning" \
|
||||
--yesno "Root filesystem is not ext4 ($ROOT_FS).\nContinue anyway?" 12 80 || exit 1
|
||||
--yesno "Root filesystem is not ext4 ($ROOT_FS).\nContinue anyway?" 12 80 || exit 0
|
||||
fi
|
||||
|
||||
NODE=$(hostname)
|
||||
|
||||
@@ -57,7 +57,7 @@ done
|
||||
|
||||
if [ ${#kernels_to_remove[@]} -eq 0 ]; then
|
||||
echo -e "${RD}No valid selection made. Exiting.${CL}"
|
||||
exit 1
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Confirm removal
|
||||
@@ -66,7 +66,7 @@ printf "%s\n" "${kernels_to_remove[@]}"
|
||||
read -rp "Proceed with removal? (y/n): " confirm
|
||||
if [[ "$confirm" != "y" ]]; then
|
||||
echo -e "${RD}Aborted.${CL}"
|
||||
exit 1
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Remove kernels
|
||||
|
||||
@@ -51,7 +51,7 @@ containers=$(pct list | tail -n +2 | awk '{print $0 " " $4}')
|
||||
|
||||
if [ -z "$containers" ]; then
|
||||
whiptail --title "LXC Container Delete" --msgbox "No LXC containers available!" 10 60
|
||||
exit 1
|
||||
exit 234
|
||||
fi
|
||||
|
||||
menu_items=("ALL" "Delete ALL containers" "OFF") # Add as first option
|
||||
@@ -72,7 +72,7 @@ CHOICES=$(whiptail --title "LXC Container Delete" \
|
||||
if [ -z "$CHOICES" ]; then
|
||||
whiptail --title "LXC Container Delete" \
|
||||
--msgbox "No containers selected!" 10 60
|
||||
exit 1
|
||||
exit 0
|
||||
fi
|
||||
|
||||
read -p "Delete containers manually or automatically? (Default: manual) m/a: " DELETE_MODE
|
||||
|
||||
@@ -47,7 +47,7 @@ function msg_warn() { echo -e "${WARN} ${YWB}${1}"; }
|
||||
# Check for root privileges
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
msg_error "Error: This script must be run as root."
|
||||
exit 1
|
||||
exit 104
|
||||
fi
|
||||
|
||||
if ! command -v ethtool >/dev/null 2>&1; then
|
||||
@@ -55,7 +55,7 @@ if ! command -v ethtool >/dev/null 2>&1; then
|
||||
apt-get update &>/dev/null
|
||||
apt-get install -y ethtool &>/dev/null || {
|
||||
msg_error "Failed to install ethtool. Exiting."
|
||||
exit 1
|
||||
exit 237
|
||||
}
|
||||
msg_ok "ethtool installed successfully"
|
||||
fi
|
||||
@@ -86,7 +86,7 @@ done
|
||||
if [ ${#INTERFACES[@]} -eq 0 ]; then
|
||||
whiptail --title "Error" --msgbox "No Intel e1000e or e1000 network interfaces found!" 10 60
|
||||
msg_error "No Intel e1000e or e1000 network interfaces found! Exiting."
|
||||
exit 1
|
||||
exit 236
|
||||
fi
|
||||
|
||||
msg_ok "Found ${BL}$COUNT${GN} Intel e1000e/e1000 interfaces"
|
||||
|
||||
@@ -41,7 +41,7 @@ header_info
|
||||
virt=$(systemd-detect-virt)
|
||||
if [ "$virt" != "none" ]; then
|
||||
msg_error "This script must be run on bare metal. Detected virtual environment: $virt"
|
||||
exit 1
|
||||
exit 232
|
||||
fi
|
||||
|
||||
# Attempt to obtain the current loaded microcode revision
|
||||
|
||||
@@ -83,7 +83,7 @@ main() {
|
||||
|
||||
if command -v pveversion >/dev/null 2>&1; then
|
||||
echo -e "\n🛑 PVE Detected, Wrong Script!\n"
|
||||
exit 1
|
||||
exit 232
|
||||
fi
|
||||
|
||||
local CODENAME
|
||||
@@ -95,7 +95,7 @@ main() {
|
||||
*)
|
||||
msg_error "Unsupported Debian codename: $CODENAME"
|
||||
echo -e "Supported: bookworm (PBS 3.x) and trixie (PBS 4.x)"
|
||||
exit 1
|
||||
exit 105
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "post-pmg-inst
|
||||
|
||||
if ! grep -q "Proxmox Mail Gateway" /etc/issue 2>/dev/null; then
|
||||
msg_error "This script is only intended for Proxmox Mail Gateway"
|
||||
exit 1
|
||||
exit 232
|
||||
fi
|
||||
|
||||
repo_state() {
|
||||
|
||||
@@ -88,19 +88,19 @@ main() {
|
||||
if [[ "$PVE_MAJOR" == "8" ]]; then
|
||||
if ((PVE_MINOR < 0 || PVE_MINOR > 9)); then
|
||||
msg_error "Unsupported Proxmox 8 version"
|
||||
exit 1
|
||||
exit 105
|
||||
fi
|
||||
start_routines_8
|
||||
elif [[ "$PVE_MAJOR" == "9" ]]; then
|
||||
if ((PVE_MINOR < 0 || PVE_MINOR > 1)); then
|
||||
msg_error "Only Proxmox 9.0-9.1.x is currently supported"
|
||||
exit 1
|
||||
exit 105
|
||||
fi
|
||||
start_routines_9
|
||||
else
|
||||
msg_error "Unsupported Proxmox VE major version: $PVE_MAJOR"
|
||||
echo -e "Supported: 8.0–8.9.x and 9.0–9.1.x"
|
||||
exit 1
|
||||
exit 105
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ header_info "$APP"
|
||||
check_root() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
msg_error "Script must be run as root"
|
||||
exit 1
|
||||
exit 104
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ select_container() {
|
||||
|
||||
if [[ ${#lxc_list[@]} -eq 0 ]]; then
|
||||
msg_error "No containers found"
|
||||
exit 1
|
||||
exit 234
|
||||
fi
|
||||
|
||||
PS3="Enter number of container to convert: "
|
||||
@@ -101,7 +101,7 @@ backup_container() {
|
||||
if [ -z "$BACKUP_PATH" ] || ! grep -q "Backup job finished successfully" "$vzdump_output"; then
|
||||
rm "$vzdump_output"
|
||||
msg_error "Backup failed"
|
||||
exit 1
|
||||
exit 235
|
||||
fi
|
||||
rm "$vzdump_output"
|
||||
msg_ok "Backup complete: $BACKUP_PATH"
|
||||
@@ -126,7 +126,7 @@ perform_conversion() {
|
||||
msg_ok "Conversion successful"
|
||||
else
|
||||
msg_error "Conversion failed"
|
||||
exit 1
|
||||
exit 235
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ function backup_container() {
|
||||
msg_ok "Backup created"
|
||||
else
|
||||
msg_error "Backup failed for container $1"
|
||||
exit 1
|
||||
exit 235
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ containers=$(pct list | tail -n +2 | awk '{print $0 " " $4}')
|
||||
|
||||
if [ -z "$containers" ]; then
|
||||
whiptail --title "LXC Container Update" --msgbox "No LXC containers available!" 10 60
|
||||
exit 1
|
||||
exit 234
|
||||
fi
|
||||
|
||||
menu_items=()
|
||||
@@ -242,7 +242,7 @@ if [[ -n "$var_container" ]]; then
|
||||
|
||||
if [[ -z "$CHOICE" ]]; then
|
||||
msg_error "No containers matched the selection criteria: $var_container ${var_tags:-community-script|proxmox-helper-scripts}"
|
||||
exit 1
|
||||
exit 234
|
||||
fi
|
||||
msg_ok "Selected containers: $CHOICE"
|
||||
else
|
||||
@@ -253,7 +253,7 @@ else
|
||||
if [ -z "$CHOICE" ]; then
|
||||
whiptail --title "LXC Container Update" \
|
||||
--msgbox "No containers selected!" 10 60
|
||||
exit 1
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -284,7 +284,7 @@ if [ "$BACKUP_CHOICE" == "yes" ]; then
|
||||
|
||||
if [ -z "$STORAGES" ]; then
|
||||
msg_error "No storage with 'backup' support found!"
|
||||
exit 1
|
||||
exit 119
|
||||
fi
|
||||
|
||||
# Determine storage based on var_backup_storage
|
||||
@@ -296,7 +296,7 @@ if [ "$BACKUP_CHOICE" == "yes" ]; then
|
||||
else
|
||||
msg_error "Specified backup storage '$var_backup_storage' not found or doesn't support backups!"
|
||||
msg_info "Available storages: $(echo $STORAGES | tr '\n' ' ')"
|
||||
exit 1
|
||||
exit 119
|
||||
fi
|
||||
else
|
||||
MENU_ITEMS=()
|
||||
@@ -308,7 +308,7 @@ if [ "$BACKUP_CHOICE" == "yes" ]; then
|
||||
|
||||
if [ -z "$STORAGE_CHOICE" ]; then
|
||||
msg_error "No storage selected!"
|
||||
exit 1
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
@@ -436,11 +436,11 @@ for container in $CHOICE; do
|
||||
msg_ok "Restored LXC from backup"
|
||||
else
|
||||
msg_error "Restored LXC from backup failed"
|
||||
exit 1
|
||||
exit 235
|
||||
fi
|
||||
else
|
||||
msg_error "Update failed for container $container. Exiting"
|
||||
exit 1
|
||||
exit "$exit_code"
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ pve_check() {
|
||||
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
|
||||
exit 105
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
@@ -169,7 +169,7 @@ pve_check() {
|
||||
if ((MINOR < 0 || MINOR > 1)); then
|
||||
msg_error "This version of Proxmox VE is not supported."
|
||||
msg_error "Supported: Proxmox VE version 9.0 – 9.1"
|
||||
exit 1
|
||||
exit 105
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
@@ -177,7 +177,7 @@ pve_check() {
|
||||
# All other unsupported versions
|
||||
msg_error "This version of Proxmox VE is not supported."
|
||||
msg_error "Supported versions: Proxmox VE 8.0 – 8.x or 9.0 – 9.1"
|
||||
exit 1
|
||||
exit 105
|
||||
}
|
||||
|
||||
function arch_check() {
|
||||
@@ -513,7 +513,7 @@ DISK_REF_IMPORTED="$(printf '%s\n' "$IMPORT_OUT" | sed -n "s/.*successfully impo
|
||||
[[ -z "$DISK_REF_IMPORTED" ]] && {
|
||||
msg_error "Unable to determine imported disk reference."
|
||||
echo "$IMPORT_OUT"
|
||||
exit 1
|
||||
exit 226
|
||||
}
|
||||
msg_ok "Imported disk (${CL}${BL}${DISK_REF_IMPORTED}${CL})"
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ pve_check() {
|
||||
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
|
||||
exit 105
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
@@ -169,7 +169,7 @@ pve_check() {
|
||||
if ((MINOR < 0 || MINOR > 1)); then
|
||||
msg_error "This version of Proxmox VE is not supported."
|
||||
msg_error "Supported: Proxmox VE version 9.0 – 9.1"
|
||||
exit 1
|
||||
exit 105
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
@@ -177,7 +177,7 @@ pve_check() {
|
||||
# All other unsupported versions
|
||||
msg_error "This version of Proxmox VE is not supported."
|
||||
msg_error "Supported versions: Proxmox VE 8.0 – 8.x or 9.0 – 9.1"
|
||||
exit 1
|
||||
exit 105
|
||||
}
|
||||
|
||||
function arch_check() {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user