Compare commits

..

1 Commits

Author SHA1 Message Date
CanbiZ (MickLesk)
f2dc760064 Update tools.func 2026-02-23 13:06:29 +01:00
54 changed files with 394 additions and 1589 deletions

View File

@@ -1,341 +0,0 @@
name: Check Node.js Version Drift
on:
workflow_dispatch:
schedule:
# Runs weekly on Monday at 06:00 UTC
- cron: "0 6 * * 1"
permissions:
contents: read
issues: write
jobs:
check-node-versions:
if: github.repository == 'community-scripts/ProxmoxVE'
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: main
- name: Install dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq jq curl > /dev/null 2>&1
- name: Check upstream Node.js versions
id: check
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
echo "================================================"
echo " Checking Node.js version drift in install scripts"
echo "================================================"
# Alpine version -> Node major cache (populated on demand)
declare -A ALPINE_NODE_CACHE
# Resolve Node.js major version from Alpine package registry
# Usage: resolve_alpine_node "3.21" => sets REPLY to major version (e.g. "22")
resolve_alpine_node() {
local alpine_ver="$1"
if [[ -n "${ALPINE_NODE_CACHE[$alpine_ver]+x}" ]]; then
REPLY="${ALPINE_NODE_CACHE[$alpine_ver]}"
return
fi
local url="https://pkgs.alpinelinux.org/package/v${alpine_ver}/main/x86_64/nodejs"
local page
page=$(curl -sf "$url" 2>/dev/null || echo "")
local full_ver=""
if [[ -n "$page" ]]; then
# Parse: "Version | 24.13.0-r1" or similar table row
full_ver=$(echo "$page" | grep -oP 'Version\s*\|\s*\K[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "")
if [[ -z "$full_ver" ]]; then
# Fallback: look for version pattern after "Version"
full_ver=$(echo "$page" | grep -oP '(?<=Version</td><td>)[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "")
fi
fi
local major=""
if [[ -n "$full_ver" ]]; then
major="${full_ver%%.*}"
fi
ALPINE_NODE_CACHE[$alpine_ver]="$major"
REPLY="$major"
}
# Extract Node major from a Dockerfile content
# Sets: DF_NODE_MAJOR, DF_SOURCE (description of where we found it)
extract_dockerfile_node() {
local content="$1"
DF_NODE_MAJOR=""
DF_SOURCE=""
# 1) FROM node:XX (e.g. node:24-alpine, node:22.9.0-bookworm-slim, node:20)
local node_from
node_from=$(echo "$content" | grep -oP '(?i)FROM\s+(--platform=[^\s]+\s+)?node:\K[0-9]+' | head -1 || echo "")
if [[ -n "$node_from" ]]; then
DF_NODE_MAJOR="$node_from"
DF_SOURCE="FROM node:${node_from}"
return
fi
# 2) nodesource/setup_XX.x
local nodesource
nodesource=$(echo "$content" | grep -oP 'nodesource/setup_\K[0-9]+' | head -1 || echo "")
if [[ -n "$nodesource" ]]; then
DF_NODE_MAJOR="$nodesource"
DF_SOURCE="nodesource/setup_${nodesource}.x"
return
fi
# 3) FROM alpine:X.Y — resolve via Alpine packages
local alpine_ver
alpine_ver=$(echo "$content" | grep -oP '(?i)FROM\s+(--platform=[^\s]+\s+)?alpine:\K[0-9]+\.[0-9]+' | head -1 || echo "")
if [[ -n "$alpine_ver" ]]; then
resolve_alpine_node "$alpine_ver"
if [[ -n "$REPLY" ]]; then
DF_NODE_MAJOR="$REPLY"
DF_SOURCE="alpine:${alpine_ver} (pkg: nodejs ${DF_NODE_MAJOR})"
return
fi
fi
}
# Extract Node major from engines.node in package.json
# Sets: ENGINES_NODE_RAW (raw string), ENGINES_MIN_MAJOR
extract_engines_node() {
local content="$1"
ENGINES_NODE_RAW=""
ENGINES_MIN_MAJOR=""
ENGINES_NODE_RAW=$(echo "$content" | jq -r '.engines.node // empty' 2>/dev/null || echo "")
if [[ -z "$ENGINES_NODE_RAW" ]]; then
return
fi
# Extract the first number (major) from the constraint
# Handles: ">=24.13.1", "^22", ">=18.0.0", ">=18.15.0 <19 || ^20", etc.
ENGINES_MIN_MAJOR=$(echo "$ENGINES_NODE_RAW" | grep -oP '\d+' | head -1 || echo "")
}
# Collect results
declare -a issue_scripts=()
declare -a report_lines=()
total=0
checked=0
drift_count=0
for script in install/*-install.sh; do
[[ ! -f "$script" ]] && continue
if ! grep -q 'setup_nodejs' "$script"; then
continue
fi
total=$((total + 1))
slug=$(basename "$script" | sed 's/-install\.sh$//')
# Extract Source URL (GitHub only)
source_url=$(head -20 "$script" | grep -oP '(?<=# Source: )https://github\.com/[^\s]+' | head -1 || echo "")
if [[ -z "$source_url" ]]; then
report_lines+=("| \`$slug\` | — | — | — | — | ⏭️ No GitHub source |")
continue
fi
repo=$(echo "$source_url" | sed -E 's|https://github\.com/||; s|/$||; s|\.git$||')
if [[ -z "$repo" || "$repo" != */* ]]; then
report_lines+=("| \`$slug\` | — | — | — | — | ⏭️ Invalid repo |")
continue
fi
checked=$((checked + 1))
# Extract our NODE_VERSION
our_version=$(grep -oP 'NODE_VERSION="(\d+)"' "$script" | head -1 | grep -oP '\d+' || echo "")
if [[ -z "$our_version" ]]; then
if grep -q 'NODE_VERSION=\$(' "$script"; then
our_version="dynamic"
else
our_version="unset"
fi
fi
# Fetch upstream Dockerfile
df_content=""
for branch in main master dev; do
df_content=$(curl -sf "https://raw.githubusercontent.com/${repo}/${branch}/Dockerfile" 2>/dev/null || echo "")
[[ -n "$df_content" ]] && break
done
DF_NODE_MAJOR=""
DF_SOURCE=""
if [[ -n "$df_content" ]]; then
extract_dockerfile_node "$df_content"
fi
# Fetch upstream package.json
pkg_content=""
for branch in main master dev; do
pkg_content=$(curl -sf "https://raw.githubusercontent.com/${repo}/${branch}/package.json" 2>/dev/null || echo "")
[[ -n "$pkg_content" ]] && break
done
ENGINES_NODE_RAW=""
ENGINES_MIN_MAJOR=""
if [[ -n "$pkg_content" ]]; then
extract_engines_node "$pkg_content"
fi
# Determine upstream recommended major version
upstream_major=""
upstream_hint=""
if [[ -n "$DF_NODE_MAJOR" ]]; then
upstream_major="$DF_NODE_MAJOR"
upstream_hint="$DF_SOURCE"
elif [[ -n "$ENGINES_MIN_MAJOR" ]]; then
upstream_major="$ENGINES_MIN_MAJOR"
upstream_hint="engines: $ENGINES_NODE_RAW"
fi
# Build display values
engines_display="${ENGINES_NODE_RAW:-—}"
dockerfile_display="${DF_SOURCE:-—}"
# Compare
status="✅"
if [[ "$our_version" == "dynamic" ]]; then
status="🔄 Dynamic"
elif [[ "$our_version" == "unset" ]]; then
status="⚠️ NODE_VERSION not set"
issue_scripts+=("$slug|$our_version|$upstream_major|$upstream_hint|$repo")
drift_count=$((drift_count + 1))
elif [[ -n "$upstream_major" && "$our_version" != "$upstream_major" ]]; then
status="🔸 Drift → upstream=$upstream_major ($upstream_hint)"
issue_scripts+=("$slug|$our_version|$upstream_major|$upstream_hint|$repo")
drift_count=$((drift_count + 1))
fi
report_lines+=("| \`$slug\` | $our_version | $engines_display | $dockerfile_display | [$repo](https://github.com/$repo) | $status |")
# Rate-limit to avoid GitHub secondary rate limits
sleep 0.3
done
# Print summary
echo ""
echo "========================================="
echo " Total scripts with setup_nodejs: $total"
echo " Checked (with GitHub source): $checked"
echo " Version drift detected: $drift_count"
echo "========================================="
# Export
{
echo "drift_count=$drift_count"
echo "total=$total"
echo "checked=$checked"
} >> "$GITHUB_OUTPUT"
# Save issue details for next step
printf '%s\n' "${issue_scripts[@]}" > /tmp/drift_scripts.txt 2>/dev/null || touch /tmp/drift_scripts.txt
# Save full report
{
echo "## Node.js Version Drift Report"
echo ""
echo "**Generated:** $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "**Scripts checked:** $total | **With GitHub source:** $checked | **Drift detected:** $drift_count"
echo ""
echo "| Script | Our Version | engines.node | Dockerfile | Upstream Repo | Status |"
echo "|--------|-------------|-------------|------------|---------------|--------|"
printf '%s\n' "${report_lines[@]}" | sort
} > /tmp/drift_report.md
cat /tmp/drift_report.md
- name: Create or update summary issue
if: steps.check.outputs.drift_count != '0'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
TITLE="[Automated] Node.js Version Drift Report"
DATE=$(date -u +%Y-%m-%d)
DRIFT_COUNT="${{ steps.check.outputs.drift_count }}"
TOTAL="${{ steps.check.outputs.total }}"
CHECKED="${{ steps.check.outputs.checked }}"
# Build checklist from drift data
CHECKLIST=""
while IFS='|' read -r slug our_version upstream_major upstream_hint repo; do
[[ -z "$slug" ]] && continue
CHECKLIST+="- [ ] **\`${slug}\`** — ours: \`${our_version}\` → upstream: \`${upstream_major}\` (${upstream_hint}) — [repo](https://github.com/${repo})"$'\n'
done < /tmp/drift_scripts.txt
# Build full report table
REPORT=$(cat /tmp/drift_report.md)
BODY=$(cat <<ISSUE_EOF
## Node.js Version Drift Report — ${DATE}
**${DRIFT_COUNT}** script(s) with version drift detected (out of ${CHECKED} checked / ${TOTAL} total).
### Scripts requiring investigation
${CHECKLIST}
### How to resolve
1. Check upstream Dockerfile / package.json to confirm the required Node.js version
2. Test the script with the new Node version
3. Update \`NODE_VERSION\` in \`install/<slug>-install.sh\`
4. Update \`NODE_VERSION\` in \`ct/<slug>.sh\` (update section) if applicable
5. Check off the item above once done
<details>
<summary>Full report</summary>
${REPORT}
</details>
---
*This issue is automatically created/updated weekly by the Node.js version drift check workflow.*
*Last updated: ${DATE}*
ISSUE_EOF
)
# Check if a matching open issue already exists
EXISTING=$(gh issue list --state open --label "automated,dependencies" --search "\"[Automated] Node.js Version Drift Report\"" --json number --jq '.[0].number // empty' 2>/dev/null || echo "")
if [[ -n "$EXISTING" ]]; then
gh issue edit "$EXISTING" --body "$BODY"
echo "Updated existing issue #$EXISTING"
else
gh issue create \
--title "$TITLE" \
--body "$BODY" \
--label "automated,dependencies"
echo "Created new summary issue"
fi
- name: Close issue if no drift
if: steps.check.outputs.drift_count == '0'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
EXISTING=$(gh issue list --state open --label "automated,dependencies" --search "\"[Automated] Node.js Version Drift Report\"" --json number --jq '.[0].number // empty' 2>/dev/null || echo "")
if [[ -n "$EXISTING" ]]; then
gh issue close "$EXISTING" --comment "All Node.js versions are in sync with upstream. Closing automatically."
echo "Closed issue #$EXISTING"
fi

111
.github/workflows/stale_pr_close.yml generated vendored
View File

@@ -1,111 +0,0 @@
name: Stale PR Management
on:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
pull_request_target:
types:
- labeled
jobs:
stale-prs:
runs-on: ubuntu-latest
permissions:
pull-requests: write
issues: write
contents: read
steps:
- name: Handle stale PRs
uses: actions/github-script@v7
with:
script: |
const now = new Date();
const owner = context.repo.owner;
const repo = context.repo.repo;
// --- When stale label is added, comment immediately ---
if (context.eventName === "pull_request_target" && context.payload.action === "labeled") {
const label = context.payload.label?.name;
if (label === "stale") {
const author = context.payload.pull_request.user.login;
await github.rest.issues.createComment({
owner,
repo,
issue_number: context.payload.pull_request.number,
body: `@${author} This PR has been marked as stale. It will be closed if no new commits are added in 7 days.`
});
}
return;
}
// --- Scheduled run: check all stale PRs ---
const { data: prs } = await github.rest.pulls.list({
owner,
repo,
state: "open",
per_page: 100
});
for (const pr of prs) {
const hasStale = pr.labels.some(l => l.name === "stale");
if (!hasStale) continue;
// Get timeline events to find when stale label was added
const { data: events } = await github.rest.issues.listEvents({
owner,
repo,
issue_number: pr.number,
per_page: 100
});
// Find the most recent time the stale label was added
const staleLabelEvents = events
.filter(e => e.event === "labeled" && e.label?.name === "stale")
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
if (staleLabelEvents.length === 0) continue;
const staleLabelDate = new Date(staleLabelEvents[0].created_at);
const daysSinceStale = (now - staleLabelDate) / (1000 * 60 * 60 * 24);
// Check for new commits since stale label was added
const { data: commits } = await github.rest.pulls.listCommits({
owner,
repo,
pull_number: pr.number
});
const lastCommitDate = new Date(commits[commits.length - 1].commit.author.date);
const author = pr.user.login;
// If there are new commits after the stale label, remove it
if (lastCommitDate > staleLabelDate) {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: pr.number,
name: "stale"
});
await github.rest.issues.createComment({
owner,
repo,
issue_number: pr.number,
body: `@${author} Recent activity detected. Removing stale label.`
});
}
// If 7 days have passed since stale label, close the PR
else if (daysSinceStale > 7) {
await github.rest.pulls.update({
owner,
repo,
pull_number: pr.number,
state: "closed"
});
await github.rest.issues.createComment({
owner,
repo,
issue_number: pr.number,
body: `@${author} Closing stale PR due to inactivity (no commits for 7 days after stale label).`
});
}
}

View File

@@ -407,63 +407,11 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
</details> </details>
## 2026-02-24
### 🚀 Updated Scripts
- adds further documentation during the installation script. [@d12rio](https://github.com/d12rio) ([#12248](https://github.com/community-scripts/ProxmoxVE/pull/12248))
- #### 🐞 Bug Fixes
- Firefly: PHP bump [@tremor021](https://github.com/tremor021) ([#12247](https://github.com/community-scripts/ProxmoxVE/pull/12247))
- #### ✨ New Features
- make searxng updateable [@shtefko](https://github.com/shtefko) ([#12207](https://github.com/community-scripts/ProxmoxVE/pull/12207))
### 📂 Github
- add: workflow to close stale PRs [@CrazyWolf13](https://github.com/CrazyWolf13) ([#12243](https://github.com/community-scripts/ProxmoxVE/pull/12243))
## 2026-02-23 ## 2026-02-23
### 🆕 New Scripts ### 🆕 New Scripts
- SeaweedFS ([#12220](https://github.com/community-scripts/ProxmoxVE/pull/12220)) - Frigate v16.4 [@MickLesk](https://github.com/MickLesk) ([#11887](https://github.com/community-scripts/ProxmoxVE/pull/11887))
- Sonobarr ([#12221](https://github.com/community-scripts/ProxmoxVE/pull/12221))
- SparkyFitness ([#12185](https://github.com/community-scripts/ProxmoxVE/pull/12185))
- Frigate v16.4 [@MickLesk](https://github.com/MickLesk) ([#11887](https://github.com/community-scripts/ProxmoxVE/pull/11887))
### 🚀 Updated Scripts
- #### ✨ New Features
- memos: unpin version due new release artifacts [@MickLesk](https://github.com/MickLesk) ([#12224](https://github.com/community-scripts/ProxmoxVE/pull/12224))
- core: Enhance signal handling, reported "status" and logs [@MickLesk](https://github.com/MickLesk) ([#12216](https://github.com/community-scripts/ProxmoxVE/pull/12216))
- #### 🔧 Refactor
- booklore v2: embed frontend, bump Java to 25, remove nginx [@MickLesk](https://github.com/MickLesk) ([#12223](https://github.com/community-scripts/ProxmoxVE/pull/12223))
### 🗑️ Deleted Scripts
- Remove: Huntarr (deprecated & Security) [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#12226](https://github.com/community-scripts/ProxmoxVE/pull/12226))
### 💾 Core
- #### 🔧 Refactor
- core: Improve error handling and logging for LXC builds [@MickLesk](https://github.com/MickLesk) ([#12208](https://github.com/community-scripts/ProxmoxVE/pull/12208))
### 🌐 Website
- #### 🐞 Bug Fixes
- calibre-web: update default credentials [@LaevaertK](https://github.com/LaevaertK) ([#12201](https://github.com/community-scripts/ProxmoxVE/pull/12201))
- #### 📝 Script Information
- chore: update Frigate documentation and website URLs [@JohnICB](https://github.com/JohnICB) ([#12218](https://github.com/community-scripts/ProxmoxVE/pull/12218))
## 2026-02-22 ## 2026-02-22

View File

@@ -30,7 +30,7 @@ function update_script() {
fi fi
if check_for_gh_release "booklore" "booklore-app/BookLore"; then if check_for_gh_release "booklore" "booklore-app/BookLore"; then
JAVA_VERSION="25" setup_java JAVA_VERSION="21" setup_java
NODE_VERSION="22" setup_nodejs NODE_VERSION="22" setup_nodejs
setup_mariadb setup_mariadb
setup_yq setup_yq
@@ -60,16 +60,11 @@ function update_script() {
$STD npm run build --configuration=production $STD npm run build --configuration=production
msg_ok "Built Frontend" msg_ok "Built Frontend"
msg_info "Embedding Frontend into Backend"
mkdir -p /opt/booklore/booklore-api/src/main/resources/static
cp -r /opt/booklore/booklore-ui/dist/booklore/browser/* /opt/booklore/booklore-api/src/main/resources/static/
msg_ok "Embedded Frontend into Backend"
msg_info "Building Backend" msg_info "Building Backend"
cd /opt/booklore/booklore-api cd /opt/booklore/booklore-api
APP_VERSION=$(get_latest_github_release "booklore-app/BookLore") APP_VERSION=$(get_latest_github_release "booklore-app/BookLore")
yq eval ".app.version = \"${APP_VERSION}\"" -i src/main/resources/application.yaml yq eval ".app.version = \"${APP_VERSION}\"" -i src/main/resources/application.yaml
$STD ./gradlew clean build -x test --no-daemon $STD ./gradlew clean build --no-daemon
mkdir -p /opt/booklore/dist 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
@@ -79,22 +74,9 @@ function update_script() {
cp "$JAR_PATH" /opt/booklore/dist/app.jar cp "$JAR_PATH" /opt/booklore/dist/app.jar
msg_ok "Built Backend" msg_ok "Built Backend"
if systemctl is-active --quiet nginx 2>/dev/null; then
msg_info "Removing Nginx (no longer needed)"
systemctl disable --now nginx
$STD apt-get purge -y nginx nginx-common
msg_ok "Removed Nginx"
fi
if ! grep -q "^SERVER_PORT=" /opt/booklore_storage/.env 2>/dev/null; then
echo "SERVER_PORT=6060" >>/opt/booklore_storage/.env
fi
sed -i 's|ExecStart=/usr/bin/java -jar|ExecStart=/usr/bin/java -XX:+UseG1GC -XX:+UseStringDeduplication -XX:+UseCompactObjectHeaders -jar|' /etc/systemd/system/booklore.service
systemctl daemon-reload
msg_info "Starting Service" msg_info "Starting Service"
systemctl start booklore systemctl start booklore
systemctl reload nginx
rm -rf /opt/booklore_bak rm -rf /opt/booklore_bak
msg_ok "Started Service" msg_ok "Started Service"
msg_ok "Updated successfully!" msg_ok "Updated successfully!"

View File

@@ -28,10 +28,7 @@ function update_script() {
msg_error "No ${APP} Installation Found!" msg_error "No ${APP} Installation Found!"
exit exit
fi fi
setup_mariadb setup_mariadb
PHP_VERSION="8.5" PHP_APACHE="YES" setup_php
if check_for_gh_release "firefly" "firefly-iii/firefly-iii"; then if check_for_gh_release "firefly" "firefly-iii/firefly-iii"; then
systemctl stop apache2 systemctl stop apache2
cp /opt/firefly/.env /opt/.env cp /opt/firefly/.env /opt/.env

6
ct/headers/huntarr Normal file
View File

@@ -0,0 +1,6 @@
__ __
/ /_ __ ______ / /_____ ___________
/ __ \/ / / / __ \/ __/ __ `/ ___/ ___/
/ / / / /_/ / / / / /_/ /_/ / / / /
/_/ /_/\__,_/_/ /_/\__/\__,_/_/ /_/

View File

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

View File

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

View File

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

View File

@@ -1,19 +1,18 @@
#!/usr/bin/env bash #!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func) source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG # Copyright (c) 2021-2026 community-scripts ORG
# Author: MickLesk (CanbiZ) # Author: BiluliB
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE # License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/seaweedfs/seaweedfs # Source: https://github.com/plexguide/Huntarr.io
APP="SeaweedFS" APP="huntarr"
var_tags="${var_tags:-storage;s3;filesystem}" var_tags="${var_tags:-arr}"
var_cpu="${var_cpu:-2}" var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-2048}" var_ram="${var_ram:-1024}"
var_disk="${var_disk:-16}" 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}"
var_fuse="${var_fuse:-yes}"
header_info "$APP" header_info "$APP"
variables variables
@@ -25,20 +24,28 @@ function update_script() {
check_container_storage check_container_storage
check_container_resources check_container_resources
if [[ ! -f /opt/seaweedfs/weed ]]; then if [[ ! -f /opt/huntarr/main.py ]]; then
msg_error "No ${APP} Installation Found!" msg_error "No ${APP} Installation Found!"
exit exit
fi fi
if check_for_gh_release "seaweedfs" "seaweedfs/seaweedfs"; then PYTHON_VERSION="3.12" setup_uv
msg_info "Stopping Service"
systemctl stop seaweedfs
msg_ok "Stopped Service"
fetch_and_deploy_gh_release "seaweedfs" "seaweedfs/seaweedfs" "prebuild" "latest" "/opt/seaweedfs" "linux_amd64.tar.gz" if check_for_gh_release "huntarr" "plexguide/Huntarr.io"; then
msg_info "Stopping Service"
systemctl stop huntarr
msg_ok "Stopped Service"
ensure_dependencies build-essential
fetch_and_deploy_gh_release "huntarr" "plexguide/Huntarr.io" "tarball"
msg_info "Updating Huntarr"
cd /opt/huntarr
$STD uv pip install -r requirements.txt --python /opt/huntarr/.venv/bin/python
msg_ok "Updated Huntarr"
msg_info "Starting Service" msg_info "Starting Service"
systemctl start seaweedfs systemctl start huntarr
msg_ok "Started Service" msg_ok "Started Service"
msg_ok "Updated successfully!" msg_ok "Updated successfully!"
fi fi
@@ -49,7 +56,7 @@ start
build_container build_container
description description
msg_ok "Completed Successfully!\n" msg_ok "Completed successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}" echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:9333${CL}" echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:9705${CL}"

View File

@@ -27,12 +27,12 @@ function update_script() {
msg_error "No ${APP} Installation Found!" msg_error "No ${APP} Installation Found!"
exit exit
fi fi
if check_for_gh_release "memos" "usememos/memos"; then if check_for_gh_release "memos" "usememos/memos" "v0.25.3"; then
msg_info "Stopping service" msg_info "Stopping service"
systemctl stop memos systemctl stop memos
msg_ok "Service stopped" msg_ok "Service stopped"
fetch_and_deploy_gh_release "memos" "usememos/memos" "prebuild" "latest" "/opt/memos" "memos*linux_amd64.tar.gz" fetch_and_deploy_gh_release "memos" "usememos/memos" "prebuild" "v0.25.3" "/opt/memos" "memos*linux_amd64.tar.gz"
msg_info "Starting service" msg_info "Starting service"
systemctl start memos systemctl start memos

View File

@@ -27,33 +27,12 @@ function update_script() {
msg_error "No ${APP} Installation Found!" msg_error "No ${APP} Installation Found!"
exit exit
fi fi
msg_ok "There is currently no update available."
chown -R searxng:searxng /usr/local/searxng/searxng-src # sed -i 's/^\([[:space:]]*limiter:\)[[:space:]]*true/\1 false/' /etc/searxng/settings.yml
if su -s /bin/bash -c "git -C /usr/local/searxng/searxng-src pull" searxng | grep -q 'Already up to date'; then # if cd /usr/local/searxng/searxng-src && git pull | grep -q 'Already up to date'; then
msg_ok "There is currently no update available." # msg_ok "There is currently no update available."
exit # fi
fi exit
msg_info "Updating SearXNG installation"
msg_info "Stopping Service"
systemctl stop searxng
msg_ok "Stopped Service"
msg_info "Updating SearXNG"
$STD su -s /bin/bash searxng -c '
python3 -m venv /usr/local/searxng/searx-pyenv &&
. /usr/local/searxng/searx-pyenv/bin/activate &&
pip install -U pip setuptools wheel pyyaml lxml msgspec typing_extensions &&
pip install --use-pep517 --no-build-isolation -e /usr/local/searxng/searxng-src
'
msg_ok "Updated SearXNG"
msg_info "Starting Services"
systemctl start searxng
msg_ok "Started Services"
msg_ok "Updated successfully!"
fi
exit
} }
start start
build_container build_container

View File

@@ -1,63 +0,0 @@
#!/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: GoldenSpringness
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/Dodelidoo-Labs/sonobarr
APP="sonobarr"
var_tags="${var_tags:-music;discovery}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-1024}"
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/sonobarr" ]]; then
msg_error "No sonobarr Installation Found!"
exit
fi
PYTHON_VERSION="3.12" setup_uv
if check_for_gh_release "sonobarr" "Dodelidoo-Labs/sonobarr"; then
msg_info "Stopping Service"
systemctl stop sonobarr
msg_ok "Stopped Service"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "sonobarr" "Dodelidoo-Labs/sonobarr" "tarball"
msg_info "Updating sonobarr"
$STD uv venv -c /opt/sonobarr/venv
$STD source /opt/sonobarr/venv/bin/activate
$STD uv pip install --no-cache-dir -r /opt/sonobarr/requirements.txt
sed -i "/release_version/s/=.*/=$(cat ~/.sonobarr)/" /etc/sonobarr/.env
msg_ok "Updated sonobarr"
msg_info "Starting Service"
systemctl start sonobarr
msg_ok "Started Service"
msg_ok "Updated successfully!"
fi
exit
}
start
build_container
description
msg_ok "Completed Successfully!\n"
echo -e "${CREATING}${GN}sonobarr setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:5000${CL}"

