Compare commits

...

64 Commits

Author SHA1 Message Date
github-actions[bot]
f067caceda Update CHANGELOG.md 2026-03-03 14:49:09 +00:00
CanbiZ (MickLesk)
56b4490554 opnsense-vm: harden temp dir, bridge detection and network selection (#12513) 2026-03-03 15:48:50 +01:00
community-scripts-pr-app[bot]
b45842d76a Update CHANGELOG.md (#12516)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-03 14:34:30 +00:00
community-scripts-pr-app[bot]
ea279ace89 Update CHANGELOG.md (#12515)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-03 14:34:19 +00:00
CanbiZ (MickLesk)
034061e744 meshcentral: increased disk space to 4GB (#12509) 2026-03-03 15:34:02 +01:00
community-scripts-pr-app[bot]
dd07ba4453 Update CHANGELOG.md (#12514)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-03 14:33:48 +00:00
CanbiZ (MickLesk)
380aa4bc0f feat(recovery): add ENOSPC disk-full detection with auto-retry using doubled disk size (#12511) 2026-03-03 15:33:19 +01:00
community-scripts-pr-app[bot]
aca721e9ee chore: update github-versions.json (#12507)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-03 12:12:27 +00:00
community-scripts-pr-app[bot]
42e546904f Update .app files (#12504)
Co-authored-by: GitHub Actions <github-actions[bot]@users.noreply.github.com>
2026-03-03 11:05:11 +01:00
community-scripts-pr-app[bot]
4045824bf1 Update CHANGELOG.md (#12506)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-03 10:03:23 +00:00
community-scripts-pr-app[bot]
738cbfd1ae Update CHANGELOG.md (#12505)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-03 10:03:06 +00:00
community-scripts-pr-app[bot]
278c3cc2d8 Update date in json (#12503)
Co-authored-by: GitHub Actions <github-actions[bot]@users.noreply.github.com>
2026-03-03 10:02:59 +00:00
CanbiZ (MickLesk)
14a7ac2618 Tinyauth: v5 Support & add Debian Version (#12501) 2026-03-03 11:02:38 +01:00
community-scripts-pr-app[bot]
a7699361c1 Update CHANGELOG.md (#12502)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-03 09:26:38 +00:00
Copilot
82a0893036 Remove Unifi Network Server scripts (dead APT repo) (#12500)
* Initial plan

* remove Unifi (not unifi-os-server) CT, install, JSON, and header files

Co-authored-by: MickLesk <47820557+MickLesk@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: MickLesk <47820557+MickLesk@users.noreply.github.com>
2026-03-03 10:26:15 +01:00
community-scripts-pr-app[bot]
f9b59d7634 chore: update github-versions.json (#12499)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-03 06:18:03 +00:00
community-scripts-pr-app[bot]
f279536eb7 Update CHANGELOG.md (#12497)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-03 05:15:00 +00:00
Bram
8f34c7cd2e Refactor JSON editor and command menu components (#12492)
- Moved the search function to the JSON editor for better organization.
- Improved search functionality in the command menu with a new filtering approach.
- Cleaned up unused imports and optimized state management.
- Enhanced user experience by refining the search results display and handling.
- Updated error handling for JSON import and file reading processes.
2026-03-03 06:14:36 +01:00
community-scripts-pr-app[bot]
f922f669a8 Update CHANGELOG.md (#12495)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-03 00:22:05 +00:00
community-scripts-pr-app[bot]
2694d60faf chore: update github-versions.json (#12494)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-03 00:21:42 +00:00
community-scripts-pr-app[bot]
a326994459 Update .app files (#12485)
Co-authored-by: GitHub Actions <github-actions[bot]@users.noreply.github.com>
2026-03-02 20:35:25 +01:00
community-scripts-pr-app[bot]
4c8d1ef010 chore: update github-versions.json (#12491)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-02 18:14:27 +00:00
community-scripts-pr-app[bot]
86a9a0aac6 Update CHANGELOG.md (#12487)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-02 17:34:35 +00:00
community-scripts-pr-app[bot]
5059ed2320 Update CHANGELOG.md (#12486)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-02 17:34:13 +00:00
community-scripts-pr-app[bot]
df65c60fe4 Update date in json (#12484)
Co-authored-by: GitHub Actions <github-actions[bot]@users.noreply.github.com>
2026-03-02 17:33:59 +00:00
push-app-to-main[bot]
cff2c90041 Add powerdns (ct) (#12481)
Co-authored-by: push-app-to-main[bot] <203845782+push-app-to-main[bot]@users.noreply.github.com>
2026-03-02 18:33:26 +01:00
Michel Roegl-Brunner
88d1494e46 Adapt workflow 2026-03-02 16:28:35 +01:00
CanbiZ (MickLesk)
8b62b8f3c5 fix(api): rewrite json_escape to use awk for reliable JSON escaping 2026-03-02 16:25:22 +01:00
CanbiZ (MickLesk)
cd38bc3a65 fix: strip G suffix from DISK_SIZE in post_update_to_api for VMs
VMs set DISK_SIZE=32G (with G suffix), but post_update_to_api used
\ directly in JSON, producing 'disk_size: 32G' which is
invalid JSON. The server rejected these with 'invalid character G'.

Now strips the G suffix and validates numeric-only before embedding.
2026-03-02 15:58:34 +01:00
CanbiZ (MickLesk)
46d25645c2 fix: add retry to initial installing POST (post_to_api / post_to_api_vm)
The initial 'installing' record MUST exist for all subsequent status
updates to succeed. Previously this was fire-and-forget with no retry,
so timeouts/503s silently dropped ~50% of installations.

Both post_to_api (LXC) and post_to_api_vm now retry up to 3 times
with 1s delay between attempts. Also captures HTTP response code to
detect failures instead of using curl -f (silent fail).
2026-03-02 15:43:29 +01:00
Michel Roegl-Brunner
3701737eff Workflow test 2026-03-02 15:35:41 +01:00
Michel Roegl-Brunner
b39e296684 Workflow test 2026-03-02 15:35:28 +01:00
Michel Roegl-Brunner
d8a7620c64 Workflow test 2026-03-02 15:26:52 +01:00
Michel Roegl-Brunner
7d5900de18 Add workflow to push json changes to pocketbase 2026-03-02 15:26:41 +01:00
community-scripts-pr-app[bot]
e8b3b936df chore: update github-versions.json (#12479)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-02 13:48:34 +00:00
Michel Roegl-Brunner
5c246310f4 Workflow test 2026-03-02 14:46:46 +01:00
Michel Roegl-Brunner
bdad2cc941 Add workflow to push json changes to pocketbase 2026-03-02 14:46:31 +01:00
community-scripts-pr-app[bot]
f23c33fee7 Update CHANGELOG.md (#12478)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-02 13:43:06 +00:00
durzo
5b207cf5bd Tracearr: prepare for imminent v1.4.19 release (#12413)
* Tracearr: prepare for upcoming v1.4.19

* remove read prompts, per review
2026-03-02 14:42:39 +01:00
community-scripts-pr-app[bot]
f20c9e4ec9 Update CHANGELOG.md (#12477)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-02 13:41:55 +00:00
CanbiZ (MickLesk)
1398ff8397 Frigate: Bump to v0.17 (#12474) 2026-03-02 14:41:25 +01:00
CanbiZ (MickLesk)
ebc3512f50 fix: improve error trace propagation for telemetry
- post_update_to_api: Attempts 2/3 now send medium_error (16KB truncated
  log) instead of short_error (generic description only). This is the
  primary fix — when attempt 1 fails (120KB payload too large/timeout),
  attempts 2/3 no longer discard all log data.

- _send_abort_telemetry: Increased container fallback from 20 to 200
  log lines (capped at 16KB). Added SILENT_LOGFILE as fallback source.
  Added exit code explanation header and error_category to payload.

- get_error_text/get_full_log: Added SILENT_LOGFILE as last-resort
  fallback when INSTALL_LOG, combined log, and BUILD_LOG are all
  empty/missing.
2026-03-02 14:38:55 +01:00
Michel Roegl-Brunner
564a8136a5 Workflow test 2026-03-02 14:38:01 +01:00
Michel Roegl-Brunner
00047c95b8 Add workflow to push json changes to pocketbase 2026-03-02 14:37:50 +01:00
Michel Roegl-Brunner
9849ce79a7 Workflow test 2026-03-02 14:36:14 +01:00
Michel Roegl-Brunner
cea9858193 Add workflow to push json changes to pocketbase 2026-03-02 14:36:02 +01:00
Michel Roegl-Brunner
ee8ea672ef Workflow test 2026-03-02 14:33:35 +01:00
Michel Roegl-Brunner
fc59910bd2 Add workflow to push json changes to pocketbase 2026-03-02 14:15:44 +01:00
community-scripts-pr-app[bot]
20ab7bc005 Update CHANGELOG.md (#12476)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-02 12:58:07 +00:00
CanbiZ (MickLesk)
17de8e761b fix: replace generic exit 1 with specific exit codes in ct/ and install/ scripts (#12475)
Part of #12467 — scripts only (no framework changes).

New exit codes 250-254 registered in api.func and error_handler.func:
- 250: App download failed or version not determined
- 251: App file extraction failed (corrupt/incomplete archive)
- 252: App required file or resource not found
- 253: App data migration required — update aborted
- 254: App user declined prompt or input timed out

Existing codes reused where applicable:
- 10: privileged/Docker required (unifi-os-server)
- 64: invalid user input (postgresql, tomcat)
- 71: system error (pulse useradd)
- 150: service failed to start (docker, npmplus)
- 153: build failed (booklore)
- 233: app not installed (evcc, endurain, grafana, loki, itsm-ng)
- 236: hardware not detected (unifi-os-server /dev/net/tun)
- 238: OS not supported (frigate)
2026-03-02 13:57:42 +01:00
community-scripts-pr-app[bot]
ab1e1cc66e chore: update github-versions.json (#12473)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-02 12:12:31 +00:00
community-scripts-pr-app[bot]
256143af42 Update CHANGELOG.md (#12471)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-02 09:56:01 +00:00
CanbiZ (MickLesk)
5dc244a8c1 core: standardize exit codes and add mappings (#12467)
* Standardize exit codes and add mappings

Replace generic exit 1 usages with specific numeric exit codes and add corresponding explanations to the error lookup. This commit updates multiple misc/* scripts to return distinct codes for validation, Proxmox/LXC, networking, download and curl errors (e.g. 103-123, 64, 107-120, 206, 0 for explicit user cancels). It also updates curl error handling to propagate the original curl exit code and adds new entries in explain_exit_code and the error handler to improve diagnostics.

* Set exit code 115 for update_os errors

Change exit status from 6 to 115 in misc/alpine-install.func's update_os() error handlers when failing to download tools.func or when the expected functions are missing. This gives a distinct exit code for these specific failure cases.

* Add tools/addon exit codes and use them

Introduce exit codes 232-238 for Tools & Addon scripts in misc/api.func and misc/error_handler.func. Update addon scripts (tools/addon/adguardhome-sync.sh, tools/addon/copyparty.sh, tools/addon/cronmaster.sh) to return specific codes instead of generic exit 1: 238 for unsupported OS and 233 when the application is not installed/upgrade prerequisites are missing. This makes failures more descriptive and aligns scripts with the central error explanations.

* Standardize exit codes in exporter addons

Unify exit codes across exporter addon scripts: return 238 for unsupported OS detections and 233 when an update is requested but the exporter is not installed. Applied to nextcloud-exporter.sh, pihole-exporter.sh, prometheus-paperless-ngx-exporter.sh, and qbittorrent-exporter.sh to make failure modes distinguishable for callers/automation.

* Use specific exit codes in addon scripts

Replace generic exit 1 with distinct exit codes across multiple addon scripts to enable finer-grained error handling in automation. Exit codes introduced: 10 for Docker/Compose missing or user-declined Docker install, 233 for "nothing to update" cases, and 238 for unsupported OS cases. Affected files: tools/addon/arcane.sh, coolify.sh, dockge.sh, dokploy.sh, filebrowser-quantum.sh, filebrowser.sh, immich-public-proxy.sh, jellystat.sh, runtipi.sh.

* Use specific exit codes in addon scripts

Replace generic exit 1 with specific exit codes across multiple addon scripts to improve error signaling and handling. Files updated: tools/addon/add-netbird-lxc.sh (exit 238 on unsupported distro), tools/addon/add-tailscale-lxc.sh (treat user cancel as exit 0), tools/addon/glances.sh (exit 233 when not installed), tools/addon/komodo.sh (distinct exits for missing compose, legacy DB, backup/download failures, docker checks), tools/addon/netdata.sh (distinct exits for unsupported PVE versions, OS/codename detection, repo lookups), and tools/addon/phpmyadmin.sh (distinct exits for unsupported OS, network/download issues, package install/start failures, and invalid input). These changes make failures easier to identify and automate recovery or reporting.

* Use specific exit codes in PVE scripts

Replace generic exit 1 with distinct exit codes across tools/pve scripts to provide clearer failure signals for callers. post-pve-install.sh now returns 105 for unsupported Proxmox versions; pve-privilege-converter.sh uses 104 for non-root, 234 when no containers, and 235 for backup/conversion failures; update-apps.sh maps backup failures to 235, missing containers/selections to 234 (and UI cancellations to 0), missing backup storage to 119, and returns the actual container update exit code on failure. These changes improve diagnostics and allow external tooling to react to specific error conditions.

* Standardize exit codes and behaviors

Adjust exit codes and abort handling across multiple PVE helper scripts to provide clearer outcomes for automation and interactive flows. Changes include:

- container-restore-from-backup.sh, core-restore-from-backup.sh: return 235 when no backups found (was 1).
- fstrim.sh: treat user cancellation of non-ext4 warning as non-error (exit 0 instead of 1).
- kernel-clean.sh: treat no selection or user abort as non-error (exit 0 instead of 1).
- lxc-delete.sh: return 234 when no containers are present; treat no selection as non-error (exit 0).
- nic-offloading-fix.sh: use specific non-zero codes for root check and tool install failures (exit 104, 237) and 236 when no matching interfaces (was 1).
- pbs_microcode.sh, post-pmg-install.sh, post-pbs-install.sh: use distinct exit codes (232 and 105) for detected VM/PVE/unsupported distro conditions instead of generic 1.

These modifications make scripts return distinct codes for different failure modes and ensure user-initiated aborts or benign conditions exit with 0 where appropriate.

* Use exit 105 for unsupported PVE versions

Standardize error handling by replacing generic exit 1 with exit 105 in pve_check() across multiple VM template scripts to indicate unsupported Proxmox VE versions. Also add API exit code 226 message for "Proxmox: VM disk import or post-creation setup failed" in misc/api.func. Affected files include misc/api.func and various vm/*-vm.sh scripts.

* Use specific exit codes in VM scripts

Replace generic exit 1 with distinct exit codes across vm/*.sh to make failures more actionable for callers. Changes include: use 226 for missing imported-disk references, 237 for pv installation failures, 115 for download/extract/ISO-related failures, 214 for insufficient disk space during FreeBSD decompression, and 119 for missing storage detection. Updated scripts: archlinux-vm.sh, docker-vm.sh, haos-vm.sh, openwrt-vm.sh, opnsense-vm.sh, truenas-vm.sh, umbrel-os-vm.sh.
2026-03-02 10:55:20 +01:00
CanbiZ (MickLesk)
9f15ca6242 Simplify lxc-attach logging and terminal handling
Remove host-side tee capture of lxc-attach output and PIPESTATUS handling; lxc-attach is now invoked directly and the exit code is taken from $?. Simplify install log retrieval by pulling /root/.install-<SESSION_ID>.log directly and removing the fallback that used the host-captured terminal log, related temp-file size checks, and timeout logic. Remove terminal-state restores and input-draining (stty/dd) and stop redirecting reads from /dev/tty so interactive reads use standard input; similar simplifications applied to the retry flow. Also remove cleanup of the discarded capture log. These changes reduce complexity and terminal manipulation, at the cost of losing the previous terminal-capture fallback for installs that failed to produce a container-side log.
2026-03-02 10:46:25 +01:00
CanbiZ (MickLesk)
750b904abc Flush terminal input and make repo cache global
Restore and sanitize terminal state before prompting by draining stale input from /dev/tty (dd iflag=nonblock) and adding a short sleep, then perform timed reads from /dev/tty in misc/build.func and misc/error_handler.func. Also make _REPO_CACHE a global associative array (declare -gA) with fallbacks in misc/tools.func so the cache survives when tools.func is sourced inside a function scope.
2026-03-02 10:40:17 +01:00
CanbiZ (MickLesk)
96b5411d1d Use PG_VERSION var; default to vendor repos
Pass PG_VERSION from the Mealie installer (replace POSTGRES_VERSION with PG_VERSION) and update misc/tools.func to prefer vendor package repos by default. Adjusted comments/examples for setup_mysql and setup_postgresql to reflect the new default behavior, and changed the local default for USE_MYSQL_REPO to true. These changes align variable naming in the installer and clarify that official MySQL/PGDG repositories are used unless explicitly disabled.
2026-03-02 10:35:30 +01:00
CanbiZ (MickLesk)
a3404102ce Update tools.func 2026-03-02 10:29:04 +01:00
CanbiZ (MickLesk)
374c4492d9 Read prompts from a fresh /dev/tty to avoid tty corruption
Replace pre-opened _RECOVERY_TTY handling with direct reads from /dev/tty in misc/build.func and misc/error_handler.func. The change opens /dev/tty at prompt time (with stty sane) so the prompt reads aren't affected by tty state corruption from lxc-attach|tee, simplifies the read logic by using a local response variable with a timeout, and removes the pre-open/close bookkeeping for _RECOVERY_TTY.
2026-03-02 10:25:19 +01:00
CanbiZ (MickLesk)
3cfe86512d Fix read failures in install scripts and recovery menu
Two critical bugs fixed:

1. Install scripts (80+) using 'read' for interactive prompts all fail because
   lxc-attach stdin was redirected from /dev/null. Change to /dev/tty so install
   scripts like immich, elementsynapse, etc. can prompt the user interactively.

2. Recovery menu read gets 'Input/output error' from /dev/tty after the
   lxc-attach|tee pipeline corrupts the terminal state. Pre-open a separate
   file descriptor to /dev/tty BEFORE the pipeline starts. This fd survives
   any tty corruption and is used as fallback for the recovery menu read.
   Fixes the 'command not found' issue where user input falls through to the
   parent shell.

Both build.func (main install + APT retry) and error_handler.func (fallback
cleanup prompt) are updated with the same pattern.
2026-03-02 10:05:55 +01:00
community-scripts-pr-app[bot]
47e3e415b9 Update .app files (#12464)
Co-authored-by: GitHub Actions <github-actions[bot]@users.noreply.github.com>
2026-03-02 09:41:21 +01:00
community-scripts-pr-app[bot]
3373533725 Update CHANGELOG.md (#12466)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-02 08:36:59 +00:00
community-scripts-pr-app[bot]
62c7c329d7 Update CHANGELOG.md (#12465)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-02 08:36:43 +00:00
community-scripts-pr-app[bot]
da04899558 Update date in json (#12463)
Co-authored-by: GitHub Actions <github-actions[bot]@users.noreply.github.com>
2026-03-02 08:36:34 +00:00
push-app-to-main[bot]
e49f09b5ec Profilarr (#12441) 2026-03-02 09:36:16 +01:00
112 changed files with 1801 additions and 786 deletions

255
.github/workflows/push-json-to-pocketbase.yml generated vendored Normal file
View 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

View File

@@ -410,20 +410,73 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
</details> </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 ### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- 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 - #### 💥 Breaking Changes
- Migrate: DokPloy, Komodo, Coolify, Dockge, Runtipi to Addons [@MickLesk](https://github.com/MickLesk) ([#12275](https://github.com/community-scripts/ProxmoxVE/pull/12275)) - 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 ### 💾 Core
- #### ✨ New Features - #### ✨ 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)) - 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 ### 🌐 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)) - frontend: improve detail view badges, addon texts, and HTML title [@MickLesk](https://github.com/MickLesk) ([#12461](https://github.com/community-scripts/ProxmoxVE/pull/12461))

View File

@@ -39,7 +39,7 @@ function update_script() {
COMPOSE_FILE=$(find /opt/komodo -maxdepth 1 -type f -name '*.compose.yaml' ! -name 'compose.env' | head -n1) COMPOSE_FILE=$(find /opt/komodo -maxdepth 1 -type f -name '*.compose.yaml' ! -name 'compose.env' | head -n1)
if [[ -z "$COMPOSE_FILE" ]]; then if [[ -z "$COMPOSE_FILE" ]]; then
msg_error "No valid compose file found in /opt/komodo!" msg_error "No valid compose file found in /opt/komodo!"
exit 1 exit 252
fi 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 pull
$STD docker compose -p komodo -f "$COMPOSE_FILE" --env-file /opt/komodo/compose.env up -d $STD docker compose -p komodo -f "$COMPOSE_FILE" --env-file /opt/komodo/compose.env up -d

View File

@@ -35,6 +35,20 @@ function update_script() {
$STD service tinyauth stop $STD service tinyauth stop
msg_ok "Service Stopped" 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" msg_info "Updating Tinyauth"
rm -f /opt/tinyauth/tinyauth rm -f /opt/tinyauth/tinyauth
curl -fsSL "https://github.com/steveiliop56/tinyauth/releases/download/v${RELEASE}/tinyauth-amd64" -o /opt/tinyauth/tinyauth curl -fsSL "https://github.com/steveiliop56/tinyauth/releases/download/v${RELEASE}/tinyauth-amd64" -o /opt/tinyauth/tinyauth

View File

@@ -26,7 +26,7 @@ function update_script() {
if [[ ! -d /opt/endurain ]]; then if [[ ! -d /opt/endurain ]]; then
msg_error "No ${APP} installation found!" msg_error "No ${APP} installation found!"
exit 1 exit 233
fi fi
if check_for_gh_release "endurain" "endurain-project/endurain"; then if check_for_gh_release "endurain" "endurain-project/endurain"; then
msg_info "Stopping Service" msg_info "Stopping Service"

View File

@@ -25,7 +25,7 @@ function update_script() {
check_container_resources check_container_resources
if ! command -v evcc >/dev/null 2>&1; then if ! command -v evcc >/dev/null 2>&1; then
msg_error "No ${APP} Installation Found!" msg_error "No ${APP} Installation Found!"
exit 1 exit 233
fi fi
if [[ -f /etc/apt/sources.list.d/evcc-stable.list ]]; then if [[ -f /etc/apt/sources.list.d/evcc-stable.list ]]; then

View File

@@ -26,7 +26,7 @@ function update_script() {
if ! dpkg -s grafana >/dev/null 2>&1; then if ! dpkg -s grafana >/dev/null 2>&1; then
msg_error "No ${APP} Installation Found!" msg_error "No ${APP} Installation Found!"
exit 1 exit 233
fi fi
if [[ -f /etc/apt/sources.list.d/grafana.list ]] || [[ ! -f /etc/apt/sources.list.d/grafana.sources ]]; then if [[ -f /etc/apt/sources.list.d/grafana.list ]] || [[ ! -f /etc/apt/sources.list.d/grafana.sources ]]; then

6
ct/headers/powerdns Normal file
View File

@@ -0,0 +1,6 @@
____ ____ _ _______
/ __ \____ _ _____ _____/ __ \/ | / / ___/
/ /_/ / __ \ | /| / / _ \/ ___/ / / / |/ /\__ \
/ ____/ /_/ / |/ |/ / __/ / / /_/ / /| /___/ /
/_/ \____/|__/|__/\___/_/ /_____/_/ |_//____/

6
ct/headers/profilarr Normal file
View File

@@ -0,0 +1,6 @@
____ _____ __
/ __ \_________ / __(_) /___ ___________
/ /_/ / ___/ __ \/ /_/ / / __ `/ ___/ ___/
/ ____/ / / /_/ / __/ / / /_/ / / / /
/_/ /_/ \____/_/ /_/_/\__,_/_/ /_/

6
ct/headers/tinyauth Normal file
View File

@@ -0,0 +1,6 @@
_______ __ __
/_ __(_)___ __ ______ ___ __/ /_/ /_
/ / / / __ \/ / / / __ `/ / / / __/ __ \
/ / / / / / / /_/ / /_/ / /_/ / /_/ / / /
/_/ /_/_/ /_/\__, /\__,_/\__,_/\__/_/ /_/
/____/

View File

@@ -1,6 +0,0 @@
__ __ _ _____
/ / / /___ (_) __(_)
/ / / / __ \/ / /_/ /
/ /_/ / / / / / __/ /
\____/_/ /_/_/_/ /_/

View File

@@ -26,7 +26,7 @@ function update_script() {
if [[ ! -f /etc/itsm-ng/config_db.php ]]; then if [[ ! -f /etc/itsm-ng/config_db.php ]]; then
msg_error "No ${APP} Installation Found!" msg_error "No ${APP} Installation Found!"
exit 1 exit 233
fi fi
setup_mariadb setup_mariadb

View File

@@ -45,7 +45,7 @@ function update_script() {
if [[ -z "$KASM_URL" ]] || [[ -z "$KASM_VERSION" ]]; then if [[ -z "$KASM_URL" ]] || [[ -z "$KASM_VERSION" ]]; then
msg_error "Unable to detect latest Kasm release URL." msg_error "Unable to detect latest Kasm release URL."
exit 1 exit 250
fi fi
msg_info "Checked for new version" msg_info "Checked for new version"

View File

@@ -43,7 +43,7 @@ function update_script() {
COMPOSE_FILE=$(find /opt/komodo -maxdepth 1 -type f -name '*.compose.yaml' ! -name 'compose.env' | head -n1) COMPOSE_FILE=$(find /opt/komodo -maxdepth 1 -type f -name '*.compose.yaml' ! -name 'compose.env' | head -n1)
if [[ -z "$COMPOSE_FILE" ]]; then if [[ -z "$COMPOSE_FILE" ]]; then
msg_error "No valid compose file found in /opt/komodo!" msg_error "No valid compose file found in /opt/komodo!"
exit 1 exit 252
fi 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 pull
$STD docker compose -p komodo -f "$COMPOSE_FILE" --env-file /opt/komodo/compose.env up -d $STD docker compose -p komodo -f "$COMPOSE_FILE" --env-file /opt/komodo/compose.env up -d

View File

@@ -26,7 +26,7 @@ function update_script() {
if ! dpkg -s loki >/dev/null 2>&1; then if ! dpkg -s loki >/dev/null 2>&1; then
msg_error "No ${APP} Installation Found!" msg_error "No ${APP} Installation Found!"
exit 1 exit 233
fi fi
CHOICE=$(msg_menu "Loki Update Options" \ CHOICE=$(msg_menu "Loki Update Options" \

View File

@@ -9,7 +9,7 @@ APP="MeshCentral"
var_tags="${var_tags:-remote-management}" var_tags="${var_tags:-remote-management}"
var_cpu="${var_cpu:-1}" var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-512}" var_ram="${var_ram:-512}"
var_disk="${var_disk:-2}" var_disk="${var_disk:-4}"
var_os="${var_os:-debian}" var_os="${var_os:-debian}"
var_version="${var_version:-13}" var_version="${var_version:-13}"
var_unprivileged="${var_unprivileged:-1}" var_unprivileged="${var_unprivileged:-1}"

View File

@@ -44,7 +44,7 @@ function update_script() {
echo -e "${TAB}${GATEWAY}${BGN}https://github.com/community-scripts/ProxmoxVE/discussions/9223${CL}" echo -e "${TAB}${GATEWAY}${BGN}https://github.com/community-scripts/ProxmoxVE/discussions/9223${CL}"
echo -e "" echo -e ""
msg_custom "⚠️" "Update aborted. Please migrate your data first." msg_custom "⚠️" "Update aborted. Please migrate your data first."
exit 1 exit 253
fi fi
fi fi

68
ct/powerdns.sh Normal file
View 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
View 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
View 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}"

View File

@@ -75,10 +75,31 @@ if [ -f \$pg_config_file ]; then
fi fi
fi fi
systemctl restart postgresql systemctl restart postgresql
sudo -u postgres psql -c "ALTER USER tracearr WITH SUPERUSER;"
EOF EOF
chmod +x /data/tracearr/prestart.sh chmod +x /data/tracearr/prestart.sh
msg_ok "Updated prestart script" 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 if check_for_gh_release "tracearr" "connorgallopo/Tracearr"; then
msg_info "Stopping Services" msg_info "Stopping Services"
systemctl stop tracearr postgresql redis systemctl stop tracearr postgresql redis
@@ -122,6 +143,8 @@ EOF
sed -i "s/^APP_VERSION=.*/APP_VERSION=$(cat /root/.tracearr)/" /data/tracearr/.env sed -i "s/^APP_VERSION=.*/APP_VERSION=$(cat /root/.tracearr)/" /data/tracearr/.env
chmod 600 /data/tracearr/.env chmod 600 /data/tracearr/.env
chown -R tracearr:tracearr /data/tracearr chown -R tracearr:tracearr /data/tracearr
mkdir -p /data/backup
chown -R tracearr:tracearr /data/backup
msg_ok "Configured Tracearr" msg_ok "Configured Tracearr"
msg_info "Starting services" msg_info "Starting services"

View File

@@ -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}"

View File

@@ -34,14 +34,14 @@ function update_script() {
msg_warn "This requires MANUAL config changes in /etc/vikunja/config.yml." 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" 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 [[ "$CONFIRM1" =~ ^[yY]$ ]] || exit 0
echo echo
msg_warn "Vikunja may not start after the update until you manually adjust the config." 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" 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 [[ "$CONFIRM2" =~ ^[yY]$ ]] || exit 0
fi fi

View File

@@ -1,5 +1,5 @@
{ {
"generated": "2026-03-02T06:20:15Z", "generated": "2026-03-03T12:12:16Z",
"versions": [ "versions": [
{ {
"slug": "2fauth", "slug": "2fauth",
@@ -200,9 +200,9 @@
{ {
"slug": "cleanuparr", "slug": "cleanuparr",
"repo": "Cleanuparr/Cleanuparr", "repo": "Cleanuparr/Cleanuparr",
"version": "v2.7.6", "version": "v2.7.7",
"pinned": false, "pinned": false,
"date": "2026-02-27T19:32:02Z" "date": "2026-03-02T13:08:32Z"
}, },
{ {
"slug": "cloudreve", "slug": "cloudreve",
@@ -291,9 +291,9 @@
{ {
"slug": "dispatcharr", "slug": "dispatcharr",
"repo": "Dispatcharr/Dispatcharr", "repo": "Dispatcharr/Dispatcharr",
"version": "v0.20.1", "version": "v0.20.2",
"pinned": false, "pinned": false,
"date": "2026-02-26T21:38:19Z" "date": "2026-03-03T01:40:33Z"
}, },
{ {
"slug": "docmost", "slug": "docmost",
@@ -312,9 +312,9 @@
{ {
"slug": "domain-monitor", "slug": "domain-monitor",
"repo": "Hosteroid/domain-monitor", "repo": "Hosteroid/domain-monitor",
"version": "v1.1.3", "version": "v1.1.4",
"pinned": false, "pinned": false,
"date": "2026-02-11T15:48:18Z" "date": "2026-03-02T09:25:01Z"
}, },
{ {
"slug": "donetick", "slug": "donetick",
@@ -382,9 +382,9 @@
{ {
"slug": "firefly", "slug": "firefly",
"repo": "firefly-iii/firefly-iii", "repo": "firefly-iii/firefly-iii",
"version": "v6.5.1", "version": "v6.5.2",
"pinned": false, "pinned": false,
"date": "2026-02-27T20:55:55Z" "date": "2026-03-03T05:42:27Z"
}, },
{ {
"slug": "fladder", "slug": "fladder",
@@ -424,9 +424,9 @@
{ {
"slug": "frigate", "slug": "frigate",
"repo": "blakeblackshear/frigate", "repo": "blakeblackshear/frigate",
"version": "v0.16.4", "version": "v0.17.0",
"pinned": true, "pinned": true,
"date": "2026-01-29T00:42:14Z" "date": "2026-02-27T03:03:01Z"
}, },
{ {
"slug": "gatus", "slug": "gatus",
@@ -452,9 +452,9 @@
{ {
"slug": "gitea-mirror", "slug": "gitea-mirror",
"repo": "RayLabsHQ/gitea-mirror", "repo": "RayLabsHQ/gitea-mirror",
"version": "v3.10.1", "version": "v3.11.0",
"pinned": false, "pinned": false,
"date": "2026-03-01T03:08:07Z" "date": "2026-03-02T10:19:59Z"
}, },
{ {
"slug": "glance", "slug": "glance",
@@ -585,9 +585,9 @@
{ {
"slug": "immich-public-proxy", "slug": "immich-public-proxy",
"repo": "alangrainger/immich-public-proxy", "repo": "alangrainger/immich-public-proxy",
"version": "v1.15.3", "version": "v1.15.4",
"pinned": false, "pinned": false,
"date": "2026-02-16T22:54:27Z" "date": "2026-03-02T21:28:06Z"
}, },
{ {
"slug": "inspircd", "slug": "inspircd",
@@ -613,9 +613,9 @@
{ {
"slug": "jackett", "slug": "jackett",
"repo": "Jackett/Jackett", "repo": "Jackett/Jackett",
"version": "v0.24.1247", "version": "v0.24.1261",
"pinned": false, "pinned": false,
"date": "2026-03-02T05:56:37Z" "date": "2026-03-03T05:54:20Z"
}, },
{ {
"slug": "jellystat", "slug": "jellystat",
@@ -634,9 +634,9 @@
{ {
"slug": "jotty", "slug": "jotty",
"repo": "fccview/jotty", "repo": "fccview/jotty",
"version": "1.20.0", "version": "1.21.0",
"pinned": false, "pinned": false,
"date": "2026-02-12T09:23:30Z" "date": "2026-03-02T11:08:54Z"
}, },
{ {
"slug": "kapowarr", "slug": "kapowarr",
@@ -872,9 +872,9 @@
{ {
"slug": "metube", "slug": "metube",
"repo": "alexta69/metube", "repo": "alexta69/metube",
"version": "2026.02.27", "version": "2026.03.02",
"pinned": false, "pinned": false,
"date": "2026-02-27T11:47:02Z" "date": "2026-03-02T19:19:10Z"
}, },
{ {
"slug": "miniflux", "slug": "miniflux",
@@ -1149,6 +1149,13 @@
"pinned": false, "pinned": false,
"date": "2026-02-23T19:50:48Z" "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", "slug": "privatebin",
"repo": "PrivateBin/PrivateBin", "repo": "PrivateBin/PrivateBin",
@@ -1156,6 +1163,13 @@
"pinned": false, "pinned": false,
"date": "2025-11-12T07:10:14Z" "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", "slug": "projectsend",
"repo": "projectsend/projectsend", "repo": "projectsend/projectsend",
@@ -1215,9 +1229,9 @@
{ {
"slug": "pulse", "slug": "pulse",
"repo": "rcourtman/Pulse", "repo": "rcourtman/Pulse",
"version": "v5.1.16", "version": "v5.1.17",
"pinned": false, "pinned": false,
"date": "2026-03-01T23:13:09Z" "date": "2026-03-02T20:15:31Z"
}, },
{ {
"slug": "pve-scripts-local", "slug": "pve-scripts-local",
@@ -1341,9 +1355,9 @@
{ {
"slug": "scanopy", "slug": "scanopy",
"repo": "scanopy/scanopy", "repo": "scanopy/scanopy",
"version": "v0.14.10", "version": "v0.14.11",
"pinned": false, "pinned": false,
"date": "2026-02-28T21:05:12Z" "date": "2026-03-02T08:48:42Z"
}, },
{ {
"slug": "scraparr", "slug": "scraparr",
@@ -1418,9 +1432,9 @@
{ {
"slug": "snowshare", "slug": "snowshare",
"repo": "TuroYT/snowshare", "repo": "TuroYT/snowshare",
"version": "v1.3.7", "version": "v1.3.8",
"pinned": false, "pinned": false,
"date": "2026-02-23T15:51:39Z" "date": "2026-03-02T07:43:42Z"
}, },
{ {
"slug": "sonarr", "slug": "sonarr",
@@ -1548,6 +1562,13 @@
"pinned": false, "pinned": false,
"date": "2026-02-13T16:30:09Z" "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", "slug": "traccar",
"repo": "traccar/traccar", "repo": "traccar/traccar",
@@ -1656,9 +1677,9 @@
{ {
"slug": "victoriametrics", "slug": "victoriametrics",
"repo": "VictoriaMetrics/VictoriaMetrics", "repo": "VictoriaMetrics/VictoriaMetrics",
"version": "v1.136.0", "version": "v1.137.0",
"pinned": false, "pinned": false,
"date": "2026-02-16T13:17:50Z" "date": "2026-03-02T10:09:29Z"
}, },
{ {
"slug": "vikunja", "slug": "vikunja",
@@ -1684,9 +1705,9 @@
{ {
"slug": "wanderer", "slug": "wanderer",
"repo": "meilisearch/meilisearch", "repo": "meilisearch/meilisearch",
"version": "v1.36.0", "version": "v1.37.0",
"pinned": false, "pinned": false,
"date": "2026-02-23T08:13:32Z" "date": "2026-03-02T09:16:36Z"
}, },
{ {
"slug": "warracker", "slug": "warracker",
@@ -1719,9 +1740,9 @@
{ {
"slug": "wealthfolio", "slug": "wealthfolio",
"repo": "afadil/wealthfolio", "repo": "afadil/wealthfolio",
"version": "v3.0.0", "version": "v3.0.2",
"pinned": false, "pinned": false,
"date": "2026-02-24T22:37:05Z" "date": "2026-03-03T05:01:49Z"
}, },
{ {
"slug": "web-check", "slug": "web-check",
@@ -1789,9 +1810,9 @@
{ {
"slug": "zigbee2mqtt", "slug": "zigbee2mqtt",
"repo": "Koenkk/zigbee2mqtt", "repo": "Koenkk/zigbee2mqtt",
"version": "2.9.0", "version": "2.9.1",
"pinned": false, "pinned": false,
"date": "2026-03-01T13:58:14Z" "date": "2026-03-02T11:16:46Z"
}, },
{ {
"slug": "zipline", "slug": "zipline",
@@ -1803,9 +1824,9 @@
{ {
"slug": "zitadel", "slug": "zitadel",
"repo": "zitadel/zitadel", "repo": "zitadel/zitadel",
"version": "v4.11.1", "version": "v4.12.0",
"pinned": false, "pinned": false,
"date": "2026-02-25T06:13:13Z" "date": "2026-03-02T08:16:10Z"
}, },
{ {
"slug": "zoraxy", "slug": "zoraxy",

View File

@@ -21,7 +21,7 @@
"resources": { "resources": {
"cpu": 1, "cpu": 1,
"ram": 512, "ram": 512,
"hdd": 2, "hdd": 4,
"os": "debian", "os": "debian",
"version": "13" "version": "13"
} }

View 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 APIs.",
"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"
}
]
}

View 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": []
}

View File

@@ -1,10 +1,10 @@
{ {
"name": "Alpine-Tinyauth", "name": "Tinyauth",
"slug": "alpine-tinyauth", "slug": "tinyauth",
"categories": [ "categories": [
6 6
], ],
"date_created": "2025-05-06", "date_created": "2026-03-03",
"type": "ct", "type": "ct",
"updateable": true, "updateable": true,
"privileged": false, "privileged": false,
@@ -17,13 +17,13 @@
"install_methods": [ "install_methods": [
{ {
"type": "default", "type": "default",
"script": "ct/alpine-tinyauth.sh", "script": "ct/tinyauth.sh",
"resources": { "resources": {
"cpu": 1, "cpu": 1,
"ram": 256, "ram": 512,
"hdd": 2, "hdd": 4,
"os": "alpine", "os": "debian",
"version": "3.23" "version": "13"
} }
}, },
{ {

View File

@@ -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"
}
]
}

View File

@@ -2,42 +2,68 @@
import type { z } from "zod"; 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 { CalendarIcon, Check, Clipboard, Download } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "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 { format } from "date-fns";
import { toast } from "sonner"; import { toast } from "sonner";
import Image from "next/image";
import type { Category } from "@/lib/types"; 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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; 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 { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Calendar } from "@/components/ui/calendar"; import { Calendar } from "@/components/ui/calendar";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { basePath } from "@/config/site-config";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { fetchCategories } from "@/lib/data"; import { fetchCategories } from "@/lib/data";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import type { Script } from "./_schemas/schemas"; import type { Script } from "./_schemas/schemas";
import { ScriptItem } from "../scripts/_components/script-item";
import InstallMethod from "./_components/install-method"; import InstallMethod from "./_components/install-method";
import { ScriptSchema } from "./_schemas/schemas"; import { ScriptSchema } from "./_schemas/schemas";
import Categories from "./_components/categories"; import Categories from "./_components/categories";
import Note from "./_components/note"; import Note from "./_components/note";
import { githubGist, nord } from "react-syntax-highlighter/dist/esm/styles/hljs"; function search(scripts: Script[], query: string): Script[] {
import SyntaxHighlighter from "react-syntax-highlighter"; const queryLower = query.toLowerCase().trim();
import { ScriptItem } from "../scripts/_components/script-item"; const searchWords = queryLower.split(/\s+/).filter(Boolean);
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; return scripts
import { search } from "@/components/command-menu"; .map((script) => {
import { basePath } from "@/config/site-config"; const nameLower = script.name.toLowerCase();
import Image from "next/image"; const descriptionLower = (script.description || "").toLowerCase();
import { useTheme } from "next-themes";
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 = { const initialScript: Script = {
name: "", name: "",
@@ -77,32 +103,32 @@ export default function JSONGenerator() {
const selectedCategoryObj = useMemo( const selectedCategoryObj = useMemo(
() => categories.find(cat => cat.id.toString() === selectedCategory), () => categories.find(cat => cat.id.toString() === selectedCategory),
[categories, selectedCategory] [categories, selectedCategory],
); );
const allScripts = useMemo( const allScripts = useMemo(
() => categories.flatMap(cat => cat.scripts || []), () => categories.flatMap(cat => cat.scripts || []),
[categories] [categories],
); );
const scripts = useMemo(() => { const scripts = useMemo(() => {
const query = searchQuery.trim() const query = searchQuery.trim();
if (query) { if (query) {
return search(allScripts, query) return search(allScripts, query);
} }
if (selectedCategoryObj) { if (selectedCategoryObj) {
return selectedCategoryObj.scripts || [] return selectedCategoryObj.scripts || [];
} }
return [] return [];
}, [allScripts, selectedCategoryObj, searchQuery]); }, [allScripts, selectedCategoryObj, searchQuery]);
useEffect(() => { useEffect(() => {
fetchCategories() fetchCategories()
.then(setCategories) .then(setCategories)
.catch((error) => console.error("Error fetching categories:", error)); .catch(error => console.error("Error fetching categories:", error));
}, []); }, []);
useEffect(() => { useEffect(() => {
@@ -122,11 +148,14 @@ export default function JSONGenerator() {
if (updated.type === "pve") { if (updated.type === "pve") {
scriptPath = `tools/pve/${updated.slug}.sh`; scriptPath = `tools/pve/${updated.slug}.sh`;
} else if (updated.type === "addon") { }
else if (updated.type === "addon") {
scriptPath = `tools/addon/${updated.slug}.sh`; scriptPath = `tools/addon/${updated.slug}.sh`;
} else if (method.type === "alpine") { }
else if (method.type === "alpine") {
scriptPath = `${updated.type}/alpine-${updated.slug}.sh`; scriptPath = `${updated.type}/alpine-${updated.slug}.sh`;
} else { }
else {
scriptPath = `${updated.type}/${updated.slug}.sh`; scriptPath = `${updated.type}/${updated.slug}.sh`;
} }
@@ -145,11 +174,13 @@ export default function JSONGenerator() {
}, []); }, []);
const handleCopy = useCallback(() => { 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)); navigator.clipboard.writeText(JSON.stringify(script, null, 2));
setIsCopied(true); setIsCopied(true);
setTimeout(() => setIsCopied(false), 2000); setTimeout(() => setIsCopied(false), 2000);
if (isValid) toast.success("Copied metadata to clipboard"); if (isValid)
toast.success("Copied metadata to clipboard");
}, [script]); }, [script]);
const importScript = (script: Script) => { const importScript = (script: Script) => {
@@ -166,11 +197,11 @@ export default function JSONGenerator() {
setIsValid(true); setIsValid(true);
setZodErrors(null); setZodErrors(null);
toast.success("Imported JSON successfully"); toast.success("Imported JSON successfully");
} catch (error) { }
catch (error) {
toast.error("Failed to read or parse the JSON file."); toast.error("Failed to read or parse the JSON file.");
} }
};
}
const handleFileImport = useCallback(() => { const handleFileImport = useCallback(() => {
const input = document.createElement("input"); const input = document.createElement("input");
@@ -180,7 +211,8 @@ export default function JSONGenerator() {
input.onchange = (e: Event) => { input.onchange = (e: Event) => {
const target = e.target as HTMLInputElement; const target = e.target as HTMLInputElement;
const file = target.files?.[0]; const file = target.files?.[0];
if (!file) return; if (!file)
return;
const reader = new FileReader(); const reader = new FileReader();
reader.onload = (event) => { reader.onload = (event) => {
@@ -189,7 +221,8 @@ export default function JSONGenerator() {
const parsed = JSON.parse(content); const parsed = JSON.parse(content);
importScript(parsed); importScript(parsed);
toast.success("Imported JSON successfully"); toast.success("Imported JSON successfully");
} catch (error) { }
catch (error) {
toast.error("Failed to read the JSON file."); toast.error("Failed to read the JSON file.");
} }
}; };
@@ -243,7 +276,10 @@ export default function JSONGenerator() {
<div className="mt-2 space-y-1"> <div className="mt-2 space-y-1">
{zodErrors.issues.map((error, index) => ( {zodErrors.issues.map((error, index) => (
<AlertDescription key={index} className="p-1 text-red-500"> <AlertDescription key={index} className="p-1 text-red-500">
{error.path.join(".")} -{error.message} {error.path.join(".")}
{" "}
-
{error.message}
</AlertDescription> </AlertDescription>
))} ))}
</div> </div>
@@ -270,7 +306,7 @@ export default function JSONGenerator() {
onOpenChange={setIsImportDialogOpen} onOpenChange={setIsImportDialogOpen}
> >
<DialogTrigger asChild> <DialogTrigger asChild>
<DropdownMenuItem onSelect={(e) => e.preventDefault()}> <DropdownMenuItem onSelect={e => e.preventDefault()}>
Import existing script Import existing script
</DropdownMenuItem> </DropdownMenuItem>
</DialogTrigger> </DialogTrigger>
@@ -292,7 +328,7 @@ export default function JSONGenerator() {
<SelectValue placeholder="Category" /> <SelectValue placeholder="Category" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{categories.map((category) => ( {categories.map(category => (
<SelectItem key={category.id} value={category.id.toString()}> <SelectItem key={category.id} value={category.id.toString()}>
{category.name} {category.name}
</SelectItem> </SelectItem>
@@ -302,40 +338,44 @@ export default function JSONGenerator() {
<Input <Input
placeholder="Search for a script..." placeholder="Search for a script..."
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={e => setSearchQuery(e.target.value)}
/> />
{!selectedCategory && !searchQuery ? ( {!selectedCategory && !searchQuery
<p className="text-muted-foreground text-sm text-center"> ? (
Select a category or search for a script <p className="text-muted-foreground text-sm text-center">
</p> Select a category or search for a script
) : scripts.length === 0 ? ( </p>
<p className="text-muted-foreground text-sm text-center"> )
No scripts found : scripts.length === 0
</p> ? (
) : ( <p className="text-muted-foreground text-sm text-center">
<div className="grid grid-cols-3 auto-rows-min h-64 overflow-y-auto gap-4"> No scripts found
{scripts.map(script => ( </p>
<div )
key={script.slug} : (
className="p-2 border rounded cursor-pointer hover:bg-accent hover:text-accent-foreground" <div className="grid grid-cols-3 auto-rows-min h-64 overflow-y-auto gap-4">
onClick={() => { {scripts.map(script => (
importScript(script); <div
setIsImportDialogOpen(false); key={script.slug}
}} className="p-2 border rounded cursor-pointer hover:bg-accent hover:text-accent-foreground"
> onClick={() => {
<Image importScript(script);
src={script.logo || `/${basePath}/logo.png`} setIsImportDialogOpen(false);
alt={script.name} }}
className="w-full h-12 object-contain mb-2" >
width={16} <Image
height={16} src={script.logo || `/${basePath}/logo.png`}
unoptimized alt={script.name}
/> className="w-full h-12 object-contain mb-2"
<p className="text-sm text-center">{script.name}</p> width={16}
</div> height={16}
))} unoptimized
</div> />
)} <p className="text-sm text-center">{script.name}</p>
</div>
))}
</div>
)}
</div> </div>
</div> </div>
</DialogContent> </DialogContent>
@@ -348,15 +388,19 @@ export default function JSONGenerator() {
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<Label> <Label>
Name <span className="text-red-500">*</span> Name
{" "}
<span className="text-red-500">*</span>
</Label> </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>
<div> <div>
<Label> <Label>
Slug <span className="text-red-500">*</span> Slug
{" "}
<span className="text-red-500">*</span>
</Label> </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> </div>
<div> <div>
@@ -366,7 +410,7 @@ export default function JSONGenerator() {
<Input <Input
placeholder="Full logo URL" placeholder="Full logo URL"
value={script.logo || ""} value={script.logo || ""}
onChange={(e) => updateScript("logo", e.target.value || null)} onChange={e => updateScript("logo", e.target.value || null)}
/> />
</div> </div>
<div> <div>
@@ -374,24 +418,28 @@ export default function JSONGenerator() {
<Input <Input
placeholder="Path to config file" placeholder="Path to config file"
value={script.config_path || ""} value={script.config_path || ""}
onChange={(e) => updateScript("config_path", e.target.value || "")} onChange={e => updateScript("config_path", e.target.value || "")}
/> />
</div> </div>
<div> <div>
<Label> <Label>
Description <span className="text-red-500">*</span> Description
{" "}
<span className="text-red-500">*</span>
</Label> </Label>
<Textarea <Textarea
placeholder="Example" placeholder="Example"
value={script.description} value={script.description}
onChange={(e) => updateScript("description", e.target.value)} onChange={e => updateScript("description", e.target.value)}
/> />
</div> </div>
<Categories script={script} setScript={setScript} categories={categories} /> <Categories script={script} setScript={setScript} categories={categories} />
<div className="flex gap-2"> <div className="flex gap-2">
<div className="flex flex-col gap-2 w-full"> <div className="flex flex-col gap-2 w-full">
<Label> <Label>
Date Created <span className="text-red-500">*</span> Date Created
{" "}
<span className="text-red-500">*</span>
</Label> </Label>
<Popover> <Popover>
<PopoverTrigger asChild className="flex-1"> <PopoverTrigger asChild className="flex-1">
@@ -415,7 +463,7 @@ export default function JSONGenerator() {
</div> </div>
<div className="flex flex-col gap-2 w-full"> <div className="flex flex-col gap-2 w-full">
<Label>Type</Label> <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"> <SelectTrigger className="flex-1">
<SelectValue placeholder="Type" /> <SelectValue placeholder="Type" />
</SelectTrigger> </SelectTrigger>
@@ -430,17 +478,17 @@ export default function JSONGenerator() {
</div> </div>
<div className="w-full flex gap-5"> <div className="w-full flex gap-5">
<div className="flex items-center space-x-2"> <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> <label>Updateable</label>
</div> </div>
<div className="flex items-center space-x-2"> <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> <label>Privileged</label>
</div> </div>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<Switch <Switch
checked={script.disable || false} checked={script.disable || false}
onCheckedChange={(checked) => updateScript("disable", checked)} onCheckedChange={checked => updateScript("disable", checked)}
/> />
<label>Disabled</label> <label>Disabled</label>
</div> </div>
@@ -448,12 +496,14 @@ export default function JSONGenerator() {
{script.disable && ( {script.disable && (
<div> <div>
<Label> <Label>
Disable Description <span className="text-red-500">*</span> Disable Description
{" "}
<span className="text-red-500">*</span>
</Label> </Label>
<Textarea <Textarea
placeholder="Explain why this script is disabled..." placeholder="Explain why this script is disabled..."
value={script.disable_description || ""} value={script.disable_description || ""}
onChange={(e) => updateScript("disable_description", e.target.value)} onChange={e => updateScript("disable_description", e.target.value)}
/> />
</div> </div>
)} )}
@@ -461,18 +511,18 @@ export default function JSONGenerator() {
placeholder="Interface Port" placeholder="Interface Port"
type="number" type="number"
value={script.interface_port || ""} 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"> <div className="flex gap-2">
<Input <Input
placeholder="Website URL" placeholder="Website URL"
value={script.website || ""} value={script.website || ""}
onChange={(e) => updateScript("website", e.target.value || null)} onChange={e => updateScript("website", e.target.value || null)}
/> />
<Input <Input
placeholder="Documentation URL" placeholder="Documentation URL"
value={script.documentation || ""} value={script.documentation || ""}
onChange={(e) => updateScript("documentation", e.target.value || null)} onChange={e => updateScript("documentation", e.target.value || null)}
/> />
</div> </div>
<InstallMethod script={script} setScript={setScript} setIsValid={setIsValid} setZodErrors={setZodErrors} /> <InstallMethod script={script} setScript={setScript} setIsValid={setIsValid} setZodErrors={setZodErrors} />
@@ -480,22 +530,20 @@ export default function JSONGenerator() {
<Input <Input
placeholder="Username" placeholder="Username"
value={script.default_credentials.username || ""} value={script.default_credentials.username || ""}
onChange={(e) => onChange={e =>
updateScript("default_credentials", { updateScript("default_credentials", {
...script.default_credentials, ...script.default_credentials,
username: e.target.value || null, username: e.target.value || null,
}) })}
}
/> />
<Input <Input
placeholder="Password" placeholder="Password"
value={script.default_credentials.password || ""} value={script.default_credentials.password || ""}
onChange={(e) => onChange={e =>
updateScript("default_credentials", { updateScript("default_credentials", {
...script.default_credentials, ...script.default_credentials,
password: e.target.value || null, password: e.target.value || null,
}) })}
}
/> />
<Note script={script} setScript={setScript} setIsValid={setIsValid} setZodErrors={setZodErrors} /> <Note script={script} setScript={setScript} setIsValid={setIsValid} setZodErrors={setZodErrors} />
</form> </form>
@@ -504,7 +552,7 @@ export default function JSONGenerator() {
<Tabs <Tabs
defaultValue="json" defaultValue="json"
className="w-full" className="w-full"
onValueChange={(value) => setCurrentTab(value as "json" | "preview")} onValueChange={value => setCurrentTab(value as "json" | "preview")}
value={currentTab} value={currentTab}
> >
<TabsList className="grid w-full grid-cols-2"> <TabsList className="grid w-full grid-cols-2">

View File

@@ -1,6 +1,7 @@
import { useRouter } from "next/navigation";
import { ArrowRightIcon, Sparkles } from "lucide-react"; import { ArrowRightIcon, Sparkles } from "lucide-react";
import { useRouter } from "next/navigation";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link";
import React from "react"; import React from "react";
import type { Category, Script } from "@/lib/types"; 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 { DialogTitle } from "./ui/dialog";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
import { Badge } from "./ui/badge"; 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) { export function formattedBadge(type: string) {
switch (type) { switch (type) {
@@ -79,11 +51,9 @@ function getRandomScript(categories: Category[], previouslySelected: Set<string>
} }
function CommandMenu() { function CommandMenu() {
const [query, setQuery] = React.useState("");
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
const [links, setLinks] = React.useState<Category[]>([]); const [links, setLinks] = React.useState<Category[]>([]);
const [isLoading, setIsLoading] = React.useState(false); const [isLoading, setIsLoading] = React.useState(false);
const [results, setResults] = React.useState<Script[]>([]);
const [selectedScripts, setSelectedScripts] = React.useState<Set<string>>(new Set()); const [selectedScripts, setSelectedScripts] = React.useState<Set<string>>(new Set());
const router = useRouter(); 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(() => { React.useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => { const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) { if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
@@ -248,46 +197,49 @@ function CommandMenu() {
<CommandDialog <CommandDialog
open={open} open={open}
onOpenChange={(open) => { onOpenChange={setOpen}
setOpen(open); filter={(value: string, search: string) => {
if (open) { const searchLower = search.toLowerCase().trim();
setQuery(""); if (!searchLower)
setResults([]); 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> <DialogTitle className="sr-only">Search scripts</DialogTitle>
<CommandInput <CommandInput placeholder="Search for a script..." />
placeholder="Search for a script..."
onValueChange={setQuery}
value={query}
/>
<CommandList> <CommandList>
<CommandEmpty> <CommandEmpty>
{isLoading ? ( {isLoading
"Searching..." ? (
) : ( "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"> <div className="flex flex-col items-center justify-center py-6 text-center">
<p className="text-xs text-muted-foreground mb-2">Want to add a new script?</p> <p className="text-sm text-muted-foreground">No scripts match your search.</p>
<Button variant="outline" size="sm" asChild> <div className="mt-4">
<Link <p className="text-xs text-muted-foreground mb-2">Want to add a new script?</p>
href={`https://github.com/community-scripts/${basePath}/tree/main/docs/contribution/GUIDE.md`} <Button variant="outline" size="sm" asChild>
target="_blank" <Link
rel="noopener noreferrer" href={`https://github.com/community-scripts/${basePath}/tree/main/docs/contribution/GUIDE.md`}
> target="_blank"
Documentation <ArrowRightIcon className="ml-2 h-4 w-4" /> rel="noopener noreferrer"
</Link> >
</Button> Documentation
</div> {" "}
</div> <ArrowRightIcon className="ml-2 h-4 w-4" />
)} </Link>
</Button>
</div>
</div>
)}
</CommandEmpty> </CommandEmpty>
{Object.entries(uniqueScriptsByCategory).map(([categoryName, scripts]) => (
{results.length > 0 ? ( <CommandGroup key={`category:${categoryName}`} heading={categoryName}>
<CommandGroup heading="Search Results"> {scripts.map(script => (
{results.map(script => (
<CommandItem <CommandItem
key={`script:${script.slug}`} key={`script:${script.slug}`}
value={`${script.name} ${script.type} ${script.description || ""}`} value={`${script.name} ${script.type} ${script.description || ""}`}
@@ -320,44 +272,7 @@ function CommandMenu() {
</CommandItem> </CommandItem>
))} ))}
</CommandGroup> </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> </CommandList>
</CommandDialog> </CommandDialog>
</> </>

View File

@@ -14,7 +14,7 @@ network_check
update_os update_os
read -r -p "${TAB3}Enter PostgreSQL version (15/16/17): " ver 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}" msg_info "Installing PostgreSQL ${ver}"
$STD apk add --no-cache postgresql${ver} postgresql${ver}-contrib postgresql${ver}-openrc sudo $STD apk add --no-cache postgresql${ver} postgresql${ver}-contrib postgresql${ver}-openrc sudo

View File

@@ -36,9 +36,9 @@ msg_ok "Installed Tinyauth"
read -r -p "${TAB3}Enter your Tinyauth subdomain (e.g. https://tinyauth.example.com): " app_url read -r -p "${TAB3}Enter your Tinyauth subdomain (e.g. https://tinyauth.example.com): " app_url
cat <<EOF >/opt/tinyauth/.env cat <<EOF >/opt/tinyauth/.env
DATABASE_PATH=/opt/tinyauth/database.db TINYAUTH_DATABASE_PATH=/opt/tinyauth/database.db
USERS='${USER}' TINYAUTH_AUTH_USERS='${USER}'
APP_URL=${app_url} TINYAUTH_APPURL=${app_url}
EOF EOF
msg_info "Creating Service" msg_info "Creating Service"

View File

@@ -25,7 +25,7 @@ case $version in
;; ;;
*) *)
msg_error "Invalid JDK version selected. Please enter 8, 11, 17 or 21." msg_error "Invalid JDK version selected. Please enter 8, 11, 17 or 21."
exit 1 exit 64
;; ;;
esac esac
;; ;;
@@ -39,7 +39,7 @@ case $version in
;; ;;
*) *)
msg_error "Invalid JDK version selected. Please enter 11, 17 or 21." msg_error "Invalid JDK version selected. Please enter 11, 17 or 21."
exit 1 exit 64
;; ;;
esac esac
;; ;;
@@ -53,13 +53,13 @@ case $version in
;; ;;
*) *)
msg_error "Invalid JDK version selected. Please enter 17 or 21." msg_error "Invalid JDK version selected. Please enter 17 or 21."
exit 1 exit 64
;; ;;
esac esac
;; ;;
*) *)
msg_error "Invalid Tomcat version selected. Please enter 9, 10.1 or 11." msg_error "Invalid Tomcat version selected. Please enter 9, 10.1 or 11."
exit 1 exit 64
;; ;;
esac esac

View File

@@ -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) 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 if [[ -z "$JAR_PATH" ]]; then
msg_error "Backend JAR not found" msg_error "Backend JAR not found"
exit 1 exit 153
fi fi
cp "$JAR_PATH" /opt/booklore/dist/app.jar cp "$JAR_PATH" /opt/booklore/dist/app.jar
msg_ok "Built Backend" msg_ok "Built Backend"

View File

@@ -86,7 +86,7 @@ EOF
msg_ok "Docker TCP socket available on $socket" msg_ok "Docker TCP socket available on $socket"
else else
msg_error "Docker failed to restart. Check journalctl -xeu docker.service" msg_error "Docker failed to restart. Check journalctl -xeu docker.service"
exit 1 exit 150
fi fi
fi fi

View File

@@ -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) 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 if [[ -z "$LATEST_VERSION" ]]; then
msg_error "Failed to determine latest EMQX version" msg_error "Failed to determine latest EMQX version"
exit 1 exit 250
fi fi
msg_ok "Latest version: v$LATEST_VERSION" msg_ok "Latest version: v$LATEST_VERSION"

View File

@@ -1,8 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG # Copyright (c) 2021-2026 community-scripts ORG
# Authors: MickLesk (CanbiZ) # Authors: MickLesk (CanbiZ) | Co-Authors: remz1337
# Co-Authors: remz1337
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE # License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://frigate.video/ | Github: https://github.com/blakeblackshear/frigate # Source: https://frigate.video/ | Github: https://github.com/blakeblackshear/frigate
@@ -17,7 +16,7 @@ update_os
source /etc/os-release source /etc/os-release
if [[ "$VERSION_ID" != "12" ]]; then if [[ "$VERSION_ID" != "12" ]]; then
msg_error "Frigate requires Debian 12 (Bookworm) due to Python 3.11 dependencies" msg_error "Frigate requires Debian 12 (Bookworm) due to Python 3.11 dependencies"
exit 1 exit 238
fi fi
msg_info "Converting APT sources to DEB822 format" msg_info "Converting APT sources to DEB822 format"
@@ -85,6 +84,7 @@ $STD apt install -y \
tclsh \ tclsh \
libopenblas-dev \ libopenblas-dev \
liblapack-dev \ liblapack-dev \
libgomp1 \
make \ make \
moreutils moreutils
msg_ok "Installed Dependencies" msg_ok "Installed Dependencies"
@@ -101,9 +101,16 @@ export NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
export TOKENIZERS_PARALLELISM=true export TOKENIZERS_PARALLELISM=true
export TRANSFORMERS_NO_ADVISORY_WARNINGS=1 export TRANSFORMERS_NO_ADVISORY_WARNINGS=1
export OPENCV_FFMPEG_LOGLEVEL=8 export OPENCV_FFMPEG_LOGLEVEL=8
export PYTHONWARNINGS="ignore:::numpy.core.getlimits"
export HAILORT_LOGGER_PATH=NONE 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" msg_info "Building Nginx"
$STD bash /opt/frigate/docker/main/build_nginx.sh $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 ldconfig
msg_ok "Built libUSB" 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" msg_info "Installing Python Dependencies"
$STD pip3 install -r /opt/frigate/docker/main/requirements.txt $STD pip3 install -r /opt/frigate/docker/main/requirements.txt
msg_ok "Installed Python Dependencies" msg_ok "Installed Python Dependencies"
msg_info "Building Python Wheels (Patience)" msg_info "Building Python Wheels (Patience)"
mkdir -p /wheels 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 $STD bash /opt/frigate/docker/main/build_pysqlite3.sh
for i in {1..3}; do 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 $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 done
msg_ok "Built Python Wheels" msg_ok "Built Python Wheels"
NODE_VERSION="22" NODE_MODULE="yarn" setup_nodejs NODE_VERSION="20" setup_nodejs
msg_info "Downloading Inference Models" msg_info "Downloading Inference Models"
mkdir -p /models /openvino-model mkdir -p /models /openvino-model
@@ -183,6 +196,10 @@ $STD pip3 install -U /wheels/*.whl
ldconfig ldconfig
msg_ok "Installed HailoRT Runtime" 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" msg_info "Installing OpenVino"
$STD pip3 install -r /opt/frigate/docker/main/requirements-ov.txt $STD pip3 install -r /opt/frigate/docker/main/requirements-ov.txt
msg_ok "Installed OpenVino" msg_ok "Installed OpenVino"
@@ -209,6 +226,8 @@ $STD make version
cd /opt/frigate/web cd /opt/frigate/web
$STD npm install $STD npm install
$STD npm run build $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/ 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 sed -i '/^s6-svc -O \.$/s/^/#/' /opt/frigate/docker/main/rootfs/etc/s6-overlay/s6-rc.d/frigate/run
msg_ok "Built Frigate Application" msg_ok "Built Frigate Application"
@@ -224,6 +243,19 @@ echo "tmpfs /tmp/cache tmpfs defaults 0 0" >>/etc/fstab
cat <<EOF >/etc/frigate.env cat <<EOF >/etc/frigate.env
DEFAULT_FFMPEG_VERSION="7.0" DEFAULT_FFMPEG_VERSION="7.0"
INCLUDED_FFMPEG_VERSIONS="7.0:5.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 EOF
cat <<EOF >/config/config.yml cat <<EOF >/config/config.yml
@@ -237,7 +269,6 @@ cameras:
input_args: -re -stream_loop -1 -fflags +genpts input_args: -re -stream_loop -1 -fflags +genpts
roles: roles:
- detect - detect
- rtmp
detect: detect:
height: 1080 height: 1080
width: 1920 width: 1920
@@ -255,6 +286,7 @@ ffmpeg:
detectors: detectors:
detector01: detector01:
type: openvino type: openvino
device: AUTO
model: model:
width: 300 width: 300
height: 300 height: 300

View File

@@ -31,7 +31,7 @@ fi
if [[ -z "$KASM_URL" ]] || [[ -z "$KASM_VERSION" ]]; then if [[ -z "$KASM_URL" ]] || [[ -z "$KASM_VERSION" ]]; then
msg_error "Unable to detect latest Kasm release URL." msg_error "Unable to detect latest Kasm release URL."
exit 1 exit 250
fi fi
msg_ok "Detected Kasm Workspaces version $KASM_VERSION" msg_ok "Detected Kasm Workspaces version $KASM_VERSION"

View File

@@ -28,7 +28,7 @@ $STD apt install -y \
msg_ok "Installed Dependencies" msg_ok "Installed Dependencies"
PYTHON_VERSION="3.12" setup_uv PYTHON_VERSION="3.12" setup_uv
POSTGRES_VERSION="16" setup_postgresql PG_VERSION="16" setup_postgresql
NODE_MODULE="yarn" NODE_VERSION="24" setup_nodejs NODE_MODULE="yarn" NODE_VERSION="24" setup_nodejs
fetch_and_deploy_gh_release "mealie" "mealie-recipes/mealie" "tarball" "latest" "/opt/mealie" 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 PG_DB_NAME="mealie_db" PG_DB_USER="mealie_user" PG_DB_GRANT_SUPERUSER="true" setup_postgresql_db

View File

@@ -51,7 +51,7 @@ while true; do
attempts=$((attempts + 1)) attempts=$((attempts + 1))
if [[ "$attempts" -ge 3 ]]; then if [[ "$attempts" -ge 3 ]]; then
msg_error "Maximum attempts reached. Exiting." msg_error "Maximum attempts reached. Exiting."
exit 1 exit 254
fi fi
done done
@@ -76,11 +76,11 @@ for i in {1..60}; do
elif [[ "$STATUS" == "unhealthy" ]]; then elif [[ "$STATUS" == "unhealthy" ]]; then
msg_error "NPMplus container is unhealthy! Check logs." msg_error "NPMplus container is unhealthy! Check logs."
docker logs "$CONTAINER_ID" docker logs "$CONTAINER_ID"
exit 1 exit 150
fi fi
fi fi
sleep 2 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 done
msg_ok "Builded and started NPMplus" msg_ok "Builded and started NPMplus"

View File

@@ -78,11 +78,11 @@ if curl -fL# -C - -o "$TMP_TAR" "$OLLAMA_URL"; then
msg_ok "Installed Ollama ${RELEASE}" msg_ok "Installed Ollama ${RELEASE}"
else else
msg_error "Extraction failed archive corrupt or incomplete" msg_error "Extraction failed archive corrupt or incomplete"
exit 1 exit 251
fi fi
else else
msg_error "Download failed $OLLAMA_URL not reachable" msg_error "Download failed $OLLAMA_URL not reachable"
exit 1 exit 250
fi fi
msg_info "Creating ollama User and Group" msg_info "Creating ollama User and Group"

View File

@@ -59,7 +59,7 @@ EOF
else else
msg_error "Failed to download or verify GPG key from $KEY_URL" msg_error "Failed to download or verify GPG key from $KEY_URL"
[[ -f "$TMP_KEY_CONTENT" ]] && rm -f "$TMP_KEY_CONTENT" [[ -f "$TMP_KEY_CONTENT" ]] && rm -f "$TMP_KEY_CONTENT"
exit 1 exit 250
fi fi
rm -f "$TMP_KEY_CONTENT" rm -f "$TMP_KEY_CONTENT"

View File

@@ -34,7 +34,7 @@ for server in "${servers[@]}"; do
done done
if ((attempt >= MAX_ATTEMPTS)); then if ((attempt >= MAX_ATTEMPTS)); then
msg_error "No more attempts - aborting script!" msg_error "No more attempts - aborting script!"
exit 1 exit 254
fi fi
done done

View File

@@ -16,7 +16,7 @@ update_os
read -r -p "${TAB3}Enter PostgreSQL version (15/16/17/18): " ver read -r -p "${TAB3}Enter PostgreSQL version (15/16/17/18): " ver
[[ $ver =~ ^(15|16|17|18)$ ]] || { [[ $ver =~ ^(15|16|17|18)$ ]] || {
echo "Invalid version" echo "Invalid version"
exit 1 exit 64
} }
PG_VERSION=$ver setup_postgresql PG_VERSION=$ver setup_postgresql

134
install/powerdns-install.sh Normal file
View 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

View 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

View File

@@ -25,7 +25,7 @@ if useradd -r -m -d /opt/pulse-home -s /usr/sbin/nologin pulse; then
msg_ok "Created User" msg_ok "Created User"
else else
msg_error "User creation failed" msg_error "User creation failed"
exit 1 exit 71
fi fi
mkdir -p /etc/pulse mkdir -p /etc/pulse

View File

@@ -34,7 +34,7 @@ while true; do
[Nn]|[Nn][Oo]|"") [Nn]|[Nn][Oo]|"")
msg_error "Terms not accepted. Installation cannot proceed." msg_error "Terms not accepted. Installation cannot proceed."
msg_error "Please review the terms and run the script again if you wish to 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." 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|') RELEASE=$(echo "$DOWNLOAD_URL" | sed 's|.*/releases/\([^/]*\)/.*|\1|')
$STD curl -fsSL -o "splunk-enterprise.tgz" "$DOWNLOAD_URL" || { $STD curl -fsSL -o "splunk-enterprise.tgz" "$DOWNLOAD_URL" || {
msg_error "Failed to download Splunk Enterprise from the provided link." msg_error "Failed to download Splunk Enterprise from the provided link."
exit 1 exit 250
} }
$STD tar -xzf "splunk-enterprise.tgz" -C /opt $STD tar -xzf "splunk-enterprise.tgz" -C /opt
rm -f "splunk-enterprise.tgz" rm -f "splunk-enterprise.tgz"

View 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

View File

@@ -44,7 +44,20 @@ $STD timescaledb-tune -yes -memory "$ram_for_tsdb"MB
$STD systemctl restart postgresql $STD systemctl restart postgresql
msg_ok "Installed TimescaleDB" 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" fetch_and_deploy_gh_release "tracearr" "connorgallopo/Tracearr" "tarball" "latest" "/opt/tracearr.build"
msg_info "Building Tracearr" msg_info "Building Tracearr"
@@ -75,6 +88,7 @@ msg_info "Configuring Tracearr"
$STD useradd -r -s /bin/false -U tracearr $STD useradd -r -s /bin/false -U tracearr
$STD chown -R tracearr:tracearr /opt/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/tracearr
install -d -m 750 -o tracearr -g tracearr /data/backup
export JWT_SECRET=$(openssl rand -hex 32) export JWT_SECRET=$(openssl rand -hex 32)
export COOKIE_SECRET=$(openssl rand -hex 32) export COOKIE_SECRET=$(openssl rand -hex 32)
cat <<EOF >/data/tracearr/.env cat <<EOF >/data/tracearr/.env
@@ -89,7 +103,6 @@ JWT_SECRET=$JWT_SECRET
COOKIE_SECRET=$COOKIE_SECRET COOKIE_SECRET=$COOKIE_SECRET
APP_VERSION=$(cat /root/.tracearr) APP_VERSION=$(cat /root/.tracearr)
#CORS_ORIGIN=http://localhost:5173 #CORS_ORIGIN=http://localhost:5173
#MOBILE_BETA_MODE=true
EOF EOF
chmod 600 /data/tracearr/.env chmod 600 /data/tracearr/.env
chown -R tracearr:tracearr /data/tracearr chown -R tracearr:tracearr /data/tracearr
@@ -140,6 +153,7 @@ if [ -f \$pg_config_file ]; then
fi fi
fi fi
systemctl restart postgresql systemctl restart postgresql
sudo -u postgres psql -c "ALTER USER tracearr WITH SUPERUSER;"
EOF EOF
chmod +x /data/tracearr/prestart.sh chmod +x /data/tracearr/prestart.sh
cat <<EOF >/lib/systemd/system/tracearr.service cat <<EOF >/lib/systemd/system/tracearr.service

View File

@@ -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

View File

@@ -16,13 +16,13 @@ update_os
if [[ "${CTTYPE:-1}" != "0" ]]; then if [[ "${CTTYPE:-1}" != "0" ]]; then
msg_error "UniFi OS Server requires a privileged LXC container." msg_error "UniFi OS Server requires a privileged LXC container."
msg_error "Recreate the container with unprivileged=0." msg_error "Recreate the container with unprivileged=0."
exit 1 exit 10
fi fi
if [[ ! -e /dev/net/tun ]]; then if [[ ! -e /dev/net/tun ]]; then
msg_error "Missing /dev/net/tun in container." msg_error "Missing /dev/net/tun in container."
msg_error "Enable TUN/TAP (var_tun=yes) or add /dev/net/tun passthrough." msg_error "Enable TUN/TAP (var_tun=yes) or add /dev/net/tun passthrough."
exit 1 exit 236
fi fi
msg_info "Installing dependencies" msg_info "Installing dependencies"
@@ -48,7 +48,7 @@ TEMP_JSON="$(mktemp)"
if ! curl -fsSL "$API_URL" -o "$TEMP_JSON"; then if ! curl -fsSL "$API_URL" -o "$TEMP_JSON"; then
rm -f "$TEMP_JSON" rm -f "$TEMP_JSON"
msg_error "Failed to fetch data from Ubiquiti API" msg_error "Failed to fetch data from Ubiquiti API"
exit 1 exit 250
fi fi
LATEST=$(jq -r ' LATEST=$(jq -r '
._embedded.firmware ._embedded.firmware
@@ -62,7 +62,7 @@ UOS_URL=$(echo "$LATEST" | jq -r '._links.data.href')
rm -f "$TEMP_JSON" rm -f "$TEMP_JSON"
if [[ -z "$UOS_URL" || -z "$UOS_VERSION" || "$UOS_URL" == "null" ]]; then if [[ -z "$UOS_URL" || -z "$UOS_VERSION" || "$UOS_URL" == "null" ]]; then
msg_error "Failed to parse UniFi OS Server version or download URL" msg_error "Failed to parse UniFi OS Server version or download URL"
exit 1 exit 250
fi fi
msg_ok "Found UniFi OS Server ${UOS_VERSION}" msg_ok "Found UniFi OS Server ${UOS_VERSION}"

View File

@@ -31,7 +31,7 @@ if gpg --verify /tmp/zerotier-install.sh >/dev/null 2>&1; then
$STD bash /tmp/zerotier-install.sh $STD bash /tmp/zerotier-install.sh
else else
msg_warn "Could not verify signature of Zerotier-One install script. Exiting..." msg_warn "Could not verify signature of Zerotier-One install script. Exiting..."
exit 1 exit 250
fi fi
msg_ok "Setup Zerotier-One" msg_ok "Setup Zerotier-One"

View File

@@ -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 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 1>&2 -e "\n${CROSS}${RD} No Network After $RETRY_NUM Tries${CL}"
echo -e "${NETWORK}Check Network Settings" echo -e "${NETWORK}Check Network Settings"
exit 1 exit 121
fi fi
msg_ok "Set up Container OS" 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}" 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}" echo -e "${INFO}${RD}Expect Issues Without Internet${CL}"
else else
echo -e "${NETWORK}Check Network Settings" echo -e "${NETWORK}Check Network Settings"
exit 1 exit 122
fi fi
fi fi
RESOLVEDIP=$(getent hosts github.com | awk '{ print $1 }') RESOLVEDIP=$(getent hosts github.com | awk '{ print $1 }')
@@ -119,12 +119,12 @@ update_os() {
local tools_content local tools_content
tools_content=$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func) || { tools_content=$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func) || {
msg_error "Failed to download tools.func" msg_error "Failed to download tools.func"
exit 6 exit 115
} }
source /dev/stdin <<<"$tools_content" source /dev/stdin <<<"$tools_content"
if ! declare -f fetch_and_deploy_gh_release >/dev/null 2>&1; then if ! declare -f fetch_and_deploy_gh_release >/dev/null 2>&1; then
msg_error "tools.func loaded but incomplete — missing expected functions" msg_error "tools.func loaded but incomplete — missing expected functions"
exit 6 exit 115
fi fi
msg_ok "Updated Container OS" msg_ok "Updated Container OS"
post_progress_to_api post_progress_to_api

View File

@@ -124,6 +124,7 @@ detect_repo_source
# * Generic/Shell errors (1-3, 10, 124-132, 134, 137, 139, 141, 143-146) # * 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) # * 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) # * Package manager errors (APT, DPKG: 100-102, 255)
# * Script Validation & Setup (103-123)
# * BSD sysexits (64-78) # * BSD sysexits (64-78)
# * Systemd/Service errors (150-154) # * Systemd/Service errors (150-154)
# * Python/pip/uv errors (160-162) # * Python/pip/uv errors (160-162)
@@ -131,7 +132,9 @@ detect_repo_source
# * MySQL/MariaDB errors (180-183) # * MySQL/MariaDB errors (180-183)
# * MongoDB errors (190-193) # * MongoDB errors (190-193)
# * Proxmox custom codes (200-231) # * Proxmox custom codes (200-231)
# * Tools & Addon Scripts (232-238)
# * Node.js/npm errors (239, 243, 245-249) # * Node.js/npm errors (239, 243, 245-249)
# * Application Install/Update errors (250-254)
# - Returns description string for given exit code # - Returns description string for given exit code
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
explain_exit_code() { explain_exit_code() {
@@ -189,6 +192,29 @@ explain_exit_code() {
101) echo "APT: Configuration error (bad sources.list, malformed config)" ;; 101) echo "APT: Configuration error (bad sources.list, malformed config)" ;;
102) echo "APT: Lock held by another process (dpkg/apt still running)" ;; 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) --- # --- BSD sysexits.h (64-78) ---
64) echo "Usage error (wrong arguments)" ;; 64) echo "Usage error (wrong arguments)" ;;
65) echo "Data format error (bad input data)" ;; 65) echo "Data format error (bad input data)" ;;
@@ -276,8 +302,18 @@ explain_exit_code() {
223) echo "Proxmox: Template not available after download" ;; 223) echo "Proxmox: Template not available after download" ;;
224) echo "Proxmox: PBS storage is for backups only" ;; 224) echo "Proxmox: PBS storage is for backups only" ;;
225) echo "Proxmox: No template available for OS/Version" ;; 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" ;; 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) --- # --- Node.js / npm / pnpm / yarn (239-249) ---
239) echo "npm/Node.js: Unexpected runtime error or dependency failure" ;; 239) echo "npm/Node.js: Unexpected runtime error or dependency failure" ;;
243) echo "Node.js: Out of memory (JavaScript heap out of memory)" ;; 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" ;; 248) echo "Node.js: Invalid C++ addon / N-API failure" ;;
249) echo "npm/pnpm/yarn: Unknown fatal error" ;; 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 --- # --- DPKG ---
255) echo "DPKG: Fatal internal error" ;; 255) echo "DPKG: Fatal internal error" ;;
@@ -303,18 +346,20 @@ explain_exit_code() {
# - Handles backslashes, quotes, newlines, tabs, and carriage returns # - Handles backslashes, quotes, newlines, tabs, and carriage returns
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
json_escape() { json_escape() {
local s="$1" # Escape a string for safe JSON embedding using awk (handles any input size).
# Strip ANSI escape sequences (color codes etc.) # Pipeline: strip ANSI → remove control chars → escape \ " TAB → join lines with \n
s=$(printf '%s' "$s" | sed 's/\x1b\[[0-9;]*[a-zA-Z]//g') printf '%s' "$1" \
s=${s//\\/\\\\} | sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' \
s=${s//"/\\"/} | tr -d '\000-\010\013\014\016-\037\177\r' \
s=${s//$'\n'/\\n} | awk '
s=${s//$'\r'/} BEGIN { ORS = "" }
s=${s//$'\t'/\\t} {
# Remove any remaining control characters (0x00-0x1F except those already handled) gsub(/\\/, "\\\\") # backslash → \\
# Also remove DEL (0x7F) and invalid high bytes that break JSON parsers gsub(/"/, "\\\"") # double quote → \"
s=$(printf '%s' "$s" | tr -d '\000-\010\013\014\016-\037\177') gsub(/\t/, "\\t") # tab → \t
printf '%s' "$s" if (NR > 1) printf "\\n"
printf "%s", $0
}'
} }
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@@ -350,6 +395,11 @@ get_error_text() {
logfile="$BUILD_LOG" logfile="$BUILD_LOG"
fi 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 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' tail -n 20 "$logfile" 2>/dev/null | sed 's/\r$//' | sed 's/\x1b\[[0-9;]*[a-zA-Z]//g'
fi fi
@@ -395,6 +445,13 @@ get_full_log() {
fi fi
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 if [[ -n "$logfile" && -s "$logfile" ]]; then
# Strip ANSI codes, carriage returns, and anonymize IP addresses (GDPR) # Strip ANSI codes, carriage returns, and anonymize IP addresses (GDPR)
sed 's/\r$//' "$logfile" 2>/dev/null | 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] Sending to: $TELEMETRY_URL" >&2
[[ "${DEV_MODE:-}" == "true" ]] && echo "[DEBUG] Payload: $JSON_PAYLOAD" >&2 [[ "${DEV_MODE:-}" == "true" ]] && echo "[DEBUG] Payload: $JSON_PAYLOAD" >&2
# Fire-and-forget: never block, never fail # Send initial "installing" record with retry.
local http_code # This record MUST exist for all subsequent updates to succeed.
if [[ "${DEV_MODE:-}" == "true" ]]; then local http_code="" attempt
http_code=$(curl -sS -w "%{http_code}" -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \ for attempt in 1 2 3; do
-H "Content-Type: application/json" \ if [[ "${DEV_MODE:-}" == "true" ]]; then
-d "$JSON_PAYLOAD" -o /dev/stderr 2>&1) || true http_code=$(curl -sS -w "%{http_code}" -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
echo "[DEBUG] HTTP response code: $http_code" >&2 -H "Content-Type: application/json" \
else -d "$JSON_PAYLOAD" -o /dev/stderr 2>&1) || http_code="000"
curl -fsS -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \ echo "[DEBUG] post_to_api attempt $attempt HTTP=$http_code" >&2
-H "Content-Type: application/json" \ else
-d "$JSON_PAYLOAD" &>/dev/null || true http_code=$(curl -sS -w "%{http_code}" -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
fi -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 POST_TO_API_DONE=true
} }
@@ -734,10 +796,15 @@ post_to_api_vm() {
EOF EOF
) )
# Fire-and-forget: never block, never fail # Send initial "installing" record with retry (must succeed for updates to work)
curl -fsS -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \ local http_code="" attempt
-H "Content-Type: application/json" \ for attempt in 1 2 3; do
-d "$JSON_PAYLOAD" &>/dev/null || true 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 POST_TO_API_DONE=true
} }
@@ -833,7 +900,7 @@ post_update_to_api() {
esac esac
# For failed/unknown status, resolve exit code and error description # 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 [[ "$pb_status" == "failed" ]] || [[ "$pb_status" == "unknown" ]]; then
if [[ "$raw_exit_code" =~ ^[0-9]+$ ]]; then if [[ "$raw_exit_code" =~ ^[0-9]+$ ]]; then
exit_code="$raw_exit_code" exit_code="$raw_exit_code"
@@ -853,6 +920,18 @@ post_update_to_api() {
short_error=$(json_escape "$(explain_exit_code "$exit_code")") short_error=$(json_escape "$(explain_exit_code "$exit_code")")
error_category=$(categorize_error "$exit_code") error_category=$(categorize_error "$exit_code")
[[ -z "$error" ]] && error="Unknown error" [[ -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 fi
# Calculate duration if timer was started # Calculate duration if timer was started
@@ -869,6 +948,11 @@ post_update_to_api() {
local http_code="" 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) ── # ── Attempt 1: Full payload with complete error text (includes full log) ──
local JSON_PAYLOAD local JSON_PAYLOAD
JSON_PAYLOAD=$( JSON_PAYLOAD=$(
@@ -880,7 +964,7 @@ post_update_to_api() {
"nsapp": "${NSAPP:-unknown}", "nsapp": "${NSAPP:-unknown}",
"status": "${pb_status}", "status": "${pb_status}",
"ct_type": ${CT_TYPE:-1}, "ct_type": ${CT_TYPE:-1},
"disk_size": ${DISK_SIZE:-0}, "disk_size": ${DISK_SIZE_API},
"core_count": ${CORE_COUNT:-0}, "core_count": ${CORE_COUNT:-0},
"ram_size": ${RAM_SIZE:-0}, "ram_size": ${RAM_SIZE:-0},
"os_type": "${var_os:-}", "os_type": "${var_os:-}",
@@ -911,7 +995,7 @@ EOF
return 0 return 0
fi fi
# ── Attempt 2: Short error text (no full log) ── # ── Attempt 2: Medium error text (truncated log ≤16KB instead of full 120KB) ──
sleep 1 sleep 1
local RETRY_PAYLOAD local RETRY_PAYLOAD
RETRY_PAYLOAD=$( RETRY_PAYLOAD=$(
@@ -923,7 +1007,7 @@ EOF
"nsapp": "${NSAPP:-unknown}", "nsapp": "${NSAPP:-unknown}",
"status": "${pb_status}", "status": "${pb_status}",
"ct_type": ${CT_TYPE:-1}, "ct_type": ${CT_TYPE:-1},
"disk_size": ${DISK_SIZE:-0}, "disk_size": ${DISK_SIZE_API},
"core_count": ${CORE_COUNT:-0}, "core_count": ${CORE_COUNT:-0},
"ram_size": ${RAM_SIZE:-0}, "ram_size": ${RAM_SIZE:-0},
"os_type": "${var_os:-}", "os_type": "${var_os:-}",
@@ -931,7 +1015,7 @@ EOF
"pve_version": "${pve_version}", "pve_version": "${pve_version}",
"method": "${METHOD:-default}", "method": "${METHOD:-default}",
"exit_code": ${exit_code}, "exit_code": ${exit_code},
"error": "${short_error}", "error": "${medium_error}",
"error_category": "${error_category}", "error_category": "${error_category}",
"install_duration": ${duration}, "install_duration": ${duration},
"cpu_vendor": "${cpu_vendor}", "cpu_vendor": "${cpu_vendor}",
@@ -954,7 +1038,7 @@ EOF
return 0 return 0
fi fi
# ── Attempt 3: Minimal payload (bare minimum to set status) ── # ── Attempt 3: Minimal payload with medium error (bare minimum to set status) ──
sleep 2 sleep 2
local MINIMAL_PAYLOAD local MINIMAL_PAYLOAD
MINIMAL_PAYLOAD=$( MINIMAL_PAYLOAD=$(
@@ -966,7 +1050,7 @@ EOF
"nsapp": "${NSAPP:-unknown}", "nsapp": "${NSAPP:-unknown}",
"status": "${pb_status}", "status": "${pb_status}",
"exit_code": ${exit_code}, "exit_code": ${exit_code},
"error": "${short_error}", "error": "${medium_error}",
"error_category": "${error_category}", "error_category": "${error_category}",
"install_duration": ${duration} "install_duration": ${duration}
} }

View File

@@ -119,7 +119,7 @@ maxkeys_check() {
# Exit if kernel parameters are unavailable # Exit if kernel parameters are unavailable
if [[ "$per_user_maxkeys" -eq 0 || "$per_user_maxbytes" -eq 0 ]]; then if [[ "$per_user_maxkeys" -eq 0 || "$per_user_maxbytes" -eq 0 ]]; then
msg_error "Unable to read kernel key parameters. Ensure proper permissions." msg_error "Unable to read kernel key parameters. Ensure proper permissions."
exit 1 exit 107
fi fi
# Fetch key usage for user ID 100000 (typical for containers) # Fetch key usage for user ID 100000 (typical for containers)
@@ -148,7 +148,7 @@ maxkeys_check() {
# Provide next steps if issues are detected # Provide next steps if issues are detected
if [[ "$failure" -eq 1 ]]; then if [[ "$failure" -eq 1 ]]; then
msg_error "Kernel key limits exceeded - see suggestions above" msg_error "Kernel key limits exceeded - see suggestions above"
exit 1 exit 108
fi fi
# Silent success - only show errors if they exist # Silent success - only show errors if they exist
@@ -355,7 +355,7 @@ get_valid_container_id() {
attempts=$((attempts + 1)) attempts=$((attempts + 1))
if [[ $attempts -ge $max_attempts ]]; then if [[ $attempts -ge $max_attempts ]]; then
msg_error "Could not find available container ID after $max_attempts attempts" msg_error "Could not find available container ID after $max_attempts attempts"
exit 1 exit 109
fi fi
done done
@@ -2035,7 +2035,7 @@ advanced_settings() {
else else
whiptail --msgbox "Default bridge 'vmbr0' not found!\n\nPlease configure a network bridge in Proxmox first." 10 58 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" msg_error "Default bridge 'vmbr0' not found"
exit 1 exit 116
fi fi
else else
if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \ if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
@@ -3023,7 +3023,7 @@ install_script() {
3 | mydefaults | MYDEFAULTS | userdefaults | USERDEFAULTS) 3 | mydefaults | MYDEFAULTS | userdefaults | USERDEFAULTS)
default_var_settings || { default_var_settings || {
msg_error "Failed to apply default.vars" msg_error "Failed to apply default.vars"
exit 1 exit 110
} }
defaults_target="/usr/local/community-scripts/default.vars" defaults_target="/usr/local/community-scripts/default.vars"
break break
@@ -3040,7 +3040,7 @@ install_script() {
break break
else else
msg_error "No App Defaults available for ${APP}" msg_error "No App Defaults available for ${APP}"
exit 1 exit 111
fi fi
;; ;;
"$SETTINGS_OPTION" | settings | SETTINGS) "$SETTINGS_OPTION" | settings | SETTINGS)
@@ -3051,7 +3051,7 @@ install_script() {
;; ;;
*) *)
msg_error "Invalid option: $CHOICE" msg_error "Invalid option: $CHOICE"
exit 1 exit 112
;; ;;
esac esac
done done
@@ -3135,7 +3135,7 @@ check_container_resources() {
read -r prompt </dev/tty read -r prompt </dev/tty
if [[ ! ${prompt,,} =~ ^(yes)$ ]]; then if [[ ! ${prompt,,} =~ ^(yes)$ ]]; then
msg_error "Aborted: under-provisioned LXC (${current_cpu} CPU/${current_ram}MB RAM < ${var_cpu} CPU/${var_ram}MB RAM)" 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 fi
else else
echo -e "" echo -e ""
@@ -3158,7 +3158,7 @@ check_container_storage() {
read -r prompt </dev/tty read -r prompt </dev/tty
if [[ ! ${prompt,,} =~ ^(y|yes)$ ]]; then if [[ ! ${prompt,,} =~ ^(y|yes)$ ]]; then
msg_error "Aborted: storage too low (${usage}% used)" msg_error "Aborted: storage too low (${usage}% used)"
exit 1 exit 114
fi fi
fi fi
} }
@@ -3446,7 +3446,7 @@ start() {
3) 3)
clear clear
exit_script exit_script
exit exit 0
;; ;;
esac esac
ensure_profile_loaded ensure_profile_loaded
@@ -3556,7 +3556,7 @@ build_container() {
export FUNCTIONS_FILE_PATH="$(curl -fsSL "$_func_url")" export FUNCTIONS_FILE_PATH="$(curl -fsSL "$_func_url")"
if [[ -z "$FUNCTIONS_FILE_PATH" || ${#FUNCTIONS_FILE_PATH} -lt 100 ]]; then if [[ -z "$FUNCTIONS_FILE_PATH" || ${#FUNCTIONS_FILE_PATH} -lt 100 ]]; then
msg_error "Failed to download install functions from: $_func_url" msg_error "Failed to download install functions from: $_func_url"
exit 1 exit 115
fi fi
# Core exports for install.func # Core exports for install.func
@@ -3938,7 +3938,7 @@ EOF
local ct_status local ct_status
ct_status=$(pct status "$CTID" 2>/dev/null || echo "unknown") ct_status=$(pct status "$CTID" 2>/dev/null || echo "unknown")
msg_error "LXC Container did not reach running state (status: ${ct_status})" msg_error "LXC Container did not reach running state (status: ${ct_status})"
exit 1 exit 117
fi fi
done done
@@ -3967,7 +3967,7 @@ EOF
echo " • Verify static IP configuration (if using static IP)" echo " • Verify static IP configuration (if using static IP)"
echo " • Check Proxmox firewall rules" echo " • Check Proxmox firewall rules"
echo " • If using Tailscale: Disable MagicDNS temporarily" echo " • If using Tailscale: Disable MagicDNS temporarily"
exit 1 exit 118
fi fi
# Verify basic connectivity (ping test) # Verify basic connectivity (ping test)
@@ -4094,21 +4094,8 @@ EOF'
# that sends "configuring" status AFTER the host already reported "failed" # that sends "configuring" status AFTER the host already reported "failed"
export CONTAINER_INSTALLING=true export CONTAINER_INSTALLING=true
# Capture lxc-attach terminal output to host-side log via tee. lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/install/${var_install}.sh)"
# This is the ONLY reliable way to get install output when: local lxc_exit=$?
# - 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
unset CONTAINER_INSTALLING unset CONTAINER_INSTALLING
@@ -4170,19 +4157,9 @@ EOF'
build_log_copied=true build_log_copied=true
fi 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 temp_install_log="/tmp/.install-temp-${SESSION_ID}.log"
local container_log_ok=false if pct pull "$CTID" "/root/.install-${SESSION_ID}.log" "$temp_install_log" 2>/dev/null; then
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
{ {
echo "================================================================================" echo "================================================================================"
echo "PHASE 2: APPLICATION INSTALLATION (Container)" echo "PHASE 2: APPLICATION INSTALLATION (Container)"
@@ -4190,24 +4167,8 @@ EOF'
cat "$temp_install_log" cat "$temp_install_log"
echo "" echo ""
} >>"$combined_log" } >>"$combined_log"
rm -f "$temp_install_log"
install_log_copied=true 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 # Point INSTALL_LOG to combined log so get_full_log() finds it
INSTALL_LOG="$combined_log" INSTALL_LOG="$combined_log"
fi fi
@@ -4261,6 +4222,7 @@ EOF'
local is_network_issue=false local is_network_issue=false
local is_apt_issue=false local is_apt_issue=false
local is_cmd_not_found=false local is_cmd_not_found=false
local is_disk_full=false
local error_explanation="" local error_explanation=""
if declare -f explain_exit_code >/dev/null 2>&1; then if declare -f explain_exit_code >/dev/null 2>&1; then
error_explanation="$(explain_exit_code "$install_exit_code")" error_explanation="$(explain_exit_code "$install_exit_code")"
@@ -4281,6 +4243,14 @@ EOF'
;; ;;
esac 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 # Command not found detection
if [[ $install_exit_code -eq 127 ]]; then if [[ $install_exit_code -eq 127 ]]; then
is_cmd_not_found=true 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 if grep -qiE ': command not found|No such file or directory.*/s?bin/' "$combined_log"; then
is_cmd_not_found=true is_cmd_not_found=true
fi fi
if grep -qiE 'ENOSPC|no space left on device|Disk quota exceeded|errno -28' "$combined_log"; then
is_disk_full=true
fi
fi fi
# Show error explanation if available # Show error explanation if available
@@ -4338,6 +4311,12 @@ EOF'
echo "" echo ""
fi 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 if [[ "$is_cmd_not_found" == true ]]; then
local missing_cmd="" local missing_cmd=""
if [[ -f "$combined_log" ]]; then if [[ -f "$combined_log" ]]; then
@@ -4357,7 +4336,7 @@ EOF'
echo -e " ${GN}3)${CL} Retry with verbose mode (full rebuild)" echo -e " ${GN}3)${CL} Retry with verbose mode (full rebuild)"
local next_option=4 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 [[ "$is_apt_issue" == true ]]; then
if [[ "$var_os" == "alpine" ]]; then if [[ "$var_os" == "alpine" ]]; then
@@ -4382,6 +4361,18 @@ EOF'
fi fi
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 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)" echo -e " ${GN}${next_option})${CL} Retry with DNS override in LXC (8.8.8.8 / 1.1.1.1)"
DNS_OPTION=$next_option DNS_OPTION=$next_option
@@ -4393,10 +4384,8 @@ EOF'
echo "" echo ""
echo -en "${YW}Select option [1-${max_option}] (default: 1, auto-remove in 60s): ${CL}" 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) local response=""
stty sane 2>/dev/null || true if read -t 60 -r response; then
if read -t 60 -r response </dev/tty; then
case "${response:-1}" in case "${response:-1}" in
1) 1)
# Remove container # Remove container
@@ -4483,10 +4472,8 @@ EOF'
# Re-run install script in existing container (don't destroy/recreate) # Re-run install script in existing container (don't destroy/recreate)
set +Eeuo pipefail set +Eeuo pipefail
trap - ERR 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)"
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=$?
local apt_retry_exit=${PIPESTATUS[0]}
stty sane 2>/dev/null || true
set -Eeuo pipefail set -Eeuo pipefail
trap 'error_handler' ERR trap 'error_handler' ERR
@@ -4546,6 +4533,35 @@ EOF'
return $? return $?
fi 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 if [[ -n "${DNS_OPTION}" && "${response}" == "${DNS_OPTION}" ]]; then
# Retry with DNS override in LXC # Retry with DNS override in LXC
handled=true handled=true
@@ -4596,9 +4612,6 @@ EOF'
exit $install_exit_code exit $install_exit_code
fi 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 # Re-enable error handling after successful install or recovery menu completion
set -Eeuo pipefail set -Eeuo pipefail
trap 'error_handler' ERR trap 'error_handler' ERR
@@ -5014,11 +5027,11 @@ create_lxc_container() {
# Storage capability check # Storage capability check
check_storage_support "rootdir" || { check_storage_support "rootdir" || {
msg_error "No valid storage found for 'rootdir' [Container]" msg_error "No valid storage found for 'rootdir' [Container]"
exit 1 exit 119
} }
check_storage_support "vztmpl" || { check_storage_support "vztmpl" || {
msg_error "No valid storage found for 'vztmpl' [Template]" msg_error "No valid storage found for 'vztmpl' [Template]"
exit 1 exit 120
} }
# Template storage selection # Template storage selection
@@ -5296,7 +5309,7 @@ create_lxc_container() {
} }
else else
msg_custom "🚫" "${YW}" "Installation cancelled" msg_custom "🚫" "${YW}" "Installation cancelled"
exit 1 exit 0
fi fi
else else
msg_error "No ${PCT_OSTYPE} templates available" msg_error "No ${PCT_OSTYPE} templates available"

View File

@@ -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." msg_error "Your default shell is currently not set to Bash. To use these scripts, please switch to the Bash shell."
echo -e "\nExiting..." echo -e "\nExiting..."
sleep 2 sleep 2
exit 1 exit 103
fi fi
} }
@@ -293,7 +293,7 @@ root_check() {
msg_error "Please run this script as root." msg_error "Please run this script as root."
echo -e "\nExiting..." echo -e "\nExiting..."
sleep 2 sleep 2
exit 1 exit 104
fi fi
} }
@@ -314,7 +314,7 @@ pve_check() {
if ((MINOR < 0 || MINOR > 9)); then if ((MINOR < 0 || MINOR > 9)); then
msg_error "This version of Proxmox VE is not supported." msg_error "This version of Proxmox VE is not supported."
msg_error "Supported: Proxmox VE version 8.0 8.9" msg_error "Supported: Proxmox VE version 8.0 8.9"
exit 1 exit 105
fi fi
return 0 return 0
fi fi
@@ -325,7 +325,7 @@ pve_check() {
if ((MINOR < 0 || MINOR > 1)); then if ((MINOR < 0 || MINOR > 1)); then
msg_error "This version of Proxmox VE is not yet supported." msg_error "This version of Proxmox VE is not yet supported."
msg_error "Supported: Proxmox VE version 9.0 9.1" msg_error "Supported: Proxmox VE version 9.0 9.1"
exit 1 exit 105
fi fi
return 0 return 0
fi fi
@@ -333,7 +333,7 @@ pve_check() {
# All other unsupported versions # All other unsupported versions
msg_error "This version of Proxmox VE is not supported." 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" 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_error "This script will not work with PiMox (ARM architecture detected)."
msg_warn "Visit https://github.com/asylumexp/Proxmox for ARM64 support." msg_warn "Visit https://github.com/asylumexp/Proxmox for ARM64 support."
sleep 2 sleep 2
exit 1 exit 106
fi fi
} }

View File

@@ -94,6 +94,29 @@ if ! declare -f explain_exit_code &>/dev/null; then
100) echo "APT: Package manager error (broken packages / dependency problems)" ;; 100) echo "APT: Package manager error (broken packages / dependency problems)" ;;
101) echo "APT: Configuration error (bad sources.list, malformed config)" ;; 101) echo "APT: Configuration error (bad sources.list, malformed config)" ;;
102) echo "APT: Lock held by another process (dpkg/apt still running)" ;; 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)" ;; 124) echo "Command timed out (timeout command)" ;;
125) echo "Command failed to start (Docker daemon or execution error)" ;; 125) echo "Command failed to start (Docker daemon or execution error)" ;;
126) echo "Command invoked cannot execute (permission problem?)" ;; 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" ;; 224) echo "Proxmox: PBS storage is for backups only" ;;
225) echo "Proxmox: No template available for OS/Version" ;; 225) echo "Proxmox: No template available for OS/Version" ;;
231) echo "Proxmox: LXC stack upgrade 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" ;;
239) echo "npm/Node.js: Unexpected runtime error or dependency failure" ;; 239) echo "npm/Node.js: Unexpected runtime error or dependency failure" ;;
243) echo "Node.js: Out of memory (JavaScript heap out of memory)" ;; 243) echo "Node.js: Out of memory (JavaScript heap out of memory)" ;;
245) echo "Node.js: Invalid command-line option" ;; 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" ;; 247) echo "Node.js: Fatal internal error" ;;
248) echo "Node.js: Invalid C++ addon / N-API failure" ;; 248) echo "Node.js: Invalid C++ addon / N-API failure" ;;
249) echo "npm/pnpm/yarn: Unknown fatal error" ;; 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" ;; 255) echo "DPKG: Fatal internal error" ;;
*) echo "Unknown error" ;; *) echo "Unknown error" ;;
esac esac
@@ -286,7 +327,9 @@ error_handler() {
echo -en "${YW}Remove broken container ${CTID}? (Y/n) [auto-remove in 60s]: ${CL}" echo -en "${YW}Remove broken container ${CTID}? (Y/n) [auto-remove in 60s]: ${CL}"
fi 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 if [[ -z "$response" || "$response" =~ ^[Yy]$ ]]; then
echo "" echo ""
if declare -f msg_info >/dev/null 2>&1; then if declare -f msg_info >/dev/null 2>&1; then
@@ -365,10 +408,29 @@ _send_abort_telemetry() {
[[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0 [[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0
[[ -z "${RANDOM_UUID:-}" ]] && 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 error_text=""
local logfile=""
if [[ -n "${INSTALL_LOG:-}" && -s "${INSTALL_LOG}" ]]; then 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 fi
# Calculate duration if start time is available # Calculate duration if start time is available
@@ -377,10 +439,17 @@ _send_abort_telemetry() {
duration=$(($(date +%s) - DIAGNOSTICS_START_TIME)) duration=$(($(date +%s) - DIAGNOSTICS_START_TIME))
fi 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 # Build JSON payload with error context
local payload 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}" 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_text" ]] && payload="${payload},\"error\":\"${error_text}\""
[[ -n "$error_category" ]] && payload="${payload},\"error_category\":\"${error_category}\""
[[ -n "$duration" ]] && payload="${payload},\"duration\":${duration}" [[ -n "$duration" ]] && payload="${payload},\"duration\":${duration}"
payload="${payload}}" payload="${payload}}"

View File

@@ -126,7 +126,7 @@ setting_up_container() {
if [ "$(hostname -I)" = "" ]; then if [ "$(hostname -I)" = "" ]; then
echo 1>&2 -e "\n${CROSS}${RD} No Network After $RETRY_NUM Tries${CL}" echo 1>&2 -e "\n${CROSS}${RD} No Network After $RETRY_NUM Tries${CL}"
echo -e "${NETWORK}Check Network Settings" echo -e "${NETWORK}Check Network Settings"
exit 1 exit 121
fi fi
rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED
systemctl disable -q --now systemd-networkd-wait-online.service systemctl disable -q --now systemd-networkd-wait-online.service
@@ -177,7 +177,7 @@ network_check() {
echo -e "${INFO}${RD}Expect Issues Without Internet${CL}" echo -e "${INFO}${RD}Expect Issues Without Internet${CL}"
else else
echo -e "${NETWORK}Check Network Settings" echo -e "${NETWORK}Check Network Settings"
exit 1 exit 122
fi fi
fi fi
@@ -242,12 +242,12 @@ EOF
local tools_content local tools_content
tools_content=$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func) || { tools_content=$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func) || {
msg_error "Failed to download tools.func" msg_error "Failed to download tools.func"
exit 6 exit 115
} }
source /dev/stdin <<<"$tools_content" source /dev/stdin <<<"$tools_content"
if ! declare -f fetch_and_deploy_gh_release >/dev/null 2>&1; then if ! declare -f fetch_and_deploy_gh_release >/dev/null 2>&1; then
msg_error "tools.func loaded but incomplete — missing expected functions" msg_error "tools.func loaded but incomplete — missing expected functions"
exit 6 exit 115
fi fi
} }

View File

@@ -934,7 +934,11 @@ upgrade_package() {
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Repository availability check with caching # 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() { verify_repo_available() {
local repo_url="$1" local repo_url="$1"
@@ -1732,6 +1736,13 @@ setup_deb822_repo() {
rm -f "$tmp_gpg" rm -f "$tmp_gpg"
return 1 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 fi
rm -f "$tmp_gpg" rm -f "$tmp_gpg"
chmod 644 "/etc/apt/keyrings/${name}.gpg" chmod 644 "/etc/apt/keyrings/${name}.gpg"
@@ -5142,7 +5153,7 @@ current_ip="$(get_current_ip)"
if [[ -z "$current_ip" ]]; then if [[ -z "$current_ip" ]]; then
echo "[ERROR] Could not detect local IP" >&2 echo "[ERROR] Could not detect local IP" >&2
exit 1 exit 123
fi fi
if [[ -f "$IP_FILE" ]]; then if [[ -f "$IP_FILE" ]]; then
@@ -5643,20 +5654,20 @@ function setup_mongodb() {
# - Handles Debian Trixie libaio1t64 transition # - Handles Debian Trixie libaio1t64 transition
# #
# Variables: # Variables:
# USE_MYSQL_REPO - Set to "true" to use official MySQL repository # USE_MYSQL_REPO - Use official MySQL repository (default: true)
# (default: false, uses distro packages) # Set to "false" to use distro packages instead
# MYSQL_VERSION - MySQL version to install when using official repo # MYSQL_VERSION - MySQL version to install when using official repo
# (e.g. 8.0, 8.4) (default: 8.0) # (e.g. 8.0, 8.4) (default: 8.0)
# #
# Examples: # Examples:
# setup_mysql # Uses distro package (recommended) # setup_mysql # Uses official MySQL repo, 8.0
# USE_MYSQL_REPO=true setup_mysql # Uses official MySQL repo # MYSQL_VERSION="8.4" setup_mysql # Specific version from MySQL repo
# USE_MYSQL_REPO=true MYSQL_VERSION="8.4" setup_mysql # Specific version # USE_MYSQL_REPO=false setup_mysql # Uses distro package instead
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
function setup_mysql() { function setup_mysql() {
local MYSQL_VERSION="${MYSQL_VERSION:-8.0}" 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 local DISTRO_ID DISTRO_CODENAME
DISTRO_ID=$(awk -F= '/^ID=/{print $2}' /etc/os-release | tr -d '"') DISTRO_ID=$(awk -F= '/^ID=/{print $2}' /etc/os-release | tr -d '"')
DISTRO_CODENAME=$(awk -F= '/^VERSION_CODENAME=/{print $2}' /etc/os-release) DISTRO_CODENAME=$(awk -F= '/^VERSION_CODENAME=/{print $2}' /etc/os-release)
@@ -6357,21 +6368,21 @@ EOF
# - Restores dumped data post-upgrade # - Restores dumped data post-upgrade
# #
# Variables: # Variables:
# USE_PGDG_REPO - Set to "true" to use official PGDG repository # USE_PGDG_REPO - Use official PGDG repository (default: true)
# (default: false, uses distro packages) # Set to "false" to use distro packages instead
# PG_VERSION - Major PostgreSQL version (e.g. 15, 16) (default: 16) # PG_VERSION - Major PostgreSQL version (e.g. 15, 16) (default: 16)
# PG_MODULES - Comma-separated list of modules (e.g. "postgis,contrib") # PG_MODULES - Comma-separated list of modules (e.g. "postgis,contrib")
# #
# Examples: # Examples:
# setup_postgresql # Uses distro package (recommended) # setup_postgresql # Uses PGDG repo, PG 16
# USE_PGDG_REPO=true setup_postgresql # Uses official PGDG repo # PG_VERSION="17" setup_postgresql # Specific version from PGDG
# USE_PGDG_REPO=true PG_VERSION="17" setup_postgresql # Specific version from PGDG # USE_PGDG_REPO=false setup_postgresql # Uses distro package instead
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
function setup_postgresql() { function setup_postgresql() {
local PG_VERSION="${PG_VERSION:-16}" local PG_VERSION="${PG_VERSION:-16}"
local PG_MODULES="${PG_MODULES:-}" 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 local DISTRO_ID DISTRO_CODENAME
DISTRO_ID=$(awk -F= '/^ID=/{print $2}' /etc/os-release | tr -d '"') DISTRO_ID=$(awk -F= '/^ID=/{print $2}' /etc/os-release | tr -d '"')
DISTRO_CODENAME=$(awk -F= '/^VERSION_CODENAME=/{print $2}' /etc/os-release) DISTRO_CODENAME=$(awk -F= '/^VERSION_CODENAME=/{print $2}' /etc/os-release)

View File

@@ -244,7 +244,7 @@ curl_handler() {
if [[ -z "$url" ]]; then if [[ -z "$url" ]]; then
msg_error "no valid url or option entered for curl_handler" msg_error "no valid url or option entered for curl_handler"
exit 1 exit 64
fi fi
$STD msg_info "Fetching: $url" $STD msg_info "Fetching: $url"
@@ -273,7 +273,7 @@ curl_handler() {
rm -f /tmp/curl_error.log rm -f /tmp/curl_error.log
fi fi
__curl_err_handler "$exit_code" "$url" "$curl_stderr" __curl_err_handler "$exit_code" "$url" "$curl_stderr"
exit 1 # hard exit if exit_code is not 0 exit "$exit_code"
fi fi
$STD printf "\r\033[K${INFO}${YW}Retry $attempt/$max_retries in ${delay}s...${CL}" >&2 $STD printf "\r\033[K${INFO}${YW}Retry $attempt/$max_retries in ${delay}s...${CL}" >&2
@@ -316,7 +316,7 @@ __curl_err_handler() {
esac esac
[[ -n "$curl_msg" ]] && printf "%s\n" "$curl_msg" >&2 [[ -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." msg_error "Your default shell is currently not set to Bash. To use these scripts, please switch to the Bash shell."
echo -e "\nExiting..." echo -e "\nExiting..."
sleep 2 sleep 2
exit exit 103
fi fi
} }
@@ -552,7 +552,7 @@ check_root() {
msg_error "Please run this script as root." msg_error "Please run this script as root."
echo -e "\nExiting..." echo -e "\nExiting..."
sleep 2 sleep 2
exit exit 104
fi 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 "Requires Proxmox Virtual Environment Version 8.1 - 8.4 or 9.0 - 9.1."
echo -e "Exiting..." echo -e "Exiting..."
sleep 2 sleep 2
exit exit 105
fi fi
} }
@@ -572,21 +572,21 @@ arch_check() {
echo -e "\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \n" echo -e "\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \n"
echo -e "Exiting..." echo -e "Exiting..."
sleep 2 sleep 2
exit exit 106
fi fi
} }
exit_script() { exit_script() {
clear clear
echo -e "\n${CROSS}${RD}User exited script${CL}\n" echo -e "\n${CROSS}${RD}User exited script${CL}\n"
exit exit 0
} }
check_hostname_conflict() { check_hostname_conflict() {
local hostname="$1" local hostname="$1"
if qm list | awk '{print $2}' | grep -qx "$hostname"; then if qm list | awk '{print $2}' | grep -qx "$hostname"; then
msg_error "Hostname $hostname already in use by another VM." msg_error "Hostname $hostname already in use by another VM."
exit 1 exit 206
fi fi
} }

View File

@@ -73,7 +73,7 @@ fi
DISTRO=$(pct exec "$CTID" -- cat /etc/os-release | grep -w "ID" | cut -d'=' -f2 | tr -d '"') DISTRO=$(pct exec "$CTID" -- cat /etc/os-release | grep -w "ID" | cut -d'=' -f2 | tr -d '"')
if [[ "$DISTRO" != "debian" && "$DISTRO" != "ubuntu" ]]; then 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" msg "\e[1;31m Error: This script only supports Debian or Ubuntu LXC containers. Detected: $DISTRO. Aborting...\e[0m"
exit 1 exit 238
fi fi
CTID_CONFIG_PATH=/etc/pve/lxc/${CTID}.conf CTID_CONFIG_PATH=/etc/pve/lxc/${CTID}.conf

View File

@@ -32,7 +32,7 @@ header_info
if ! command -v pveversion &>/dev/null; then if ! command -v pveversion &>/dev/null; then
msg_error "This script must be run on the Proxmox VE host (not inside an LXC container)" msg_error "This script must be run on the Proxmox VE host (not inside an LXC container)"
exit 1 exit 232
fi fi
while true; do while true; do
@@ -64,7 +64,7 @@ while [[ -z "${CTID}" ]]; do
CTID=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Containers on $NODE" --radiolist \ CTID=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Containers on $NODE" --radiolist \
"\nSelect a container to add Tailscale to:\n" \ "\nSelect a container to add Tailscale to:\n" \
16 $((MSG_MAX_LENGTH + 23)) 6 \ 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 done
CTID_CONFIG_PATH="/etc/pve/lxc/${CTID}.conf" CTID_CONFIG_PATH="/etc/pve/lxc/${CTID}.conf"

View File

@@ -69,7 +69,7 @@ elif [[ -f "/etc/debian_version" ]]; then
SERVICE_PATH="/etc/systemd/system/adguardhome-sync.service" SERVICE_PATH="/etc/systemd/system/adguardhome-sync.service"
else else
msg_error "Unsupported OS detected. Exiting." msg_error "Unsupported OS detected. Exiting."
exit 1 exit 238
fi fi
# ============================================================================== # ==============================================================================
@@ -312,7 +312,7 @@ if [[ "${type:-}" == "update" ]]; then
update update
else else
msg_error "${APP} is not installed. Nothing to update." msg_error "${APP} is not installed. Nothing to update."
exit 1 exit 233
fi fi
exit 0 exit 0
fi fi

View File

@@ -87,11 +87,11 @@ function update() {
function check_docker() { function check_docker() {
if ! command -v docker &>/dev/null; then if ! command -v docker &>/dev/null; then
msg_error "Docker is not installed. This script requires an existing Docker LXC. Exiting." msg_error "Docker is not installed. This script requires an existing Docker LXC. Exiting."
exit 1 exit 10
fi fi
if ! docker compose version &>/dev/null; then if ! docker compose version &>/dev/null; then
msg_error "Docker Compose plugin is not available. Please install it before running this script. Exiting." msg_error "Docker Compose plugin is not available. Please install it before running this script. Exiting."
exit 1 exit 10
fi fi
msg_ok "Docker $(docker --version | cut -d' ' -f3 | tr -d ',') and Docker Compose are available" msg_ok "Docker $(docker --version | cut -d' ' -f3 | tr -d ',') and Docker Compose are available"
} }
@@ -171,7 +171,7 @@ if [[ "${type:-}" == "update" ]]; then
update update
else else
msg_error "${APP} is not installed. Nothing to update." msg_error "${APP} is not installed. Nothing to update."
exit 1 exit 233
fi fi
exit 0 exit 0
fi fi

View File

@@ -93,7 +93,7 @@ function check_or_install_docker() {
msg_ok "Docker Compose is available" msg_ok "Docker Compose is available"
else else
msg_error "Docker Compose plugin is not available. Please install it." msg_error "Docker Compose plugin is not available. Please install it."
exit 1 exit 10
fi fi
return return
fi fi
@@ -103,7 +103,7 @@ function check_or_install_docker() {
read -r install_docker_prompt read -r install_docker_prompt
if [[ ! "${install_docker_prompt,,}" =~ ^(y|yes)$ ]]; then if [[ ! "${install_docker_prompt,,}" =~ ^(y|yes)$ ]]; then
msg_error "Docker is required for ${APP}. Exiting." msg_error "Docker is required for ${APP}. Exiting."
exit 1 exit 10
fi fi
msg_info "Installing Docker" msg_info "Installing Docker"
@@ -165,7 +165,7 @@ if [[ "${type:-}" == "update" ]]; then
update update
else else
msg_error "${APP} is not installed. Nothing to update." msg_error "${APP} is not installed. Nothing to update."
exit 1 exit 233
fi fi
exit 0 exit 0
fi fi

View File

@@ -49,7 +49,7 @@ elif grep -qE 'ID=debian|ID=ubuntu' /etc/os-release; then
SERVICE_PATH="/etc/systemd/system/copyparty.service" SERVICE_PATH="/etc/systemd/system/copyparty.service"
else else
msg_error "Unsupported OS detected. Exiting." msg_error "Unsupported OS detected. Exiting."
exit 1 exit 238
fi fi
# ============================================================================== # ==============================================================================
@@ -318,7 +318,7 @@ if [[ "${type:-}" == "update" ]]; then
update update
else else
msg_error "${APP} is not installed. Nothing to update." msg_error "${APP} is not installed. Nothing to update."
exit 1 exit 233
fi fi
exit 0 exit 0
fi fi

View File

@@ -51,7 +51,7 @@ EOF
# ============================================================================== # ==============================================================================
if ! grep -qE 'ID=debian|ID=ubuntu' /etc/os-release 2>/dev/null; then 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." echo -e "${CROSS} Unsupported OS detected. This script only supports Debian and Ubuntu."
exit 1 exit 238
fi fi
# ============================================================================== # ==============================================================================
@@ -183,7 +183,7 @@ if [[ "${type:-}" == "update" ]]; then
update update
else else
msg_error "${APP} is not installed. Nothing to update." msg_error "${APP} is not installed. Nothing to update."
exit 1 exit 233
fi fi
exit 0 exit 0
fi fi

View File

@@ -99,7 +99,7 @@ function check_or_install_docker() {
msg_ok "Docker Compose is available" msg_ok "Docker Compose is available"
else else
msg_error "Docker Compose plugin is not available. Please install it." msg_error "Docker Compose plugin is not available. Please install it."
exit 1 exit 10
fi fi
return return
fi fi
@@ -109,7 +109,7 @@ function check_or_install_docker() {
read -r install_docker_prompt read -r install_docker_prompt
if [[ ! "${install_docker_prompt,,}" =~ ^(y|yes)$ ]]; then if [[ ! "${install_docker_prompt,,}" =~ ^(y|yes)$ ]]; then
msg_error "Docker is required for ${APP}. Exiting." msg_error "Docker is required for ${APP}. Exiting."
exit 1 exit 10
fi fi
msg_info "Installing Docker" msg_info "Installing Docker"
@@ -160,7 +160,7 @@ if [[ "${type:-}" == "update" ]]; then
update update
else else
msg_error "${APP} is not installed. Nothing to update." msg_error "${APP} is not installed. Nothing to update."
exit 1 exit 233
fi fi
exit 0 exit 0
fi fi

View File

@@ -92,7 +92,7 @@ function check_or_install_docker() {
msg_ok "Docker Compose is available" msg_ok "Docker Compose is available"
else else
msg_error "Docker Compose plugin is not available. Please install it." msg_error "Docker Compose plugin is not available. Please install it."
exit 1 exit 10
fi fi
return return
fi fi
@@ -102,7 +102,7 @@ function check_or_install_docker() {
read -r install_docker_prompt read -r install_docker_prompt
if [[ ! "${install_docker_prompt,,}" =~ ^(y|yes)$ ]]; then if [[ ! "${install_docker_prompt,,}" =~ ^(y|yes)$ ]]; then
msg_error "Docker is required for ${APP}. Exiting." msg_error "Docker is required for ${APP}. Exiting."
exit 1 exit 10
fi fi
msg_info "Installing Docker" msg_info "Installing Docker"
@@ -163,7 +163,7 @@ if [[ "${type:-}" == "update" ]]; then
update update
else else
msg_error "${APP} is not installed. Nothing to update." msg_error "${APP} is not installed. Nothing to update."
exit 1 exit 233
fi fi
exit 0 exit 0
fi fi

View File

@@ -54,7 +54,7 @@ elif [[ -f "/etc/debian_version" ]]; then
PKG_MANAGER="apt-get install -y" PKG_MANAGER="apt-get install -y"
else else
echo -e "${CROSS} Unsupported OS detected. Exiting." echo -e "${CROSS} Unsupported OS detected. Exiting."
exit 1 exit 238
fi fi
header_info header_info

View File

@@ -52,7 +52,7 @@ elif [[ -f "/etc/debian_version" ]]; then
PKG_MANAGER="apt-get install -y" PKG_MANAGER="apt-get install -y"
else else
echo -e "${CROSS} Unsupported OS detected. Exiting." echo -e "${CROSS} Unsupported OS detected. Exiting."
exit 1 exit 238
fi fi
header_info header_info

View File

@@ -93,7 +93,7 @@ EOF
update_glances_debian() { update_glances_debian() {
if [[ ! -d /opt/glances/.venv ]]; then if [[ ! -d /opt/glances/.venv ]]; then
msg_error "$APP is not installed" msg_error "$APP is not installed"
exit 1 exit 233
fi fi
msg_info "Updating $APP" msg_info "Updating $APP"
cd /opt/glances cd /opt/glances
@@ -160,7 +160,7 @@ EOF
update_glances_alpine() { update_glances_alpine() {
if [[ ! -d /opt/glances/.venv ]]; then if [[ ! -d /opt/glances/.venv ]]; then
msg_error "$APP is not installed" msg_error "$APP is not installed"
exit 1 exit 233
fi fi
msg_info "Updating $APP" msg_info "Updating $APP"
cd /opt/glances cd /opt/glances

View File

@@ -52,13 +52,13 @@ EOF
# ============================================================================== # ==============================================================================
if [[ -f "/etc/alpine-release" ]]; then if [[ -f "/etc/alpine-release" ]]; then
msg_error "Alpine is not supported for ${APP}. Use Debian." msg_error "Alpine is not supported for ${APP}. Use Debian."
exit 1 exit 238
elif [[ -f "/etc/debian_version" ]]; then elif [[ -f "/etc/debian_version" ]]; then
OS="Debian" OS="Debian"
SERVICE_PATH="/etc/systemd/system/immich-proxy.service" SERVICE_PATH="/etc/systemd/system/immich-proxy.service"
else else
echo -e "${CROSS} Unsupported OS detected. Exiting." echo -e "${CROSS} Unsupported OS detected. Exiting."
exit 1 exit 238
fi fi
# ============================================================================== # ==============================================================================
@@ -231,7 +231,7 @@ if [[ "${type:-}" == "update" ]]; then
update update
else else
msg_error "${APP} is not installed. Nothing to update." msg_error "${APP} is not installed. Nothing to update."
exit 1 exit 233
fi fi
exit 0 exit 0
fi fi

View File

@@ -52,13 +52,13 @@ EOF
# ============================================================================== # ==============================================================================
if [[ -f "/etc/alpine-release" ]]; then if [[ -f "/etc/alpine-release" ]]; then
msg_error "Alpine is not supported for ${APP}. Use Debian/Ubuntu." msg_error "Alpine is not supported for ${APP}. Use Debian/Ubuntu."
exit 1 exit 238
elif [[ -f "/etc/debian_version" ]]; then elif [[ -f "/etc/debian_version" ]]; then
OS="Debian" OS="Debian"
SERVICE_PATH="/etc/systemd/system/jellystat.service" SERVICE_PATH="/etc/systemd/system/jellystat.service"
else else
echo -e "${CROSS} Unsupported OS detected. Exiting." echo -e "${CROSS} Unsupported OS detected. Exiting."
exit 1 exit 238
fi fi
# ============================================================================== # ==============================================================================
@@ -326,7 +326,7 @@ if [[ "${type:-}" == "update" ]]; then
update update
else else
msg_error "${APP} is not installed. Nothing to update." msg_error "${APP} is not installed. Nothing to update."
exit 1 exit 233
fi fi
exit 0 exit 0
fi fi

View File

@@ -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) COMPOSE_FILE=$(find "$INSTALL_PATH" -maxdepth 1 -type f -name '*.compose.yaml' ! -name 'compose.env' | head -n1)
if [[ -z "${COMPOSE_FILE:-}" ]]; then if [[ -z "${COMPOSE_FILE:-}" ]]; then
msg_error "No valid compose file found in ${INSTALL_PATH}!" msg_error "No valid compose file found in ${INSTALL_PATH}!"
exit 1 exit 233
fi fi
COMPOSE_BASENAME=$(basename "$COMPOSE_FILE") 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}This configuration is no longer supported since Komodo v1.18.0.${CL}"
echo -e "${YW}Please follow the migration guide:${CL}" echo -e "${YW}Please follow the migration guide:${CL}"
echo -e "${BGN}https://github.com/community-scripts/ProxmoxVE/discussions/5689${CL}\n" echo -e "${BGN}https://github.com/community-scripts/ProxmoxVE/discussions/5689${CL}\n"
exit 1 exit 238
fi fi
} }
@@ -79,14 +79,14 @@ function update() {
BACKUP_FILE="${INSTALL_PATH}/${COMPOSE_BASENAME}.bak_$(date +%Y%m%d_%H%M%S)" BACKUP_FILE="${INSTALL_PATH}/${COMPOSE_BASENAME}.bak_$(date +%Y%m%d_%H%M%S)"
cp "$COMPOSE_FILE" "$BACKUP_FILE" || { cp "$COMPOSE_FILE" "$BACKUP_FILE" || {
msg_error "Failed to create backup of ${COMPOSE_BASENAME}!" 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}" GITHUB_URL="https://raw.githubusercontent.com/moghtech/komodo/main/compose/${COMPOSE_BASENAME}"
if ! curl -fsSL "$GITHUB_URL" -o "$COMPOSE_FILE"; then if ! curl -fsSL "$GITHUB_URL" -o "$COMPOSE_FILE"; then
msg_error "Failed to download ${COMPOSE_BASENAME} from GitHub!" msg_error "Failed to download ${COMPOSE_BASENAME} from GitHub!"
mv "$BACKUP_FILE" "$COMPOSE_FILE" mv "$BACKUP_FILE" "$COMPOSE_FILE"
exit 1 exit 115
fi fi
if ! grep -qxF 'COMPOSE_KOMODO_BACKUPS_PATH=/etc/komodo/backups' "$COMPOSE_ENV"; then 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" msg_ok "Docker Compose is available"
else else
msg_error "Docker Compose plugin is not available. Please install it." msg_error "Docker Compose plugin is not available. Please install it."
exit 1 exit 10
fi fi
return return
fi fi
@@ -139,7 +139,7 @@ function check_or_install_docker() {
read -r install_docker_prompt read -r install_docker_prompt
if [[ ! "${install_docker_prompt,,}" =~ ^(y|yes)$ ]]; then if [[ ! "${install_docker_prompt,,}" =~ ^(y|yes)$ ]]; then
msg_error "Docker is required for ${APP}. Exiting." msg_error "Docker is required for ${APP}. Exiting."
exit 1 exit 10
fi fi
msg_info "Installing Docker" msg_info "Installing Docker"
@@ -239,7 +239,7 @@ if [[ "${type:-}" == "update" ]]; then
update update
else else
msg_error "${APP} is not installed. Nothing to update." msg_error "${APP} is not installed. Nothing to update."
exit 1 exit 233
fi fi
exit 0 exit 0
fi fi

View File

@@ -58,7 +58,7 @@ pve_check() {
if ((MINOR < 0 || MINOR > 9)); then if ((MINOR < 0 || MINOR > 9)); then
msg_error "This version of Proxmox VE is not supported." msg_error "This version of Proxmox VE is not supported."
msg_error "Supported: Proxmox VE version 8.0 8.9" msg_error "Supported: Proxmox VE version 8.0 8.9"
exit 1 exit 105
fi fi
return 0 return 0
fi fi
@@ -69,7 +69,7 @@ pve_check() {
if ((MINOR < 0 || MINOR > 1)); then if ((MINOR < 0 || MINOR > 1)); then
msg_error "This version of Proxmox VE is not yet supported." msg_error "This version of Proxmox VE is not yet supported."
msg_error "Supported: Proxmox VE version 9.09.1.x" msg_error "Supported: Proxmox VE version 9.09.1.x"
exit 1 exit 105
fi fi
return 0 return 0
fi fi
@@ -77,19 +77,19 @@ pve_check() {
# All other unsupported versions # All other unsupported versions
msg_error "This version of Proxmox VE is not supported." msg_error "This version of Proxmox VE is not supported."
msg_error "Supported versions: Proxmox VE 8.0 8.9 or 9.09.1.x" msg_error "Supported versions: Proxmox VE 8.0 8.9 or 9.09.1.x"
exit 1 exit 105
} }
detect_codename() { detect_codename() {
source /etc/os-release source /etc/os-release
if [[ "$ID" != "debian" ]]; then if [[ "$ID" != "debian" ]]; then
msg_error "Unsupported base OS: $ID (only Proxmox VE / Debian supported)." msg_error "Unsupported base OS: $ID (only Proxmox VE / Debian supported)."
exit 1 exit 238
fi fi
CODENAME="${VERSION_CODENAME:-}" CODENAME="${VERSION_CODENAME:-}"
if [[ -z "$CODENAME" ]]; then if [[ -z "$CODENAME" ]]; then
msg_error "Could not detect Debian codename." msg_error "Could not detect Debian codename."
exit 1 exit 71
fi fi
echo "$CODENAME" echo "$CODENAME"
} }
@@ -124,7 +124,7 @@ install() {
PKG=$(get_latest_repo_pkg "$REPO_URL") PKG=$(get_latest_repo_pkg "$REPO_URL")
if [[ -z "$PKG" ]]; then if [[ -z "$PKG" ]]; then
msg_error "Could not find netdata-repo package for Debian $CODENAME" msg_error "Could not find netdata-repo package for Debian $CODENAME"
exit 1 exit 237
fi fi
curl -fsSL "${REPO_URL}${PKG}" -o "$PKG" curl -fsSL "${REPO_URL}${PKG}" -o "$PKG"
$STD dpkg -i "$PKG" $STD dpkg -i "$PKG"

View File

@@ -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 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." echo -e "${CROSS} Unsupported OS detected. This script only supports Debian and Ubuntu."
exit 1 exit 238
fi fi
# ============================================================================== # ==============================================================================
@@ -170,7 +170,7 @@ if [[ "${type:-}" == "update" ]]; then
update update
else else
msg_error "Nextcloud-Exporter is not installed. Nothing to update." msg_error "Nextcloud-Exporter is not installed. Nothing to update."
exit 1 exit 233
fi fi
exit 0 exit 0
fi fi

View File

@@ -52,7 +52,7 @@ elif [[ -f "/etc/debian_version" ]]; then
INSTALL_DIR="$INSTALL_DIR_DEBIAN" INSTALL_DIR="$INSTALL_DIR_DEBIAN"
else else
echo -e "${CROSS} Unsupported OS detected. Exiting." echo -e "${CROSS} Unsupported OS detected. Exiting."
exit 1 exit 238
fi fi
header_info header_info
@@ -72,7 +72,7 @@ function check_internet() {
msg_ok "Internet connectivity OK" msg_ok "Internet connectivity OK"
else else
msg_error "Internet connectivity or GitHub unreachable (Status $HTTP_CODE). Exiting." msg_error "Internet connectivity or GitHub unreachable (Status $HTTP_CODE). Exiting."
exit 1 exit 115
fi fi
} }
@@ -105,7 +105,7 @@ function install_php_and_modules() {
msg_info "Installing missing PHP packages: ${MISSING_PACKAGES[*]}" msg_info "Installing missing PHP packages: ${MISSING_PACKAGES[*]}"
if ! apt-get update &>/dev/null || ! apt-get install -y "${MISSING_PACKAGES[@]}" &>/dev/null; then if ! apt-get update &>/dev/null || ! apt-get install -y "${MISSING_PACKAGES[@]}" &>/dev/null; then
msg_error "Failed to install required PHP modules. Exiting." msg_error "Failed to install required PHP modules. Exiting."
exit 1 exit 237
fi fi
msg_ok "Installed missing PHP packages" msg_ok "Installed missing PHP packages"
else else
@@ -132,7 +132,7 @@ function install_phpmyadmin() {
msg_info "Downloading ${TARBALL_URL}" msg_info "Downloading ${TARBALL_URL}"
if ! curl -fsSL "$TARBALL_URL" -o /tmp/phpmyadmin.tar.gz; then if ! curl -fsSL "$TARBALL_URL" -o /tmp/phpmyadmin.tar.gz; then
msg_error "Download failed: $TARBALL_URL" msg_error "Download failed: $TARBALL_URL"
exit 1 exit 115
fi fi
mkdir -p "$INSTALL_DIR" mkdir -p "$INSTALL_DIR"
@@ -188,7 +188,7 @@ EOF
msg_ok "Started PHP-FPM service: $PHP_FPM_SERVICE" msg_ok "Started PHP-FPM service: $PHP_FPM_SERVICE"
else else
msg_error "Failed to start PHP-FPM service: $PHP_FPM_SERVICE" msg_error "Failed to start PHP-FPM service: $PHP_FPM_SERVICE"
exit 1 exit 150
fi fi
$STD rc-service lighttpd start $STD rc-service lighttpd start
@@ -237,7 +237,7 @@ function update_phpmyadmin() {
if ! curl -fsSL "$TARBALL_URL" -o /tmp/phpmyadmin.tar.gz; then if ! curl -fsSL "$TARBALL_URL" -o /tmp/phpmyadmin.tar.gz; then
msg_error "Download failed: $TARBALL_URL" msg_error "Download failed: $TARBALL_URL"
exit 1 exit 115
fi fi
BACKUP_DIR="/tmp/phpmyadmin-backup-$(date +%Y%m%d-%H%M%S)" 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}" echo -e "${YW}⚠️ Invalid input. Exiting.${CL}"
exit 1 exit 112
;; ;;
esac esac
else else

View File

@@ -41,7 +41,7 @@ elif grep -qE 'ID=debian|ID=ubuntu' /etc/os-release; then
SERVICE_PATH="/etc/systemd/system/pihole-exporter.service" SERVICE_PATH="/etc/systemd/system/pihole-exporter.service"
else else
echo -e "${CROSS} Unsupported OS detected. Exiting." echo -e "${CROSS} Unsupported OS detected. Exiting."
exit 1 exit 238
fi fi
# ============================================================================== # ==============================================================================
@@ -207,7 +207,7 @@ if [[ "${type:-}" == "update" ]]; then
update update
else else
msg_error "Pihole-Exporter is not installed. Nothing to update." msg_error "Pihole-Exporter is not installed. Nothing to update."
exit 1 exit 233
fi fi
exit 0 exit 0
fi fi

View File

@@ -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 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." echo -e "${CROSS} Unsupported OS detected. This script only supports Debian and Ubuntu."
exit 1 exit 238
fi fi
# ============================================================================== # ==============================================================================
@@ -144,7 +144,7 @@ if [[ "${type:-}" == "update" ]]; then
update update
else else
msg_error "Prometheus-Paperless-NGX-Exporter is not installed. Nothing to update." msg_error "Prometheus-Paperless-NGX-Exporter is not installed. Nothing to update."
exit 1 exit 233
fi fi
exit 0 exit 0
fi fi

View File

@@ -41,7 +41,7 @@ elif grep -qE 'ID=debian|ID=ubuntu' /etc/os-release; then
SERVICE_PATH="/etc/systemd/system/qbittorrent-exporter.service" SERVICE_PATH="/etc/systemd/system/qbittorrent-exporter.service"
else else
echo -e "${CROSS} Unsupported OS detected. Exiting." echo -e "${CROSS} Unsupported OS detected. Exiting."
exit 1 exit 238
fi fi
# ============================================================================== # ==============================================================================
@@ -200,7 +200,7 @@ if [[ "${type:-}" == "update" ]]; then
update update
else else
msg_error "qBittorrent-Exporter is not installed. Nothing to update." msg_error "qBittorrent-Exporter is not installed. Nothing to update."
exit 1 exit 233
fi fi
exit 0 exit 0
fi fi

View File

@@ -62,7 +62,7 @@ function check_or_install_docker() {
msg_ok "Docker Compose is available" msg_ok "Docker Compose is available"
else else
msg_error "Docker Compose plugin is not available. Please install it." msg_error "Docker Compose plugin is not available. Please install it."
exit 1 exit 10
fi fi
return return
fi fi
@@ -72,7 +72,7 @@ function check_or_install_docker() {
read -r install_docker_prompt read -r install_docker_prompt
if [[ ! "${install_docker_prompt,,}" =~ ^(y|yes)$ ]]; then if [[ ! "${install_docker_prompt,,}" =~ ^(y|yes)$ ]]; then
msg_error "Docker is required for ${APP}. Exiting." msg_error "Docker is required for ${APP}. Exiting."
exit 1 exit 10
fi fi
msg_info "Installing Docker" msg_info "Installing Docker"
@@ -168,14 +168,14 @@ if [[ "${type:-}" == "update" ]]; then
update update
else else
msg_error "${APP} is not installed. Nothing to update." msg_error "${APP} is not installed. Nothing to update."
exit 1 exit 233
fi fi
exit 0 exit 0
fi fi
if [[ -f /etc/alpine-release ]]; then if [[ -f /etc/alpine-release ]]; then
msg_error "${APP} does not support Alpine Linux. Please use a Debian or Ubuntu based LXC." msg_error "${APP} does not support Alpine Linux. Please use a Debian or Ubuntu based LXC."
exit 1 exit 238
fi fi
header_info header_info

View File

@@ -62,7 +62,7 @@ function msg_error() {
} }
if [ -z "$(ls -A /var/lib/docker/volumes/hass_config/_data/backups/)" ]; then if [ -z "$(ls -A /var/lib/docker/volumes/hass_config/_data/backups/)" ]; then
msg_error "No backups found! \n" msg_error "No backups found! \n"
exit 1 exit 235
fi fi
DIR=/var/lib/docker/volumes/hass_config/_data/restore DIR=/var/lib/docker/volumes/hass_config/_data/restore
if [ -d "$DIR" ]; then if [ -d "$DIR" ]; then

View File

@@ -62,7 +62,7 @@ function msg_error() {
} }
if [ -z "$(ls -A /root/.homeassistant/backups/)" ]; then if [ -z "$(ls -A /root/.homeassistant/backups/)" ]; then
msg_error "No backups found! \n" msg_error "No backups found! \n"
exit 1 exit 235
fi fi
DIR=/root/.homeassistant/restore DIR=/root/.homeassistant/restore
if [ -d "$DIR" ]; then if [ -d "$DIR" ]; then

View File

@@ -39,7 +39,7 @@ ROOT_FS=$(df -Th "/" | awk 'NR==2 {print $2}')
if [ "$ROOT_FS" != "ext4" ]; then if [ "$ROOT_FS" != "ext4" ]; then
whiptail --backtitle "Proxmox VE Helper Scripts" \ whiptail --backtitle "Proxmox VE Helper Scripts" \
--title "Warning" \ --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 fi
NODE=$(hostname) NODE=$(hostname)

View File

@@ -57,7 +57,7 @@ done
if [ ${#kernels_to_remove[@]} -eq 0 ]; then if [ ${#kernels_to_remove[@]} -eq 0 ]; then
echo -e "${RD}No valid selection made. Exiting.${CL}" echo -e "${RD}No valid selection made. Exiting.${CL}"
exit 1 exit 0
fi fi
# Confirm removal # Confirm removal
@@ -66,7 +66,7 @@ printf "%s\n" "${kernels_to_remove[@]}"
read -rp "Proceed with removal? (y/n): " confirm read -rp "Proceed with removal? (y/n): " confirm
if [[ "$confirm" != "y" ]]; then if [[ "$confirm" != "y" ]]; then
echo -e "${RD}Aborted.${CL}" echo -e "${RD}Aborted.${CL}"
exit 1 exit 0
fi fi
# Remove kernels # Remove kernels

View File

@@ -51,7 +51,7 @@ containers=$(pct list | tail -n +2 | awk '{print $0 " " $4}')
if [ -z "$containers" ]; then if [ -z "$containers" ]; then
whiptail --title "LXC Container Delete" --msgbox "No LXC containers available!" 10 60 whiptail --title "LXC Container Delete" --msgbox "No LXC containers available!" 10 60
exit 1 exit 234
fi fi
menu_items=("ALL" "Delete ALL containers" "OFF") # Add as first option 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 if [ -z "$CHOICES" ]; then
whiptail --title "LXC Container Delete" \ whiptail --title "LXC Container Delete" \
--msgbox "No containers selected!" 10 60 --msgbox "No containers selected!" 10 60
exit 1 exit 0
fi fi
read -p "Delete containers manually or automatically? (Default: manual) m/a: " DELETE_MODE read -p "Delete containers manually or automatically? (Default: manual) m/a: " DELETE_MODE

View File

@@ -47,7 +47,7 @@ function msg_warn() { echo -e "${WARN} ${YWB}${1}"; }
# Check for root privileges # Check for root privileges
if [ "$(id -u)" -ne 0 ]; then if [ "$(id -u)" -ne 0 ]; then
msg_error "Error: This script must be run as root." msg_error "Error: This script must be run as root."
exit 1 exit 104
fi fi
if ! command -v ethtool >/dev/null 2>&1; then 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 update &>/dev/null
apt-get install -y ethtool &>/dev/null || { apt-get install -y ethtool &>/dev/null || {
msg_error "Failed to install ethtool. Exiting." msg_error "Failed to install ethtool. Exiting."
exit 1 exit 237
} }
msg_ok "ethtool installed successfully" msg_ok "ethtool installed successfully"
fi fi
@@ -86,7 +86,7 @@ done
if [ ${#INTERFACES[@]} -eq 0 ]; then if [ ${#INTERFACES[@]} -eq 0 ]; then
whiptail --title "Error" --msgbox "No Intel e1000e or e1000 network interfaces found!" 10 60 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." msg_error "No Intel e1000e or e1000 network interfaces found! Exiting."
exit 1 exit 236
fi fi
msg_ok "Found ${BL}$COUNT${GN} Intel e1000e/e1000 interfaces" msg_ok "Found ${BL}$COUNT${GN} Intel e1000e/e1000 interfaces"

View File

@@ -41,7 +41,7 @@ header_info
virt=$(systemd-detect-virt) virt=$(systemd-detect-virt)
if [ "$virt" != "none" ]; then if [ "$virt" != "none" ]; then
msg_error "This script must be run on bare metal. Detected virtual environment: $virt" msg_error "This script must be run on bare metal. Detected virtual environment: $virt"
exit 1 exit 232
fi fi
# Attempt to obtain the current loaded microcode revision # Attempt to obtain the current loaded microcode revision

View File

@@ -83,7 +83,7 @@ main() {
if command -v pveversion >/dev/null 2>&1; then if command -v pveversion >/dev/null 2>&1; then
echo -e "\n🛑 PVE Detected, Wrong Script!\n" echo -e "\n🛑 PVE Detected, Wrong Script!\n"
exit 1 exit 232
fi fi
local CODENAME local CODENAME
@@ -95,7 +95,7 @@ main() {
*) *)
msg_error "Unsupported Debian codename: $CODENAME" msg_error "Unsupported Debian codename: $CODENAME"
echo -e "Supported: bookworm (PBS 3.x) and trixie (PBS 4.x)" echo -e "Supported: bookworm (PBS 3.x) and trixie (PBS 4.x)"
exit 1 exit 105
;; ;;
esac esac
} }

View File

@@ -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 if ! grep -q "Proxmox Mail Gateway" /etc/issue 2>/dev/null; then
msg_error "This script is only intended for Proxmox Mail Gateway" msg_error "This script is only intended for Proxmox Mail Gateway"
exit 1 exit 232
fi fi
repo_state() { repo_state() {

View File

@@ -88,19 +88,19 @@ main() {
if [[ "$PVE_MAJOR" == "8" ]]; then if [[ "$PVE_MAJOR" == "8" ]]; then
if ((PVE_MINOR < 0 || PVE_MINOR > 9)); then if ((PVE_MINOR < 0 || PVE_MINOR > 9)); then
msg_error "Unsupported Proxmox 8 version" msg_error "Unsupported Proxmox 8 version"
exit 1 exit 105
fi fi
start_routines_8 start_routines_8
elif [[ "$PVE_MAJOR" == "9" ]]; then elif [[ "$PVE_MAJOR" == "9" ]]; then
if ((PVE_MINOR < 0 || PVE_MINOR > 1)); then if ((PVE_MINOR < 0 || PVE_MINOR > 1)); then
msg_error "Only Proxmox 9.0-9.1.x is currently supported" msg_error "Only Proxmox 9.0-9.1.x is currently supported"
exit 1 exit 105
fi fi
start_routines_9 start_routines_9
else else
msg_error "Unsupported Proxmox VE major version: $PVE_MAJOR" msg_error "Unsupported Proxmox VE major version: $PVE_MAJOR"
echo -e "Supported: 8.08.9.x and 9.09.1.x" echo -e "Supported: 8.08.9.x and 9.09.1.x"
exit 1 exit 105
fi fi
} }

View File

@@ -25,7 +25,7 @@ header_info "$APP"
check_root() { check_root() {
if [[ $EUID -ne 0 ]]; then if [[ $EUID -ne 0 ]]; then
msg_error "Script must be run as root" msg_error "Script must be run as root"
exit 1 exit 104
fi fi
} }
@@ -63,7 +63,7 @@ select_container() {
if [[ ${#lxc_list[@]} -eq 0 ]]; then if [[ ${#lxc_list[@]} -eq 0 ]]; then
msg_error "No containers found" msg_error "No containers found"
exit 1 exit 234
fi fi
PS3="Enter number of container to convert: " 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 if [ -z "$BACKUP_PATH" ] || ! grep -q "Backup job finished successfully" "$vzdump_output"; then
rm "$vzdump_output" rm "$vzdump_output"
msg_error "Backup failed" msg_error "Backup failed"
exit 1 exit 235
fi fi
rm "$vzdump_output" rm "$vzdump_output"
msg_ok "Backup complete: $BACKUP_PATH" msg_ok "Backup complete: $BACKUP_PATH"
@@ -126,7 +126,7 @@ perform_conversion() {
msg_ok "Conversion successful" msg_ok "Conversion successful"
else else
msg_error "Conversion failed" msg_error "Conversion failed"
exit 1 exit 235
fi fi
} }

View File

@@ -140,7 +140,7 @@ function backup_container() {
msg_ok "Backup created" msg_ok "Backup created"
else else
msg_error "Backup failed for container $1" msg_error "Backup failed for container $1"
exit 1 exit 235
fi fi
} }
@@ -183,7 +183,7 @@ containers=$(pct list | tail -n +2 | awk '{print $0 " " $4}')
if [ -z "$containers" ]; then if [ -z "$containers" ]; then
whiptail --title "LXC Container Update" --msgbox "No LXC containers available!" 10 60 whiptail --title "LXC Container Update" --msgbox "No LXC containers available!" 10 60
exit 1 exit 234
fi fi
menu_items=() menu_items=()
@@ -242,7 +242,7 @@ if [[ -n "$var_container" ]]; then
if [[ -z "$CHOICE" ]]; then if [[ -z "$CHOICE" ]]; then
msg_error "No containers matched the selection criteria: $var_container ${var_tags:-community-script|proxmox-helper-scripts}" msg_error "No containers matched the selection criteria: $var_container ${var_tags:-community-script|proxmox-helper-scripts}"
exit 1 exit 234
fi fi
msg_ok "Selected containers: $CHOICE" msg_ok "Selected containers: $CHOICE"
else else
@@ -253,7 +253,7 @@ else
if [ -z "$CHOICE" ]; then if [ -z "$CHOICE" ]; then
whiptail --title "LXC Container Update" \ whiptail --title "LXC Container Update" \
--msgbox "No containers selected!" 10 60 --msgbox "No containers selected!" 10 60
exit 1 exit 0
fi fi
fi fi
@@ -284,7 +284,7 @@ if [ "$BACKUP_CHOICE" == "yes" ]; then
if [ -z "$STORAGES" ]; then if [ -z "$STORAGES" ]; then
msg_error "No storage with 'backup' support found!" msg_error "No storage with 'backup' support found!"
exit 1 exit 119
fi fi
# Determine storage based on var_backup_storage # Determine storage based on var_backup_storage
@@ -296,7 +296,7 @@ if [ "$BACKUP_CHOICE" == "yes" ]; then
else else
msg_error "Specified backup storage '$var_backup_storage' not found or doesn't support backups!" msg_error "Specified backup storage '$var_backup_storage' not found or doesn't support backups!"
msg_info "Available storages: $(echo $STORAGES | tr '\n' ' ')" msg_info "Available storages: $(echo $STORAGES | tr '\n' ' ')"
exit 1 exit 119
fi fi
else else
MENU_ITEMS=() MENU_ITEMS=()
@@ -308,7 +308,7 @@ if [ "$BACKUP_CHOICE" == "yes" ]; then
if [ -z "$STORAGE_CHOICE" ]; then if [ -z "$STORAGE_CHOICE" ]; then
msg_error "No storage selected!" msg_error "No storage selected!"
exit 1 exit 0
fi fi
fi fi
fi fi
@@ -436,11 +436,11 @@ for container in $CHOICE; do
msg_ok "Restored LXC from backup" msg_ok "Restored LXC from backup"
else else
msg_error "Restored LXC from backup failed" msg_error "Restored LXC from backup failed"
exit 1 exit 235
fi fi
else else
msg_error "Update failed for container $container. Exiting" msg_error "Update failed for container $container. Exiting"
exit 1 exit "$exit_code"
fi fi
done done

View File

@@ -158,7 +158,7 @@ pve_check() {
if ((MINOR < 0 || MINOR > 9)); then if ((MINOR < 0 || MINOR > 9)); then
msg_error "This version of Proxmox VE is not supported." msg_error "This version of Proxmox VE is not supported."
msg_error "Supported: Proxmox VE version 8.0 8.9" msg_error "Supported: Proxmox VE version 8.0 8.9"
exit 1 exit 105
fi fi
return 0 return 0
fi fi
@@ -169,7 +169,7 @@ pve_check() {
if ((MINOR < 0 || MINOR > 1)); then if ((MINOR < 0 || MINOR > 1)); then
msg_error "This version of Proxmox VE is not supported." msg_error "This version of Proxmox VE is not supported."
msg_error "Supported: Proxmox VE version 9.0 9.1" msg_error "Supported: Proxmox VE version 9.0 9.1"
exit 1 exit 105
fi fi
return 0 return 0
fi fi
@@ -177,7 +177,7 @@ pve_check() {
# All other unsupported versions # All other unsupported versions
msg_error "This version of Proxmox VE is not supported." 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" msg_error "Supported versions: Proxmox VE 8.0 8.x or 9.0 9.1"
exit 1 exit 105
} }
function arch_check() { function arch_check() {
@@ -513,7 +513,7 @@ DISK_REF_IMPORTED="$(printf '%s\n' "$IMPORT_OUT" | sed -n "s/.*successfully impo
[[ -z "$DISK_REF_IMPORTED" ]] && { [[ -z "$DISK_REF_IMPORTED" ]] && {
msg_error "Unable to determine imported disk reference." msg_error "Unable to determine imported disk reference."
echo "$IMPORT_OUT" echo "$IMPORT_OUT"
exit 1 exit 226
} }
msg_ok "Imported disk (${CL}${BL}${DISK_REF_IMPORTED}${CL})" msg_ok "Imported disk (${CL}${BL}${DISK_REF_IMPORTED}${CL})"

View File

@@ -158,7 +158,7 @@ pve_check() {
if ((MINOR < 0 || MINOR > 9)); then if ((MINOR < 0 || MINOR > 9)); then
msg_error "This version of Proxmox VE is not supported." msg_error "This version of Proxmox VE is not supported."
msg_error "Supported: Proxmox VE version 8.0 8.9" msg_error "Supported: Proxmox VE version 8.0 8.9"
exit 1 exit 105
fi fi
return 0 return 0
fi fi
@@ -169,7 +169,7 @@ pve_check() {
if ((MINOR < 0 || MINOR > 1)); then if ((MINOR < 0 || MINOR > 1)); then
msg_error "This version of Proxmox VE is not supported." msg_error "This version of Proxmox VE is not supported."
msg_error "Supported: Proxmox VE version 9.0 9.1" msg_error "Supported: Proxmox VE version 9.0 9.1"
exit 1 exit 105
fi fi
return 0 return 0
fi fi
@@ -177,7 +177,7 @@ pve_check() {
# All other unsupported versions # All other unsupported versions
msg_error "This version of Proxmox VE is not supported." 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" msg_error "Supported versions: Proxmox VE 8.0 8.x or 9.0 9.1"
exit 1 exit 105
} }
function arch_check() { function arch_check() {

View File

@@ -158,7 +158,7 @@ pve_check() {
if ((MINOR < 0 || MINOR > 9)); then if ((MINOR < 0 || MINOR > 9)); then
msg_error "This version of Proxmox VE is not supported." msg_error "This version of Proxmox VE is not supported."
msg_error "Supported: Proxmox VE version 8.0 8.9" msg_error "Supported: Proxmox VE version 8.0 8.9"
exit 1 exit 105
fi fi
return 0 return 0
fi fi
@@ -169,7 +169,7 @@ pve_check() {
if ((MINOR < 0 || MINOR > 1)); then if ((MINOR < 0 || MINOR > 1)); then
msg_error "This version of Proxmox VE is not supported." msg_error "This version of Proxmox VE is not supported."
msg_error "Supported: Proxmox VE version 9.0 9.1" msg_error "Supported: Proxmox VE version 9.0 9.1"
exit 1 exit 105
fi fi
return 0 return 0
fi fi
@@ -177,7 +177,7 @@ pve_check() {
# All other unsupported versions # All other unsupported versions
msg_error "This version of Proxmox VE is not supported." 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" msg_error "Supported versions: Proxmox VE 8.0 8.x or 9.0 9.1"
exit 1 exit 105
} }
function arch_check() { function arch_check() {

View File

@@ -638,7 +638,7 @@ DISK_REF_IMPORTED="$(printf '%s\n' "$IMPORT_OUT" | sed -n "s/.*successfully impo
[[ -z "$DISK_REF_IMPORTED" ]] && { [[ -z "$DISK_REF_IMPORTED" ]] && {
msg_error "Unable to determine imported disk reference." msg_error "Unable to determine imported disk reference."
echo "$IMPORT_OUT" echo "$IMPORT_OUT"
exit 1 exit 226
} }
msg_ok "Imported disk (${CL}${BL}${DISK_REF_IMPORTED}${CL})" msg_ok "Imported disk (${CL}${BL}${DISK_REF_IMPORTED}${CL})"

Some files were not shown because too many files have changed in this diff Show More