Compare commits

..

41 Commits

Author SHA1 Message Date
CanbiZ (MickLesk)
c637e504ad Update tools.func 2026-02-27 11:47:46 +01:00
MickLesk
506dcfc99d Improve error outputs across core functions 2026-02-26 22:42:42 +01:00
community-scripts-pr-app[bot]
0d6f5560ff Update CHANGELOG.md (#12367)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-26 19:21:17 +00:00
Tobias
2f7c7c4ea7 Update overseerr.sh (#12366) 2026-02-26 20:20:41 +01:00
community-scripts-pr-app[bot]
b7f94befba chore: update github-versions.json (#12365)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-26 18:16:29 +00:00
community-scripts-pr-app[bot]
dc3029822b Update CHANGELOG.md (#12359)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-26 14:54:54 +00:00
CanbiZ (MickLesk)
3fb677d768 Add workflow to close unauthorized new-script PRs (#12356) 2026-02-26 15:53:47 +01:00
CanbiZ (MickLesk)
e2a83549cb Restrict GitHub source extraction to '# Source:'
When extracting GitHub source URLs in the workflow, only search the "# Source:" line first to avoid matching other URLs (such as license links). Update the grep pipeline to filter for the Source line (case-insensitive) before extracting the https://github.com/... pattern and add explanatory comments.
2026-02-26 14:08:45 +01:00
community-scripts-pr-app[bot]
3911b09d1d chore: update github-versions.json (#12354)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-26 12:15:04 +00:00
community-scripts-pr-app[bot]
c24e9ead1c Update .app files (#12350)
Co-authored-by: GitHub Actions <github-actions[bot]@users.noreply.github.com>
2026-02-26 11:17:15 +01:00
community-scripts-pr-app[bot]
603d5b8f5e Update CHANGELOG.md (#12351)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-26 10:15:03 +00:00
community-scripts-pr-app[bot]
4fd70137b4 Update CHANGELOG.md (#12349)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-26 10:14:45 +00:00
community-scripts-pr-app[bot]
7398ffd9f4 Update date in json (#12348)
Co-authored-by: GitHub Actions <github-actions[bot]@users.noreply.github.com>
2026-02-26 10:14:37 +00:00
push-app-to-main[bot]
f1c765c534 Kima-Hub (#12319)
* Add kima-hub (ct)

* Change default disk size to 20

Updated the disk size default from 24 to 20.

* Modify date_created and hdd size in kima-hub.json

Updated the creation date and HDD size for kima-hub.

* remove: ai

* Update kima-hub.sh

* Update kima-hub-install.sh

---------

Co-authored-by: push-app-to-main[bot] <203845782+push-app-to-main[bot]@users.noreply.github.com>
Co-authored-by: CanbiZ (MickLesk) <47820557+MickLesk@users.noreply.github.com>
Co-authored-by: Tobias <96661824+CrazyWolf13@users.noreply.github.com>
2026-02-26 11:14:18 +01:00
community-scripts-pr-app[bot]
40aa06940c Update CHANGELOG.md (#12347)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-26 07:46:30 +00:00
Chris
117786376a [QOL] Immich: add warning regarding library compilation time (#12345) 2026-02-26 08:45:59 +01:00
community-scripts-pr-app[bot]
c5a635cdd7 chore: update github-versions.json (#12346)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-26 06:22:51 +00:00
community-scripts-pr-app[bot]
165e3f22cd Update CHANGELOG.md (#12344)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-26 00:19:57 +00:00
community-scripts-pr-app[bot]
2561a50d05 chore: update github-versions.json (#12343)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-26 00:19:34 +00:00
community-scripts-pr-app[bot]
6db5479b26 Update CHANGELOG.md (#12341)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-25 21:47:40 +00:00
Tobias
e74ddff49a fix: overseer migration (#12340) 2026-02-25 22:47:14 +01:00
community-scripts-pr-app[bot]
80132b0332 Update CHANGELOG.md (#12338)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-25 20:02:12 +00:00
CanbiZ (MickLesk)
83a19adbb4 tools.func: Improve GitHub/Codeberg API error handling and error output (#12330) 2026-02-25 21:01:45 +01:00
community-scripts-pr-app[bot]
6b196a7c81 chore: update github-versions.json (#12334)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-25 18:24:21 +00:00
community-scripts-pr-app[bot]
30082a1ba7 Update CHANGELOG.md (#12332)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-25 17:27:38 +00:00
Tobias
7741caa6ba add: vikunja: daemon reload (#12323) 2026-02-25 18:27:11 +01:00
community-scripts-pr-app[bot]
a3841d3cef Update CHANGELOG.md (#12331)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-25 16:32:03 +00:00
CanbiZ (MickLesk)
89cdabd040 opnsense-VM: Use ip link to verify bridge existence (#12329) 2026-02-25 17:31:41 +01:00
CanbiZ (MickLesk)
cbb82812b2 wger: Use $http_host for proxy Host header (#12327) 2026-02-25 17:31:14 +01:00
community-scripts-pr-app[bot]
1c463369c7 Update .app files (#12325)
Co-authored-by: GitHub Actions <github-actions[bot]@users.noreply.github.com>
2026-02-25 16:18:48 +01:00
community-scripts-pr-app[bot]
1a1e707db3 Update CHANGELOG.md (#12326)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-25 15:16:59 +00:00
push-app-to-main[bot]
95a63e66a8 Zerobyte (#12321)
* Add zerobyte (ct)

* Refactor zerobyte.sh to streamline Node.js setup

Removed redundant NODE_VERSION setup for Node.js during the build process.

* Update note to include FUSE pre-configuration detail

* Update date_created from 2026-02-18 to 2026-02-25

* Update zerobyte-install.sh

---------

Co-authored-by: push-app-to-main[bot] <203845782+push-app-to-main[bot]@users.noreply.github.com>
Co-authored-by: CanbiZ (MickLesk) <47820557+MickLesk@users.noreply.github.com>
2026-02-25 16:16:23 +01:00
CanbiZ (MickLesk)
b09f2db2a9 Handle job-control signals and clear tostop
Prevent terminal job-control signals from suspending the script during recovery by trapping TSTP, TTIN and TTOU (instead of only TSTP) and restoring them on exit. Also clear the terminal 'tostop' flag in stop_spinner() with `stty -tostop` to avoid background spinner I/O from stopping the process group.
2026-02-25 14:45:11 +01:00
CanbiZ (MickLesk)
340d999b2b force gitea sync 2026-02-25 14:25:37 +01:00
community-scripts-pr-app[bot]
8c9874c4aa Update CHANGELOG.md (#12318)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-25 13:08:50 +00:00
community-scripts-pr-app[bot]
e9b98c2ab0 Update CHANGELOG.md (#12317)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-25 13:08:28 +00:00
CanbiZ (MickLesk)
dd46dd2d87 core: remove duplicate traps, consolidate error handling and harden signal traps (#12316)
* fix(zammad): configure Elasticsearch for LXC container startup

- Set discovery.type: single-node (required for single-node ES)
- Set xpack.security.enabled: false (not needed in local LXC)
- Set bootstrap.memory_lock: false (fails in unprivileged LXC)
- Add startup wait loop (up to 60s) to ensure ES is ready before
  Zammad installation continues

Fixes #12301-related recurring Elasticsearch startup failures

* refactor(api): eliminate duplicate traps, harden error handling & telemetry

Phase 1 - Structural:
- Remove api_exit_script() and 5 inline traps from build.func
- error_handler.func is now the sole trap owner via catch_errors()
- Update api.func comment reference (api_exit_script -> on_exit)

Phase 2 - Quality:
- Add stop_spinner() + cursor restore to error_handler(), on_interrupt(),
  on_terminate(), on_hangup() to prevent spinner/cursor artifacts
- Enhance _send_abort_telemetry() with error text (last 20 log lines),
  duration calculation, and 2 retry attempts (was fire-and-forget)
- Harden json_escape() to also strip DEL (0x7F) character

* fix(build): show spinner during post_update_to_api to prevent Ctrl+Z abort

post_update_to_api can take up to 33 seconds worst-case (3 curl attempts
x 10s timeout + sleep delays). Without any terminal output during this
time, users think the script is stuck and press Ctrl+Z, which prevents
the recovery menu from ever appearing.

Add msg_info spinner before both post_update_to_api calls in the failure
path (initial report + final force retry after recovery menu).

* fix(build): prevent SIGTSTP from killing recovery dialog

- Replace msg_info/stop_spinner with plain echo for telemetry reporting
  The background spinner process in non-interactive shells (bash -c)
  can trigger SIGTSTP, stopping the entire process group before the
  recovery dialog appears. Plain echo avoids this.

- Add trap '' TSTP at failure path entry to ignore suspension signals
  Prevents Ctrl+Z or terminal-related SIGTSTP from interrupting the
  recovery menu. Restored with trap - TSTP before exit.

- Root cause: msg_info starts a background process (spinner &) that
  is not properly detached in non-interactive shells where job control
  (set -m) is OFF. The disown builtin has no effect without job
  control, leaving the spinner in the same process group. This can
  cause terminal I/O conflicts during the 33-second post_update_to_api
  retry window, resulting in [2]+ Stopped.

* fix(test): initialize colors and remove illegal local in test harness

- Call load_functions() after sourcing core.func to initialize
  color/formatting/icon variables (RD, GN, YW, CL, TAB, etc.)
- Remove 'local' keyword from top-level scope (not inside function)
- Default REPO_SOURCE to ref_api instead of main

* chore: remove test-recovery-dialog.sh from branch

* Revert "fix(zammad): configure Elasticsearch for LXC container startup"

This reverts commit 10e450b72f.

* fix(build): show telemetry status only in verbose mode

Telemetry reporting is an implementation detail that doesn't help
the user during failure recovery. Wrap echo statements with
VERBOSE check so they only appear when verbose mode is enabled.
2026-02-25 14:08:24 +01:00
Slaviša Arežina
719761de6c Update Nginx client_max_body_size for Passbolt (#12313) 2026-02-25 14:08:02 +01:00
community-scripts-pr-app[bot]
765cd6d123 Update CHANGELOG.md (#12315)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-25 12:55:32 +00:00
CanbiZ (MickLesk)
26915e3979 Zammad: configure Elasticsearch before zammad start (#12308)
* fix(zammad): configure Elasticsearch for LXC container startup

- Set discovery.type: single-node (required for single-node ES)
- Set xpack.security.enabled: false (not needed in local LXC)
- Set bootstrap.memory_lock: false (fails in unprivileged LXC)
- Add startup wait loop (up to 60s) to ensure ES is ready before
  Zammad installation continues

Fixes #12301-related recurring Elasticsearch startup failures

* Update install/zammad-install.sh

Co-authored-by: Slaviša Arežina <58952836+tremor021@users.noreply.github.com>

---------

Co-authored-by: Slaviša Arežina <58952836+tremor021@users.noreply.github.com>
2026-02-25 13:55:07 +01:00
community-scripts-pr-app[bot]
34343bc61c chore: update github-versions.json (#12314)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-25 12:15:02 +00:00
26 changed files with 1182 additions and 203 deletions

View File

@@ -214,11 +214,12 @@ jobs:
total=$((total + 1))
slug=$(basename "$script" | sed 's/-install\.sh$//')
# Extract Source URL (GitHub only)
# Extract Source URL (GitHub only) from the "# Source:" line
# Supports both:
# # Source: https://github.com/owner/repo
# # Source: https://example.com | Github: https://github.com/owner/repo
source_url=$(head -20 "$script" | grep -oP 'https://github\.com/[^\s|]+' | head -1 || echo "")
# NOTE: Must filter for "# Source:" line first to avoid matching the License URL
source_url=$(head -20 "$script" | grep -i '# Source:' | grep -oP 'https://github\.com/[^\s|]+' | head -1 || echo "")
if [[ -z "$source_url" ]]; then
report_lines+=("| \`$slug\` | — | — | — | — | ⏭️ No GitHub source |")
continue

119
.github/workflows/close-new-script-prs.yml generated vendored Normal file
View File

@@ -0,0 +1,119 @@
name: Close Unauthorized New Script PRs
on:
pull_request_target:
branches: ["main"]
types: [opened, labeled]
jobs:
check-new-script:
if: github.repository == 'community-scripts/ProxmoxVE'
runs-on: coolify-runner
permissions:
pull-requests: write
contents: read
steps:
- name: Close PR if unauthorized new script submission
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const prNumber = pr.number;
const author = pr.user.login;
const authorType = pr.user.type; // "User" or "Bot"
const owner = context.repo.owner;
const repo = context.repo.repo;
// --- Only act on PRs with the "new script" label ---
const labels = pr.labels.map(l => l.name);
if (!labels.includes("new script")) {
core.info(`PR #${prNumber} does not have "new script" label — skipping.`);
return;
}
// --- Allow our bots ---
const allowedBots = [
"push-app-to-main[bot]",
"push-app-to-main",
];
if (allowedBots.includes(author)) {
core.info(`PR #${prNumber} by allowed bot "${author}" — skipping.`);
return;
}
// --- Check if author is a member of the contributor team ---
const teamSlug = "contributor";
let isMember = false;
try {
const { status } = await github.rest.teams.getMembershipForUserInOrg({
org: owner,
team_slug: teamSlug,
username: author,
});
// status 200 means the user is a member (active or pending)
isMember = true;
} catch (error) {
if (error.status === 404) {
isMember = false;
} else {
core.warning(`Could not check team membership for ${author}: ${error.message}`);
// Fallback: check org membership
try {
await github.rest.orgs.checkMembershipForUser({
org: owner,
username: author,
});
isMember = true;
} catch {
isMember = false;
}
}
}
if (isMember) {
core.info(`PR #${prNumber} by contributor "${author}" — skipping.`);
return;
}
// --- Unauthorized: close the PR with a comment ---
core.info(`Closing PR #${prNumber} by "${author}" — not a contributor or allowed bot.`);
const comment = [
`👋 Hi @${author},`,
``,
`Thank you for your interest in contributing a new script!`,
``,
`However, **new scripts must first be submitted to our development repository** for testing and review before they can be merged here.`,
``,
`> 🛑 New scripts must be submitted to [**ProxmoxVED**](https://github.com/community-scripts/ProxmoxVED) for testing.`,
`> PRs without prior testing will be closed.`,
``,
`Please open your PR at **https://github.com/community-scripts/ProxmoxVED** instead.`,
`Once your script has been tested and approved there, it will be pushed to this repository automatically.`,
``,
`This PR will now be closed. Thank you for understanding! 🙏`,
].join("\n");
await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body: comment,
});
await github.rest.pulls.update({
owner,
repo,
pull_number: prNumber,
state: "closed",
});
// Add a label to indicate why it was closed
await github.rest.issues.addLabels({
owner,
repo,
issue_number: prNumber,
labels: ["not a script issue"],
});

View File

@@ -407,10 +407,43 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
</details>
## 2026-02-25
## 2026-02-26
### 🆕 New Scripts
- Kima-Hub ([#12319](https://github.com/community-scripts/ProxmoxVE/pull/12319))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- hotfix: overseer version [@CrazyWolf13](https://github.com/CrazyWolf13) ([#12366](https://github.com/community-scripts/ProxmoxVE/pull/12366))
- #### ✨ New Features
- [QOL] Immich: add warning regarding library compilation time [@vhsdream](https://github.com/vhsdream) ([#12345](https://github.com/community-scripts/ProxmoxVE/pull/12345))
### 📂 Github
- github: add workflow to autom. close unauthorized new-script PRs [@MickLesk](https://github.com/MickLesk) ([#12356](https://github.com/community-scripts/ProxmoxVE/pull/12356))
## 2026-02-25
### 🆕 New Scripts
- Zerobyte ([#12321](https://github.com/community-scripts/ProxmoxVE/pull/12321))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- fix: overseer migration [@CrazyWolf13](https://github.com/CrazyWolf13) ([#12340](https://github.com/community-scripts/ProxmoxVE/pull/12340))
- add: vikunja: daemon reload [@CrazyWolf13](https://github.com/CrazyWolf13) ([#12323](https://github.com/community-scripts/ProxmoxVE/pull/12323))
- opnsense-VM: Use ip link to verify bridge existence [@MickLesk](https://github.com/MickLesk) ([#12329](https://github.com/community-scripts/ProxmoxVE/pull/12329))
- wger: Use $http_host for proxy Host header [@MickLesk](https://github.com/MickLesk) ([#12327](https://github.com/community-scripts/ProxmoxVE/pull/12327))
- Passbolt: Update Nginx config `client_max_body_size` [@tremor021](https://github.com/tremor021) ([#12313](https://github.com/community-scripts/ProxmoxVE/pull/12313))
- Zammad: configure Elasticsearch before zammad start [@MickLesk](https://github.com/MickLesk) ([#12308](https://github.com/community-scripts/ProxmoxVE/pull/12308))
- #### 🔧 Refactor
- OpenProject: Various fixes [@tremor021](https://github.com/tremor021) ([#12246](https://github.com/community-scripts/ProxmoxVE/pull/12246))
@@ -421,6 +454,14 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
- Fix detection of ssh keys [@1-tempest](https://github.com/1-tempest) ([#12230](https://github.com/community-scripts/ProxmoxVE/pull/12230))
- #### ✨ New Features
- tools.func: Improve GitHub/Codeberg API error handling and error output [@MickLesk](https://github.com/MickLesk) ([#12330](https://github.com/community-scripts/ProxmoxVE/pull/12330))
- #### 🔧 Refactor
- core: remove duplicate traps, consolidate error handling and harden signal traps [@MickLesk](https://github.com/MickLesk) ([#12316](https://github.com/community-scripts/ProxmoxVE/pull/12316))
### 📂 Github
- github: improvements for node drift wf [@MickLesk](https://github.com/MickLesk) ([#12309](https://github.com/community-scripts/ProxmoxVE/pull/12309))

6
ct/headers/kima-hub Normal file
View File

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

6
ct/headers/zerobyte Normal file
View File

@@ -0,0 +1,6 @@
_____ __ __
/__ / ___ _________ / /_ __ __/ /____
/ / / _ \/ ___/ __ \/ __ \/ / / / __/ _ \
/ /__/ __/ / / /_/ / /_/ / /_/ / /_/ __/
/____/\___/_/ \____/_.___/\__, /\__/\___/
/____/

View File

@@ -97,7 +97,7 @@ EOF
if [[ -f ~/.immich_library_revisions ]]; then
libraries=("libjxl" "libheif" "libraw" "imagemagick" "libvips")
cd "$BASE_DIR"
msg_info "Checking for updates to custom image-processing libraries"
msg_warn "Checking for updates to custom image-processing libraries (recompile time: 2-15min per library)"
$STD git pull
for library in "${libraries[@]}"; do
compile_"$library"

79
ct/kima-hub.sh Normal file
View File

@@ -0,0 +1,79 @@
#!/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/Chevron7Locked/kima-hub
APP="Kima-Hub"
var_tags="${var_tags:-music;streaming;media}"
var_cpu="${var_cpu:-4}"
var_ram="${var_ram:-8192}"
var_disk="${var_disk:-20}"
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/kima-hub ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "kima-hub" "Chevron7Locked/kima-hub"; then
msg_info "Stopping Services"
systemctl stop kima-frontend kima-backend kima-analyzer kima-analyzer-clap
msg_ok "Stopped Services"
msg_info "Backing up Data"
cp /opt/kima-hub/backend/.env /opt/kima-hub-backend-env.bak
cp /opt/kima-hub/frontend/.env /opt/kima-hub-frontend-env.bak
msg_ok "Backed up Data"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "kima-hub" "Chevron7Locked/kima-hub" "tarball"
msg_info "Restoring Data"
cp /opt/kima-hub-backend-env.bak /opt/kima-hub/backend/.env
cp /opt/kima-hub-frontend-env.bak /opt/kima-hub/frontend/.env
rm -f /opt/kima-hub-backend-env.bak /opt/kima-hub-frontend-env.bak
msg_ok "Restored Data"
msg_info "Rebuilding Backend"
cd /opt/kima-hub/backend
$STD npm install
$STD npm run build
$STD npx prisma generate
$STD npx prisma migrate deploy
msg_ok "Rebuilt Backend"
msg_info "Rebuilding Frontend"
cd /opt/kima-hub/frontend
$STD npm install
$STD npm run build
msg_ok "Rebuilt Frontend"
msg_info "Starting Services"
systemctl start kima-backend kima-frontend kima-analyzer kima-analyzer-clap
msg_ok "Started 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}:3030${CL}"

View File

@@ -28,7 +28,7 @@ function update_script() {
exit
fi
if [[ -f "$HOME/.overseerr" ]] && [[ "$(cat "$HOME/.overseerr")" == "1.34.0" ]]; then
if [[ -f "$HOME/.overseerr" ]] && [[ "$(printf '%s\n' "1.35.0" "$(cat "$HOME/.overseerr")" | sort -V | head -n1)" == "1.35.0" ]]; then
echo
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Overseerr v1.34.0 detected."

View File

@@ -65,6 +65,7 @@ function update_script() {
msg_ok "Stopped Service"
fetch_and_deploy_gh_release "vikunja" "go-vikunja/vikunja" "binary"
$STD systemctl daemon-reload
msg_info "Starting Service"
systemctl start vikunja

71
ct/zerobyte.sh Normal file
View File

@@ -0,0 +1,71 @@
#!/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: community-scripts
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/nicotsx/zerobyte
APP="Zerobyte"
var_tags="${var_tags:-backup;encryption;restic}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-6144}"
var_disk="${var_disk:-10}"
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/zerobyte ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "zerobyte" "nicotsx/zerobyte"; then
msg_info "Stopping Service"
systemctl stop zerobyte
msg_ok "Stopped Service"
msg_info "Backing up Configuration"
cp /opt/zerobyte/.env /opt/zerobyte.env.bak
msg_ok "Backed up Configuration"
NODE_VERSION="24" setup_nodejs
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "zerobyte" "nicotsx/zerobyte" "tarball"
msg_info "Building Zerobyte"
export NODE_OPTIONS="--max-old-space-size=3072"
cd /opt/zerobyte
$STD bun install
$STD node ./node_modules/vite/bin/vite.js build
msg_ok "Built Zerobyte"
msg_info "Restoring Configuration"
cp /opt/zerobyte.env.bak /opt/zerobyte/.env
rm -f /opt/zerobyte.env.bak
msg_ok "Restored Configuration"
msg_info "Starting Service"
systemctl start zerobyte
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}:4096${CL}"

View File

@@ -1,5 +1,5 @@
{
"generated": "2026-02-25T06:25:10Z",
"generated": "2026-02-26T18:16:20Z",
"versions": [
{
"slug": "2fauth",
@@ -39,9 +39,9 @@
{
"slug": "ampache",
"repo": "ampache/ampache",
"version": "7.9.0",
"version": "7.9.1",
"pinned": false,
"date": "2026-02-19T07:01:25Z"
"date": "2026-02-25T08:52:58Z"
},
{
"slug": "argus",
@@ -109,9 +109,9 @@
{
"slug": "bazarr",
"repo": "morpheus65535/bazarr",
"version": "v1.5.5",
"version": "v1.5.6",
"pinned": false,
"date": "2026-02-01T18:00:34Z"
"date": "2026-02-26T11:33:11Z"
},
{
"slug": "bentopdf",
@@ -151,9 +151,9 @@
{
"slug": "booklore",
"repo": "booklore-app/BookLore",
"version": "v2.0.1",
"version": "v2.0.2",
"pinned": false,
"date": "2026-02-24T04:15:33Z"
"date": "2026-02-25T19:59:20Z"
},
{
"slug": "bookstack",
@@ -242,9 +242,9 @@
{
"slug": "cosmos",
"repo": "azukaar/Cosmos-Server",
"version": "v0.20.2",
"version": "v0.21.2",
"pinned": false,
"date": "2026-01-24T00:12:39Z"
"date": "2026-02-26T11:32:33Z"
},
{
"slug": "cronicle",
@@ -270,16 +270,16 @@
{
"slug": "databasus",
"repo": "databasus/databasus",
"version": "v3.16.2",
"version": "v3.16.3",
"pinned": false,
"date": "2026-02-22T21:10:12Z"
"date": "2026-02-25T19:57:26Z"
},
{
"slug": "dawarich",
"repo": "Freika/dawarich",
"version": "1.2.0",
"version": "1.3.0",
"pinned": false,
"date": "2026-02-15T22:33:56Z"
"date": "2026-02-25T19:30:25Z"
},
{
"slug": "discopanel",
@@ -452,9 +452,9 @@
{
"slug": "gitea-mirror",
"repo": "RayLabsHQ/gitea-mirror",
"version": "v3.9.4",
"version": "v3.9.5",
"pinned": false,
"date": "2026-02-24T06:17:56Z"
"date": "2026-02-26T05:32:12Z"
},
{
"slug": "glance",
@@ -606,16 +606,16 @@
{
"slug": "invoiceninja",
"repo": "invoiceninja/invoiceninja",
"version": "v5.12.66",
"version": "v5.12.68",
"pinned": false,
"date": "2026-02-24T09:12:50Z"
"date": "2026-02-25T19:38:19Z"
},
{
"slug": "jackett",
"repo": "Jackett/Jackett",
"version": "v0.24.1205",
"version": "v0.24.1218",
"pinned": false,
"date": "2026-02-25T05:49:14Z"
"date": "2026-02-26T05:55:11Z"
},
{
"slug": "jellystat",
@@ -627,9 +627,9 @@
{
"slug": "joplin-server",
"repo": "laurent22/joplin",
"version": "v3.5.12",
"version": "v3.5.13",
"pinned": false,
"date": "2026-01-17T14:20:33Z"
"date": "2026-02-25T21:19:11Z"
},
{
"slug": "jotty",
@@ -666,12 +666,19 @@
"pinned": false,
"date": "2026-02-20T09:19:45Z"
},
{
"slug": "kima-hub",
"repo": "Chevron7Locked/kima-hub",
"version": "v1.5.7",
"pinned": false,
"date": "2026-02-23T23:58:59Z"
},
{
"slug": "kimai",
"repo": "kimai/kimai",
"version": "2.49.0",
"version": "2.50.0",
"pinned": false,
"date": "2026-02-15T20:40:19Z"
"date": "2026-02-25T20:13:51Z"
},
{
"slug": "kitchenowl",
@@ -711,9 +718,9 @@
{
"slug": "kubo",
"repo": "ipfs/kubo",
"version": "v0.39.0",
"version": "v0.40.0",
"pinned": false,
"date": "2025-11-27T03:47:38Z"
"date": "2026-02-25T23:16:17Z"
},
{
"slug": "kutt",
@@ -809,9 +816,9 @@
{
"slug": "mail-archiver",
"repo": "s1t5/mail-archiver",
"version": "2602.3",
"version": "2602.4",
"pinned": false,
"date": "2026-02-22T20:24:18Z"
"date": "2026-02-26T08:43:01Z"
},
{
"slug": "managemydamnlife",
@@ -823,9 +830,9 @@
{
"slug": "manyfold",
"repo": "manyfold3d/manyfold",
"version": "v0.132.1",
"version": "v0.133.1",
"pinned": false,
"date": "2026-02-09T22:02:28Z"
"date": "2026-02-26T15:50:34Z"
},
{
"slug": "mealie",
@@ -963,9 +970,9 @@
{
"slug": "oauth2-proxy",
"repo": "oauth2-proxy/oauth2-proxy",
"version": "v7.14.2",
"version": "v7.14.3",
"pinned": false,
"date": "2026-01-18T00:26:09Z"
"date": "2026-02-26T14:10:21Z"
},
{
"slug": "ombi",
@@ -995,6 +1002,13 @@
"pinned": false,
"date": "2026-02-03T09:00:43Z"
},
{
"slug": "openproject",
"repo": "jemalloc/jemalloc",
"version": "5.3.0",
"pinned": false,
"date": "2022-05-06T19:14:21Z"
},
{
"slug": "ots",
"repo": "Luzifer/ots",
@@ -1047,9 +1061,9 @@
{
"slug": "paperless-gpt",
"repo": "icereed/paperless-gpt",
"version": "v0.25.0",
"version": "v0.25.1",
"pinned": false,
"date": "2026-02-16T08:31:48Z"
"date": "2026-02-26T14:50:11Z"
},
{
"slug": "paperless-ngx",
@@ -1159,9 +1173,9 @@
{
"slug": "prometheus",
"repo": "prometheus/prometheus",
"version": "v3.9.1",
"version": "v3.10.0",
"pinned": false,
"date": "2026-01-07T17:05:53Z"
"date": "2026-02-26T01:19:51Z"
},
{
"slug": "prometheus-alertmanager",
@@ -1257,9 +1271,9 @@
{
"slug": "radicale",
"repo": "Kozea/Radicale",
"version": "v3.6.0",
"version": "v3.6.1",
"pinned": false,
"date": "2026-01-10T06:56:46Z"
"date": "2026-02-24T06:36:23Z"
},
{
"slug": "rclone",
@@ -1285,9 +1299,9 @@
{
"slug": "recyclarr",
"repo": "recyclarr/recyclarr",
"version": "v8.3.1",
"version": "v8.3.2",
"pinned": false,
"date": "2026-02-25T01:01:31Z"
"date": "2026-02-25T22:39:51Z"
},
{
"slug": "reitti",
@@ -1383,9 +1397,9 @@
{
"slug": "signoz",
"repo": "SigNoz/signoz-otel-collector",
"version": "v0.144.1",
"version": "v0.144.2",
"pinned": false,
"date": "2026-02-25T05:57:17Z"
"date": "2026-02-26T05:57:26Z"
},
{
"slug": "silverbullet",
@@ -1593,9 +1607,9 @@
{
"slug": "tunarr",
"repo": "chrisbenincasa/tunarr",
"version": "v1.1.16",
"version": "v1.1.17",
"pinned": false,
"date": "2026-02-23T21:24:47Z"
"date": "2026-02-25T19:56:36Z"
},
{
"slug": "uhf",
@@ -1656,9 +1670,9 @@
{
"slug": "vikunja",
"repo": "go-vikunja/vikunja",
"version": "v1.1.0",
"version": "v2.0.0",
"pinned": false,
"date": "2026-02-09T10:34:29Z"
"date": "2026-02-25T13:58:47Z"
},
{
"slug": "wallabag",
@@ -1772,6 +1786,13 @@
"pinned": false,
"date": "2026-02-24T15:15:46Z"
},
{
"slug": "zerobyte",
"repo": "restic/restic",
"version": "v0.18.1",
"pinned": false,
"date": "2025-09-21T18:24:38Z"
},
{
"slug": "zigbee2mqtt",
"repo": "Koenkk/zigbee2mqtt",

View File

@@ -51,6 +51,10 @@
{
"text": "Logs: `/var/log/immich`",
"type": "info"
},
{
"text": "During first install, 5 custom libraries need to be compiled from source. Depending on your CPU, this can take anywhere between 15 minutes and 2 hours. Please be patient. Touch grass or something.",
"type": "warning"
}
]
}

View File

@@ -0,0 +1,48 @@
{
"name": "Kima-Hub",
"slug": "kima-hub",
"categories": [
13
],
"date_created": "2026-02-26",
"type": "ct",
"updateable": true,
"privileged": false,
"interface_port": 3030,
"documentation": "https://github.com/Chevron7Locked/kima-hub#readme",
"website": "https://github.com/Chevron7Locked/kima-hub",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/kima-hub.webp",
"config_path": "/opt/kima-hub/backend/.env",
"description": "Self-hosted, on-demand audio streaming platform with AI-powered vibe matching, mood detection, smart playlists, and Lidarr/Audiobookshelf integration.",
"install_methods": [
{
"type": "default",
"script": "ct/kima-hub.sh",
"resources": {
"cpu": 4,
"ram": 8192,
"hdd": 20,
"os": "Debian",
"version": "13"
}
}
],
"default_credentials": {
"username": null,
"password": null
},
"notes": [
{
"text": "First user to register becomes the administrator.",
"type": "info"
},
{
"text": "Mount your music library to /music in the container.",
"type": "warning"
},
{
"text": "Audio analysis (mood/vibe detection) requires significant RAM (2-4GB per worker).",
"type": "info"
}
]
}

View File

@@ -0,0 +1,40 @@
{
"name": "Zerobyte",
"slug": "zerobyte",
"categories": [
7
],
"date_created": "2026-02-25",
"type": "ct",
"updateable": true,
"privileged": false,
"interface_port": 4096,
"documentation": "https://github.com/nicotsx/zerobyte#readme",
"website": "https://github.com/nicotsx/zerobyte",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/zerobyte.webp",
"config_path": "/opt/zerobyte/.env",
"description": "Zerobyte is a backup automation tool built on top of Restic that provides a modern web interface to schedule, manage, and monitor encrypted backups across multiple storage backends including NFS, SMB, WebDAV, SFTP, S3, and local directories.",
"install_methods": [
{
"type": "default",
"script": "ct/zerobyte.sh",
"resources": {
"cpu": 2,
"ram": 6144,
"hdd": 10,
"os": "Debian",
"version": "13"
}
}
],
"default_credentials": {
"username": null,
"password": null
},
"notes": [
{
"text": "For remote mount support (NFS, SMB, WebDAV, SFTP), enable FUSE device passthrough on the LXC container. (FUSE is pre-configured)",
"type": "info"
}
]
}

View File

@@ -154,7 +154,7 @@ sed -i -e "/^#shared_preload/s/^#//;/^shared_preload/s/''/'vchord.so'/" /etc/pos
systemctl restart postgresql.service
PG_DB_NAME="immich" PG_DB_USER="immich" PG_DB_GRANT_SUPERUSER="true" PG_DB_SKIP_ALTER_ROLE="true" setup_postgresql_db
msg_info "Compiling Custom Photo-processing Library (extreme patience)"
msg_warn "Compiling Custom Photo-processing Libraries (can take anywhere from 15min to 2h)"
LD_LIBRARY_PATH=/usr/local/lib
export LD_RUN_PATH=/usr/local/lib
STAGING_DIR=/opt/staging

212
install/kima-hub-install.sh Normal file
View File

@@ -0,0 +1,212 @@
#!/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/Chevron7Locked/kima-hub
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
msg_info "Installing Dependencies"
$STD apt-get install -y \
build-essential \
git \
openssl \
ffmpeg \
python3 \
python3-pip \
python3-dev \
python3-numpy \
redis-server
msg_ok "Installed Dependencies"
PG_VERSION="16" PG_MODULES="pgvector" setup_postgresql
PG_DB_NAME="kima" PG_DB_USER="kima" PG_DB_GRANT_SUPERUSER="true" setup_postgresql_db
NODE_VERSION="20" setup_nodejs
msg_info "Configuring Redis"
systemctl enable -q --now redis-server
msg_ok "Configured Redis"
fetch_and_deploy_gh_release "kima-hub" "Chevron7Locked/kima-hub" "tarball"
msg_info "Installing Python Dependencies"
export PIP_BREAK_SYSTEM_PACKAGES=1
$STD pip3 install --no-cache-dir \
tensorflow \
essentia-tensorflow \
redis \
psycopg2-binary \
laion-clap \
torch \
torchaudio \
librosa \
transformers \
pgvector \
python-dotenv \
requests
msg_ok "Installed Python Dependencies"
msg_info "Downloading Essentia ML Models"
mkdir -p /opt/kima-hub/models
cd /opt/kima-hub/models
curl -fsSL -o msd-musicnn-1.pb "https://essentia.upf.edu/models/autotagging/msd/msd-musicnn-1.pb"
curl -fsSL -o mood_happy-msd-musicnn-1.pb "https://essentia.upf.edu/models/classification-heads/mood_happy/mood_happy-msd-musicnn-1.pb"
curl -fsSL -o mood_sad-msd-musicnn-1.pb "https://essentia.upf.edu/models/classification-heads/mood_sad/mood_sad-msd-musicnn-1.pb"
curl -fsSL -o mood_relaxed-msd-musicnn-1.pb "https://essentia.upf.edu/models/classification-heads/mood_relaxed/mood_relaxed-msd-musicnn-1.pb"
curl -fsSL -o mood_aggressive-msd-musicnn-1.pb "https://essentia.upf.edu/models/classification-heads/mood_aggressive/mood_aggressive-msd-musicnn-1.pb"
curl -fsSL -o mood_party-msd-musicnn-1.pb "https://essentia.upf.edu/models/classification-heads/mood_party/mood_party-msd-musicnn-1.pb"
curl -fsSL -o mood_acoustic-msd-musicnn-1.pb "https://essentia.upf.edu/models/classification-heads/mood_acoustic/mood_acoustic-msd-musicnn-1.pb"
curl -fsSL -o mood_electronic-msd-musicnn-1.pb "https://essentia.upf.edu/models/classification-heads/mood_electronic/mood_electronic-msd-musicnn-1.pb"
curl -fsSL -o danceability-msd-musicnn-1.pb "https://essentia.upf.edu/models/classification-heads/danceability/danceability-msd-musicnn-1.pb"
curl -fsSL -o voice_instrumental-msd-musicnn-1.pb "https://essentia.upf.edu/models/classification-heads/voice_instrumental/voice_instrumental-msd-musicnn-1.pb"
msg_ok "Downloaded Essentia ML Models"
msg_info "Downloading CLAP Model"
curl -fsSL -o /opt/kima-hub/models/music_audioset_epoch_15_esc_90.14.pt "https://huggingface.co/lukewys/laion_clap/resolve/main/music_audioset_epoch_15_esc_90.14.pt"
msg_ok "Downloaded CLAP Model"
msg_info "Building Backend"
cd /opt/kima-hub/backend
$STD npm ci
$STD npm run build
msg_ok "Built Backend"
msg_info "Configuring Backend"
SESSION_SECRET=$(openssl rand -hex 32)
ENCRYPTION_KEY=$(openssl rand -hex 32)
cat <<EOF >/opt/kima-hub/backend/.env
NODE_ENV=production
DATABASE_URL=postgresql://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME}
REDIS_URL=redis://localhost:6379
PORT=3006
MUSIC_PATH=/music
TRANSCODE_CACHE_PATH=/opt/kima-hub/cache/transcodes
SESSION_SECRET=${SESSION_SECRET}
SETTINGS_ENCRYPTION_KEY=${ENCRYPTION_KEY}
INTERNAL_API_SECRET=$(openssl rand -hex 16)
EOF
msg_ok "Configured Backend"
msg_info "Running Database Migrations"
cd /opt/kima-hub/backend
$STD npx prisma generate
$STD npx prisma migrate deploy
msg_ok "Ran Database Migrations"
msg_info "Building Frontend"
cd /opt/kima-hub/frontend
$STD npm ci
export NEXT_PUBLIC_BACKEND_URL=http://127.0.0.1:3006
$STD npm run build
msg_ok "Built Frontend"
msg_info "Configuring Frontend"
cat <<EOF >/opt/kima-hub/frontend/.env
NODE_ENV=production
BACKEND_URL=http://localhost:3006
PORT=3030
EOF
msg_ok "Configured Frontend"
msg_info "Creating Directories"
mkdir -p /opt/kima-hub/cache/transcodes
mkdir -p /music
msg_ok "Created Directories"
msg_info "Creating Services"
cat <<EOF >/etc/systemd/system/kima-backend.service
[Unit]
Description=Kima Hub Backend
After=network.target postgresql.service redis-server.service
Wants=postgresql.service redis-server.service
[Service]
Type=simple
User=root
WorkingDirectory=/opt/kima-hub/backend
EnvironmentFile=/opt/kima-hub/backend/.env
ExecStart=/usr/bin/node /opt/kima-hub/backend/dist/index.js
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
cat <<EOF >/etc/systemd/system/kima-frontend.service
[Unit]
Description=Kima Hub Frontend
After=network.target kima-backend.service
[Service]
Type=simple
User=root
WorkingDirectory=/opt/kima-hub/frontend
EnvironmentFile=/opt/kima-hub/frontend/.env
ExecStart=/usr/bin/npm start
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
cat <<EOF >/etc/systemd/system/kima-analyzer.service
[Unit]
Description=Kima Hub Audio Analyzer (Essentia)
After=network.target postgresql.service redis-server.service kima-backend.service
[Service]
Type=simple
User=root
WorkingDirectory=/opt/kima-hub/services/audio-analyzer
Environment=DATABASE_URL=postgresql://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME}
Environment=REDIS_URL=redis://localhost:6379
Environment=MUSIC_PATH=/music
Environment=BATCH_SIZE=10
Environment=SLEEP_INTERVAL=5
Environment=NUM_WORKERS=2
Environment=THREADS_PER_WORKER=1
ExecStart=/usr/bin/python3 /opt/kima-hub/services/audio-analyzer/analyzer.py
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
cat <<EOF >/etc/systemd/system/kima-analyzer-clap.service
[Unit]
Description=Kima Hub CLAP Audio Analyzer
After=network.target postgresql.service redis-server.service kima-backend.service kima-analyzer.service
[Service]
Type=simple
User=root
WorkingDirectory=/opt/kima-hub/services/audio-analyzer-clap
Environment=DATABASE_URL=postgresql://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME}
Environment=REDIS_URL=redis://localhost:6379
Environment=BACKEND_URL=http://localhost:3006
Environment=MUSIC_PATH=/music
Environment=SLEEP_INTERVAL=5
Environment=NUM_WORKERS=1
ExecStart=/usr/bin/python3 /opt/kima-hub/services/audio-analyzer-clap/analyzer.py
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now kima-backend kima-frontend kima-analyzer kima-analyzer-clap
msg_ok "Created Services"
motd_ssh
customize
cleanup_lxc

View File

@@ -164,7 +164,7 @@ server {
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;

View File

@@ -28,12 +28,23 @@ setup_deb822_repo \
"stable" \
"main"
$STD apt install -y elasticsearch
sed -i 's/^-Xms.*/-Xms2g/' /etc/elasticsearch/jvm.options
sed -i 's/^-Xmx.*/-Xmx2g/' /etc/elasticsearch/jvm.options
sed -i 's/^#\{0,2\} *-Xms[0-9]*g.*/-Xms2g/' /etc/elasticsearch/jvm.options
sed -i 's/^#\{0,2\} *-Xmx[0-9]*g.*/-Xmx2g/' /etc/elasticsearch/jvm.options
cat <<EOF >>/etc/elasticsearch/elasticsearch.yml
discovery.type: single-node
xpack.security.enabled: false
bootstrap.memory_lock: false
EOF
$STD /usr/share/elasticsearch/bin/elasticsearch-plugin install ingest-attachment -b
systemctl daemon-reload
systemctl enable -q elasticsearch
systemctl restart -q elasticsearch
for i in $(seq 1 30); do
if curl -s http://localhost:9200 >/dev/null 2>&1; then
break
fi
sleep 2
done
msg_ok "Setup Elasticsearch"
msg_info "Installing Zammad"

View File

@@ -0,0 +1,96 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: community-scripts
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/nicotsx/zerobyte
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
msg_info "Installing Dependencies"
echo "davfs2 davfs2/suid_file boolean false" | debconf-set-selections
$STD apt-get install -y \
bzip2 \
fuse3 \
sshfs \
davfs2 \
openssh-client
msg_ok "Installed Dependencies"
fetch_and_deploy_gh_release "restic" "restic/restic" "singlefile" "latest" "/usr/local/bin" "restic_*_linux_amd64.bz2"
mv /usr/local/bin/restic /usr/local/bin/restic.bz2
bzip2 -d /usr/local/bin/restic.bz2
chmod +x /usr/local/bin/restic
fetch_and_deploy_gh_release "rclone" "rclone/rclone" "prebuild" "latest" "/opt/rclone" "rclone-*-linux-amd64.zip"
ln -sf /opt/rclone/rclone /usr/local/bin/rclone
fetch_and_deploy_gh_release "shoutrrr" "nicholas-fedor/shoutrrr" "prebuild" "latest" "/opt/shoutrrr" "shoutrrr_linux_amd64_*.tar.gz"
ln -sf /opt/shoutrrr/shoutrrr /usr/local/bin/shoutrrr
msg_info "Installing Bun"
export BUN_INSTALL="/root/.bun"
curl -fsSL https://bun.sh/install | $STD bash
ln -sf /root/.bun/bin/bun /usr/local/bin/bun
ln -sf /root/.bun/bin/bunx /usr/local/bin/bunx
msg_ok "Installed Bun"
NODE_VERSION="24" setup_nodejs
fetch_and_deploy_gh_release "zerobyte" "nicotsx/zerobyte" "tarball"
msg_info "Building Zerobyte (Patience)"
cd /opt/zerobyte
export VITE_RESTIC_VERSION=$(cat ~/.restic)
export VITE_RCLONE_VERSION=$(cat ~/.rclone)
export VITE_SHOUTRRR_VERSION=$(cat ~/.shoutrrr)
export NODE_OPTIONS="--max-old-space-size=3072"
$STD bun install
$STD node ./node_modules/vite/bin/vite.js build
msg_ok "Built Zerobyte"
msg_info "Configuring Zerobyte"
mkdir -p /var/lib/zerobyte/{data,restic/cache,repositories,volumes}
APP_SECRET=$(openssl rand -hex 32)
cat <<EOF >/opt/zerobyte/.env
BASE_URL=http://${LOCAL_IP}:4096
APP_SECRET=${APP_SECRET}
PORT=4096
ZEROBYTE_DATABASE_URL=/var/lib/zerobyte/data/zerobyte.db
RESTIC_CACHE_DIR=/var/lib/zerobyte/restic/cache
ZEROBYTE_REPOSITORIES_DIR=/var/lib/zerobyte/repositories
ZEROBYTE_VOLUMES_DIR=/var/lib/zerobyte/volumes
MIGRATIONS_PATH=/opt/zerobyte/app/drizzle
NODE_ENV=production
EOF
msg_ok "Configured Zerobyte"
msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/zerobyte.service
[Unit]
Description=Zerobyte Backup Automation
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/zerobyte
EnvironmentFile=/opt/zerobyte/.env
ExecStart=/usr/local/bin/bun .output/server/index.mjs
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now zerobyte
msg_ok "Created Service"
motd_ssh
customize
cleanup_lxc

View File

@@ -312,7 +312,8 @@ json_escape() {
s=${s//$'\r'/}
s=${s//$'\t'/\\t}
# Remove any remaining control characters (0x00-0x1F except those already handled)
s=$(printf '%s' "$s" | tr -d '\000-\010\013\014\016-\037')
# Also remove DEL (0x7F) and invalid high bytes that break JSON parsers
s=$(printf '%s' "$s" | tr -d '\000-\010\013\014\016-\037\177')
printf '%s' "$s"
}
@@ -982,7 +983,7 @@ EOF
fi
# All 3 attempts failed — do NOT set POST_UPDATE_DONE=true.
# This allows the EXIT trap (api_exit_script) to retry with 3 fresh attempts.
# This allows the EXIT trap (on_exit in error_handler.func) to retry.
# No infinite loop risk: EXIT trap fires exactly once.
}

View File

@@ -118,7 +118,7 @@ maxkeys_check() {
# Exit if kernel parameters are unavailable
if [[ "$per_user_maxkeys" -eq 0 || "$per_user_maxbytes" -eq 0 ]]; then
echo -e "${CROSS}${RD} Error: Unable to read kernel parameters. Ensure proper permissions.${CL}"
msg_error "Unable to read kernel key parameters. Ensure proper permissions."
exit 1
fi
@@ -135,19 +135,19 @@ maxkeys_check() {
# Check if key or byte usage is near limits
failure=0
if [[ "$used_lxc_keys" -gt "$threshold_keys" ]]; then
echo -e "${CROSS}${RD} Warning: Key usage is near the limit (${used_lxc_keys}/${per_user_maxkeys}).${CL}"
msg_warn "Key usage is near the limit (${used_lxc_keys}/${per_user_maxkeys})"
echo -e "${INFO} Suggested action: Set ${GN}kernel.keys.maxkeys=${new_limit_keys}${CL} in ${BOLD}/etc/sysctl.d/98-community-scripts.conf${CL}."
failure=1
fi
if [[ "$used_lxc_bytes" -gt "$threshold_bytes" ]]; then
echo -e "${CROSS}${RD} Warning: Key byte usage is near the limit (${used_lxc_bytes}/${per_user_maxbytes}).${CL}"
msg_warn "Key byte usage is near the limit (${used_lxc_bytes}/${per_user_maxbytes})"
echo -e "${INFO} Suggested action: Set ${GN}kernel.keys.maxbytes=${new_limit_bytes}${CL} in ${BOLD}/etc/sysctl.d/98-community-scripts.conf${CL}."
failure=1
fi
# Provide next steps if issues are detected
if [[ "$failure" -eq 1 ]]; then
echo -e "${INFO} To apply changes, run: ${BOLD}service procps force-reload${CL}"
msg_error "Kernel key limits exceeded - see suggestions above"
exit 1
fi
@@ -2034,6 +2034,7 @@ advanced_settings() {
((STEP++))
else
whiptail --msgbox "Default bridge 'vmbr0' not found!\n\nPlease configure a network bridge in Proxmox first." 10 58
msg_error "Default bridge 'vmbr0' not found"
exit 1
fi
else
@@ -3049,7 +3050,7 @@ install_script() {
CHOICE=""
;;
*)
echo -e "${CROSS}${RD}Invalid option: $CHOICE${CL}"
msg_error "Invalid option: $CHOICE"
exit 1
;;
esac
@@ -3128,12 +3129,12 @@ check_container_resources() {
current_cpu=$(nproc)
if [[ "$current_ram" -lt "$var_ram" ]] || [[ "$current_cpu" -lt "$var_cpu" ]]; then
echo -e "\n${INFO}${HOLD} ${GN}Required: ${var_cpu} CPU, ${var_ram}MB RAM ${CL}| ${RD}Current: ${current_cpu} CPU, ${current_ram}MB RAM${CL}"
msg_warn "Under-provisioned: Required ${var_cpu} CPU/${var_ram}MB RAM, Current ${current_cpu} CPU/${current_ram}MB RAM"
echo -e "${YWB}Please ensure that the ${APP} LXC is configured with at least ${var_cpu} vCPU and ${var_ram} MB RAM for the build process.${CL}\n"
echo -ne "${INFO}${HOLD} May cause data loss! ${INFO} Continue update with under-provisioned LXC? <yes/No> "
read -r prompt
if [[ ! ${prompt,,} =~ ^(yes)$ ]]; then
echo -e "${CROSS}${HOLD} ${YWB}Exiting based on user input.${CL}"
msg_error "Aborted: under-provisioned LXC (${current_cpu} CPU/${current_ram}MB RAM < ${var_cpu} CPU/${var_ram}MB RAM)"
exit 1
fi
else
@@ -3152,11 +3153,11 @@ check_container_storage() {
local used_size=$(df /boot --output=used | tail -n 1)
usage=$((100 * used_size / total_size))
if ((usage > 80)); then
echo -e "${INFO}${HOLD} ${YWB}Warning: Storage is dangerously low (${usage}%).${CL}"
msg_warn "Storage is dangerously low (${usage}% used on /boot)"
echo -ne "Continue anyway? <y/N> "
read -r prompt
if [[ ! ${prompt,,} =~ ^(y|yes)$ ]]; then
echo -e "${CROSS}${HOLD}${YWB}Exiting based on user input.${CL}"
msg_error "Aborted: storage too low (${usage}% used)"
exit 1
fi
fi
@@ -3546,10 +3547,16 @@ build_container() {
# Build PCT_OPTIONS as string for export
TEMP_DIR=$(mktemp -d)
pushd "$TEMP_DIR" >/dev/null
local _func_url
if [ "$var_os" == "alpine" ]; then
export FUNCTIONS_FILE_PATH="$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/alpine-install.func)"
_func_url="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/alpine-install.func"
else
export FUNCTIONS_FILE_PATH="$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/install.func)"
_func_url="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/install.func"
fi
export FUNCTIONS_FILE_PATH="$(curl -fsSL "$_func_url")"
if [[ -z "$FUNCTIONS_FILE_PATH" || ${#FUNCTIONS_FILE_PATH} -lt 100 ]]; then
msg_error "Failed to download install functions from: $_func_url"
exit 1
fi
# Core exports for install.func
@@ -3920,7 +3927,9 @@ EOF
fi
sleep 1
if [ "$i" -eq 10 ]; then
msg_error "LXC Container did not reach running state"
local ct_status
ct_status=$(pct status "$CTID" 2>/dev/null || echo "unknown")
msg_error "LXC Container did not reach running state (status: ${ct_status})"
exit 1
fi
done
@@ -3944,7 +3953,7 @@ EOF
if [ -z "$ip_in_lxc" ]; then
msg_error "No IP assigned to CT $CTID after 20s"
echo -e "${YW}Troubleshooting:${CL}"
msg_custom "🔧" "${YW}" "Troubleshooting:"
echo " • Verify bridge ${BRG} exists and has connectivity"
echo " • Check if DHCP server is reachable (if using DHCP)"
echo " • Verify static IP configuration (if using static IP)"
@@ -3966,8 +3975,7 @@ EOF
done
if [ "$ping_success" = false ]; then
msg_warn "Network configured (IP: $ip_in_lxc) but connectivity test failed"
echo -e "${YW}Container may have limited internet access. Installation will continue...${CL}"
msg_warn "Network configured (IP: $ip_in_lxc) but connectivity test failed - installation will continue"
else
msg_ok "Network in LXC is reachable (ping)"
fi
@@ -4011,7 +4019,10 @@ EOF
http://dl-cdn.alpinelinux.org/alpine/latest-stable/main
http://dl-cdn.alpinelinux.org/alpine/latest-stable/community
EOF'
pct exec "$CTID" -- ash -c "apk add bash newt curl openssh nano mc ncurses jq >/dev/null"
pct exec "$CTID" -- ash -c "apk add bash newt curl openssh nano mc ncurses jq >/dev/null" || {
msg_error "Failed to install base packages in Alpine container"
exit 1
}
else
sleep 3
LANG=${LANG:-en_US.UTF-8}
@@ -4098,6 +4109,12 @@ EOF'
# Installation failed?
if [[ $install_exit_code -ne 0 ]]; then
# Prevent job-control signals from suspending the script during recovery.
# In non-interactive shells (bash -c), background processes (spinner) can
# trigger terminal-related signals that stop the entire process group.
# TSTP = Ctrl+Z, TTIN = bg read from tty, TTOU = bg write to tty (tostop)
trap '' TSTP TTIN TTOU
msg_error "Installation failed in container ${CTID} (exit code: ${install_exit_code})"
# Copy install log from container BEFORE API call so get_error_text() can read it
@@ -4173,7 +4190,12 @@ EOF'
fi
# Report failure to telemetry API (now with log available on host)
# NOTE: Do NOT use msg_info/spinner here — the background spinner process
# causes SIGTSTP in non-interactive shells (bash -c "$(curl ...)"), which
# stops the entire process group and prevents the recovery dialog from appearing.
$STD echo -e "${TAB}⏳ Reporting failure to telemetry..."
post_update_to_api "failed" "$install_exit_code"
$STD echo -e "${TAB}${CM:-} Failure reported"
# Defense-in-depth: Ensure error handling stays disabled during recovery.
# Some functions (e.g. silent/$STD) unconditionally re-enable set -Eeuo pipefail
@@ -4537,8 +4559,12 @@ EOF'
# Force one final status update attempt after cleanup
# This ensures status is updated even if the first attempt failed (e.g., HTTP 400)
$STD echo -e "${TAB}⏳ Finalizing telemetry report..."
post_update_to_api "failed" "$install_exit_code" "force"
$STD echo -e "${TAB}${CM:-} Telemetry finalized"
# Restore default job-control signal handling before exit
trap - TSTP TTIN TTOU
exit $install_exit_code
fi
@@ -4893,8 +4919,7 @@ create_lxc_container() {
return 0
fi
echo
echo "An update for the Proxmox LXC stack is available:"
msg_info "An update for the Proxmox LXC stack is available"
echo " pve-container: installed=${_pvec_i:-n/a} candidate=${_pvec_c:-n/a}"
echo " lxc-pve : installed=${_lxcp_i:-n/a} candidate=${_lxcp_c:-n/a}"
echo
@@ -4946,7 +4971,6 @@ create_lxc_container() {
exit 205
}
if qm status "$CTID" &>/dev/null || pct status "$CTID" &>/dev/null; then
echo -e "ID '$CTID' is already in use."
unset CTID
msg_error "Cannot use ID that is already in use."
exit 206
@@ -5004,17 +5028,40 @@ create_lxc_container() {
msg_info "Validating storage '$CONTAINER_STORAGE'"
STORAGE_TYPE=$(grep -E "^[^:]+: $CONTAINER_STORAGE$" /etc/pve/storage.cfg | cut -d: -f1 | head -1)
if [[ -z "$STORAGE_TYPE" ]]; then
msg_error "Storage '$CONTAINER_STORAGE' not found in /etc/pve/storage.cfg"
exit 213
fi
case "$STORAGE_TYPE" in
iscsidirect) exit 212 ;;
iscsi | zfs) exit 213 ;;
cephfs) exit 219 ;;
pbs) exit 224 ;;
iscsidirect)
msg_error "Storage '$CONTAINER_STORAGE' uses iSCSI-direct which does not support container rootfs."
exit 212
;;
iscsi | zfs)
msg_error "Storage '$CONTAINER_STORAGE' ($STORAGE_TYPE) does not support container rootdir content."
exit 213
;;
cephfs)
msg_error "Storage '$CONTAINER_STORAGE' uses CephFS which is not supported for LXC rootfs."
exit 219
;;
pbs)
msg_error "Storage '$CONTAINER_STORAGE' is a Proxmox Backup Server — cannot be used for containers."
exit 224
;;
linstor | rbd | nfs | cifs)
pvesm status -storage "$CONTAINER_STORAGE" &>/dev/null || exit 217
if ! pvesm status -storage "$CONTAINER_STORAGE" &>/dev/null; then
msg_error "Storage '$CONTAINER_STORAGE' ($STORAGE_TYPE) is not accessible or inactive."
exit 217
fi
;;
esac
pvesm status -content rootdir 2>/dev/null | awk 'NR>1{print $1}' | grep -qx "$CONTAINER_STORAGE" || exit 213
if ! pvesm status -content rootdir 2>/dev/null | awk 'NR>1{print $1}' | grep -qx "$CONTAINER_STORAGE"; then
msg_error "Storage '$CONTAINER_STORAGE' ($STORAGE_TYPE) does not support 'rootdir' content."
exit 213
fi
msg_ok "Storage '$CONTAINER_STORAGE' ($STORAGE_TYPE) validated"
msg_info "Validating template storage '$TEMPLATE_STORAGE'"
@@ -5087,8 +5134,7 @@ create_lxc_container() {
# If still no template, try to find alternatives
if [[ -z "$TEMPLATE" ]]; then
echo ""
echo "[DEBUG] No template found for ${PCT_OSTYPE} ${PCT_OSVERSION}, searching for alternatives..."
msg_warn "No template found for ${PCT_OSTYPE} ${PCT_OSVERSION}, searching for alternatives..."
# Get all available versions for this OS type
AVAILABLE_VERSIONS=()
@@ -5362,13 +5408,19 @@ create_lxc_container() {
if [[ ! -s "$TEMPLATE_PATH" || "$(stat -c%s "$TEMPLATE_PATH" 2>/dev/null || echo 0)" -lt 1000000 ]]; then
msg_info "Template file missing or too small downloading"
rm -f "$TEMPLATE_PATH"
pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >/dev/null 2>&1
pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >/dev/null 2>&1 || {
msg_error "Failed to download template '$TEMPLATE' to storage '$TEMPLATE_STORAGE'"
exit 222
}
msg_ok "Template downloaded"
elif ! tar -tf "$TEMPLATE_PATH" &>/dev/null; then
if [[ -n "$ONLINE_TEMPLATE" ]]; then
msg_info "Template appears corrupted re-downloading"
rm -f "$TEMPLATE_PATH"
pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >/dev/null 2>&1
pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >/dev/null 2>&1 || {
msg_error "Failed to re-download template '$TEMPLATE'"
exit 222
}
msg_ok "Template re-downloaded"
else
msg_warn "Template appears corrupted, but no online version exists. Skipping re-download."
@@ -5410,20 +5462,17 @@ create_lxc_container() {
if ! pct create "$CTID" "local:vztmpl/${TEMPLATE}" $PCT_OPTIONS >>"$LOGFILE" 2>&1; then
# Local fallback also failed - check for LXC stack version issue
if grep -qiE 'unsupported .* version' "$LOGFILE"; then
echo
echo "pct reported 'unsupported ... version' your LXC stack might be too old for this template."
echo "We can try to upgrade 'pve-container' and 'lxc-pve' now and retry automatically."
msg_warn "pct reported 'unsupported version' LXC stack might be too old for this template"
offer_lxc_stack_upgrade_and_maybe_retry "yes"
rc=$?
case $rc in
0) : ;; # success - container created, continue
2)
echo "Upgrade was declined. Please update and re-run:
apt update && apt install --only-upgrade pve-container lxc-pve"
msg_error "Upgrade declined. Please update and re-run: apt update && apt install --only-upgrade pve-container lxc-pve"
exit 231
;;
3)
echo "Upgrade and/or retry failed. Please inspect: $LOGFILE"
msg_error "Upgrade and/or retry failed. Please inspect: $LOGFILE"
exit 231
;;
esac
@@ -5442,20 +5491,17 @@ create_lxc_container() {
else
# Already on local storage and still failed - check LXC stack version
if grep -qiE 'unsupported .* version' "$LOGFILE"; then
echo
echo "pct reported 'unsupported ... version' your LXC stack might be too old for this template."
echo "We can try to upgrade 'pve-container' and 'lxc-pve' now and retry automatically."
msg_warn "pct reported 'unsupported version' LXC stack might be too old for this template"
offer_lxc_stack_upgrade_and_maybe_retry "yes"
rc=$?
case $rc in
0) : ;; # success - container created, continue
2)
echo "Upgrade was declined. Please update and re-run:
apt update && apt install --only-upgrade pve-container lxc-pve"
msg_error "Upgrade declined. Please update and re-run: apt update && apt install --only-upgrade pve-container lxc-pve"
exit 231
;;
3)
echo "Upgrade and/or retry failed. Please inspect: $LOGFILE"
msg_error "Upgrade and/or retry failed. Please inspect: $LOGFILE"
exit 231
;;
esac
@@ -5608,44 +5654,21 @@ ensure_log_on_host() {
fi
}
# ------------------------------------------------------------------------------
# api_exit_script()
# ==============================================================================
# TRAP MANAGEMENT
# ==============================================================================
# All traps (ERR, EXIT, INT, TERM, HUP) are set by catch_errors() in
# error_handler.func — called at the top of this file after sourcing.
#
# - Exit trap handler for reporting to API telemetry
# - Captures exit code and reports to PocketBase using centralized error descriptions
# - Uses explain_exit_code() from api.func for consistent error messages
# - ALWAYS sends telemetry FIRST before log collection to prevent pct pull
# hangs from blocking status updates (container may be dead/unresponsive)
# - For non-zero exit codes: posts "failed" status
# - For zero exit codes where post_update_to_api was never called:
# catches orphaned "installing" records (e.g., script exited cleanly
# but description() was never reached)
# ------------------------------------------------------------------------------
api_exit_script() {
local exit_code=$?
if [ $exit_code -ne 0 ]; then
# ALWAYS send telemetry FIRST - ensure status is reported even if
# ensure_log_on_host hangs (e.g. pct pull on dead container)
post_update_to_api "failed" "$exit_code" 2>/dev/null || true
# Best-effort log collection (non-critical after telemetry is sent)
if declare -f ensure_log_on_host >/dev/null 2>&1; then
ensure_log_on_host 2>/dev/null || true
fi
# Stop orphaned container if we're in the install phase
if [[ "${CONTAINER_INSTALLING:-}" == "true" && -n "${CTID:-}" ]] && command -v pct &>/dev/null; then
pct stop "$CTID" 2>/dev/null || true
fi
elif [[ "${POST_TO_API_DONE:-}" == "true" && "${POST_UPDATE_DONE:-}" != "true" ]]; then
# Script exited with 0 but never sent a completion status
# exit_code=0 is never an error — report as success
post_update_to_api "done" "0"
fi
}
if command -v pveversion >/dev/null 2>&1; then
trap 'api_exit_script' EXIT
fi
trap 'local _ec=$?; if [[ $_ec -ne 0 ]]; then post_update_to_api "failed" "$_ec" 2>/dev/null || true; if declare -f ensure_log_on_host &>/dev/null; then ensure_log_on_host 2>/dev/null || true; fi; fi' ERR
trap 'post_update_to_api "failed" "129" 2>/dev/null || true; if [[ -n "${CTID:-}" ]] && command -v pct &>/dev/null; then pct stop "$CTID" 2>/dev/null || true; fi; exit 129' SIGHUP
trap 'post_update_to_api "failed" "130" 2>/dev/null || true; if [[ -n "${CTID:-}" ]] && command -v pct &>/dev/null; then pct stop "$CTID" 2>/dev/null || true; fi; exit 130' SIGINT
trap 'post_update_to_api "failed" "143" 2>/dev/null || true; if [[ -n "${CTID:-}" ]] && command -v pct &>/dev/null; then pct stop "$CTID" 2>/dev/null || true; fi; exit 143' SIGTERM
# Do NOT set duplicate traps here. The handlers in error_handler.func
# (on_exit, on_interrupt, on_terminate, on_hangup, error_handler) already:
# - Send telemetry via post_update_to_api / _send_abort_telemetry
# - Stop orphaned containers via _stop_container_if_installing
# - Collect logs via ensure_log_on_host
# - Clean up lock files and spinner processes
#
# Previously, inline traps here overwrote catch_errors() traps, causing:
# - error_handler() never fired (no error output, no cleanup dialog)
# - on_hangup() never fired (SSH disconnect → stuck records)
# - Duplicated logic in two places (hard to debug)
# ==============================================================================

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."
echo -e "\nExiting..."
sleep 2
exit
exit 1
fi
}
@@ -293,7 +293,7 @@ root_check() {
msg_error "Please run this script as root."
echo -e "\nExiting..."
sleep 2
exit
exit 1
fi
}
@@ -345,11 +345,10 @@ pve_check() {
# ------------------------------------------------------------------------------
arch_check() {
if [ "$(dpkg --print-architecture)" != "amd64" ]; then
echo -e "\n ${INFO}${YWB}This script will not work with PiMox! \n"
echo -e "\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \n"
echo -e "Exiting..."
msg_error "This script will not work with PiMox (ARM architecture detected)."
msg_warn "Visit https://github.com/asylumexp/Proxmox for ARM64 support."
sleep 2
exit
exit 1
fi
}
@@ -530,7 +529,9 @@ silent() {
if [[ $rc -ne 0 ]]; then
# Source explain_exit_code if needed
if ! declare -f explain_exit_code >/dev/null 2>&1; then
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
if ! source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func); then
explain_exit_code() { echo "unknown (error_handler.func download failed)"; }
fi
fi
local explanation
@@ -607,6 +608,7 @@ stop_spinner() {
unset SPINNER_PID SPINNER_MSG
stty sane 2>/dev/null || true
stty -tostop 2>/dev/null || true
}
# ==============================================================================
@@ -784,8 +786,8 @@ fatal() {
# ------------------------------------------------------------------------------
exit_script() {
clear
echo -e "\n${CROSS}${RD}User exited script${CL}\n"
exit
msg_error "User exited script"
exit 0
}
# ------------------------------------------------------------------------------
@@ -806,6 +808,7 @@ get_header() {
if [ ! -s "$local_header_path" ]; then
if ! curl -fsSL "$header_url" -o "$local_header_path"; then
msg_warn "Failed to download header: $header_url"
return 1
fi
fi
@@ -846,10 +849,10 @@ header_info() {
ensure_tput() {
if ! command -v tput >/dev/null 2>&1; then
if grep -qi 'alpine' /etc/os-release; then
apk add --no-cache ncurses >/dev/null 2>&1
apk add --no-cache ncurses >/dev/null 2>&1 || msg_warn "Failed to install ncurses (tput may be unavailable)"
elif command -v apt-get >/dev/null 2>&1; then
apt-get update -qq >/dev/null
apt-get install -y -qq ncurses-bin >/dev/null 2>&1
apt-get install -y -qq ncurses-bin >/dev/null 2>&1 || msg_warn "Failed to install ncurses-bin (tput may be unavailable)"
fi
fi
}
@@ -1309,6 +1312,7 @@ prompt_select() {
# Validate options
if [[ $num_options -eq 0 ]]; then
msg_warn "prompt_select called with no options"
echo "" >&2
return 1
fi
@@ -1551,22 +1555,30 @@ check_or_create_swap() {
local swap_size_mb
swap_size_mb=$(prompt_input "Enter swap size in MB (e.g., 2048 for 2GB):" "2048" 60)
if ! [[ "$swap_size_mb" =~ ^[0-9]+$ ]]; then
msg_error "Invalid size input. Aborting."
msg_error "Invalid swap size: '${swap_size_mb}' (must be a number in MB)"
return 1
fi
local swap_file="/swapfile"
msg_info "Creating ${swap_size_mb}MB swap file at $swap_file"
if dd if=/dev/zero of="$swap_file" bs=1M count="$swap_size_mb" status=progress &&
chmod 600 "$swap_file" &&
mkswap "$swap_file" &&
swapon "$swap_file"; then
msg_ok "Swap file created and activated successfully"
else
msg_error "Failed to create or activate swap"
if ! dd if=/dev/zero of="$swap_file" bs=1M count="$swap_size_mb" status=progress; then
msg_error "Failed to allocate swap file (dd failed)"
return 1
fi
if ! chmod 600 "$swap_file"; then
msg_error "Failed to set permissions on $swap_file"
return 1
fi
if ! mkswap "$swap_file"; then
msg_error "Failed to format swap file (mkswap failed)"
return 1
fi
if ! swapon "$swap_file"; then
msg_error "Failed to activate swap (swapon failed)"
return 1
fi
msg_ok "Swap file created and activated successfully"
}
# ------------------------------------------------------------------------------
@@ -1648,7 +1660,7 @@ function get_lxc_ip() {
LOCAL_IP="$(get_current_ip || true)"
if [[ -z "$LOCAL_IP" ]]; then
msg_error "Could not determine LOCAL_IP"
msg_error "Could not determine LOCAL_IP (checked: eth0, hostname -I, ip route, IPv6 targets)"
return 1
fi
fi

View File

@@ -199,11 +199,16 @@ error_handler() {
return 0
fi
# Stop spinner and restore cursor FIRST — before any output
# This prevents spinner text overlapping with error messages
if declare -f stop_spinner >/dev/null 2>&1; then
stop_spinner 2>/dev/null || true
fi
printf "\e[?25h"
local explanation
explanation="$(explain_exit_code "$exit_code")"
printf "\e[?25h"
# ALWAYS report failure to API immediately - don't wait for container checks
# This ensures we capture failures that occur before/after container exists
if declare -f post_update_to_api &>/dev/null; then
@@ -359,9 +364,39 @@ _send_abort_telemetry() {
command -v curl &>/dev/null || return 0
[[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0
[[ -z "${RANDOM_UUID:-}" ]] && return 0
curl -fsS -m 5 -X POST "${TELEMETRY_URL:-https://telemetry.community-scripts.org/telemetry}" \
-H "Content-Type: application/json" \
-d "{\"random_id\":\"${RANDOM_UUID}\",\"execution_id\":\"${EXECUTION_ID:-${RANDOM_UUID}}\",\"type\":\"${TELEMETRY_TYPE:-lxc}\",\"nsapp\":\"${NSAPP:-${app:-unknown}}\",\"status\":\"failed\",\"exit_code\":${exit_code}}" &>/dev/null || true
# Collect last 20 log lines for error diagnosis (best-effort)
local error_text=""
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
fi
# Calculate duration if start time is available
local duration=""
if [[ -n "${DIAGNOSTICS_START_TIME:-}" ]]; then
duration=$(($(date +%s) - DIAGNOSTICS_START_TIME))
fi
# Build JSON payload with error context
local payload
payload="{\"random_id\":\"${RANDOM_UUID}\",\"execution_id\":\"${EXECUTION_ID:-${RANDOM_UUID}}\",\"type\":\"${TELEMETRY_TYPE:-lxc}\",\"nsapp\":\"${NSAPP:-${app:-unknown}}\",\"status\":\"failed\",\"exit_code\":${exit_code}"
[[ -n "$error_text" ]] && payload="${payload},\"error\":\"${error_text}\""
[[ -n "$duration" ]] && payload="${payload},\"duration\":${duration}"
payload="${payload}}"
local api_url="${TELEMETRY_URL:-https://telemetry.community-scripts.org/telemetry}"
# 2 attempts (retry once on failure) — original had no retry
local attempt
for attempt in 1 2; do
if curl -fsS -m 5 -X POST "$api_url" \
-H "Content-Type: application/json" \
-d "$payload" &>/dev/null; then
return 0
fi
[[ $attempt -eq 1 ]] && sleep 1
done
return 0
}
# ------------------------------------------------------------------------------
@@ -437,6 +472,12 @@ on_exit() {
# - Exits with code 130 (128 + SIGINT=2)
# ------------------------------------------------------------------------------
on_interrupt() {
# Stop spinner and restore cursor before any output
if declare -f stop_spinner >/dev/null 2>&1; then
stop_spinner 2>/dev/null || true
fi
printf "\e[?25h" 2>/dev/null || true
_send_abort_telemetry "130"
_stop_container_if_installing
if declare -f msg_error >/dev/null 2>&1; then
@@ -456,6 +497,12 @@ on_interrupt() {
# - Exits with code 143 (128 + SIGTERM=15)
# ------------------------------------------------------------------------------
on_terminate() {
# Stop spinner and restore cursor before any output
if declare -f stop_spinner >/dev/null 2>&1; then
stop_spinner 2>/dev/null || true
fi
printf "\e[?25h" 2>/dev/null || true
_send_abort_telemetry "143"
_stop_container_if_installing
if declare -f msg_error >/dev/null 2>&1; then
@@ -478,6 +525,11 @@ on_terminate() {
# - Exits with code 129 (128 + SIGHUP=1)
# ------------------------------------------------------------------------------
on_hangup() {
# Stop spinner (no cursor restore needed — terminal is already gone)
if declare -f stop_spinner >/dev/null 2>&1; then
stop_spinner 2>/dev/null || true
fi
_send_abort_telemetry "129"
_stop_container_if_installing
exit 129

View File

@@ -201,6 +201,7 @@ install_packages_with_retry() {
fi
done
msg_error "Failed to install packages after $((max_retries + 1)) attempts: ${packages[*]}"
return 1
}
@@ -231,6 +232,7 @@ upgrade_packages_with_retry() {
fi
done
msg_error "Failed to upgrade packages after $((max_retries + 1)) attempts: ${packages[*]}"
return 1
}
@@ -675,6 +677,7 @@ verify_repo_available() {
if curl -fsSL --max-time 10 "${repo_url}/dists/${suite}/Release" &>/dev/null; then
return 0
fi
msg_warn "Repository not available: ${repo_url} (suite: ${suite})"
return 1
}
@@ -783,16 +786,25 @@ github_api_call() {
for attempt in $(seq 1 $max_retries); do
local http_code
http_code=$(curl -fsSL -w "%{http_code}" -o "$output_file" \
http_code=$(curl -sSL -w "%{http_code}" -o "$output_file" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"${header_args[@]}" \
"$url" 2>/dev/null || echo "000")
"$url" 2>/dev/null) || true
case "$http_code" in
200)
return 0
;;
401)
msg_error "GitHub API authentication failed (HTTP 401)."
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
msg_error "Your GITHUB_TOKEN appears to be invalid or expired."
else
msg_error "The repository may require authentication. Try: export GITHUB_TOKEN=\"ghp_your_token\""
fi
return 1
;;
403)
# Rate limit - check if we can retry
if [[ $attempt -lt $max_retries ]]; then
@@ -801,11 +813,22 @@ github_api_call() {
retry_delay=$((retry_delay * 2))
continue
fi
msg_error "GitHub API rate limit exceeded. Set GITHUB_TOKEN to increase limits."
msg_error "GitHub API rate limit exceeded (HTTP 403)."
msg_error "To increase the limit, export a GitHub token before running the script:"
msg_error " export GITHUB_TOKEN=\"ghp_your_token_here\""
return 1
;;
404)
msg_error "GitHub API endpoint not found: $url"
msg_error "GitHub repository or release not found (HTTP 404): $url"
return 1
;;
000 | "")
if [[ $attempt -lt $max_retries ]]; then
sleep "$retry_delay"
continue
fi
msg_error "GitHub API connection failed (no response)."
msg_error "Check your network/DNS: curl -sSL https://api.github.com/rate_limit"
return 1
;;
*)
@@ -813,12 +836,13 @@ github_api_call() {
sleep "$retry_delay"
continue
fi
msg_error "GitHub API call failed with HTTP $http_code"
msg_error "GitHub API call failed (HTTP $http_code)."
return 1
;;
esac
done
msg_error "GitHub API call failed after ${max_retries} attempts: ${url}"
return 1
}
@@ -833,14 +857,18 @@ codeberg_api_call() {
for attempt in $(seq 1 $max_retries); do
local http_code
http_code=$(curl -fsSL -w "%{http_code}" -o "$output_file" \
http_code=$(curl -sSL -w "%{http_code}" -o "$output_file" \
-H "Accept: application/json" \
"$url" 2>/dev/null || echo "000")
"$url" 2>/dev/null) || true
case "$http_code" in
200)
return 0
;;
401)
msg_error "Codeberg API authentication failed (HTTP 401)."
return 1
;;
403)
# Rate limit - retry
if [[ $attempt -lt $max_retries ]]; then
@@ -849,11 +877,20 @@ codeberg_api_call() {
retry_delay=$((retry_delay * 2))
continue
fi
msg_error "Codeberg API rate limit exceeded."
msg_error "Codeberg API rate limit exceeded (HTTP 403)."
return 1
;;
404)
msg_error "Codeberg API endpoint not found: $url"
msg_error "Codeberg repository or release not found (HTTP 404): $url"
return 1
;;
000 | "")
if [[ $attempt -lt $max_retries ]]; then
sleep "$retry_delay"
continue
fi
msg_error "Codeberg API connection failed (no response)."
msg_error "Check your network/DNS: curl -sSL https://codeberg.org"
return 1
;;
*)
@@ -861,12 +898,13 @@ codeberg_api_call() {
sleep "$retry_delay"
continue
fi
msg_error "Codeberg API call failed with HTTP $http_code"
msg_error "Codeberg API call failed (HTTP $http_code)."
return 1
;;
esac
done
msg_error "Codeberg API call failed after ${max_retries} attempts: ${url}"
return 1
}
@@ -1336,7 +1374,9 @@ setup_deb822_repo() {
[[ -n "$enabled" ]] && echo "Enabled: $enabled"
} >/etc/apt/sources.list.d/${name}.sources
$STD apt update
$STD apt update || {
msg_warn "apt update failed after adding repository: ${name}"
}
}
# ------------------------------------------------------------------------------
@@ -1344,12 +1384,16 @@ setup_deb822_repo() {
# ------------------------------------------------------------------------------
hold_package_version() {
local package="$1"
$STD apt-mark hold "$package"
$STD apt-mark hold "$package" || {
msg_warn "Failed to hold package version: ${package}"
}
}
unhold_package_version() {
local package="$1"
$STD apt-mark unhold "$package"
$STD apt-mark unhold "$package" || {
msg_warn "Failed to unhold package version: ${package}"
}
}
# ------------------------------------------------------------------------------
@@ -1379,6 +1423,7 @@ enable_and_start_service() {
local service="$1"
if ! systemctl enable "$service" &>/dev/null; then
msg_error "Failed to enable service: $service"
return 1
fi
@@ -1421,6 +1466,7 @@ extract_version_from_json() {
version=$(echo "$json" | jq -r ".${field} // empty")
if [[ -z "$version" ]]; then
msg_warn "JSON field '${field}' is empty in API response"
return 1
fi
@@ -1440,6 +1486,7 @@ get_latest_github_release() {
local temp_file=$(mktemp)
if ! github_api_call "https://api.github.com/repos/${repo}/releases/latest" "$temp_file"; then
msg_warn "GitHub API call failed for ${repo}"
rm -f "$temp_file"
return 1
fi
@@ -1449,6 +1496,7 @@ get_latest_github_release() {
rm -f "$temp_file"
if [[ -z "$version" ]]; then
msg_error "Could not determine latest version for ${repo}"
return 1
fi
@@ -1465,6 +1513,7 @@ get_latest_codeberg_release() {
# Codeberg API: get all releases and pick the first non-draft/non-prerelease
if ! codeberg_api_call "https://codeberg.org/api/v1/repos/${repo}/releases" "$temp_file"; then
msg_warn "Codeberg API call failed for ${repo}"
rm -f "$temp_file"
return 1
fi
@@ -1480,6 +1529,7 @@ get_latest_codeberg_release() {
rm -f "$temp_file"
if [[ -z "$version" ]]; then
msg_error "Could not determine latest version for ${repo}"
return 1
fi
@@ -1567,13 +1617,34 @@ get_latest_gh_tag() {
"${header_args[@]}" \
"https://api.github.com/repos/${repo}/tags?per_page=100" 2>/dev/null) || true
if [[ "$http_code" == "401" ]]; then
msg_error "GitHub API authentication failed (HTTP 401)."
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
msg_error "Your GITHUB_TOKEN appears to be invalid or expired."
else
msg_error "The repository may require authentication. Try: export GITHUB_TOKEN=\"ghp_your_token\""
fi
rm -f /tmp/gh_tags.json
return 1
fi
if [[ "$http_code" == "403" ]]; then
msg_warn "GitHub API rate limit exceeded while fetching tags for ${repo}"
msg_error "GitHub API rate limit exceeded (HTTP 403)."
msg_error "To increase the limit, export a GitHub token before running the script:"
msg_error " export GITHUB_TOKEN=\"ghp_your_token_here\""
rm -f /tmp/gh_tags.json
return 1
fi
if [[ "$http_code" == "000" || -z "$http_code" ]]; then
msg_error "GitHub API connection failed (no response)."
msg_error "Check your network/DNS: curl -sSL https://api.github.com/rate_limit"
rm -f /tmp/gh_tags.json
return 1
fi
if [[ "$http_code" != "200" ]] || [[ ! -s /tmp/gh_tags.json ]]; then
msg_error "Unable to fetch tags for ${repo} (HTTP ${http_code})"
rm -f /tmp/gh_tags.json
return 1
fi
@@ -1590,6 +1661,7 @@ get_latest_gh_tag() {
sort -V | tail -n1)
if [[ -z "$latest" ]]; then
msg_warn "No matching tags found for ${repo}${prefix:+ (prefix: $prefix)}"
return 1
fi
@@ -1659,6 +1731,15 @@ check_for_gh_release() {
if [[ "$http_code" == "200" ]] && [[ -s /tmp/gh_check.json ]]; then
releases_json="[$(</tmp/gh_check.json)]"
elif [[ "$http_code" == "401" ]]; then
msg_error "GitHub API authentication failed (HTTP 401)."
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
msg_error "Your GITHUB_TOKEN appears to be invalid or expired."
else
msg_error "The repository may require authentication. Try: export GITHUB_TOKEN=\"ghp_your_token\""
fi
rm -f /tmp/gh_check.json
return 1
elif [[ "$http_code" == "403" ]]; then
msg_error "GitHub API rate limit exceeded (HTTP 403)."
msg_error "To increase the limit, export a GitHub token before running the script:"
@@ -1679,12 +1760,26 @@ check_for_gh_release() {
if [[ "$http_code" == "200" ]] && [[ -s /tmp/gh_check.json ]]; then
releases_json=$(</tmp/gh_check.json)
elif [[ "$http_code" == "401" ]]; then
msg_error "GitHub API authentication failed (HTTP 401)."
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
msg_error "Your GITHUB_TOKEN appears to be invalid or expired."
else
msg_error "The repository may require authentication. Try: export GITHUB_TOKEN=\"ghp_your_token\""
fi
rm -f /tmp/gh_check.json
return 1
elif [[ "$http_code" == "403" ]]; then
msg_error "GitHub API rate limit exceeded (HTTP 403)."
msg_error "To increase the limit, export a GitHub token before running the script:"
msg_error " export GITHUB_TOKEN=\"ghp_your_token_here\""
rm -f /tmp/gh_check.json
return 1
elif [[ "$http_code" == "000" || -z "$http_code" ]]; then
msg_error "GitHub API connection failed (no response)."
msg_error "Check your network/DNS: curl -sSL https://api.github.com/rate_limit"
rm -f /tmp/gh_check.json
return 1
else
msg_error "Unable to fetch releases for ${app} (HTTP ${http_code})"
rm -f /tmp/gh_check.json
@@ -1802,7 +1897,7 @@ check_for_codeberg_release() {
releases_json=$(curl -fsSL --max-time 20 \
-H 'Accept: application/json' \
"https://codeberg.org/api/v1/repos/${source}/releases" 2>/dev/null) || {
msg_error "Unable to fetch releases for ${app}"
msg_error "Unable to fetch releases for ${app} (codeberg.org/api/v1/repos/${source}/releases)"
return 1
}
@@ -1935,12 +2030,12 @@ function download_with_progress() {
if [[ -z "$content_length" ]]; then
if ! curl -fL# -o "$output" "$url"; then
msg_error "Download failed"
msg_error "Download failed: $url"
return 1
fi
else
if ! curl -fsSL "$url" | pv -s "$content_length" >"$output"; then
msg_error "Download failed"
msg_error "Download failed: $url"
return 1
fi
fi
@@ -2483,7 +2578,10 @@ _gh_scan_older_releases() {
-H 'Accept: application/vnd.github+json' \
-H 'X-GitHub-Api-Version: 2022-11-28' \
"${header[@]}" \
"https://api.github.com/repos/${repo}/releases?per_page=15" 2>/dev/null) || return 1
"https://api.github.com/repos/${repo}/releases?per_page=15" 2>/dev/null) || {
msg_warn "Failed to fetch older releases for ${repo}"
return 1
}
local count
count=$(echo "$releases_list" | jq 'length')
@@ -2608,12 +2706,22 @@ function fetch_and_deploy_gh_release() {
done
if ! $success; then
if [[ "$http_code" == "403" ]]; then
if [[ "$http_code" == "401" ]]; then
msg_error "GitHub API authentication failed (HTTP 401)."
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
msg_error "Your GITHUB_TOKEN appears to be invalid or expired."
else
msg_error "The repository may require authentication. Try: export GITHUB_TOKEN=\"ghp_your_token\""
fi
elif [[ "$http_code" == "403" ]]; then
msg_error "GitHub API rate limit exceeded (HTTP 403)."
msg_error "To increase the limit, export a GitHub token before running the script:"
msg_error " export GITHUB_TOKEN=\"ghp_your_token_here\""
elif [[ "$http_code" == "000" || -z "$http_code" ]]; then
msg_error "GitHub API connection failed (no response)."
msg_error "Check your network/DNS: curl -sSL https://api.github.com/rate_limit"
else
msg_error "Failed to fetch release metadata from $api_url after $max_retries attempts (HTTP $http_code)"
msg_error "Failed to fetch release metadata (HTTP $http_code)"
fi
return 1
fi
@@ -3015,7 +3123,9 @@ function setup_composer() {
# Scenario 1: Already installed - just self-update
if [[ -n "$INSTALLED_VERSION" ]]; then
msg_info "Update Composer $INSTALLED_VERSION"
$STD "$COMPOSER_BIN" self-update --no-interaction || true
$STD "$COMPOSER_BIN" self-update --no-interaction || {
msg_warn "Composer self-update failed, continuing with current version"
}
local UPDATED_VERSION
UPDATED_VERSION=$("$COMPOSER_BIN" --version 2>/dev/null | awk '{print $3}')
cache_installed_version "composer" "$UPDATED_VERSION"
@@ -3051,7 +3161,9 @@ function setup_composer() {
fi
chmod +x "$COMPOSER_BIN"
$STD "$COMPOSER_BIN" self-update --no-interaction || true
$STD "$COMPOSER_BIN" self-update --no-interaction || {
msg_warn "Composer self-update failed after fresh install"
}
local FINAL_VERSION
FINAL_VERSION=$("$COMPOSER_BIN" --version 2>/dev/null | awk '{print $3}')
@@ -5122,7 +5234,9 @@ function setup_mysql() {
ensure_apt_working || return 1
# Perform upgrade with retry logic (non-fatal if fails)
upgrade_packages_with_retry "mysql-server" "mysql-client" || true
upgrade_packages_with_retry "mysql-server" "mysql-client" || {
msg_warn "MySQL package upgrade had issues, continuing with current version"
}
cache_installed_version "mysql" "$MYSQL_VERSION"
msg_ok "Update MySQL $MYSQL_VERSION"
@@ -5312,7 +5426,9 @@ function setup_nodejs() {
}
# Force APT cache refresh after repository setup
$STD apt update
$STD apt update || {
msg_warn "apt update failed after Node.js repository setup"
}
ensure_dependencies curl ca-certificates gnupg
@@ -5555,7 +5671,10 @@ EOF
if [[ "$DISTRO_ID" == "ubuntu" ]]; then
# Ubuntu: Use ondrej/php PPA
msg_info "Adding ondrej/php PPA for Ubuntu"
$STD apt install -y software-properties-common
$STD apt install -y software-properties-common || {
msg_error "Failed to install software-properties-common"
return 1
}
# Don't use $STD for add-apt-repository as it uses background processes
add-apt-repository -y ppa:ondrej/php >>"$(get_active_logfile)" 2>&1
else
@@ -5566,7 +5685,9 @@ EOF
}
fi
ensure_apt_working || return 1
$STD apt update
$STD apt update || {
msg_warn "apt update failed after PHP repository setup"
}
# Get available PHP version from repository
local AVAILABLE_PHP_VERSION=""
@@ -5861,7 +5982,9 @@ function setup_postgresql() {
}
fi
$STD systemctl enable --now postgresql 2>/dev/null || true
$STD systemctl enable --now postgresql 2>/dev/null || {
msg_warn "Failed to enable/start PostgreSQL service"
}
# Add PostgreSQL binaries to PATH
if ! grep -q '/usr/lib/postgresql' /etc/environment 2>/dev/null; then
@@ -5875,7 +5998,9 @@ function setup_postgresql() {
if [[ -n "$PG_MODULES" ]]; then
IFS=',' read -ra MODULES <<<"$PG_MODULES"
for module in "${MODULES[@]}"; do
$STD apt install -y "postgresql-${PG_VERSION}-${module}" 2>/dev/null || true
$STD apt install -y "postgresql-${PG_VERSION}-${module}" 2>/dev/null || {
msg_warn "Failed to install PostgreSQL module: ${module}"
}
done
fi
}
@@ -6534,7 +6659,9 @@ function setup_clickhouse() {
ensure_apt_working || return 1
# Perform upgrade with retry logic (non-fatal if fails)
upgrade_packages_with_retry "clickhouse-server" "clickhouse-client" || true
upgrade_packages_with_retry "clickhouse-server" "clickhouse-client" || {
msg_warn "ClickHouse package upgrade had issues, continuing with current version"
}
cache_installed_version "clickhouse" "$CLICKHOUSE_VERSION"
msg_ok "Update ClickHouse $CLICKHOUSE_VERSION"
return 0
@@ -6669,7 +6796,9 @@ function setup_rust() {
}
# Update to latest patch version
$STD rustup update "$RUST_TOOLCHAIN" </dev/null || true
$STD rustup update "$RUST_TOOLCHAIN" </dev/null || {
msg_warn "Rust toolchain update had issues"
}
# Ensure PATH is updated for current shell session
export PATH="$CARGO_BIN:$PATH"
@@ -7071,7 +7200,10 @@ function setup_docker() {
docker-ce-cli \
containerd.io \
docker-buildx-plugin \
docker-compose-plugin
docker-compose-plugin || {
msg_error "Failed to update Docker packages"
return 1
}
msg_ok "Updated Docker to $DOCKER_LATEST_VERSION"
else
msg_ok "Docker is up-to-date ($DOCKER_CURRENT_VERSION)"
@@ -7083,7 +7215,10 @@ function setup_docker() {
docker-ce-cli \
containerd.io \
docker-buildx-plugin \
docker-compose-plugin
docker-compose-plugin || {
msg_error "Failed to install Docker packages"
return 1
}
DOCKER_CURRENT_VERSION=$(docker --version | grep -oP '\d+\.\d+\.\d+' | head -1)
msg_ok "Installed Docker $DOCKER_CURRENT_VERSION"

View File

@@ -28,7 +28,7 @@ INSTALL_PATH="/opt/immich-proxy"
CONFIG_PATH="/opt/immich-proxy/app"
DEFAULT_PORT=3000
# Initialize all core functions (colors, formatting, icons, STD mode)
# Initialize all core functions (colors, formatting, icons, $STD mode)
load_functions
init_tool_telemetry "" "addon"

View File

@@ -288,8 +288,8 @@ function default_settings() {
echo -e "${DGN}Using Hostname: ${BGN}${HN}${CL}"
echo -e "${DGN}Allocated Cores: ${BGN}${CORE_COUNT}${CL}"
echo -e "${DGN}Allocated RAM: ${BGN}${RAM_SIZE}${CL}"
if ! grep -q "^iface ${BRG}" /etc/network/interfaces; then
msg_error "Bridge '${BRG}' does not exist in /etc/network/interfaces"
if ! ip link show "${BRG}" &>/dev/null; then
msg_error "Bridge '${BRG}' does not exist"
exit
else
echo -e "${DGN}Using LAN Bridge: ${BGN}${BRG}${CL}"
@@ -305,8 +305,8 @@ function default_settings() {
if [ "$NETWORK_MODE" = "dual" ]; then
echo -e "${DGN}Network Mode: ${BGN}Dual Interface (Firewall)${CL}"
echo -e "${DGN}Using WAN MAC Address: ${BGN}${WAN_MAC}${CL}"
if ! grep -q "^iface ${WAN_BRG}" /etc/network/interfaces; then
msg_error "Bridge '${WAN_BRG}' does not exist in /etc/network/interfaces"
if ! ip link show "${WAN_BRG}" &>/dev/null; then
msg_error "Bridge '${WAN_BRG}' does not exist"
exit
else
echo -e "${DGN}Using WAN Bridge: ${BGN}${WAN_BRG}${CL}"
@@ -424,8 +424,8 @@ function advanced_settings() {
if [ -z $BRG ]; then
BRG="vmbr0"
fi
if ! grep -q "^iface ${BRG}" /etc/network/interfaces; then
msg_error "Bridge '${BRG}' does not exist in /etc/network/interfaces"
if ! ip link show "${BRG}" &>/dev/null; then
msg_error "Bridge '${BRG}' does not exist"
exit
fi
echo -e "${DGN}Using LAN Bridge: ${BGN}$BRG${CL}"
@@ -474,8 +474,8 @@ function advanced_settings() {
if [ -z $WAN_BRG ]; then
WAN_BRG="vmbr1"
fi
if ! grep -q "^iface ${WAN_BRG}" /etc/network/interfaces; then
msg_error "WAN Bridge '${WAN_BRG}' does not exist in /etc/network/interfaces"
if ! ip link show "${WAN_BRG}" &>/dev/null; then
msg_error "WAN Bridge '${WAN_BRG}' does not exist"
exit
fi
echo -e "${DGN}Using WAN Bridge: ${BGN}$WAN_BRG${CL}"