View File

@@ -1,83 +0,0 @@
#!/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: Tom Frenzel (tomfrenzel)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/CodeWithCJ/SparkyFitness
APP="SparkyFitness"
var_tags="${var_tags:-health;fitness}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-2048}"
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/sparkyfitness ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
NODE_VERSION="25" setup_nodejs
if check_for_gh_release "sparkyfitness" "CodeWithCJ/SparkyFitness"; then
msg_info "Stopping Services"
systemctl stop sparkyfitness-server nginx
msg_ok "Stopped Services"
msg_info "Backing up data"
mkdir -p /opt/sparkyfitness_backup
if [[ -d /opt/sparkyfitness/SparkyFitnessServer/uploads ]]; then
cp -r /opt/sparkyfitness/SparkyFitnessServer/uploads /opt/sparkyfitness_backup/
fi
if [[ -d /opt/sparkyfitness/SparkyFitnessServer/backup ]]; then
cp -r /opt/sparkyfitness/SparkyFitnessServer/backup /opt/sparkyfitness_backup/
fi
msg_ok "Backed up data"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "sparkyfitness" "CodeWithCJ/SparkyFitness" "tarball"
msg_info "Updating Sparky Fitness Backend"
cd /opt/sparkyfitness/SparkyFitnessServer
$STD npm install
msg_ok "Updated Sparky Fitness Backend"
msg_info "Updating Sparky Fitness Frontend (Patience)"
cd /opt/sparkyfitness/SparkyFitnessFrontend
$STD npm install
$STD npm run build
cp -a /opt/sparkyfitness/SparkyFitnessFrontend/dist/. /var/www/sparkyfitness/
msg_ok "Updated Sparky Fitness Frontend"
msg_info "Restoring data"
cp -r /opt/sparkyfitness_backup/. /opt/sparkyfitness/SparkyFitnessServer/
rm -rf /opt/sparkyfitness_backup
msg_ok "Restored data"
msg_info "Starting Services"
$STD systemctl start sparkyfitness-server nginx
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}${CL}"

