diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b018625f..519cf4eae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -406,6 +406,32 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit ## 2026-02-17 +### 🆕 New Scripts + + - Databasus ([#12018](https://github.com/community-scripts/ProxmoxVE/pull/12018)) + +### 🚀 Updated Scripts + + - #### 🐞 Bug Fixes + + - fix: pterodactyl-panel add symlink [@CrazyWolf13](https://github.com/CrazyWolf13) ([#11997](https://github.com/community-scripts/ProxmoxVE/pull/11997)) + +### 💾 Core + + - #### 🐞 Bug Fixes + + - core: call get_lxc_ip in start() before updates [@MickLesk](https://github.com/MickLesk) ([#12015](https://github.com/community-scripts/ProxmoxVE/pull/12015)) + + - #### ✨ New Features + + - core: smart recovery for failed installs | extend exit_codes [@MickLesk](https://github.com/MickLesk) ([#11221](https://github.com/community-scripts/ProxmoxVE/pull/11221)) + +### 🧰 Tools + + - #### 🔧 Refactor + + - Immich Public Proxy: centralize and fix systemd service creation [@MickLesk](https://github.com/MickLesk) ([#12025](https://github.com/community-scripts/ProxmoxVE/pull/12025)) + ## 2026-02-16 ### 🆕 New Scripts diff --git a/ct/databasus.sh b/ct/databasus.sh new file mode 100644 index 000000000..a189ebdba --- /dev/null +++ b/ct/databasus.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func) +# Copyright (c) 2021-2026 community-scripts ORG +# Author: MickLesk (CanbiZ) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://github.com/databasus/databasus + +APP="Databasus" +var_tags="${var_tags:-backup;postgresql;database}" +var_cpu="${var_cpu:-2}" +var_ram="${var_ram:-2048}" +var_disk="${var_disk:-8}" +var_os="${var_os:-debian}" +var_version="${var_version:-13}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + + if [[ ! -f /opt/databasus/databasus ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + + if check_for_gh_release "databasus" "databasus/databasus"; then + msg_info "Stopping Databasus" + $STD systemctl stop databasus + msg_ok "Stopped Databasus" + + msg_info "Backing up Configuration" + cp /opt/databasus/.env /opt/databasus.env.bak + msg_ok "Backed up Configuration" + + CLEAN_INSTALL=1 fetch_and_deploy_gh_release "databasus" "databasus/databasus" "tarball" "latest" "/opt/databasus" + + msg_info "Updating Databasus" + cd /opt/databasus/frontend + $STD npm ci + $STD npm run build + cd /opt/databasus/backend + $STD go mod download + $STD /root/go/bin/swag init -g cmd/main.go -o swagger + $STD env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o databasus ./cmd/main.go + mv /opt/databasus/backend/databasus /opt/databasus/databasus + cp -r /opt/databasus/frontend/dist/* /opt/databasus/ui/build/ + cp -r /opt/databasus/backend/migrations /opt/databasus/ + chown -R postgres:postgres /opt/databasus + msg_ok "Updated Databasus" + + msg_info "Restoring Configuration" + cp /opt/databasus.env.bak /opt/databasus/.env + rm -f /opt/databasus.env.bak + chown postgres:postgres /opt/databasus/.env + msg_ok "Restored Configuration" + + msg_info "Starting Databasus" + $STD systemctl start databasus + msg_ok "Started Databasus" + 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}" diff --git a/ct/headers/databasus b/ct/headers/databasus new file mode 100644 index 000000000..66ae243be --- /dev/null +++ b/ct/headers/databasus @@ -0,0 +1,6 @@ + ____ __ __ + / __ \____ _/ /_____ _/ /_ ____ ________ _______ + / / / / __ `/ __/ __ `/ __ \/ __ `/ ___/ / / / ___/ + / /_/ / /_/ / /_/ /_/ / /_/ / /_/ (__ ) /_/ (__ ) +/_____/\__,_/\__/\__,_/_.___/\__,_/____/\__,_/____/ + diff --git a/ct/pterodactyl-panel.sh b/ct/pterodactyl-panel.sh index 15a428990..2abc77478 100644 --- a/ct/pterodactyl-panel.sh +++ b/ct/pterodactyl-panel.sh @@ -71,6 +71,7 @@ EOF $STD php artisan migrate --seed --force --no-interaction chown -R www-data:www-data /opt/pterodactyl-panel/* chmod -R 755 /opt/pterodactyl-panel/storage /opt/pterodactyl-panel/bootstrap/cache/ + ln -s /opt/pterodactyl-panel /var/www/pterodactyl rm -rf "/opt/pterodactyl-panel/panel.tar.gz" echo "${RELEASE}" >/opt/${APP}_version.txt msg_ok "Updated $APP to v${RELEASE}" diff --git a/frontend/public/json/databasus.json b/frontend/public/json/databasus.json new file mode 100644 index 000000000..f0ff2c9a6 --- /dev/null +++ b/frontend/public/json/databasus.json @@ -0,0 +1,44 @@ +{ + "name": "Databasus", + "slug": "databasus", + "categories": [ + 7 + ], + "date_created": "2026-02-17", + "type": "ct", + "updateable": true, + "privileged": false, + "interface_port": 80, + "documentation": "https://github.com/databasus/databasus", + "website": "https://github.com/databasus/databasus", + "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/databasus.webp", + "config_path": "/opt/databasus/.env", + "description": "Free, open source and self-hosted solution for automated PostgreSQL backups. With multiple storage options, notifications, scheduling, and a beautiful web interface for managing database backups across multiple PostgreSQL instances.", + "install_methods": [ + { + "type": "default", + "script": "ct/databasus.sh", + "resources": { + "cpu": 2, + "ram": 2048, + "hdd": 8, + "os": "Debian", + "version": "13" + } + } + ], + "default_credentials": { + "username": "admin@localhost", + "password": "See /root/databasus.creds" + }, + "notes": [ + { + "text": "Supports PostgreSQL versions 12-18 with cloud and self-hosted instances", + "type": "info" + }, + { + "text": "Features: Scheduled backups, multiple storage providers, notifications, encryption", + "type": "info" + } + ] +} diff --git a/install/databasus-install.sh b/install/databasus-install.sh new file mode 100644 index 000000000..fa17a4d58 --- /dev/null +++ b/install/databasus-install.sh @@ -0,0 +1,171 @@ +#!/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/databasus/databasus + +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 \ + valkey +msg_ok "Installed Dependencies" + +PG_VERSION="17" setup_postgresql +setup_go +NODE_VERSION="24" setup_nodejs + +fetch_and_deploy_gh_release "databasus" "databasus/databasus" "tarball" "latest" "/opt/databasus" + +msg_info "Building Databasus (Patience)" +cd /opt/databasus/frontend +$STD npm ci +$STD npm run build +cd /opt/databasus/backend +$STD go mod tidy +$STD go mod download +$STD go install github.com/swaggo/swag/cmd/swag@latest +$STD /root/go/bin/swag init -g cmd/main.go -o swagger +$STD env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o databasus ./cmd/main.go +mv /opt/databasus/backend/databasus /opt/databasus/databasus +mkdir -p /databasus-data/{pgdata,temp,backups,data,logs} +mkdir -p /opt/databasus/ui/build +mkdir -p /opt/databasus/migrations +cp -r /opt/databasus/frontend/dist/* /opt/databasus/ui/build/ +cp -r /opt/databasus/backend/migrations/* /opt/databasus/migrations/ +chown -R postgres:postgres /databasus-data +msg_ok "Built Databasus" + +msg_info "Configuring Databasus" +JWT_SECRET=$(openssl rand -hex 32) +ENCRYPTION_KEY=$(openssl rand -hex 32) +# Create PostgreSQL version symlinks for compatibility +for v in 12 13 14 15 16 18; do + ln -sf /usr/lib/postgresql/17 /usr/lib/postgresql/$v +done +# Install goose for migrations +$STD go install github.com/pressly/goose/v3/cmd/goose@latest +ln -sf /root/go/bin/goose /usr/local/bin/goose +cat </opt/databasus/.env +# Environment +ENV_MODE=production + +# Server +SERVER_PORT=4005 +SERVER_HOST=0.0.0.0 + +# Database +DATABASE_DSN=host=localhost user=postgres password=postgres dbname=databasus port=5432 sslmode=disable +DATABASE_URL=postgres://postgres:postgres@localhost:5432/databasus?sslmode=disable + +# Migrations +GOOSE_DRIVER=postgres +GOOSE_DBSTRING=postgres://postgres:postgres@localhost:5432/databasus?sslmode=disable +GOOSE_MIGRATION_DIR=/opt/databasus/migrations + +# Valkey (Redis-compatible cache) +VALKEY_HOST=localhost +VALKEY_PORT=6379 + +# Security +JWT_SECRET=${JWT_SECRET} +ENCRYPTION_KEY=${ENCRYPTION_KEY} + +# Paths +DATA_DIR=/databasus-data/data +BACKUP_DIR=/databasus-data/backups +LOG_DIR=/databasus-data/logs +EOF +chown postgres:postgres /opt/databasus/.env +chmod 600 /opt/databasus/.env +msg_ok "Configured Databasus" + +msg_info "Configuring Valkey" +cat </etc/valkey/valkey.conf +port 6379 +bind 127.0.0.1 +protected-mode yes +save "" +maxmemory 256mb +maxmemory-policy allkeys-lru +EOF +systemctl enable -q --now valkey-server +systemctl restart valkey-server +msg_ok "Configured Valkey" + +msg_info "Creating Database" +# Configure PostgreSQL to allow local password auth for databasus +PG_HBA="/etc/postgresql/17/main/pg_hba.conf" +if ! grep -q "databasus" "$PG_HBA"; then + sed -i '/^local\s*all\s*all/i local databasus postgres trust' "$PG_HBA" + sed -i '/^host\s*all\s*all\s*127/i host databasus postgres 127.0.0.1/32 trust' "$PG_HBA" + systemctl reload postgresql +fi +$STD sudo -u postgres psql -c "CREATE DATABASE databasus;" 2>/dev/null || true +$STD sudo -u postgres psql -c "ALTER USER postgres WITH SUPERUSER CREATEROLE CREATEDB;" 2>/dev/null || true +msg_ok "Created Database" + +msg_info "Creating Databasus Service" +cat </etc/systemd/system/databasus.service +[Unit] +Description=Databasus - Database Backup Management +After=network.target postgresql.service valkey.service +Requires=postgresql.service valkey.service + +[Service] +Type=simple +WorkingDirectory=/opt/databasus +EnvironmentFile=/opt/databasus/.env +ExecStart=/opt/databasus/databasus +Restart=always +RestartSec=5 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target +EOF +$STD systemctl daemon-reload +$STD systemctl enable -q --now databasus +msg_ok "Created Databasus Service" + +msg_info "Configuring Nginx" +cat </etc/nginx/sites-available/databasus +server { + listen 80; + server_name _; + + location / { + proxy_pass http://127.0.0.1:4005; + proxy_http_version 1.1; + proxy_set_header Upgrade \$http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto \$scheme; + proxy_cache_bypass \$http_upgrade; + proxy_buffering off; + proxy_read_timeout 86400s; + proxy_send_timeout 86400s; + } +} +EOF +ln -sf /etc/nginx/sites-available/databasus /etc/nginx/sites-enabled/databasus +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 diff --git a/install/pterodactyl-panel-install.sh b/install/pterodactyl-panel-install.sh index 37eeba8fb..42a9b2ce4 100644 --- a/install/pterodactyl-panel-install.sh +++ b/install/pterodactyl-panel-install.sh @@ -80,6 +80,7 @@ $STD php artisan p:user:make --no-interaction --admin=1 --email "$ADMIN_EMAIL" - echo "* * * * * php /opt/pterodactyl-panel/artisan schedule:run >> /dev/null 2>&1" | crontab -u www-data - chown -R www-data:www-data /opt/pterodactyl-panel/* chmod -R 755 /opt/pterodactyl-panel/storage/* /opt/pterodactyl-panel/bootstrap/cache/ +ln -s /opt/pterodactyl-panel /var/www/pterodactyl { echo "" echo "pterodactyl Admin Username: admin" diff --git a/misc/api.func b/misc/api.func index 977e326c5..baef2a1ad 100644 --- a/misc/api.func +++ b/misc/api.func @@ -117,16 +117,17 @@ detect_repo_source # - Canonical source of truth for ALL exit code mappings # - Used by both api.func (telemetry) and error_handler.func (error display) # - Supports: -# * Generic/Shell errors (1, 2, 124, 126-130, 134, 137, 139, 141, 143) -# * curl/wget errors (6, 7, 22, 28, 35) +# * Generic/Shell errors (1-3, 10, 124-132, 134, 137, 139, 141, 143-146) +# * curl/wget errors (4-8, 16, 18, 22-28, 30, 32-36, 39, 44-48, 51-52, 55-57, 59, 61, 63, 75, 78-79, 92, 95) # * Package manager errors (APT, DPKG: 100-102, 255) +# * BSD sysexits (64-78) # * Systemd/Service errors (150-154) # * Python/pip/uv errors (160-162) # * PostgreSQL errors (170-173) # * MySQL/MariaDB errors (180-183) # * MongoDB errors (190-193) # * Proxmox custom codes (200-231) -# * Node.js/npm errors (243, 245-249) +# * Node.js/npm errors (239, 243, 245-249) # - Returns description string for given exit code # ------------------------------------------------------------------------------ explain_exit_code() { @@ -135,6 +136,7 @@ explain_exit_code() { # --- Generic / Shell --- 1) echo "General error / Operation not permitted" ;; 2) echo "Misuse of shell builtins (e.g. syntax error)" ;; + 3) echo "General syntax or argument error" ;; 10) echo "Docker / privileged mode required (unsupported environment)" ;; # --- curl / wget errors (commonly seen in downloads) --- @@ -142,16 +144,41 @@ explain_exit_code() { 5) echo "curl: Could not resolve proxy" ;; 6) echo "curl: DNS resolution failed (could not resolve host)" ;; 7) echo "curl: Failed to connect (network unreachable / host down)" ;; - 8) echo "curl: FTP server reply error" ;; + 8) echo "curl: Server reply error (FTP/SFTP or apk untrusted key)" ;; + 16) echo "curl: HTTP/2 framing layer error" ;; + 18) echo "curl: Partial file (transfer not completed)" ;; 22) echo "curl: HTTP error returned (404, 429, 500+)" ;; 23) echo "curl: Write error (disk full or permissions)" ;; + 24) echo "curl: Write to local file failed" ;; 25) echo "curl: Upload failed" ;; + 26) echo "curl: Read error on local file (I/O)" ;; + 27) echo "curl: Out of memory (memory allocation failed)" ;; 28) echo "curl: Operation timeout (network slow or server not responding)" ;; 30) echo "curl: FTP port command failed" ;; + 32) echo "curl: FTP SIZE command failed" ;; + 33) echo "curl: HTTP range error" ;; + 34) echo "curl: HTTP post error" ;; 35) echo "curl: SSL/TLS handshake failed (certificate error)" ;; + 36) echo "curl: FTP bad download resume" ;; + 39) echo "curl: LDAP search failed" ;; + 44) echo "curl: Internal error (bad function call order)" ;; + 45) echo "curl: Interface error (failed to bind to specified interface)" ;; + 46) echo "curl: Bad password entered" ;; + 47) echo "curl: Too many redirects" ;; + 48) echo "curl: Unknown command line option specified" ;; + 51) echo "curl: SSL peer certificate or SSH host key verification failed" ;; + 52) echo "curl: Empty reply from server (got nothing)" ;; + 55) echo "curl: Failed sending network data" ;; 56) echo "curl: Receive error (connection reset by peer)" ;; + 57) echo "curl: Unrecoverable poll/select error (system I/O failure)" ;; + 59) echo "curl: Couldn't use specified SSL cipher" ;; + 61) echo "curl: Bad/unrecognized transfer encoding" ;; + 63) echo "curl: Maximum file size exceeded" ;; 75) echo "Temporary failure (retry later)" ;; 78) echo "curl: Remote file not found (404 on FTP/file)" ;; + 79) echo "curl: SSH session error (key exchange/auth failed)" ;; + 92) echo "curl: HTTP/2 stream error (protocol violation)" ;; + 95) echo "curl: HTTP/3 layer error" ;; # --- Package manager / APT / DPKG --- 100) echo "APT: Package manager error (broken packages / dependency problems)" ;; @@ -175,15 +202,21 @@ explain_exit_code() { # --- Common shell/system errors --- 124) echo "Command timed out (timeout command)" ;; + 125) echo "Command failed to start (Docker daemon or execution error)" ;; 126) echo "Command invoked cannot execute (permission problem?)" ;; 127) echo "Command not found" ;; 128) echo "Invalid argument to exit" ;; + 129) echo "Killed by SIGHUP (terminal closed / hangup)" ;; 130) echo "Aborted by user (SIGINT)" ;; + 131) echo "Killed by SIGQUIT (core dumped)" ;; + 132) echo "Killed by SIGILL (illegal CPU instruction)" ;; 134) echo "Process aborted (SIGABRT - possibly Node.js heap overflow)" ;; 137) echo "Killed (SIGKILL / Out of memory?)" ;; 139) echo "Segmentation fault (core dumped)" ;; 141) echo "Broken pipe (SIGPIPE - output closed prematurely)" ;; 143) echo "Terminated (SIGTERM)" ;; + 144) echo "Killed by signal 16 (SIGUSR1 / SIGSTKFLT)" ;; + 146) echo "Killed by signal 18 (SIGTSTP)" ;; # --- Systemd / Service errors (150-154) --- 150) echo "Systemd: Service failed to start" ;; @@ -191,7 +224,6 @@ explain_exit_code() { 152) echo "Permission denied (EACCES)" ;; 153) echo "Build/compile failed (make/gcc/cmake)" ;; 154) echo "Node.js: Native addon build failed (node-gyp)" ;; - # --- Python / pip / uv (160-162) --- 160) echo "Python: Virtualenv / uv environment missing or broken" ;; 161) echo "Python: Dependency resolution failed" ;; @@ -242,7 +274,8 @@ explain_exit_code() { 225) echo "Proxmox: No template available for OS/Version" ;; 231) echo "Proxmox: LXC stack upgrade failed" ;; - # --- Node.js / npm / pnpm / yarn (243-249) --- + # --- Node.js / npm / pnpm / yarn (239-249) --- + 239) echo "npm/Node.js: Unexpected runtime error or dependency failure" ;; 243) echo "Node.js: Out of memory (JavaScript heap out of memory)" ;; 245) echo "Node.js: Invalid command-line option" ;; 246) echo "Node.js: Internal JavaScript Parse Error" ;; diff --git a/misc/build.func b/misc/build.func index a6cd78b52..b0d7afbee 100644 --- a/misc/build.func +++ b/misc/build.func @@ -3427,6 +3427,7 @@ start() { VERBOSE="no" set_std_mode ensure_profile_loaded + get_lxc_ip update_script update_motd_ip cleanup_lxc @@ -3454,6 +3455,7 @@ start() { ;; esac ensure_profile_loaded + get_lxc_ip update_script update_motd_ip cleanup_lxc @@ -4038,6 +4040,13 @@ EOF' msg_ok "Customized LXC Container" + # Optional DNS override for retry scenarios (inside LXC, never on host) + if [[ "${DNS_RETRY_OVERRIDE:-false}" == "true" ]]; then + msg_info "Applying DNS retry override in LXC (8.8.8.8, 1.1.1.1)" + pct exec "$CTID" -- bash -c "printf 'nameserver 8.8.8.8\nnameserver 1.1.1.1\n' >/etc/resolv.conf" >/dev/null 2>&1 || true + msg_ok "DNS override applied in LXC" + fi + # Install SSH keys install_ssh_keys_into_ct @@ -4150,32 +4159,322 @@ EOF' # Prompt user for cleanup with 60s timeout echo "" - echo -en "${TAB}❓${TAB}${YW}Remove broken container ${CTID}? (Y/n) [auto-remove in 60s]: ${CL}" + + # Detect error type for smart recovery options + local is_oom=false + local is_network_issue=false + local is_apt_issue=false + local is_cmd_not_found=false + local error_explanation="" + if declare -f explain_exit_code >/dev/null 2>&1; then + error_explanation="$(explain_exit_code "$install_exit_code")" + fi + + # OOM detection: exit codes 134 (SIGABRT/heap), 137 (SIGKILL/OOM), 243 (Node.js heap) + if [[ $install_exit_code -eq 134 || $install_exit_code -eq 137 || $install_exit_code -eq 243 ]]; then + is_oom=true + fi + + # APT/DPKG detection: exit codes 100-102 (APT), 255 (DPKG with log evidence) + case "$install_exit_code" in + 100 | 101 | 102) is_apt_issue=true ;; + 255) + if [[ -f "$combined_log" ]] && grep -qiE 'dpkg|apt-get|apt\.conf|broken packages|unmet dependencies|E: Sub-process|E: Failed' "$combined_log"; then + is_apt_issue=true + fi + ;; + esac + + # Command not found detection + if [[ $install_exit_code -eq 127 ]]; then + is_cmd_not_found=true + fi + + # Network-related detection (curl/apt/git fetch failures and transient network issues) + case "$install_exit_code" in + 6 | 7 | 22 | 28 | 35 | 52 | 56 | 57 | 75 | 78) is_network_issue=true ;; + 100) + # APT can fail due to network (Failed to fetch) + if [[ -f "$combined_log" ]] && grep -qiE 'Failed to fetch|Could not resolve|Connection failed|Network is unreachable|Temporary failure resolving' "$combined_log"; then + is_network_issue=true + fi + ;; + 128) + if [[ -f "$combined_log" ]] && grep -qiE 'RPC failed|early EOF|fetch-pack|HTTP/2 stream|Could not resolve host|Temporary failure resolving|Failed to fetch|Connection reset|Network is unreachable' "$combined_log"; then + is_network_issue=true + fi + ;; + esac + + # Exit 1 subclassification: analyze logs to identify actual root cause + # Many exit 1 errors are actually APT, OOM, network, or command-not-found issues + if [[ $install_exit_code -eq 1 && -f "$combined_log" ]]; then + if grep -qiE 'E: Unable to|E: Package|E: Failed to fetch|dpkg.*error|broken packages|unmet dependencies|dpkg --configure -a' "$combined_log"; then + is_apt_issue=true + fi + if grep -qiE 'Cannot allocate memory|Out of memory|oom-killer|Killed process|JavaScript heap' "$combined_log"; then + is_oom=true + fi + if grep -qiE 'Could not resolve|DNS|Connection refused|Network is unreachable|No route to host|Temporary failure resolving|Failed to fetch' "$combined_log"; then + is_network_issue=true + fi + if grep -qiE ': command not found|No such file or directory.*/s?bin/' "$combined_log"; then + is_cmd_not_found=true + fi + fi + + # Show error explanation if available + if [[ -n "$error_explanation" ]]; then + echo -e "${TAB}${RD}Error: ${error_explanation}${CL}" + echo "" + fi + + # Show specific hints for known error types + if [[ $install_exit_code -eq 10 ]]; then + echo -e "${TAB}${INFO} This error usually means the container needs ${GN}privileged${CL} mode or Docker/nesting support." + echo -e "${TAB}${INFO} Recreate with: Advanced Install → Container Type: ${GN}Privileged${CL}" + echo "" + fi + + if [[ $install_exit_code -eq 125 || $install_exit_code -eq 126 ]]; then + echo -e "${TAB}${INFO} The command exists but cannot be executed. This may be a ${GN}permission${CL} issue." + echo -e "${TAB}${INFO} If using Docker, ensure the container is ${GN}privileged${CL} or has correct permissions." + echo "" + fi + + if [[ "$is_cmd_not_found" == true ]]; then + local missing_cmd="" + if [[ -f "$combined_log" ]]; then + missing_cmd=$(grep -oiE '[a-zA-Z0-9_.-]+: command not found' "$combined_log" | tail -1 | sed 's/: command not found//') + fi + if [[ -n "$missing_cmd" ]]; then + echo -e "${TAB}${INFO} Missing command: ${GN}${missing_cmd}${CL}" + fi + echo "" + fi + + # Build recovery menu based on error type + echo -e "${YW}What would you like to do?${CL}" + echo "" + echo -e " ${GN}1)${CL} Remove container and exit" + echo -e " ${GN}2)${CL} Keep container for debugging" + echo -e " ${GN}3)${CL} Retry with verbose mode (full rebuild)" + + local next_option=4 + local APT_OPTION="" OOM_OPTION="" DNS_OPTION="" + + if [[ "$is_apt_issue" == true ]]; then + if [[ "$var_os" == "alpine" ]]; then + echo -e " ${GN}${next_option})${CL} Repair APK state and re-run install (in-place)" + else + echo -e " ${GN}${next_option})${CL} Repair APT/DPKG state and re-run install (in-place)" + fi + APT_OPTION=$next_option + next_option=$((next_option + 1)) + fi + + if [[ "$is_oom" == true ]]; then + local recovery_attempt="${RECOVERY_ATTEMPT:-0}" + if [[ $recovery_attempt -lt 2 ]]; then + local new_ram=$((RAM_SIZE * 2)) + local new_cpu=$((CORE_COUNT * 2)) + echo -e " ${GN}${next_option})${CL} Retry with more resources (RAM: ${RAM_SIZE}→${new_ram} MiB, CPU: ${CORE_COUNT}→${new_cpu} cores)" + OOM_OPTION=$next_option + next_option=$((next_option + 1)) + else + echo -e " ${DGN}-)${CL} ${DGN}OOM retry exhausted (already retried ${recovery_attempt}x)${CL}" + fi + fi + + if [[ "$is_network_issue" == true ]]; then + echo -e " ${GN}${next_option})${CL} Retry with DNS override in LXC (8.8.8.8 / 1.1.1.1)" + DNS_OPTION=$next_option + next_option=$((next_option + 1)) + fi + + local max_option=$((next_option - 1)) + + echo "" + echo -en "${YW}Select option [1-${max_option}] (default: 1, auto-remove in 60s): ${CL}" if read -t 60 -r response; then - if [[ -z "$response" || "$response" =~ ^[Yy]$ ]]; then + case "${response:-1}" in + 1) # Remove container - echo "" - msg_info "Removing container ${CTID}" + echo -e "\n${TAB}${HOLD}${YW}Removing container ${CTID}${CL}" pct stop "$CTID" &>/dev/null || true pct destroy "$CTID" &>/dev/null || true - msg_ok "Container ${CTID} removed" - elif [[ "$response" =~ ^[Nn]$ ]]; then - echo "" - msg_warn "Container ${CTID} kept for debugging" - + echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}" + ;; + 2) + echo -e "\n${TAB}${YW}Container ${CTID} kept for debugging${CL}" # Dev mode: Setup MOTD/SSH for debugging access to broken container if [[ "${DEV_MODE_MOTD:-false}" == "true" ]]; then echo -e "${TAB}${HOLD}${DGN}Setting up MOTD and SSH for debugging...${CL}" if pct exec "$CTID" -- bash -c " - source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/install.func) - declare -f motd_ssh >/dev/null 2>&1 && motd_ssh || true - " >/dev/null 2>&1; then + source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/install.func) + declare -f motd_ssh >/dev/null 2>&1 && motd_ssh || true + " >/dev/null 2>&1; then local ct_ip=$(pct exec "$CTID" ip a s dev eth0 2>/dev/null | awk '/inet / {print $2}' | cut -d/ -f1) echo -e "${BFR}${CM}${GN}MOTD/SSH ready - SSH into container: ssh root@${ct_ip}${CL}" fi fi - fi + exit $install_exit_code + ;; + 3) + # Retry with verbose mode (full rebuild) + echo -e "\n${TAB}${HOLD}${YW}Removing container ${CTID} for rebuild...${CL}" + pct stop "$CTID" &>/dev/null || true + pct destroy "$CTID" &>/dev/null || true + echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}" + echo "" + # Get new container ID + local old_ctid="$CTID" + export CTID=$(get_valid_container_id "$CTID") + export VERBOSE="yes" + export var_verbose="yes" + + # Show rebuild summary + echo -e "${YW}Rebuilding with preserved settings:${CL}" + echo -e " Container ID: ${old_ctid} → ${CTID}" + echo -e " RAM: ${RAM_SIZE} MiB | CPU: ${CORE_COUNT} cores | Disk: ${DISK_SIZE} GB" + echo -e " Network: ${NET:-dhcp} | Bridge: ${BRG:-vmbr0}" + echo -e " Verbose: ${GN}enabled${CL}" + echo "" + msg_info "Restarting installation..." + # Re-run build_container + build_container + return $? + ;; + *) + # Handle dynamic smart recovery options via named option variables + local handled=false + + if [[ -n "${APT_OPTION}" && "${response}" == "${APT_OPTION}" ]]; then + # Package manager in-place repair: fix broken state and re-run install script + handled=true + if [[ "$var_os" == "alpine" ]]; then + echo -e "\n${TAB}${HOLD}${YW}Repairing APK state in container ${CTID}...${CL}" + pct exec "$CTID" -- ash -c " + apk fix 2>/dev/null || true + apk cache clean 2>/dev/null || true + apk update 2>/dev/null || true + " >/dev/null 2>&1 || true + echo -e "${BFR}${CM}${GN}APK state repaired in container ${CTID}${CL}" + else + echo -e "\n${TAB}${HOLD}${YW}Repairing APT/DPKG state in container ${CTID}...${CL}" + pct exec "$CTID" -- bash -c " + DEBIAN_FRONTEND=noninteractive dpkg --configure -a 2>/dev/null || true + apt-get -f install -y 2>/dev/null || true + apt-get clean 2>/dev/null + apt-get update 2>/dev/null || true + " >/dev/null 2>&1 || true + echo -e "${BFR}${CM}${GN}APT/DPKG state repaired in container ${CTID}${CL}" + fi + echo "" + export VERBOSE="yes" + export var_verbose="yes" + + echo -e "${YW}Re-running installation in existing container ${CTID}:${CL}" + echo -e " RAM: ${RAM_SIZE} MiB | CPU: ${CORE_COUNT} cores | Disk: ${DISK_SIZE} GB" + echo -e " Verbose: ${GN}enabled${CL}" + echo "" + msg_info "Re-running installation script..." + + # Re-run install script in existing container (don't destroy/recreate) + set +Eeuo pipefail + trap - ERR + lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/install/${var_install}.sh)" + local apt_retry_exit=$? + set -Eeuo pipefail + trap 'error_handler' ERR + + # Check for error flag from retry + local apt_retry_code=0 + if [[ -n "${SESSION_ID:-}" ]]; then + local retry_error_flag="/root/.install-${SESSION_ID}.failed" + if pct exec "$CTID" -- test -f "$retry_error_flag" 2>/dev/null; then + apt_retry_code=$(pct exec "$CTID" -- cat "$retry_error_flag" 2>/dev/null || echo "1") + pct exec "$CTID" -- rm -f "$retry_error_flag" 2>/dev/null || true + fi + fi + + if [[ $apt_retry_code -eq 0 && $apt_retry_exit -ne 0 ]]; then + apt_retry_code=$apt_retry_exit + fi + + if [[ $apt_retry_code -eq 0 ]]; then + msg_ok "Installation completed successfully after APT repair!" + post_update_to_api "done" "0" "force" + return 0 + else + msg_error "Installation still failed after APT repair (exit code: ${apt_retry_code})" + install_exit_code=$apt_retry_code + fi + fi + + if [[ -n "${OOM_OPTION}" && "${response}" == "${OOM_OPTION}" ]]; then + # Retry with doubled resources + handled=true + echo -e "\n${TAB}${HOLD}${YW}Removing container ${CTID} for rebuild with more resources...${CL}" + pct stop "$CTID" &>/dev/null || true + pct destroy "$CTID" &>/dev/null || true + echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}" + echo "" + local old_ctid="$CTID" + local old_ram="$RAM_SIZE" + local old_cpu="$CORE_COUNT" + export CTID=$(get_valid_container_id "$CTID") + export RAM_SIZE=$((RAM_SIZE * 2)) + export CORE_COUNT=$((CORE_COUNT * 2)) + export var_ram="$RAM_SIZE" + export var_cpu="$CORE_COUNT" + export VERBOSE="yes" + export var_verbose="yes" + export RECOVERY_ATTEMPT=$(( ${RECOVERY_ATTEMPT:-0} + 1 )) + + echo -e "${YW}Rebuilding with increased resources (attempt ${RECOVERY_ATTEMPT}/2):${CL}" + echo -e " Container ID: ${old_ctid} → ${CTID}" + echo -e " RAM: ${old_ram} → ${GN}${RAM_SIZE}${CL} MiB (x2)" + echo -e " CPU: ${old_cpu} → ${GN}${CORE_COUNT}${CL} cores (x2)" + echo -e " Disk: ${DISK_SIZE} GB | Network: ${NET:-dhcp} | Bridge: ${BRG:-vmbr0}" + echo -e " Verbose: ${GN}enabled${CL}" + echo "" + msg_info "Restarting installation..." + build_container + return $? + fi + + if [[ -n "${DNS_OPTION}" && "${response}" == "${DNS_OPTION}" ]]; then + # Retry with DNS override in LXC + handled=true + echo -e "\n${TAB}${HOLD}${YW}Removing container ${CTID} for rebuild with DNS override...${CL}" + pct stop "$CTID" &>/dev/null || true + pct destroy "$CTID" &>/dev/null || true + echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}" + echo "" + local old_ctid="$CTID" + export CTID=$(get_valid_container_id "$CTID") + export DNS_RETRY_OVERRIDE="true" + export VERBOSE="yes" + export var_verbose="yes" + + echo -e "${YW}Rebuilding with DNS override in LXC:${CL}" + echo -e " Container ID: ${old_ctid} → ${CTID}" + echo -e " DNS: ${GN}8.8.8.8, 1.1.1.1${CL} (inside LXC only)" + echo -e " Verbose: ${GN}enabled${CL}" + echo "" + msg_info "Restarting installation..." + build_container + return $? + fi + + if [[ "$handled" == false ]]; then + echo -e "\n${TAB}${YW}Invalid option. Container ${CTID} kept.${CL}" + exit $install_exit_code + fi + ;; + esac else # Timeout - auto-remove echo "" @@ -5274,5 +5573,6 @@ if command -v pveversion >/dev/null 2>&1; then trap 'api_exit_script' EXIT fi trap 'local _ec=$?; if [[ $_ec -ne 0 ]]; then ensure_log_on_host; post_update_to_api "failed" "$_ec"; fi' ERR +trap 'ensure_log_on_host; post_update_to_api "failed" "129"; exit 129' SIGHUP trap 'ensure_log_on_host; post_update_to_api "failed" "130"; exit 130' SIGINT trap 'ensure_log_on_host; post_update_to_api "failed" "143"; exit 143' SIGTERM diff --git a/misc/error_handler.func b/misc/error_handler.func index f46ba5f8d..febd28c3b 100644 --- a/misc/error_handler.func +++ b/misc/error_handler.func @@ -37,21 +37,47 @@ if ! declare -f explain_exit_code &>/dev/null; then case "$code" in 1) echo "General error / Operation not permitted" ;; 2) echo "Misuse of shell builtins (e.g. syntax error)" ;; + 3) echo "General syntax or argument error" ;; 10) echo "Docker / privileged mode required (unsupported environment)" ;; 4) echo "curl: Feature not supported or protocol error" ;; 5) echo "curl: Could not resolve proxy" ;; 6) echo "curl: DNS resolution failed (could not resolve host)" ;; 7) echo "curl: Failed to connect (network unreachable / host down)" ;; - 8) echo "curl: FTP server reply error" ;; + 8) echo "curl: Server reply error (FTP/SFTP or apk untrusted key)" ;; + 16) echo "curl: HTTP/2 framing layer error" ;; + 18) echo "curl: Partial file (transfer not completed)" ;; 22) echo "curl: HTTP error returned (404, 429, 500+)" ;; 23) echo "curl: Write error (disk full or permissions)" ;; + 24) echo "curl: Write to local file failed" ;; 25) echo "curl: Upload failed" ;; + 26) echo "curl: Read error on local file (I/O)" ;; + 27) echo "curl: Out of memory (memory allocation failed)" ;; 28) echo "curl: Operation timeout (network slow or server not responding)" ;; 30) echo "curl: FTP port command failed" ;; + 32) echo "curl: FTP SIZE command failed" ;; + 33) echo "curl: HTTP range error" ;; + 34) echo "curl: HTTP post error" ;; 35) echo "curl: SSL/TLS handshake failed (certificate error)" ;; + 36) echo "curl: FTP bad download resume" ;; + 39) echo "curl: LDAP search failed" ;; + 44) echo "curl: Internal error (bad function call order)" ;; + 45) echo "curl: Interface error (failed to bind to specified interface)" ;; + 46) echo "curl: Bad password entered" ;; + 47) echo "curl: Too many redirects" ;; + 48) echo "curl: Unknown command line option specified" ;; + 51) echo "curl: SSL peer certificate or SSH host key verification failed" ;; + 52) echo "curl: Empty reply from server (got nothing)" ;; + 55) echo "curl: Failed sending network data" ;; 56) echo "curl: Receive error (connection reset by peer)" ;; + 57) echo "curl: Unrecoverable poll/select error (system I/O failure)" ;; + 59) echo "curl: Couldn't use specified SSL cipher" ;; + 61) echo "curl: Bad/unrecognized transfer encoding" ;; + 63) echo "curl: Maximum file size exceeded" ;; 75) echo "Temporary failure (retry later)" ;; 78) echo "curl: Remote file not found (404 on FTP/file)" ;; + 79) echo "curl: SSH session error (key exchange/auth failed)" ;; + 92) echo "curl: HTTP/2 stream error (protocol violation)" ;; + 95) echo "curl: HTTP/3 layer error" ;; 64) echo "Usage error (wrong arguments)" ;; 65) echo "Data format error (bad input data)" ;; 66) echo "Input file not found (cannot open input)" ;; @@ -69,15 +95,21 @@ if ! declare -f explain_exit_code &>/dev/null; then 101) echo "APT: Configuration error (bad sources.list, malformed config)" ;; 102) echo "APT: Lock held by another process (dpkg/apt still running)" ;; 124) echo "Command timed out (timeout command)" ;; + 125) echo "Command failed to start (Docker daemon or execution error)" ;; 126) echo "Command invoked cannot execute (permission problem?)" ;; 127) echo "Command not found" ;; 128) echo "Invalid argument to exit" ;; - 130) echo "Terminated by Ctrl+C (SIGINT)" ;; + 129) echo "Killed by SIGHUP (terminal closed / hangup)" ;; + 130) echo "Aborted by user (SIGINT)" ;; + 131) echo "Killed by SIGQUIT (core dumped)" ;; + 132) echo "Killed by SIGILL (illegal CPU instruction)" ;; 134) echo "Process aborted (SIGABRT - possibly Node.js heap overflow)" ;; 137) echo "Killed (SIGKILL / Out of memory?)" ;; 139) echo "Segmentation fault (core dumped)" ;; 141) echo "Broken pipe (SIGPIPE - output closed prematurely)" ;; 143) echo "Terminated (SIGTERM)" ;; + 144) echo "Killed by signal 16 (SIGUSR1 / SIGSTKFLT)" ;; + 146) echo "Killed by signal 18 (SIGTSTP)" ;; 150) echo "Systemd: Service failed to start" ;; 151) echo "Systemd: Service unit not found" ;; 152) echo "Permission denied (EACCES)" ;; @@ -123,6 +155,7 @@ if ! declare -f explain_exit_code &>/dev/null; then 224) echo "Proxmox: PBS storage is for backups only" ;; 225) echo "Proxmox: No template available for OS/Version" ;; 231) echo "Proxmox: LXC stack upgrade failed" ;; + 239) echo "npm/Node.js: Unexpected runtime error or dependency failure" ;; 243) echo "Node.js: Out of memory (JavaScript heap out of memory)" ;; 245) echo "Node.js: Invalid command-line option" ;; 246) echo "Node.js: Internal JavaScript Parse Error" ;; diff --git a/tools/addon/immich-public-proxy.sh b/tools/addon/immich-public-proxy.sh index c98a92708..a147f80d1 100644 --- a/tools/addon/immich-public-proxy.sh +++ b/tools/addon/immich-public-proxy.sh @@ -104,6 +104,10 @@ function update() { $STD npm run build msg_ok "Built ${APP}" + msg_info "Updating service" + create_service + msg_ok "Updated service" + msg_info "Starting service" systemctl start immich-proxy msg_ok "Started service" @@ -112,6 +116,27 @@ function update() { fi } +function create_service() { + cat <"$SERVICE_PATH" +[Unit] +Description=Immich Public Proxy +After=network.target + +[Service] +Type=simple +User=root +WorkingDirectory=${INSTALL_PATH}/app +EnvironmentFile=${CONFIG_PATH}/.env +ExecStart=/usr/bin/node ${INSTALL_PATH}/app/dist/index.js +Restart=always +RestartSec=10 + +[Install] +WantedBy=multi-user.target +EOF + systemctl daemon-reload +} + # ============================================================================== # INSTALL # ============================================================================== @@ -173,23 +198,7 @@ EOF msg_ok "Created configuration" msg_info "Creating service" - cat <"$SERVICE_PATH" -[Unit] -Description=Immich Public Proxy -After=network.target - -[Service] -Type=simple -User=root -WorkingDirectory=${INSTALL_PATH} -EnvironmentFile=${CONFIG_PATH}/.env -ExecStart=/usr/bin/node ${INSTALL_PATH}/app/server.js -Restart=always -RestartSec=10 - -[Install] -WantedBy=multi-user.target -EOF + create_service systemctl enable -q --now immich-proxy msg_ok "Created and started service"