View File

@@ -28,10 +28,14 @@
} }
], ],
"default_credentials": { "default_credentials": {
"username": "admin", "username": null,
"password": "admin123" "password": null
}, },
"notes": [ "notes": [
{
"text": "No credentials are set by this script. Complete setup and create credentials in the first-run wizard.",
"type": "info"
},
{ {
"text": "Upload your Calibre library metadata.db during first setup wizard.", "text": "Upload your Calibre library metadata.db during first setup wizard.",
"type": "info" "type": "info"

View File

@@ -10,8 +10,8 @@
"privileged": false, "privileged": false,
"config_path": "/config/config.yml", "config_path": "/config/config.yml",
"interface_port": 5000, "interface_port": 5000,
"documentation": "https://docs.frigate.video/", "documentation": "https://frigate.io/",
"website": "https://frigate.video/", "website": "https://frigate.io/",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/frigate-light.webp", "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/frigate-light.webp",
"description": "Frigate is a complete and local NVR (Network Video Recorder) with realtime AI object detection for CCTV cameras.", "description": "Frigate is a complete and local NVR (Network Video Recorder) with realtime AI object detection for CCTV cameras.",
"install_methods": [ "install_methods": [

View File

@@ -1,5 +1,5 @@
{ {
"generated": "2026-02-24T06:23:39Z", "generated": "2026-02-23T06:26:10Z",
"versions": [ "versions": [
{ {
"slug": "2fauth", "slug": "2fauth",
@@ -25,9 +25,9 @@
{ {
"slug": "adventurelog", "slug": "adventurelog",
"repo": "seanmorley15/adventurelog", "repo": "seanmorley15/adventurelog",
"version": "v0.12.0", "version": "v0.11.0",
"pinned": false, "pinned": false,
"date": "2026-02-23T14:06:45Z" "date": "2025-09-01T16:19:38Z"
}, },
{ {
"slug": "alpine-redlib", "slug": "alpine-redlib",
@@ -151,9 +151,9 @@
{ {
"slug": "booklore", "slug": "booklore",
"repo": "booklore-app/BookLore", "repo": "booklore-app/BookLore",
"version": "v2.0.1", "version": "v1.18.5",
"pinned": false, "pinned": false,
"date": "2026-02-24T04:15:33Z" "date": "2026-01-24T17:15:32Z"
}, },
{ {
"slug": "bookstack", "slug": "bookstack",
@@ -200,9 +200,9 @@
{ {
"slug": "cleanuparr", "slug": "cleanuparr",
"repo": "Cleanuparr/Cleanuparr", "repo": "Cleanuparr/Cleanuparr",
"version": "v2.7.4", "version": "v2.7.0",
"pinned": false, "pinned": false,
"date": "2026-02-23T18:41:16Z" "date": "2026-02-22T18:17:27Z"
}, },
{ {
"slug": "cloudreve", "slug": "cloudreve",
@@ -228,9 +228,9 @@
{ {
"slug": "configarr", "slug": "configarr",
"repo": "raydak-labs/configarr", "repo": "raydak-labs/configarr",
"version": "v1.23.0", "version": "v1.22.0",
"pinned": false, "pinned": false,
"date": "2026-02-23T12:28:13Z" "date": "2026-02-20T21:55:46Z"
}, },
{ {
"slug": "convertx", "slug": "convertx",
@@ -249,9 +249,9 @@
{ {
"slug": "cronicle", "slug": "cronicle",
"repo": "jhuckaby/Cronicle", "repo": "jhuckaby/Cronicle",
"version": "v0.9.107", "version": "v0.9.106",
"pinned": false, "pinned": false,
"date": "2026-02-23T17:48:27Z" "date": "2026-02-11T17:11:46Z"
}, },
{ {
"slug": "cronmaster", "slug": "cronmaster",
@@ -382,9 +382,9 @@
{ {
"slug": "firefly", "slug": "firefly",
"repo": "firefly-iii/firefly-iii", "repo": "firefly-iii/firefly-iii",
"version": "v6.5.0", "version": "v6.4.23",
"pinned": false, "pinned": false,
"date": "2026-02-23T19:19:00Z" "date": "2026-02-20T07:02:05Z"
}, },
{ {
"slug": "fladder", "slug": "fladder",
@@ -421,13 +421,6 @@
"pinned": false, "pinned": false,
"date": "2026-01-25T18:20:14Z" "date": "2026-01-25T18:20:14Z"
}, },
{
"slug": "frigate",
"repo": "blakeblackshear/frigate",
"version": "v0.16.4",
"pinned": true,
"date": "2026-01-29T00:42:14Z"
},
{ {
"slug": "gatus", "slug": "gatus",
"repo": "TwiN/gatus", "repo": "TwiN/gatus",
@@ -438,9 +431,9 @@
{ {
"slug": "ghostfolio", "slug": "ghostfolio",
"repo": "ghostfolio/ghostfolio", "repo": "ghostfolio/ghostfolio",
"version": "2.243.0", "version": "2.242.0",
"pinned": false, "pinned": false,
"date": "2026-02-23T19:31:36Z" "date": "2026-02-22T10:01:44Z"
}, },
{ {
"slug": "gitea", "slug": "gitea",
@@ -452,9 +445,9 @@
{ {
"slug": "gitea-mirror", "slug": "gitea-mirror",
"repo": "RayLabsHQ/gitea-mirror", "repo": "RayLabsHQ/gitea-mirror",
"version": "v3.9.4", "version": "v3.9.2",
"pinned": false, "pinned": false,
"date": "2026-02-24T06:17:56Z" "date": "2025-11-08T05:36:48Z"
}, },
{ {
"slug": "glance", "slug": "glance",
@@ -582,6 +575,13 @@
"pinned": false, "pinned": false,
"date": "2025-12-23T14:53:51Z" "date": "2025-12-23T14:53:51Z"
}, },
{
"slug": "huntarr",
"repo": "plexguide/Huntarr.io",
"version": "9.3.7",
"pinned": false,
"date": "2026-02-19T01:03:53Z"
},
{ {
"slug": "immich-public-proxy", "slug": "immich-public-proxy",
"repo": "alangrainger/immich-public-proxy", "repo": "alangrainger/immich-public-proxy",
@@ -613,9 +613,9 @@
{ {
"slug": "jackett", "slug": "jackett",
"repo": "Jackett/Jackett", "repo": "Jackett/Jackett",
"version": "v0.24.1193", "version": "v0.24.1184",
"pinned": false, "pinned": false,
"date": "2026-02-24T05:58:04Z" "date": "2026-02-23T05:55:36Z"
}, },
{ {
"slug": "jellystat", "slug": "jellystat",
@@ -858,9 +858,9 @@
{ {
"slug": "memos", "slug": "memos",
"repo": "usememos/memos", "repo": "usememos/memos",
"version": "v0.26.2", "version": "v0.25.3",
"pinned": false, "pinned": true,
"date": "2026-02-23T13:28:34Z" "date": "2025-11-25T15:40:41Z"
}, },
{ {
"slug": "metube", "slug": "metube",
@@ -1117,9 +1117,9 @@
{ {
"slug": "planka", "slug": "planka",
"repo": "plankanban/planka", "repo": "plankanban/planka",
"version": "v2.0.2", "version": "v2.0.1",
"pinned": false, "pinned": false,
"date": "2026-02-23T17:47:15Z" "date": "2026-02-17T15:26:55Z"
}, },
{ {
"slug": "plant-it", "slug": "plant-it",
@@ -1138,9 +1138,9 @@
{ {
"slug": "pocketid", "slug": "pocketid",
"repo": "pocket-id/pocket-id", "repo": "pocket-id/pocket-id",
"version": "v2.3.0", "version": "v2.2.0",
"pinned": false, "pinned": false,
"date": "2026-02-23T19:50:48Z" "date": "2026-01-11T15:01:07Z"
}, },
{ {
"slug": "privatebin", "slug": "privatebin",
@@ -1243,9 +1243,9 @@
{ {
"slug": "qui", "slug": "qui",
"repo": "autobrr/qui", "repo": "autobrr/qui",
"version": "v1.14.1", "version": "v1.14.0",
"pinned": false, "pinned": false,
"date": "2026-02-23T13:13:31Z" "date": "2026-02-21T22:23:46Z"
}, },
{ {
"slug": "radarr", "slug": "radarr",
@@ -1271,9 +1271,9 @@
{ {
"slug": "rdtclient", "slug": "rdtclient",
"repo": "rogerfar/rdt-client", "repo": "rogerfar/rdt-client",
"version": "v2.0.124", "version": "v2.0.123",
"pinned": false, "pinned": false,
"date": "2026-02-24T03:18:03Z" "date": "2026-02-21T23:08:13Z"
}, },
{ {
"slug": "reactive-resume", "slug": "reactive-resume",
@@ -1345,13 +1345,6 @@
"pinned": false, "pinned": false,
"date": "2026-02-12T14:20:56Z" "date": "2026-02-12T14:20:56Z"
}, },
{
"slug": "seaweedfs",
"repo": "seaweedfs/seaweedfs",
"version": "4.13",
"pinned": false,
"date": "2026-02-17T01:09:45Z"
},
{ {
"slug": "seelf", "slug": "seelf",
"repo": "YuukanOO/seelf", "repo": "YuukanOO/seelf",
@@ -1404,16 +1397,16 @@
{ {
"slug": "snipeit", "slug": "snipeit",
"repo": "grokability/snipe-it", "repo": "grokability/snipe-it",
"version": "v8.4.0", "version": "v8.3.7",
"pinned": false, "pinned": false,
"date": "2026-02-23T20:59:43Z" "date": "2025-12-12T09:13:40Z"
}, },
{ {
"slug": "snowshare", "slug": "snowshare",
"repo": "TuroYT/snowshare", "repo": "TuroYT/snowshare",
"version": "v1.3.7", "version": "v1.3.6",
"pinned": false, "pinned": false,
"date": "2026-02-23T15:51:39Z" "date": "2026-02-19T11:06:29Z"
}, },
{ {
"slug": "sonarr", "slug": "sonarr",
@@ -1422,13 +1415,6 @@
"pinned": false, "pinned": false,
"date": "2025-11-05T01:56:48Z" "date": "2025-11-05T01:56:48Z"
}, },
{
"slug": "sonobarr",
"repo": "Dodelidoo-Labs/sonobarr",
"version": "0.11.0",
"pinned": false,
"date": "2026-01-21T19:07:21Z"
},
{ {
"slug": "speedtest-tracker", "slug": "speedtest-tracker",
"repo": "alexjustesen/speedtest-tracker", "repo": "alexjustesen/speedtest-tracker",
@@ -1453,9 +1439,9 @@
{ {
"slug": "stirling-pdf", "slug": "stirling-pdf",
"repo": "Stirling-Tools/Stirling-PDF", "repo": "Stirling-Tools/Stirling-PDF",
"version": "v2.5.3", "version": "v2.5.2",
"pinned": false, "pinned": false,
"date": "2026-02-23T23:23:39Z" "date": "2026-02-20T23:20:20Z"
}, },
{ {
"slug": "streamlink-webui", "slug": "streamlink-webui",
@@ -1565,9 +1551,9 @@
{ {
"slug": "traefik", "slug": "traefik",
"repo": "traefik/traefik", "repo": "traefik/traefik",
"version": "v3.6.9", "version": "v3.6.8",
"pinned": false, "pinned": false,
"date": "2026-02-23T17:21:17Z" "date": "2026-02-11T16:44:37Z"
}, },
{ {
"slug": "trilium", "slug": "trilium",
@@ -1579,9 +1565,9 @@
{ {
"slug": "trip", "slug": "trip",
"repo": "itskovacs/TRIP", "repo": "itskovacs/TRIP",
"version": "1.41.0", "version": "1.40.0",
"pinned": false, "pinned": false,
"date": "2026-02-23T17:57:31Z" "date": "2026-02-10T20:12:53Z"
}, },
{ {
"slug": "tududi", "slug": "tududi",
@@ -1593,9 +1579,9 @@
{ {
"slug": "tunarr", "slug": "tunarr",
"repo": "chrisbenincasa/tunarr", "repo": "chrisbenincasa/tunarr",
"version": "v1.1.16", "version": "v1.1.15",
"pinned": false, "pinned": false,
"date": "2026-02-23T21:24:47Z" "date": "2026-02-19T23:51:17Z"
}, },
{ {
"slug": "uhf", "slug": "uhf",
@@ -1642,9 +1628,9 @@
{ {
"slug": "vaultwarden", "slug": "vaultwarden",
"repo": "dani-garcia/vaultwarden", "repo": "dani-garcia/vaultwarden",
"version": "1.35.4", "version": "1.35.3",
"pinned": false, "pinned": false,
"date": "2026-02-23T21:43:25Z" "date": "2026-02-10T20:37:03Z"
}, },
{ {
"slug": "victoriametrics", "slug": "victoriametrics",
@@ -1677,9 +1663,9 @@
{ {
"slug": "wanderer", "slug": "wanderer",
"repo": "meilisearch/meilisearch", "repo": "meilisearch/meilisearch",
"version": "v1.36.0", "version": "v1.35.1",
"pinned": false, "pinned": false,
"date": "2026-02-23T08:13:32Z" "date": "2026-02-16T17:01:17Z"
}, },
{ {
"slug": "warracker", "slug": "warracker",
@@ -1740,16 +1726,16 @@
{ {
"slug": "wishlist", "slug": "wishlist",
"repo": "cmintey/wishlist", "repo": "cmintey/wishlist",
"version": "v0.60.1", "version": "v0.60.0",
"pinned": false, "pinned": false,
"date": "2026-02-24T04:01:37Z" "date": "2026-02-10T04:05:26Z"
}, },
{ {
"slug": "wizarr", "slug": "wizarr",
"repo": "wizarrrr/wizarr", "repo": "wizarrrr/wizarr",
"version": "v2026.2.0", "version": "v2025.12.0",
"pinned": false, "pinned": false,
"date": "2026-02-23T19:25:28Z" "date": "2025-12-09T14:30:23Z"
}, },
{ {
"slug": "writefreely", "slug": "writefreely",

View File

@@ -0,0 +1,36 @@
{
"name": "Huntarr",
"slug": "huntarr",
"categories": [
14
],
"date_created": "2025-06-18",
"type": "ct",
"updateable": true,
"privileged": false,
"interface_port": 9705,
"documentation": "https://github.com/plexguide/Huntarr.io/wiki",
"config_path": "/opt/huntarr",
"website": "https://github.com/plexguide/Huntarr.io",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/huntarr.webp",
"description": "Huntarr is a tool that automates the search for missing or low-quality media content in your collection. It works seamlessly with applications like Sonarr, Radarr, Lidarr, Readarr, and Whisparr, enhancing their functionality with continuous background scans to identify and update missed or outdated content. Through a user-friendly web interface accessible on port 9705, Huntarr provides real-time statistics, log views, and extensive configuration options. The software is especially useful for users who want to keep their media library up to date by automatically searching for missing episodes or higher-quality versions. Huntarr is well-suited for self-hosted environments and can easily run in LXC containers or Docker setups.",
"disable": false,
"install_methods": [
{
"type": "default",
"script": "ct/huntarr.sh",
"resources": {
"cpu": 2,
"ram": 1024,
"hdd": 4,
"os": "debian",
"version": "13"
}
}
],
"default_credentials": {
"username": null,
"password": null
},
"notes": []
}

View File

@@ -6,7 +6,7 @@
], ],
"date_created": "2025-08-26", "date_created": "2025-08-26",
"type": "ct", "type": "ct",
"updateable": true, "updateable": false,
"privileged": false, "privileged": false,
"interface_port": 8888, "interface_port": 8888,
"documentation": "https://docs.searxng.org/", "documentation": "https://docs.searxng.org/",

View File

@@ -1,48 +0,0 @@
{
"name": "SeaweedFS",
"slug": "seaweedfs",
"categories": [
11
],
"date_created": "2026-02-23",
"type": "ct",
"updateable": true,
"privileged": false,
"interface_port": 9333,
"documentation": "https://github.com/seaweedfs/seaweedfs/wiki",
"website": "https://seaweedfs.com/",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/seaweedfs.webp",
"config_path": "",
"description": "SeaweedFS is a fast distributed storage system for blobs, objects, files, and data lakes, with O(1) disk seek, S3 API, FUSE mount, WebDAV, and cloud tiering support.",
"install_methods": [
{
"type": "default",
"script": "ct/seaweedfs.sh",
"resources": {
"cpu": 2,
"ram": 2048,
"hdd": 16,
"os": "Debian",
"version": "13"
}
}
],
"default_credentials": {
"username": null,
"password": null
},
"notes": [
{
"text": "Master UI available at port 9333, Filer UI at port 8888, S3 API at port 8333.",
"type": "info"
},
{
"text": "Data is stored in /opt/seaweedfs-data.",
"type": "info"
},
{
"text": "FUSE mounting requires fuse3 (pre-installed).",
"type": "info"
}
]
}

View File

@@ -1,40 +0,0 @@
{
"name": "Sonobarr",
"slug": "sonobarr",
"categories": [
14
],
"date_created": "2026-02-23",
"type": "ct",
"updateable": true,
"privileged": false,
"interface_port": 5000,
"documentation": "https://github.com/Dodelidoo-Labs/sonobarr",
"config_path": "/etc/sonobarr/.env",
"website": "https://github.com/Dodelidoo-Labs/sonobarr",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/sonobarr.webp",
"description": "Sonobarr marries your existing Lidarr library with Last.fms discovery graph to surface artists you'll actually like. It runs as a Flask + Socket.IO application, ships with a polished Bootstrap UI, and includes admin tooling so folks can share a single instance safely.",
"install_methods": [
{
"type": "default",
"script": "ct/sonobarr.sh",
"resources": {
"cpu": 1,
"ram": 1024,
"hdd": 20,
"os": "Debian",
"version": "13"
}
}
],
"default_credentials": {
"username": null,
"password": null
},
"notes": [
{
"text": "Default generated admin password is in the env file (sonobarr_superadmin_password)",
"type": "info"
}
]
}

View File

@@ -1,35 +0,0 @@
{
"name": "SparkyFitness",
"slug": "sparkyfitness",
"categories": [
9
],
"date_created": "2026-02-23",
"type": "ct",
"updateable": true,
"privileged": false,
"interface_port": 80,
"documentation": "https://codewithcj.github.io/SparkyFitness",
"website": "https://github.com/CodeWithCJ/SparkyFitness",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/sparkyfitness.webp",
"config_path": "/etc/sparkyfitness/.env",
"description": "A self-hosted, privacy-first alternative to MyFitnessPal. Track nutrition, exercise, body metrics, and health data while keeping full control of your data.",
"install_methods": [
{
"type": "default",
"script": "ct/sparkyfitness.sh",
"resources": {
"cpu": 2,
"ram": 2048,
"hdd": 4,
"os": "Debian",
"version": "13"
}
}
],
"default_credentials": {
"username": null,
"password": null
},
"notes": []
}

View File

@@ -13,7 +13,11 @@ setting_up_container
network_check network_check
update_os update_os
JAVA_VERSION="25" setup_java msg_info "Installing Dependencies"
$STD apt install -y nginx
msg_ok "Installed Dependencies"
JAVA_VERSION="21" setup_java
NODE_VERSION="22" setup_nodejs NODE_VERSION="22" setup_nodejs
setup_mariadb setup_mariadb
setup_yq setup_yq
@@ -26,11 +30,6 @@ $STD npm install --force
$STD npm run build --configuration=production $STD npm run build --configuration=production
msg_ok "Built Frontend" msg_ok "Built Frontend"
msg_info "Embedding Frontend into Backend"
mkdir -p /opt/booklore/booklore-api/src/main/resources/static
cp -r /opt/booklore/booklore-ui/dist/booklore/browser/* /opt/booklore/booklore-api/src/main/resources/static/
msg_ok "Embedded Frontend into Backend"
msg_info "Creating Environment" msg_info "Creating Environment"
mkdir -p /opt/booklore_storage/{data,books,bookdrop} mkdir -p /opt/booklore_storage/{data,books,bookdrop}
cat <<EOF >/opt/booklore_storage/.env cat <<EOF >/opt/booklore_storage/.env
@@ -42,7 +41,6 @@ DATABASE_PASSWORD=${MARIADB_DB_PASS}
# App Configuration (Spring Boot mapping from app.* properties) # App Configuration (Spring Boot mapping from app.* properties)
APP_PATH_CONFIG=/opt/booklore_storage/data APP_PATH_CONFIG=/opt/booklore_storage/data
APP_BOOKDROP_FOLDER=/opt/booklore_storage/bookdrop APP_BOOKDROP_FOLDER=/opt/booklore_storage/bookdrop
SERVER_PORT=6060
EOF EOF
msg_ok "Created Environment" msg_ok "Created Environment"
@@ -50,7 +48,7 @@ msg_info "Building Backend"
cd /opt/booklore/booklore-api cd /opt/booklore/booklore-api
APP_VERSION=$(get_latest_github_release "booklore-app/BookLore") APP_VERSION=$(get_latest_github_release "booklore-app/BookLore")
yq eval ".app.version = \"${APP_VERSION}\"" -i src/main/resources/application.yaml yq eval ".app.version = \"${APP_VERSION}\"" -i src/main/resources/application.yaml
$STD ./gradlew clean build -x test --no-daemon $STD ./gradlew clean build --no-daemon
mkdir -p /opt/booklore/dist 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
@@ -60,6 +58,16 @@ 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"
msg_info "Configuring Nginx"
rm -rf /usr/share/nginx/html
ln -s /opt/booklore/booklore-ui/dist/booklore/browser /usr/share/nginx/html
rm -f /etc/nginx/sites-enabled/default
cp /opt/booklore/nginx.conf /etc/nginx/nginx.conf
sed -i 's/listen \${BOOKLORE_PORT};/listen 6060;/' /etc/nginx/nginx.conf
sed -i 's/listen \[::\]:${BOOKLORE_PORT};/listen [::]:6060;/' /etc/nginx/nginx.conf
systemctl restart nginx
msg_ok "Configured Nginx"
msg_info "Creating Service" msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/booklore.service cat <<EOF >/etc/systemd/system/booklore.service
[Unit] [Unit]
@@ -70,7 +78,7 @@ After=network.target mariadb.service
Type=simple Type=simple
User=root User=root
WorkingDirectory=/opt/booklore/dist WorkingDirectory=/opt/booklore/dist
ExecStart=/usr/bin/java -XX:+UseG1GC -XX:+UseStringDeduplication -XX:+UseCompactObjectHeaders -jar /opt/booklore/dist/app.jar ExecStart=/usr/bin/java -jar /opt/booklore/dist/app.jar
EnvironmentFile=/opt/booklore_storage/.env EnvironmentFile=/opt/booklore_storage/.env
SuccessExitStatus=143 SuccessExitStatus=143
TimeoutStopSec=10 TimeoutStopSec=10

View File

@@ -21,8 +21,6 @@ msg_ok "Installed Dependencies"
NODE_VERSION="22" NODE_MODULE="yarn" setup_nodejs NODE_VERSION="22" NODE_MODULE="yarn" setup_nodejs
echo "${TAB3}It is important to choose the name for your server before you install Synapse, because it cannot be changed later."
echo "${TAB3}The server name determines the “domain” part of user-ids for users on your server: these will all be of the format @user:my.domain.name. It also determines how other matrix servers will reach yours for federation."
read -p "${TAB3}Please enter the name for your server: " servername read -p "${TAB3}Please enter the name for your server: " servername
msg_info "Installing Element Synapse" msg_info "Installing Element Synapse"

View File

@@ -13,7 +13,7 @@ setting_up_container
network_check network_check
update_os update_os
PHP_VERSION="8.5" PHP_APACHE="YES" setup_php PHP_VERSION="8.4" PHP_APACHE="YES" setup_php
setup_composer setup_composer
setup_mariadb setup_mariadb
MARIADB_DB_NAME="firefly" MARIADB_DB_USER="firefly" setup_mariadb_db MARIADB_DB_NAME="firefly" MARIADB_DB_USER="firefly" setup_mariadb_db

View File

@@ -175,10 +175,7 @@ cp -a /opt/frigate/docker/main/rootfs/. /
sed -i '/^.*unset DEBIAN_FRONTEND.*$/d' /opt/frigate/docker/main/install_deps.sh sed -i '/^.*unset DEBIAN_FRONTEND.*$/d' /opt/frigate/docker/main/install_deps.sh
echo "libedgetpu1-max libedgetpu/accepted-eula boolean true" | debconf-set-selections echo "libedgetpu1-max libedgetpu/accepted-eula boolean true" | debconf-set-selections
echo "libedgetpu1-max libedgetpu/install-confirm-max boolean true" | debconf-set-selections echo "libedgetpu1-max libedgetpu/install-confirm-max boolean true" | debconf-set-selections
# Allow Frigate's Intel media packages to overwrite files from system GPU driver packages
echo 'force-overwrite' >/etc/dpkg/dpkg.cfg.d/force-overwrite
$STD bash /opt/frigate/docker/main/install_deps.sh $STD bash /opt/frigate/docker/main/install_deps.sh
rm -f /etc/dpkg/dpkg.cfg.d/force-overwrite
$STD pip3 install -U /wheels/*.whl $STD pip3 install -U /wheels/*.whl
ldconfig ldconfig
msg_ok "Installed HailoRT Runtime" msg_ok "Installed HailoRT Runtime"

View File

@@ -0,0 +1,45 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: BiluliB
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/plexguide/Huntarr.io
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
msg_ok "Installed Dependencies"
PYTHON_VERSION="3.12" setup_uv
fetch_and_deploy_gh_release "huntarr" "plexguide/Huntarr.io" "tarball"
msg_info "Configure Huntarr"
$STD uv venv --clear /opt/huntarr/.venv
$STD uv pip install --python /opt/huntarr/.venv/bin/python -r /opt/huntarr/requirements.txt
msg_ok "Configured Huntrarr"
msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/huntarr.service
[Unit]
Description=Huntarr Service
After=network.target
[Service]
WorkingDirectory=/opt/huntarr
ExecStart=/opt/huntarr/.venv/bin/python /opt/huntarr/main.py
Restart=always
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now huntarr
msg_ok "Created Service"
motd_ssh
customize
cleanup_lxc

View File

@@ -14,7 +14,7 @@ setting_up_container
network_check network_check
update_os update_os
fetch_and_deploy_gh_release "memos" "usememos/memos" "prebuild" "latest" "/opt/memos" "memos*linux_amd64.tar.gz" fetch_and_deploy_gh_release "memos" "usememos/memos" "prebuild" "v0.25.3" "/opt/memos" "memos*linux_amd64.tar.gz"
mkdir -p /opt/memos_data mkdir -p /opt/memos_data
msg_info "Creating Service" msg_info "Creating Service"

View File

@@ -1,50 +0,0 @@
#!/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/seaweedfs/seaweedfs
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 fuse3
msg_ok "Installed Dependencies"
fetch_and_deploy_gh_release "seaweedfs" "seaweedfs/seaweedfs" "prebuild" "latest" "/opt/seaweedfs" "linux_amd64.tar.gz"
msg_info "Setting up SeaweedFS"
mkdir -p /opt/seaweedfs-data
ln -sf /opt/seaweedfs/weed /usr/local/bin/weed
msg_ok "Set up SeaweedFS"
msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/seaweedfs.service
[Unit]
Description=SeaweedFS Server
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/seaweedfs
ExecStart=/opt/seaweedfs/weed server -dir=/opt/seaweedfs-data -master.port=9333 -volume.port=8080 -filer -s3
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now seaweedfs
msg_ok "Created Service"
motd_ssh
customize
cleanup_lxc

View File

@@ -1,52 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: GoldenSpringness
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/Dodelidoo-Labs/sonobarr
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
fetch_and_deploy_gh_release "sonobarr" "Dodelidoo-Labs/sonobarr" "tarball"
PYTHON_VERSION="3.12" setup_uv
msg_info "Setting up sonobarr"
$STD uv venv -c /opt/sonobarr/venv
source /opt/sonobarr/venv/bin/activate
$STD uv pip install --no-cache-dir -r /opt/sonobarr/requirements.txt
mkdir -p /etc/sonobarr
mv /opt/sonobarr/.sample-env /etc/sonobarr/.env
sed -i "s/^secret_key=.*/secret_key=$(openssl rand -hex 16)/" /etc/sonobarr/.env
sed -i "s/^sonobarr_superadmin_password=.*/sonobarr_superadmin_password=$(openssl rand -hex 16)/" /etc/sonobarr/.env
echo "release_version=$(cat ~/.sonobarr)" >>/etc/sonobarr/.env
echo "sonobarr_config_dir=/etc/sonobarr" >>/etc/sonobarr.env
msg_ok "Set up sonobarr"
msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/sonobarr.service
[Unit]
Description=sonobarr Service
After=network.target
[Service]
WorkingDirectory=/opt/sonobarr/src
EnvironmentFile=/etc/sonobarr/.env
Environment="PATH=/opt/sonobarr/venv/bin"
ExecStart=/bin/bash -c 'gunicorn Sonobarr:app -c ../gunicorn_config.py'
Restart=always
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now sonobarr
msg_ok "Created Service"
motd_ssh
customize
cleanup_lxc

View File

@@ -1,95 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: Tom Frenzel (tomfrenzel)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/CodeWithCJ/SparkyFitness
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 nginx
msg_ok "Installed Dependencies"
NODE_VERSION="25" setup_nodejs
PG_VERSION="18" setup_postgresql
PG_DB_NAME="sparkyfitness" PG_DB_USER="sparky" PG_DB_GRANT_SUPERUSER="true" setup_postgresql_db
fetch_and_deploy_gh_release sparkyfitness "CodeWithCJ/SparkyFitness" "tarball" "latest"
msg_info "Configuring Sparky Fitness"
mkdir -p "/etc/sparkyfitness" "/var/lib/sparkyfitness/uploads" "/var/lib/sparkyfitness/backup" "/var/www/sparkyfitness"
cp "/opt/sparkyfitness/docker/.env.example" "/etc/sparkyfitness/.env"
sed \
-i \
-e "s|^#\?SPARKY_FITNESS_DB_HOST=.*|SPARKY_FITNESS_DB_HOST=localhost|" \
-e "s|^#\?SPARKY_FITNESS_DB_PORT=.*|SPARKY_FITNESS_DB_PORT=5432|" \
-e "s|^SPARKY_FITNESS_DB_NAME=.*|SPARKY_FITNESS_DB_NAME=sparkyfitness|" \
-e "s|^SPARKY_FITNESS_DB_USER=.*|SPARKY_FITNESS_DB_USER=sparky|" \
-e "s|^SPARKY_FITNESS_DB_PASSWORD=.*|SPARKY_FITNESS_DB_PASSWORD=${PG_DB_PASS}|" \
-e "s|^SPARKY_FITNESS_APP_DB_USER=.*|SPARKY_FITNESS_APP_DB_USER=sparky_app|" \
-e "s|^SPARKY_FITNESS_APP_DB_PASSWORD=.*|SPARKY_FITNESS_APP_DB_PASSWORD=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c20)|" \
-e "s|^SPARKY_FITNESS_SERVER_HOST=.*|SPARKY_FITNESS_SERVER_HOST=localhost|" \
-e "s|^SPARKY_FITNESS_SERVER_PORT=.*|SPARKY_FITNESS_SERVER_PORT=3010|" \
-e "s|^SPARKY_FITNESS_FRONTEND_URL=.*|SPARKY_FITNESS_FRONTEND_URL=http://${LOCAL_IP}:80|" \
-e "s|^SPARKY_FITNESS_API_ENCRYPTION_KEY=.*|SPARKY_FITNESS_API_ENCRYPTION_KEY=$(openssl rand -hex 32)|" \
-e "s|^BETTER_AUTH_SECRET=.*|BETTER_AUTH_SECRET=$(openssl rand -hex 32)|" \
"/etc/sparkyfitness/.env"
msg_ok "Configured Sparky Fitness"
msg_info "Building Backend"
cd /opt/sparkyfitness/SparkyFitnessServer
$STD npm install
msg_ok "Built Backend"
msg_info "Building Frontend (Patience)"
cd /opt/sparkyfitness/SparkyFitnessFrontend
$STD npm install
$STD npm run build
cp -a /opt/sparkyfitness/SparkyFitnessFrontend/dist/. /var/www/sparkyfitness/
msg_ok "Built Frontend"
msg_info "Creating SparkyFitness Service"
cat <<EOF >/etc/systemd/system/sparkyfitness-server.service
[Unit]
Description=SparkyFitness Backend Service
After=network.target postgresql.service
Requires=postgresql.service
[Service]
Type=simple
WorkingDirectory=/opt/sparkyfitness/SparkyFitnessServer
EnvironmentFile=/etc/sparkyfitness/.env
ExecStart=/usr/bin/node SparkyFitnessServer.js
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now sparkyfitness-server
msg_ok "Created SparkyFitness Service"
msg_info "Configuring Nginx"
sed \
-e 's|${SPARKY_FITNESS_SERVER_HOST}|127.0.0.1|g' \
-e 's|${SPARKY_FITNESS_SERVER_PORT}|3010|g' \
-e 's|root /usr/share/nginx/html;|root /var/www/sparkyfitness;|g' \
-e 's|server_name localhost;|server_name _;|g' \
"/opt/sparkyfitness/docker/nginx.conf" >/etc/nginx/sites-available/sparkyfitness
ln -sf /etc/nginx/sites-available/sparkyfitness /etc/nginx/sites-enabled/sparkyfitness
rm -f /etc/nginx/sites-enabled/default
$STD nginx -t
$STD systemctl enable -q --now nginx
$STD systemctl reload nginx
msg_ok "Configured Nginx"
motd_ssh
customize
cleanup_lxc

View File

@@ -25,9 +25,7 @@ get_lxc_ip
# post_progress_to_api() # post_progress_to_api()
# #
# - Lightweight progress ping from inside the container # - Lightweight progress ping from inside the container
# - Updates the existing telemetry record status # - Updates the existing telemetry record status from "installing" to "configuring"
# - Arguments:
# * $1: status (optional, default: "configuring")
# - Signals that the installation is actively progressing (not stuck) # - Signals that the installation is actively progressing (not stuck)
# - Fire-and-forget: never blocks or fails the script # - Fire-and-forget: never blocks or fails the script
# - Only executes if DIAGNOSTICS=yes and RANDOM_UUID is set # - Only executes if DIAGNOSTICS=yes and RANDOM_UUID is set
@@ -37,11 +35,9 @@ post_progress_to_api() {
[[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0 [[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0
[[ -z "${RANDOM_UUID:-}" ]] && return 0 [[ -z "${RANDOM_UUID:-}" ]] && return 0
local progress_status="${1:-configuring}"
curl -fsS -m 5 -X POST "https://telemetry.community-scripts.org/telemetry" \ curl -fsS -m 5 -X POST "https://telemetry.community-scripts.org/telemetry" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "{\"random_id\":\"${RANDOM_UUID}\",\"execution_id\":\"${EXECUTION_ID:-${RANDOM_UUID}}\",\"type\":\"lxc\",\"nsapp\":\"${app:-unknown}\",\"status\":\"${progress_status}\"}" &>/dev/null || true -d "{\"random_id\":\"${RANDOM_UUID}\",\"execution_id\":\"${EXECUTION_ID:-${RANDOM_UUID}}\",\"type\":\"lxc\",\"nsapp\":\"${app:-unknown}\",\"status\":\"configuring\"}" &>/dev/null || true
} }
# This function enables IPv6 if it's not disabled and sets verbose mode # This function enables IPv6 if it's not disabled and sets verbose mode

View File

@@ -35,11 +35,7 @@
TELEMETRY_URL="https://telemetry.community-scripts.org/telemetry" TELEMETRY_URL="https://telemetry.community-scripts.org/telemetry"
# Timeout for telemetry requests (seconds) # Timeout for telemetry requests (seconds)
# Progress pings (validation/configuring) use the short timeout
TELEMETRY_TIMEOUT=5 TELEMETRY_TIMEOUT=5
# Final status updates (success/failed) use the longer timeout
# PocketBase may need more time under load (FindRecord + UpdateRecord)
STATUS_TIMEOUT=10
# ============================================================================== # ==============================================================================
# SECTION 0: REPOSITORY SOURCE DETECTION # SECTION 0: REPOSITORY SOURCE DETECTION
@@ -354,55 +350,6 @@ get_error_text() {
fi fi
} }
# ------------------------------------------------------------------------------
# get_full_log()
#
# - Returns the FULL installation log (build + install combined)
# - Calls ensure_log_on_host() to pull container log if needed
# - Strips ANSI escape codes and carriage returns
# - Truncates to max_bytes (default: 120KB) to stay within API limits
# - Used for the error telemetry field (full trace instead of 20 lines)
# ------------------------------------------------------------------------------
get_full_log() {
local max_bytes="${1:-122880}" # 120KB default
local logfile=""
# Ensure logs are available on host (pulls from container if needed)
if declare -f ensure_log_on_host >/dev/null 2>&1; then
ensure_log_on_host
fi
# Try combined log first (most complete)
if [[ -n "${CTID:-}" && -n "${SESSION_ID:-}" ]]; then
local combined_log="/tmp/${NSAPP:-lxc}-${CTID}-${SESSION_ID}.log"
if [[ -s "$combined_log" ]]; then
logfile="$combined_log"
fi
fi
# Fall back to INSTALL_LOG
if [[ -z "$logfile" || ! -s "$logfile" ]]; then
if [[ -n "${INSTALL_LOG:-}" && -s "${INSTALL_LOG}" ]]; then
logfile="$INSTALL_LOG"
fi
fi
# Fall back to BUILD_LOG
if [[ -z "$logfile" || ! -s "$logfile" ]]; then
if [[ -n "${BUILD_LOG:-}" && -s "${BUILD_LOG}" ]]; then
logfile="$BUILD_LOG"
fi
fi
if [[ -n "$logfile" && -s "$logfile" ]]; then
# Strip ANSI codes, carriage returns, and anonymize IP addresses (GDPR)
sed 's/\r$//' "$logfile" 2>/dev/null |
sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' |
sed -E 's/([0-9]{1,3}\.)[0-9]{1,3}\.[0-9]{1,3}/\1x.x/g' |
head -c "$max_bytes"
fi
}
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# build_error_string() # build_error_string()
# #
@@ -745,10 +692,7 @@ EOF
# post_progress_to_api() # post_progress_to_api()
# #
# - Lightweight progress ping from host or container # - Lightweight progress ping from host or container
# - Updates the existing telemetry record status # - Updates the existing telemetry record status to "configuring"
# - Arguments:
# * $1: status (optional, default: "configuring")
# Valid values: "validation", "configuring"
# - Signals that the installation is actively progressing (not stuck) # - Signals that the installation is actively progressing (not stuck)
# - Fire-and-forget: never blocks or fails the script # - Fire-and-forget: never blocks or fails the script
# - Only executes if DIAGNOSTICS=yes and RANDOM_UUID is set # - Only executes if DIAGNOSTICS=yes and RANDOM_UUID is set
@@ -759,13 +703,12 @@ post_progress_to_api() {
[[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0 [[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0
[[ -z "${RANDOM_UUID:-}" ]] && return 0 [[ -z "${RANDOM_UUID:-}" ]] && return 0
local progress_status="${1:-configuring}"
local app_name="${NSAPP:-${app:-unknown}}" local app_name="${NSAPP:-${app:-unknown}}"
local telemetry_type="${TELEMETRY_TYPE:-lxc}" local telemetry_type="${TELEMETRY_TYPE:-lxc}"
curl -fsS -m 5 -X POST "${TELEMETRY_URL:-https://telemetry.community-scripts.org/telemetry}" \ curl -fsS -m 5 -X POST "${TELEMETRY_URL:-https://telemetry.community-scripts.org/telemetry}" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "{\"random_id\":\"${RANDOM_UUID}\",\"execution_id\":\"${EXECUTION_ID:-${RANDOM_UUID}}\",\"type\":\"${telemetry_type}\",\"nsapp\":\"${app_name}\",\"status\":\"${progress_status}\"}" &>/dev/null || true -d "{\"random_id\":\"${RANDOM_UUID}\",\"execution_id\":\"${EXECUTION_ID:-${RANDOM_UUID}}\",\"type\":\"${telemetry_type}\",\"nsapp\":\"${app_name}\",\"status\":\"configuring\"}" &>/dev/null || true
} }
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@@ -839,15 +782,11 @@ post_update_to_api() {
else else
exit_code=1 exit_code=1
fi fi
# Get full installation log for error field # Get log lines and build structured error string
local log_text="" local error_text=""
log_text=$(get_full_log 122880) || true # 120KB max error_text=$(get_error_text)
if [[ -z "$log_text" ]]; then
# Fallback to last 20 lines
log_text=$(get_error_text)
fi
local full_error local full_error
full_error=$(build_error_string "$exit_code" "$log_text") full_error=$(build_error_string "$exit_code" "$error_text")
error=$(json_escape "$full_error") error=$(json_escape "$full_error")
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")
@@ -868,7 +807,7 @@ post_update_to_api() {
local http_code="" local http_code=""
# ── Attempt 1: Full payload with complete error text (includes full log) ── # ── Attempt 1: Full payload with complete error text ──
local JSON_PAYLOAD local JSON_PAYLOAD
JSON_PAYLOAD=$( JSON_PAYLOAD=$(
cat <<EOF cat <<EOF
@@ -901,7 +840,7 @@ post_update_to_api() {
EOF EOF
) )
http_code=$(curl -sS -w "%{http_code}" -m "${STATUS_TIMEOUT}" -X POST "${TELEMETRY_URL}" \ http_code=$(curl -sS -w "%{http_code}" -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "$JSON_PAYLOAD" -o /dev/null 2>/dev/null) || http_code="000" -d "$JSON_PAYLOAD" -o /dev/null 2>/dev/null) || http_code="000"
@@ -944,7 +883,7 @@ EOF
EOF EOF
) )
http_code=$(curl -sS -w "%{http_code}" -m "${STATUS_TIMEOUT}" -X POST "${TELEMETRY_URL}" \ http_code=$(curl -sS -w "%{http_code}" -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "$RETRY_PAYLOAD" -o /dev/null 2>/dev/null) || http_code="000" -d "$RETRY_PAYLOAD" -o /dev/null 2>/dev/null) || http_code="000"
@@ -972,18 +911,12 @@ EOF
EOF EOF
) )
http_code=$(curl -sS -w "%{http_code}" -m "${STATUS_TIMEOUT}" -X POST "${TELEMETRY_URL}" \ curl -sS -w "%{http_code}" -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "$MINIMAL_PAYLOAD" -o /dev/null 2>/dev/null) || http_code="000" -d "$MINIMAL_PAYLOAD" -o /dev/null 2>/dev/null || true
if [[ "$http_code" =~ ^2[0-9]{2}$ ]]; then # Tried 3 times - mark as done regardless to prevent infinite loops
POST_UPDATE_DONE=true POST_UPDATE_DONE=true
return 0
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.
# No infinite loop risk: EXIT trap fires exactly once.
} }
# ============================================================================== # ==============================================================================
@@ -1036,16 +969,16 @@ categorize_error() {
# Python environment errors # Python environment errors
# (already covered: 160-162 under dependency) # (already covered: 160-162 under dependency)
# Aborted by user (SIGHUP=terminal closed, SIGINT=Ctrl+C, SIGTERM=killed) # Aborted by user
129 | 130 | 143) echo "user_aborted" ;; 130) echo "aborted" ;;
# Resource errors (OOM, SIGKILL, SIGABRT) # Resource errors (OOM, SIGKILL, SIGABRT)
134 | 137) echo "resource" ;; 134 | 137) echo "resource" ;;
# Signal/Process errors (SIGPIPE, SIGSEGV) # Signal/Process errors (SIGTERM, SIGPIPE, SIGSEGV)
139 | 141) echo "signal" ;; 139 | 141 | 143) echo "signal" ;;
# Shell errors (general error, syntax error) # Shell errors (general error, syntax error)
1 | 2) echo "shell" ;; 1 | 2) echo "shell" ;;
# Default - truly unknown # Default - truly unknown
@@ -1359,27 +1292,9 @@ post_update_to_api_extended() {
EOF EOF
) )
local http_code curl -fsS -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
http_code=$(curl -sS -w "%{http_code}" -m "${STATUS_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "$JSON_PAYLOAD" -o /dev/null 2>/dev/null) || http_code="000" -d "$JSON_PAYLOAD" &>/dev/null || true
if [[ "$http_code" =~ ^2[0-9]{2}$ ]]; then POST_UPDATE_DONE=true
POST_UPDATE_DONE=true
return 0
fi
# Retry with minimal payload
sleep 1
http_code=$(curl -sS -w "%{http_code}" -m "${STATUS_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
-H "Content-Type: application/json" \
-d "{\"random_id\":\"${RANDOM_UUID}\",\"execution_id\":\"${EXECUTION_ID:-${RANDOM_UUID}}\",\"type\":\"${TELEMETRY_TYPE:-lxc}\",\"nsapp\":\"${NSAPP:-unknown}\",\"status\":\"${pb_status}\",\"exit_code\":${exit_code},\"install_duration\":${duration:-0}}" \
-o /dev/null 2>/dev/null) || http_code="000"
if [[ "$http_code" =~ ^2[0-9]{2}$ ]]; then
POST_UPDATE_DONE=true
return 0
fi
# Do NOT set POST_UPDATE_DONE=true — let EXIT trap retry
} }

View File

@@ -3578,13 +3578,6 @@ build_container() {
# DEV_MODE exports (optional, for debugging) # DEV_MODE exports (optional, for debugging)
export BUILD_LOG="$BUILD_LOG" export BUILD_LOG="$BUILD_LOG"
export INSTALL_LOG="/root/.install-${SESSION_ID}.log" export INSTALL_LOG="/root/.install-${SESSION_ID}.log"
# Keep host-side logging on BUILD_LOG (not exported — invisible to container)
# Without this, get_active_logfile() would return INSTALL_LOG (a container path)
# and all host msg_info/msg_ok/msg_error would write to /root/.install-SESSION.log
# on the HOST instead of BUILD_LOG, causing incomplete telemetry logs.
_HOST_LOGFILE="$BUILD_LOG"
export dev_mode="${dev_mode:-}" export dev_mode="${dev_mode:-}"
export DEV_MODE_MOTD="${DEV_MODE_MOTD:-false}" export DEV_MODE_MOTD="${DEV_MODE_MOTD:-false}"
export DEV_MODE_KEEP="${DEV_MODE_KEEP:-false}" export DEV_MODE_KEEP="${DEV_MODE_KEEP:-false}"
@@ -3671,9 +3664,6 @@ $PCT_OPTIONS_STRING"
create_lxc_container || exit $? create_lxc_container || exit $?
# Transition to 'configuring' — container created, now setting up OS/userland
post_progress_to_api "configuring"
LXC_CONFIG="/etc/pve/lxc/${CTID}.conf" LXC_CONFIG="/etc/pve/lxc/${CTID}.conf"
# ============================================================================ # ============================================================================
@@ -3915,6 +3905,7 @@ EOF
for i in {1..10}; do for i in {1..10}; do
if pct status "$CTID" | grep -q "status: running"; then if pct status "$CTID" | grep -q "status: running"; then
msg_ok "Started LXC Container" msg_ok "Started LXC Container"
post_progress_to_api # Signal container is running
break break
fi fi
sleep 1 sleep 1
@@ -3969,6 +3960,7 @@ EOF
echo -e "${YW}Container may have limited internet access. Installation will continue...${CL}" echo -e "${YW}Container may have limited internet access. Installation will continue...${CL}"
else else
msg_ok "Network in LXC is reachable (ping)" msg_ok "Network in LXC is reachable (ping)"
post_progress_to_api # Signal network is ready
fi fi
fi fi
# Function to get correct GID inside container # Function to get correct GID inside container
@@ -4040,6 +4032,7 @@ EOF'
fi fi
msg_ok "Customized LXC Container" msg_ok "Customized LXC Container"
post_progress_to_api # Signal ready for app installation
# Optional DNS override for retry scenarios (inside LXC, never on host) # Optional DNS override for retry scenarios (inside LXC, never on host)
if [[ "${DNS_RETRY_OVERRIDE:-false}" == "true" ]]; then if [[ "${DNS_RETRY_OVERRIDE:-false}" == "true" ]]; then
@@ -4059,26 +4052,11 @@ EOF'
set +Eeuo pipefail # Disable ALL error handling temporarily set +Eeuo pipefail # Disable ALL error handling temporarily
trap - ERR # Remove ERR trap completely trap - ERR # Remove ERR trap completely
# Signal handlers use this flag to stop the container on abort (SIGHUP/SIGINT/SIGTERM) lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/install/${var_install}.sh)"
# Without this, SSH disconnects leave the container running as an orphan process local lxc_exit=$?
# that sends "configuring" status AFTER the host already reported "failed"
export CONTAINER_INSTALLING=true
# Capture lxc-attach terminal output to host-side log via tee. set -Eeuo pipefail # Re-enable error handling
# This is the ONLY reliable way to get install output when: trap 'error_handler' ERR # Restore ERR trap
# - 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).
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)" 2>&1 | tee "$_LXC_CAPTURE_LOG"
local lxc_exit=${PIPESTATUS[0]}
unset CONTAINER_INSTALLING
# Keep error handling DISABLED during failure detection and recovery
# Re-enabling it here would cause any pct exec/pull failure to trigger
# error_handler() on the host, bypassing the recovery menu entirely
# Check for error flag file in container (more reliable than lxc-attach exit code) # Check for error flag file in container (more reliable than lxc-attach exit code)
local install_exit_code=0 local install_exit_code=0
@@ -4130,17 +4108,7 @@ EOF'
# Copy and append INSTALL_LOG from container (with timeout to prevent hangs) # Copy and append INSTALL_LOG from container (with timeout to prevent hangs)
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 timeout 8 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)"
@@ -4148,25 +4116,9 @@ 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 # Point INSTALL_LOG to combined log so get_error_text() finds it
# Fallback: host-captured terminal output from lxc-attach
# This captures everything the user saw, including errors when install.func
# failed to load (DNS issues, etc.) and no container-side logging was set up.
{
echo "================================================================================"
echo "PHASE 2: APPLICATION INSTALLATION (Container - captured from terminal)"
echo "================================================================================"
# Strip ANSI escape codes from terminal capture
sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' "$_LXC_CAPTURE_LOG" | sed 's/\r$//'
echo ""
} >>"$combined_log"
install_log_copied=true
fi
rm -f "$temp_install_log"
if [[ "$install_log_copied" == true ]]; then
# Point INSTALL_LOG to combined log so get_full_log() finds it
INSTALL_LOG="$combined_log" INSTALL_LOG="$combined_log"
fi fi
fi fi
@@ -4426,9 +4378,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)" 2>&1 | tee "$_LXC_CAPTURE_LOG" local apt_retry_exit=$?
local apt_retry_exit=${PIPESTATUS[0]}
set -Eeuo pipefail set -Eeuo pipefail
trap 'error_handler' ERR trap 'error_handler' ERR
@@ -4533,13 +4484,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
set -Eeuo pipefail
trap 'error_handler' ERR
} }
destroy_lxc() { destroy_lxc() {
@@ -4947,9 +4891,6 @@ create_lxc_container() {
# Report installation start to API early - captures failures in storage/template/create # Report installation start to API early - captures failures in storage/template/create
post_to_api post_to_api
# Transition to 'validation' — Proxmox-internal checks (storage, template, cluster)
post_progress_to_api "validation"
# 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]"
@@ -5479,6 +5420,7 @@ create_lxc_container() {
} }
msg_ok "LXC Container ${BL}$CTID${CL} ${GN}was successfully created." msg_ok "LXC Container ${BL}$CTID${CL} ${GN}was successfully created."
post_progress_to_api # Signal container creation complete
} }
# ============================================================================== # ==============================================================================
@@ -5619,13 +5561,9 @@ api_exit_script() {
# ALWAYS send telemetry FIRST - ensure status is reported even if # ALWAYS send telemetry FIRST - ensure status is reported even if
# ensure_log_on_host hangs (e.g. pct pull on dead container) # ensure_log_on_host hangs (e.g. pct pull on dead container)
post_update_to_api "failed" "$exit_code" 2>/dev/null || true post_update_to_api "failed" "$exit_code" 2>/dev/null || true
# Best-effort log collection (non-critical after telemetry is sent) # Best-effort log collection with timeout (non-critical after telemetry is sent)
if declare -f ensure_log_on_host >/dev/null 2>&1; then if declare -f ensure_log_on_host >/dev/null 2>&1; then
ensure_log_on_host 2>/dev/null || true timeout 10 bash -c '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 fi
elif [[ "${POST_TO_API_DONE:-}" == "true" && "${POST_UPDATE_DONE:-}" != "true" ]]; then elif [[ "${POST_TO_API_DONE:-}" == "true" && "${POST_UPDATE_DONE:-}" != "true" ]]; then
# Script exited with 0 but never sent a completion status # Script exited with 0 but never sent a completion status
@@ -5637,7 +5575,7 @@ api_exit_script() {
if command -v pveversion >/dev/null 2>&1; then if command -v pveversion >/dev/null 2>&1; then
trap 'api_exit_script' EXIT trap 'api_exit_script' EXIT
fi 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 'local _ec=$?; if [[ $_ec -ne 0 ]]; then post_update_to_api "failed" "$_ec" 2>/dev/null || true; timeout 10 bash -c "ensure_log_on_host" 2>/dev/null || true; 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" "129" 2>/dev/null || true; timeout 10 bash -c "ensure_log_on_host" 2>/dev/null || true; 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" "130" 2>/dev/null || true; timeout 10 bash -c "ensure_log_on_host" 2>/dev/null || true; 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 trap 'post_update_to_api "failed" "143" 2>/dev/null || true; timeout 10 bash -c "ensure_log_on_host" 2>/dev/null || true; exit 143' SIGTERM

View File

@@ -395,20 +395,12 @@ ssh_check() {
# get_active_logfile() # get_active_logfile()
# #
# - Returns the appropriate log file based on execution context # - Returns the appropriate log file based on execution context
# - _HOST_LOGFILE: Override for host context (keeps host logging on BUILD_LOG
# even after INSTALL_LOG is exported for the container)
# - INSTALL_LOG: Container operations (application installation)
# - BUILD_LOG: Host operations (container creation) # - BUILD_LOG: Host operations (container creation)
# - INSTALL_LOG: Container operations (application installation)
# - Fallback to BUILD_LOG if neither is set # - Fallback to BUILD_LOG if neither is set
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
get_active_logfile() { get_active_logfile() {
# Host override: _HOST_LOGFILE is set (not exported) in build.func to keep if [[ -n "${INSTALL_LOG:-}" ]]; then
# host-side logging in BUILD_LOG after INSTALL_LOG is exported for the container.
# Without this, all host msg_info/msg_ok/msg_error would write to
# /root/.install-SESSION.log (a container path) instead of BUILD_LOG.
if [[ -n "${_HOST_LOGFILE:-}" ]]; then
echo "$_HOST_LOGFILE"
elif [[ -n "${INSTALL_LOG:-}" ]]; then
echo "$INSTALL_LOG" echo "$INSTALL_LOG"
elif [[ -n "${BUILD_LOG:-}" ]]; then elif [[ -n "${BUILD_LOG:-}" ]]; then
echo "$BUILD_LOG" echo "$BUILD_LOG"
@@ -488,7 +480,7 @@ log_section() {
# silent() # silent()
# #
# - Executes command with output redirected to active log file # - Executes command with output redirected to active log file
# - On error: displays last 20 lines of log and exits with original exit code # - On error: displays last 10 lines of log and exits with original exit code
# - Temporarily disables error trap to capture exit code correctly # - Temporarily disables error trap to capture exit code correctly
# - Sources explain_exit_code() for detailed error messages # - Sources explain_exit_code() for detailed error messages
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@@ -530,10 +522,9 @@ silent() {
msg_custom "→" "${YWB}" "${cmd}" msg_custom "→" "${YWB}" "${cmd}"
if [[ -s "$logfile" ]]; then if [[ -s "$logfile" ]]; then
echo -e "\n${TAB}--- Last 20 lines of log ---" echo -e "\n${TAB}--- Last 10 lines of log ---"
tail -n 20 "$logfile" tail -n 10 "$logfile"
echo -e "${TAB}-----------------------------------" echo -e "${TAB}-----------------------------------\n"
echo -e "${TAB}📋 Full log: ${logfile}\n"
fi fi
exit "$rc" exit "$rc"
@@ -1505,7 +1496,7 @@ cleanup_lxc() {
fi fi
msg_ok "Cleaned" msg_ok "Cleaned"
# Send progress ping if available (defined in install.func) # Send progress ping if available (defined in install.func)
if declare -f post_progress_to_api &>/dev/null; then if declare -f post_progress_to_api &>/dev/null; then
post_progress_to_api post_progress_to_api

View File

@@ -208,10 +208,6 @@ error_handler() {
# This ensures we capture failures that occur before/after container exists # This ensures we capture failures that occur before/after container exists
if declare -f post_update_to_api &>/dev/null; then if declare -f post_update_to_api &>/dev/null; then
post_update_to_api "failed" "$exit_code" 2>/dev/null || true post_update_to_api "failed" "$exit_code" 2>/dev/null || true
else
# Container context: post_update_to_api not available (api.func not sourced)
# Send status directly via curl so container failures are never lost
_send_abort_telemetry "$exit_code" 2>/dev/null || true
fi fi
# Use msg_error if available, fallback to echo # Use msg_error if available, fallback to echo
@@ -240,54 +236,67 @@ error_handler() {
active_log="$SILENT_LOGFILE" active_log="$SILENT_LOGFILE"
fi fi
# If active_log points to a container-internal path that doesn't exist on host,
# fall back to BUILD_LOG (host-side log)
if [[ -n "$active_log" && ! -s "$active_log" && -n "${BUILD_LOG:-}" && -s "${BUILD_LOG}" ]]; then
active_log="$BUILD_LOG"
fi
# Show last log lines if available
if [[ -n "$active_log" && -s "$active_log" ]]; then if [[ -n "$active_log" && -s "$active_log" ]]; then
echo -e "\n${TAB}--- Last 20 lines of log ---" echo -e "\n${TAB}--- Last 20 lines of log ---"
tail -n 20 "$active_log" tail -n 20 "$active_log"
echo -e "${TAB}-----------------------------------\n" echo -e "${TAB}-----------------------------------\n"
fi
# Detect context: Container (INSTALL_LOG set + inside container /root) vs Host # Detect context: Container (INSTALL_LOG set + /root exists) vs Host (BUILD_LOG)
if [[ -n "${INSTALL_LOG:-}" && -f "${INSTALL_LOG:-}" && -d /root ]]; then if [[ -n "${INSTALL_LOG:-}" && -d /root ]]; then
# CONTAINER CONTEXT: Copy log and create flag file for host # CONTAINER CONTEXT: Copy log and create flag file for host
local container_log="/root/.install-${SESSION_ID:-error}.log" local container_log="/root/.install-${SESSION_ID:-error}.log"
cp "${INSTALL_LOG}" "$container_log" 2>/dev/null || true cp "$active_log" "$container_log" 2>/dev/null || true
# Create error flag file with exit code for host detection # Create error flag file with exit code for host detection
echo "$exit_code" >"/root/.install-${SESSION_ID:-error}.failed" 2>/dev/null || true echo "$exit_code" >"/root/.install-${SESSION_ID:-error}.failed" 2>/dev/null || true
# Log path is shown by host as combined log - no need to show container path # Log path is shown by host as combined log - no need to show container path
else else
# HOST CONTEXT: Show local log path and offer container cleanup # HOST CONTEXT: Show local log path and offer container cleanup
if [[ -n "$active_log" && -s "$active_log" ]]; then
if declare -f msg_custom >/dev/null 2>&1; then if declare -f msg_custom >/dev/null 2>&1; then
msg_custom "📋" "${YW}" "Full log: ${active_log}" msg_custom "📋" "${YW}" "Full log: ${active_log}"
else else
echo -e "${YW}Full log:${CL} ${BL}${active_log}${CL}" echo -e "${YW}Full log:${CL} ${BL}${active_log}${CL}"
fi fi
fi
# Offer to remove container if it exists (build errors after container creation) # Offer to remove container if it exists (build errors after container creation)
if [[ -n "${CTID:-}" ]] && command -v pct &>/dev/null && pct status "$CTID" &>/dev/null; then if [[ -n "${CTID:-}" ]] && command -v pct &>/dev/null && pct status "$CTID" &>/dev/null; then
echo "" echo ""
if declare -f msg_custom >/dev/null 2>&1; then if declare -f msg_custom >/dev/null 2>&1; then
echo -en "${TAB}${TAB}${YW}Remove broken container ${CTID}? (Y/n) [auto-remove in 60s]: ${CL}" echo -en "${TAB}${TAB}${YW}Remove broken container ${CTID}? (Y/n) [auto-remove in 60s]: ${CL}"
else else
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; then if read -t 60 -r response; then
if [[ -z "$response" || "$response" =~ ^[Yy]$ ]]; then if [[ -z "$response" || "$response" =~ ^[Yy]$ ]]; then
echo ""
if declare -f msg_info >/dev/null 2>&1; then
msg_info "Removing container ${CTID}"
else
echo -e "${YW}Removing container ${CTID}${CL}"
fi
pct stop "$CTID" &>/dev/null || true
pct destroy "$CTID" &>/dev/null || true
if declare -f msg_ok >/dev/null 2>&1; then
msg_ok "Container ${CTID} removed"
else
echo -e "${GN}${CL} Container ${CTID} removed"
fi
elif [[ "$response" =~ ^[Nn]$ ]]; then
echo ""
if declare -f msg_warn >/dev/null 2>&1; then
msg_warn "Container ${CTID} kept for debugging"
else
echo -e "${YW}Container ${CTID} kept for debugging${CL}"
fi
fi
else
# Timeout - auto-remove
echo "" echo ""
if declare -f msg_info >/dev/null 2>&1; then if declare -f msg_info >/dev/null 2>&1; then
msg_info "Removing container ${CTID}" msg_info "No response - removing container ${CTID}"
else else
echo -e "${YW}Removing container ${CTID}${CL}" echo -e "${YW}No response - removing container ${CTID}${CL}"
fi fi
pct stop "$CTID" &>/dev/null || true pct stop "$CTID" &>/dev/null || true
pct destroy "$CTID" &>/dev/null || true pct destroy "$CTID" &>/dev/null || true
@@ -296,35 +305,13 @@ error_handler() {
else else
echo -e "${GN}${CL} Container ${CTID} removed" echo -e "${GN}${CL} Container ${CTID} removed"
fi fi
elif [[ "$response" =~ ^[Nn]$ ]]; then
echo ""
if declare -f msg_warn >/dev/null 2>&1; then
msg_warn "Container ${CTID} kept for debugging"
else
echo -e "${YW}Container ${CTID} kept for debugging${CL}"
fi
fi fi
else
# Timeout - auto-remove
echo ""
if declare -f msg_info >/dev/null 2>&1; then
msg_info "No response - removing container ${CTID}"
else
echo -e "${YW}No response - removing container ${CTID}${CL}"
fi
pct stop "$CTID" &>/dev/null || true
pct destroy "$CTID" &>/dev/null || true
if declare -f msg_ok >/dev/null 2>&1; then
msg_ok "Container ${CTID} removed"
else
echo -e "${GN}${CL} Container ${CTID} removed"
fi
fi
# Force one final status update attempt after cleanup # Force one final status update attempt after cleanup
# This ensures status is updated even if the first attempt failed (e.g., HTTP 400) # This ensures status is updated even if the first attempt failed (e.g., HTTP 400)
if declare -f post_update_to_api &>/dev/null; then if declare -f post_update_to_api &>/dev/null; then
post_update_to_api "failed" "$exit_code" "force" post_update_to_api "failed" "$exit_code" "force"
fi
fi fi
fi fi
fi fi
@@ -333,97 +320,40 @@ error_handler() {
} }
# ============================================================================== # ==============================================================================
# SECTION 3: TELEMETRY & CLEANUP HELPERS FOR SIGNAL HANDLERS # SECTION 3: SIGNAL HANDLERS
# ==============================================================================
# ------------------------------------------------------------------------------
# _send_abort_telemetry()
#
# - Sends failure/abort status to telemetry API
# - Works in BOTH host context (post_update_to_api available) and
# container context (only curl available, api.func not sourced)
# - Container context is critical: without this, container-side failures
# and signal exits are never reported, leaving records stuck in
# "installing" or "configuring" forever
# - Arguments: $1 = exit_code
# ------------------------------------------------------------------------------
_send_abort_telemetry() {
local exit_code="${1:-1}"
# Try full API function first (host context - api.func sourced)
if declare -f post_update_to_api &>/dev/null; then
post_update_to_api "failed" "$exit_code" 2>/dev/null || true
return
fi
# Fallback: direct curl (container context - api.func NOT sourced)
# This is the ONLY way containers can report failures to 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
}
# ------------------------------------------------------------------------------
# _stop_container_if_installing()
#
# - Stops the LXC container if we're in the install phase
# - Prevents orphaned container processes when the host exits due to a signal
# (SSH disconnect, Ctrl+C, SIGTERM) — without this, the container keeps
# running and may send "configuring" status AFTER the host already sent
# "failed", leaving records permanently stuck in "configuring"
# - Only acts when:
# * CONTAINER_INSTALLING flag is set (during lxc-attach in build_container)
# * CTID is set (container was created)
# * pct command is available (we're on the Proxmox host, not inside a container)
# - Does NOT destroy the container — just stops it for potential debugging
# ------------------------------------------------------------------------------
_stop_container_if_installing() {
[[ "${CONTAINER_INSTALLING:-}" == "true" ]] || return 0
[[ -n "${CTID:-}" ]] || return 0
command -v pct &>/dev/null || return 0
pct stop "$CTID" 2>/dev/null || true
}
# ==============================================================================
# SECTION 4: SIGNAL HANDLERS
# ============================================================================== # ==============================================================================
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# on_exit() # on_exit()
# #
# - EXIT trap handler — runs on EVERY script termination # - EXIT trap handler
# - Catches orphaned "installing"/"configuring" records: # - Cleans up lock files if lockfile variable is set
# * If post_to_api sent "installing" but post_update_to_api never ran # - Exits with captured exit code
# * Reports final status to prevent records stuck forever # - Always runs on script termination (success or failure)
# - Best-effort log collection for failed installs # - For signal exits (>128): sends telemetry FIRST before log collection
# - Stops orphaned container processes on failure # to prevent pct pull hangs from blocking status updates
# - Cleans up lock files
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
on_exit() { on_exit() {
local exit_code=$? local exit_code=$?
# Report orphaned "installing" records to telemetry API # Report orphaned "installing" records to telemetry API
# Catches ALL exit paths: errors, signals, AND clean exits where # Catches ALL exit paths: errors (non-zero), signals, AND clean exits where
# post_to_api was called but post_update_to_api was never called # post_to_api was called ("installing" sent) but post_update_to_api was never called
if [[ "${POST_TO_API_DONE:-}" == "true" && "${POST_UPDATE_DONE:-}" != "true" ]]; then if [[ "${POST_TO_API_DONE:-}" == "true" && "${POST_UPDATE_DONE:-}" != "true" ]]; then
if [[ $exit_code -ne 0 ]]; then if declare -f post_update_to_api >/dev/null 2>&1; then
_send_abort_telemetry "$exit_code" # ALWAYS send telemetry FIRST - ensure status is reported even if
elif declare -f post_update_to_api >/dev/null 2>&1; then # ensure_log_on_host hangs (e.g. pct pull on dead/unresponsive container)
post_update_to_api "done" "0" 2>/dev/null || true if [[ $exit_code -ne 0 ]]; then
post_update_to_api "failed" "$exit_code" 2>/dev/null || true
else
# exit_code=0 is never an error — report as success
post_update_to_api "done" "0" 2>/dev/null || true
fi
# Best-effort log collection with timeout (non-critical after telemetry is sent)
if declare -f ensure_log_on_host >/dev/null 2>&1; then
timeout 10 bash -c 'ensure_log_on_host' 2>/dev/null || true
fi
fi fi
fi fi
# Best-effort log collection on failure (non-critical, telemetry already sent)
if [[ $exit_code -ne 0 ]] && 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 and exiting with error
if [[ $exit_code -ne 0 ]]; then
_stop_container_if_installing
fi
[[ -n "${lockfile:-}" && -e "$lockfile" ]] && rm -f "$lockfile" [[ -n "${lockfile:-}" && -e "$lockfile" ]] && rm -f "$lockfile"
exit "$exit_code" exit "$exit_code"
} }
@@ -432,13 +362,22 @@ on_exit() {
# on_interrupt() # on_interrupt()
# #
# - SIGINT (Ctrl+C) trap handler # - SIGINT (Ctrl+C) trap handler
# - Reports status FIRST (time-critical: container may be dying) # - Reports to telemetry FIRST (time-critical: container may be dying)
# - Stops orphaned container to prevent "configuring" ghost records # - Displays "Interrupted by user" message
# - Exits with code 130 (128 + SIGINT=2) # - Exits with code 130 (128 + SIGINT=2)
# - Output redirected to /dev/null fallback to prevent SIGPIPE on closed terminals
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
on_interrupt() { on_interrupt() {
_send_abort_telemetry "130" # CRITICAL: Send telemetry FIRST before any cleanup or output
_stop_container_if_installing # If ensure_log_on_host hangs (e.g. pct pull on dying container),
# the status update would never be sent, leaving records stuck in "installing"
if declare -f post_update_to_api >/dev/null 2>&1; then
post_update_to_api "failed" "130" 2>/dev/null || true
fi
# Best-effort log collection with timeout (non-critical after telemetry is sent)
if declare -f ensure_log_on_host >/dev/null 2>&1; then
timeout 10 bash -c 'ensure_log_on_host' 2>/dev/null || true
fi
if declare -f msg_error >/dev/null 2>&1; then if declare -f msg_error >/dev/null 2>&1; then
msg_error "Interrupted by user (SIGINT)" 2>/dev/null || true msg_error "Interrupted by user (SIGINT)" 2>/dev/null || true
else else
@@ -451,13 +390,23 @@ on_interrupt() {
# on_terminate() # on_terminate()
# #
# - SIGTERM trap handler # - SIGTERM trap handler
# - Reports status FIRST (time-critical: process being killed) # - Reports to telemetry FIRST (time-critical: process being killed)
# - Stops orphaned container to prevent "configuring" ghost records # - Displays "Terminated by signal" message
# - Exits with code 143 (128 + SIGTERM=15) # - Exits with code 143 (128 + SIGTERM=15)
# - Triggered by external process termination
# - Output redirected to /dev/null fallback to prevent SIGPIPE on closed terminals
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
on_terminate() { on_terminate() {
_send_abort_telemetry "143" # CRITICAL: Send telemetry FIRST before any cleanup or output
_stop_container_if_installing # Same rationale as on_interrupt: ensure status gets reported even if
# ensure_log_on_host hangs or terminal is already closed
if declare -f post_update_to_api >/dev/null 2>&1; then
post_update_to_api "failed" "143" 2>/dev/null || true
fi
# Best-effort log collection with timeout (non-critical after telemetry is sent)
if declare -f ensure_log_on_host >/dev/null 2>&1; then
timeout 10 bash -c 'ensure_log_on_host' 2>/dev/null || true
fi
if declare -f msg_error >/dev/null 2>&1; then if declare -f msg_error >/dev/null 2>&1; then
msg_error "Terminated by signal (SIGTERM)" 2>/dev/null || true msg_error "Terminated by signal (SIGTERM)" 2>/dev/null || true
else else
@@ -466,25 +415,8 @@ on_terminate() {
exit 143 exit 143
} }
# ------------------------------------------------------------------------------
# on_hangup()
#
# - SIGHUP trap handler (SSH disconnect, terminal closed)
# - CRITICAL: This was previously MISSING from catch_errors(), causing
# container processes to become orphans on SSH disconnect — the #1 cause
# of records stuck in "installing" and "configuring" states
# - Reports status via direct curl (terminal is already closed, no output)
# - Stops orphaned container to prevent ghost records
# - Exits with code 129 (128 + SIGHUP=1)
# ------------------------------------------------------------------------------
on_hangup() {
_send_abort_telemetry "129"
_stop_container_if_installing
exit 129
}
# ============================================================================== # ==============================================================================
# SECTION 5: INITIALIZATION # SECTION 4: INITIALIZATION
# ============================================================================== # ==============================================================================
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@@ -496,11 +428,10 @@ on_hangup() {
# * set -o pipefail: Pipeline fails if any command fails # * set -o pipefail: Pipeline fails if any command fails
# * set -u: (optional) Exit on undefined variable (if STRICT_UNSET=1) # * set -u: (optional) Exit on undefined variable (if STRICT_UNSET=1)
# - Sets up traps: # - Sets up traps:
# * ERR → error_handler (script errors) # * ERR → error_handler
# * EXIT → on_exit (any termination — cleanup + orphan detection) # * EXIT → on_exit
# * INT → on_interrupt (Ctrl+C) # * INT → on_interrupt
# * TERM → on_terminate (kill / systemd stop) # * TERM → on_terminate
# * HUP → on_hangup (SSH disconnect / terminal closed)
# - Call this function early in every script # - Call this function early in every script
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
catch_errors() { catch_errors() {
@@ -513,5 +444,4 @@ catch_errors() {
trap on_exit EXIT trap on_exit EXIT
trap on_interrupt INT trap on_interrupt INT
trap on_terminate TERM trap on_terminate TERM
trap on_hangup HUP
} }

View File

@@ -51,9 +51,7 @@ get_lxc_ip
# post_progress_to_api() # post_progress_to_api()
# #
# - Lightweight progress ping from inside the container # - Lightweight progress ping from inside the container
# - Updates the existing telemetry record status # - Updates the existing telemetry record status from "installing" to "configuring"
# - Arguments:
# * $1: status (optional, default: "configuring")
# - Signals that the installation is actively progressing (not stuck) # - Signals that the installation is actively progressing (not stuck)
# - Fire-and-forget: never blocks or fails the script # - Fire-and-forget: never blocks or fails the script
# - Only executes if DIAGNOSTICS=yes and RANDOM_UUID is set # - Only executes if DIAGNOSTICS=yes and RANDOM_UUID is set
@@ -63,11 +61,9 @@ post_progress_to_api() {
[[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0 [[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0
[[ -z "${RANDOM_UUID:-}" ]] && return 0 [[ -z "${RANDOM_UUID:-}" ]] && return 0
local progress_status="${1:-configuring}"
curl -fsS -m 5 -X POST "https://telemetry.community-scripts.org/telemetry" \ curl -fsS -m 5 -X POST "https://telemetry.community-scripts.org/telemetry" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "{\"random_id\":\"${RANDOM_UUID}\",\"execution_id\":\"${EXECUTION_ID:-${RANDOM_UUID}}\",\"type\":\"lxc\",\"nsapp\":\"${app:-unknown}\",\"status\":\"${progress_status}\"}" &>/dev/null || true -d "{\"random_id\":\"${RANDOM_UUID}\",\"execution_id\":\"${EXECUTION_ID:-${RANDOM_UUID}}\",\"type\":\"lxc\",\"nsapp\":\"${app:-unknown}\",\"status\":\"configuring\"}" &>/dev/null || true
} }
# ============================================================================== # ==============================================================================

View File

@@ -169,7 +169,7 @@ get_active_logfile() {
# silent() # silent()
# #
# - Executes command with output redirected to active log file # - Executes command with output redirected to active log file
# - On error: displays last 20 lines of log and exits with original exit code # - On error: displays last 10 lines of log and exits with original exit code
# - Temporarily disables error trap to capture exit code correctly # - Temporarily disables error trap to capture exit code correctly
# - Sources explain_exit_code() for detailed error messages # - Sources explain_exit_code() for detailed error messages
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@@ -207,8 +207,8 @@ silent() {
msg_custom "→" "${YWB}" "${cmd}" msg_custom "→" "${YWB}" "${cmd}"
if [[ -s "$logfile" ]]; then if [[ -s "$logfile" ]]; then
echo -e "\n${TAB}--- Last 20 lines of log ---" echo -e "\n${TAB}--- Last 10 lines of log ---"
tail -n 20 "$logfile" tail -n 10 "$logfile"
echo -e "${TAB}----------------------------\n" echo -e "${TAB}----------------------------\n"
fi fi

View File

@@ -63,9 +63,8 @@ THIN="discard=on,ssd=1,"
set -e set -e
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
trap cleanup EXIT trap cleanup EXIT
trap 'post_update_to_api "failed" "130"' SIGINT trap 'post_update_to_api "failed" "INTERRUPTED"' SIGINT
trap 'post_update_to_api "failed" "143"' SIGTERM trap 'post_update_to_api "failed" "TERMINATED"' SIGTERM
trap 'post_update_to_api "failed" "129"; exit 129' SIGHUP
function error_handler() { function error_handler() {
local exit_code="$?" local exit_code="$?"
local line_number="$1" local line_number="$1"

View File

@@ -63,9 +63,8 @@ THIN="discard=on,ssd=1,"
set -e set -e
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
trap cleanup EXIT trap cleanup EXIT
trap 'post_update_to_api "failed" "130"' SIGINT trap 'post_update_to_api "failed" "INTERRUPTED"' SIGINT
trap 'post_update_to_api "failed" "143"' SIGTERM trap 'post_update_to_api "failed" "TERMINATED"' SIGTERM
trap 'post_update_to_api "failed" "129"; exit 129' SIGHUP
function error_handler() { function error_handler() {
local exit_code="$?" local exit_code="$?"
local line_number="$1" local line_number="$1"

View File

@@ -63,9 +63,8 @@ THIN="discard=on,ssd=1,"
set -e set -e
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
trap cleanup EXIT trap cleanup EXIT
trap 'post_update_to_api "failed" "130"' SIGINT trap 'post_update_to_api "failed" "INTERRUPTED"' SIGINT
trap 'post_update_to_api "failed" "143"' SIGTERM trap 'post_update_to_api "failed" "TERMINATED"' SIGTERM
trap 'post_update_to_api "failed" "129"; exit 129' SIGHUP
function error_handler() { function error_handler() {
local exit_code="$?" local exit_code="$?"
local line_number="$1" local line_number="$1"

View File

@@ -37,9 +37,8 @@ THIN="discard=on,ssd=1,"
set -e set -e
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
trap cleanup EXIT trap cleanup EXIT
trap 'post_update_to_api "failed" "130"' SIGINT trap 'post_update_to_api "failed" "INTERRUPTED"' SIGINT
trap 'post_update_to_api "failed" "143"' SIGTERM trap 'post_update_to_api "failed" "TERMINATED"' SIGTERM
trap 'post_update_to_api "failed" "129"; exit 129' SIGHUP
function error_handler() { function error_handler() {
local exit_code="$?" local exit_code="$?"

View File

@@ -67,9 +67,8 @@ THIN="discard=on,ssd=1,"
set -e set -e
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
trap cleanup EXIT trap cleanup EXIT
trap 'post_update_to_api "failed" "130"' SIGINT trap 'post_update_to_api "failed" "INTERRUPTED"' SIGINT
trap 'post_update_to_api "failed" "143"' SIGTERM trap 'post_update_to_api "failed" "TERMINATED"' SIGTERM
trap 'post_update_to_api "failed" "129"; exit 129' SIGHUP
function error_handler() { function error_handler() {
local exit_code="$?" local exit_code="$?"
local line_number="$1" local line_number="$1"

View File

@@ -64,9 +64,8 @@ THIN="discard=on,ssd=1,"
set -e set -e
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
trap cleanup EXIT trap cleanup EXIT
trap 'post_update_to_api "failed" "130"' SIGINT trap 'post_update_to_api "failed" "INTERRUPTED"' SIGINT
trap 'post_update_to_api "failed" "143"' SIGTERM trap 'post_update_to_api "failed" "TERMINATED"' SIGTERM
trap 'post_update_to_api "failed" "129"; exit 129' SIGHUP
function error_handler() { function error_handler() {
local exit_code="$?" local exit_code="$?"
local line_number="$1" local line_number="$1"

View File

@@ -63,9 +63,8 @@ THIN="discard=on,ssd=1,"
set -e set -e
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
trap cleanup EXIT trap cleanup EXIT
trap 'post_update_to_api "failed" "130"' SIGINT trap 'post_update_to_api "failed" "INTERRUPTED"' SIGINT
trap 'post_update_to_api "failed" "143"' SIGTERM trap 'post_update_to_api "failed" "TERMINATED"' SIGTERM
trap 'post_update_to_api "failed" "129"; exit 129' SIGHUP
function error_handler() { function error_handler() {
local exit_code="$?" local exit_code="$?"
local line_number="$1" local line_number="$1"

View File

@@ -68,9 +68,8 @@ CLOUD="${TAB}☁️${TAB}${CL}"
set -Eeo pipefail set -Eeo pipefail
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
trap cleanup EXIT trap cleanup EXIT
trap 'post_update_to_api "failed" "130"' SIGINT trap 'post_update_to_api "failed" "INTERRUPTED"' SIGINT
trap 'post_update_to_api "failed" "143"' SIGTERM trap 'post_update_to_api "failed" "TERMINATED"' SIGTERM
trap 'post_update_to_api "failed" "129"; exit 129' SIGHUP
function error_handler() { function error_handler() {
local exit_code="$?" local exit_code="$?"
local line_number="$1" local line_number="$1"

View File

@@ -44,9 +44,6 @@ CROSS="${RD}✗${CL}"
set -Eeo pipefail set -Eeo pipefail
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
trap cleanup EXIT trap cleanup EXIT
trap 'post_update_to_api "failed" "130"' SIGINT
trap 'post_update_to_api "failed" "143"' SIGTERM
trap 'post_update_to_api "failed" "129"; exit 129' SIGHUP
function error_handler() { function error_handler() {
local exit_code="$?" local exit_code="$?"
local line_number="$1" local line_number="$1"

View File

@@ -64,9 +64,8 @@ THIN="discard=on,ssd=1,"
set -e set -e
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
trap cleanup EXIT trap cleanup EXIT
trap 'post_update_to_api "failed" "130"' SIGINT trap 'post_update_to_api "failed" "INTERRUPTED"' SIGINT
trap 'post_update_to_api "failed" "143"' SIGTERM trap 'post_update_to_api "failed" "TERMINATED"' SIGTERM
trap 'post_update_to_api "failed" "129"; exit 129' SIGHUP
function error_handler() { function error_handler() {
local exit_code="$?" local exit_code="$?"
local line_number="$1" local line_number="$1"

View File

@@ -72,9 +72,8 @@ THIN="discard=on,ssd=1,"
set -e set -e
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
trap cleanup EXIT trap cleanup EXIT
trap 'post_update_to_api "failed" "130"' SIGINT trap 'post_update_to_api "failed" "INTERRUPTED"' SIGINT
trap 'post_update_to_api "failed" "143"' SIGTERM trap 'post_update_to_api "failed" "TERMINATED"' SIGTERM
trap 'post_update_to_api "failed" "129"; exit 129' SIGHUP
function error_handler() { function error_handler() {
local exit_code="$?" local exit_code="$?"
local line_number="$1" local line_number="$1"

View File

@@ -62,9 +62,8 @@ CLOUD="${TAB}☁️${TAB}${CL}"
set -e set -e
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
trap cleanup EXIT trap cleanup EXIT
trap 'post_update_to_api "failed" "130"' SIGINT trap 'post_update_to_api "failed" "INTERRUPTED"' SIGINT
trap 'post_update_to_api "failed" "143"' SIGTERM trap 'post_update_to_api "failed" "TERMINATED"' SIGTERM
trap 'post_update_to_api "failed" "129"; exit 129' SIGHUP
function error_handler() { function error_handler() {
local exit_code="$?" local exit_code="$?"
local line_number="$1" local line_number="$1"

View File

@@ -60,9 +60,8 @@ THIN="discard=on,ssd=1,"
set -e set -e
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
trap cleanup EXIT trap cleanup EXIT
trap 'post_update_to_api "failed" "130"' SIGINT trap 'post_update_to_api "failed" "INTERRUPTED"' SIGINT
trap 'post_update_to_api "failed" "143"' SIGTERM trap 'post_update_to_api "failed" "TERMINATED"' SIGTERM
trap 'post_update_to_api "failed" "129"; exit 129' SIGHUP
function error_handler() { function error_handler() {
local exit_code="$?" local exit_code="$?"
local line_number="$1" local line_number="$1"

View File

@@ -63,9 +63,8 @@ THIN="discard=on,ssd=1,"
set -e set -e
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
trap cleanup EXIT trap cleanup EXIT
trap 'post_update_to_api "failed" "130"' SIGINT trap 'post_update_to_api "failed" "INTERRUPTED"' SIGINT
trap 'post_update_to_api "failed" "143"' SIGTERM trap 'post_update_to_api "failed" "TERMINATED"' SIGTERM
trap 'post_update_to_api "failed" "129"; exit 129' SIGHUP
function error_handler() { function error_handler() {
local exit_code="$?" local exit_code="$?"
local line_number="$1" local line_number="$1"

View File

@@ -62,9 +62,8 @@ THIN="discard=on,ssd=1,"
set -e set -e
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
trap cleanup EXIT trap cleanup EXIT
trap 'post_update_to_api "failed" "130"' SIGINT trap 'post_update_to_api "failed" "INTERRUPTED"' SIGINT
trap 'post_update_to_api "failed" "143"' SIGTERM trap 'post_update_to_api "failed" "TERMINATED"' SIGTERM
trap 'post_update_to_api "failed" "129"; exit 129' SIGHUP
function error_handler() { function error_handler() {
local exit_code="$?" local exit_code="$?"
local line_number="$1" local line_number="$1"

View File

@@ -62,9 +62,8 @@ THIN="discard=on,ssd=1,"
set -e set -e
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
trap cleanup EXIT trap cleanup EXIT
trap 'post_update_to_api "failed" "130"' SIGINT trap 'post_update_to_api "failed" "INTERRUPTED"' SIGINT
trap 'post_update_to_api "failed" "143"' SIGTERM trap 'post_update_to_api "failed" "TERMINATED"' SIGTERM
trap 'post_update_to_api "failed" "129"; exit 129' SIGHUP
function error_handler() { function error_handler() {
local exit_code="$?" local exit_code="$?"
local line_number="$1" local line_number="$1"