mirror of
https://github.com/community-scripts/ProxmoxVE.git
synced 2026-03-03 09:25:55 +01:00
* Standardize exit codes and add mappings Replace generic exit 1 usages with specific numeric exit codes and add corresponding explanations to the error lookup. This commit updates multiple misc/* scripts to return distinct codes for validation, Proxmox/LXC, networking, download and curl errors (e.g. 103-123, 64, 107-120, 206, 0 for explicit user cancels). It also updates curl error handling to propagate the original curl exit code and adds new entries in explain_exit_code and the error handler to improve diagnostics. * Set exit code 115 for update_os errors Change exit status from 6 to 115 in misc/alpine-install.func's update_os() error handlers when failing to download tools.func or when the expected functions are missing. This gives a distinct exit code for these specific failure cases. * Add tools/addon exit codes and use them Introduce exit codes 232-238 for Tools & Addon scripts in misc/api.func and misc/error_handler.func. Update addon scripts (tools/addon/adguardhome-sync.sh, tools/addon/copyparty.sh, tools/addon/cronmaster.sh) to return specific codes instead of generic exit 1: 238 for unsupported OS and 233 when the application is not installed/upgrade prerequisites are missing. This makes failures more descriptive and aligns scripts with the central error explanations. * Standardize exit codes in exporter addons Unify exit codes across exporter addon scripts: return 238 for unsupported OS detections and 233 when an update is requested but the exporter is not installed. Applied to nextcloud-exporter.sh, pihole-exporter.sh, prometheus-paperless-ngx-exporter.sh, and qbittorrent-exporter.sh to make failure modes distinguishable for callers/automation. * Use specific exit codes in addon scripts Replace generic exit 1 with distinct exit codes across multiple addon scripts to enable finer-grained error handling in automation. Exit codes introduced: 10 for Docker/Compose missing or user-declined Docker install, 233 for "nothing to update" cases, and 238 for unsupported OS cases. Affected files: tools/addon/arcane.sh, coolify.sh, dockge.sh, dokploy.sh, filebrowser-quantum.sh, filebrowser.sh, immich-public-proxy.sh, jellystat.sh, runtipi.sh. * Use specific exit codes in addon scripts Replace generic exit 1 with specific exit codes across multiple addon scripts to improve error signaling and handling. Files updated: tools/addon/add-netbird-lxc.sh (exit 238 on unsupported distro), tools/addon/add-tailscale-lxc.sh (treat user cancel as exit 0), tools/addon/glances.sh (exit 233 when not installed), tools/addon/komodo.sh (distinct exits for missing compose, legacy DB, backup/download failures, docker checks), tools/addon/netdata.sh (distinct exits for unsupported PVE versions, OS/codename detection, repo lookups), and tools/addon/phpmyadmin.sh (distinct exits for unsupported OS, network/download issues, package install/start failures, and invalid input). These changes make failures easier to identify and automate recovery or reporting. * Use specific exit codes in PVE scripts Replace generic exit 1 with distinct exit codes across tools/pve scripts to provide clearer failure signals for callers. post-pve-install.sh now returns 105 for unsupported Proxmox versions; pve-privilege-converter.sh uses 104 for non-root, 234 when no containers, and 235 for backup/conversion failures; update-apps.sh maps backup failures to 235, missing containers/selections to 234 (and UI cancellations to 0), missing backup storage to 119, and returns the actual container update exit code on failure. These changes improve diagnostics and allow external tooling to react to specific error conditions. * Standardize exit codes and behaviors Adjust exit codes and abort handling across multiple PVE helper scripts to provide clearer outcomes for automation and interactive flows. Changes include: - container-restore-from-backup.sh, core-restore-from-backup.sh: return 235 when no backups found (was 1). - fstrim.sh: treat user cancellation of non-ext4 warning as non-error (exit 0 instead of 1). - kernel-clean.sh: treat no selection or user abort as non-error (exit 0 instead of 1). - lxc-delete.sh: return 234 when no containers are present; treat no selection as non-error (exit 0). - nic-offloading-fix.sh: use specific non-zero codes for root check and tool install failures (exit 104, 237) and 236 when no matching interfaces (was 1). - pbs_microcode.sh, post-pmg-install.sh, post-pbs-install.sh: use distinct exit codes (232 and 105) for detected VM/PVE/unsupported distro conditions instead of generic 1. These modifications make scripts return distinct codes for different failure modes and ensure user-initiated aborts or benign conditions exit with 0 where appropriate. * Use exit 105 for unsupported PVE versions Standardize error handling by replacing generic exit 1 with exit 105 in pve_check() across multiple VM template scripts to indicate unsupported Proxmox VE versions. Also add API exit code 226 message for "Proxmox: VM disk import or post-creation setup failed" in misc/api.func. Affected files include misc/api.func and various vm/*-vm.sh scripts. * Use specific exit codes in VM scripts Replace generic exit 1 with distinct exit codes across vm/*.sh to make failures more actionable for callers. Changes include: use 226 for missing imported-disk references, 237 for pv installation failures, 115 for download/extract/ISO-related failures, 214 for insufficient disk space during FreeBSD decompression, and 119 for missing storage detection. Updated scripts: archlinux-vm.sh, docker-vm.sh, haos-vm.sh, openwrt-vm.sh, opnsense-vm.sh, truenas-vm.sh, umbrel-os-vm.sh.
1422 lines
50 KiB
Bash
1422 lines
50 KiB
Bash
# Copyright (c) 2021-2026 community-scripts ORG
|
|
# Author: michelroegl-brunner | MickLesk
|
|
# License: MIT | https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/LICENSE
|
|
|
|
# ==============================================================================
|
|
# API.FUNC - TELEMETRY & DIAGNOSTICS API
|
|
# ==============================================================================
|
|
#
|
|
# Provides functions for sending anonymous telemetry data via the community
|
|
# telemetry ingest service at telemetry.community-scripts.org.
|
|
#
|
|
# Features:
|
|
# - Container/VM creation statistics
|
|
# - Installation success/failure tracking
|
|
# - Error code mapping and reporting
|
|
# - Privacy-respecting anonymous telemetry
|
|
#
|
|
# Usage:
|
|
# source <(curl -fsSL .../api.func)
|
|
# post_to_api # Report LXC container creation
|
|
# post_to_api_vm # Report VM creation
|
|
# post_update_to_api # Report installation status
|
|
#
|
|
# Privacy:
|
|
# - Only anonymous statistics (no personal data)
|
|
# - User can opt-out via DIAGNOSTICS=no
|
|
# - Random UUID for session tracking only
|
|
# - Data retention: 30 days
|
|
#
|
|
# ==============================================================================
|
|
|
|
# ==============================================================================
|
|
# Telemetry Configuration
|
|
# ==============================================================================
|
|
TELEMETRY_URL="https://telemetry.community-scripts.org/telemetry"
|
|
|
|
# Timeout for telemetry requests (seconds)
|
|
# Progress pings (validation/configuring) use the short timeout
|
|
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
|
|
# ==============================================================================
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# detect_repo_source()
|
|
#
|
|
# - Dynamically detects which GitHub/Gitea repo the scripts were loaded from
|
|
# - Inspects /proc/$$/cmdline and $0 to find the source URL
|
|
# - Maps detected repo to one of three canonical values:
|
|
# * "ProxmoxVE" — official community-scripts/ProxmoxVE (production)
|
|
# * "ProxmoxVED" — official community-scripts/ProxmoxVED (development)
|
|
# * "external" — any fork or unknown source
|
|
# - Fallback: "ProxmoxVED" (CI sed transforms ProxmoxVED → ProxmoxVE on promotion)
|
|
# - Sets and exports REPO_SOURCE global variable
|
|
# - Skips detection if REPO_SOURCE is already set (e.g., by environment)
|
|
# ------------------------------------------------------------------------------
|
|
detect_repo_source() {
|
|
# Allow explicit override via environment
|
|
[[ -n "${REPO_SOURCE:-}" ]] && return 0
|
|
|
|
local content="" owner_repo=""
|
|
|
|
# Method 1: Read from /proc/$$/cmdline
|
|
# When invoked via: bash -c "$(curl -fsSL https://.../ct/app.sh)"
|
|
# the full CT/VM script content is in /proc/$$/cmdline (same PID through source chain)
|
|
if [[ -r /proc/$$/cmdline ]]; then
|
|
content=$(tr '\0' ' ' </proc/$$/cmdline 2>/dev/null) || true
|
|
fi
|
|
|
|
# Method 2: Read from the original script file (bash ct/app.sh / bash vm/app.sh)
|
|
if [[ -z "$content" ]] || ! echo "$content" | grep -qE 'githubusercontent\.com|community-scripts\.org' 2>/dev/null; then
|
|
if [[ -f "$0" ]] && [[ "$0" != *bash* ]]; then
|
|
content=$(head -10 "$0" 2>/dev/null) || true
|
|
fi
|
|
fi
|
|
|
|
# Extract owner/repo from URL patterns found in the script content
|
|
if [[ -n "$content" ]]; then
|
|
# GitHub raw URL: raw.githubusercontent.com/OWNER/REPO/...
|
|
owner_repo=$(echo "$content" | grep -oE 'raw\.githubusercontent\.com/[^/]+/[^/]+' | head -1 | sed 's|raw\.githubusercontent\.com/||') || true
|
|
|
|
# Gitea URL: git.community-scripts.org/OWNER/REPO/...
|
|
if [[ -z "$owner_repo" ]]; then
|
|
owner_repo=$(echo "$content" | grep -oE 'git\.community-scripts\.org/[^/]+/[^/]+' | head -1 | sed 's|git\.community-scripts\.org/||') || true
|
|
fi
|
|
fi
|
|
|
|
# Map detected owner/repo to canonical repo_source value
|
|
case "$owner_repo" in
|
|
community-scripts/ProxmoxVE) REPO_SOURCE="ProxmoxVE" ;;
|
|
community-scripts/ProxmoxVED) REPO_SOURCE="ProxmoxVED" ;;
|
|
"")
|
|
# No URL detected — use hardcoded fallback
|
|
# This value must match the repo: ProxmoxVE for production, ProxmoxVED for dev
|
|
REPO_SOURCE="ProxmoxVE"
|
|
;;
|
|
*)
|
|
# Fork or unknown repo
|
|
REPO_SOURCE="external"
|
|
;;
|
|
esac
|
|
|
|
export REPO_SOURCE
|
|
}
|
|
|
|
# Run detection immediately when api.func is sourced
|
|
detect_repo_source
|
|
|
|
# ==============================================================================
|
|
# SECTION 1: ERROR CODE DESCRIPTIONS
|
|
# ==============================================================================
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# explain_exit_code()
|
|
#
|
|
# - Maps numeric exit codes to human-readable error descriptions
|
|
# - 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-3, 10, 124-132, 134, 137, 139, 141, 143-146)
|
|
# * curl/wget errors (4-8, 16, 18, 22-28, 30, 32-36, 39, 44-48, 51-52, 55-57, 59, 61, 63, 75, 78-79, 92, 95)
|
|
# * Package manager errors (APT, DPKG: 100-102, 255)
|
|
# * Script Validation & Setup (103-123)
|
|
# * BSD sysexits (64-78)
|
|
# * Systemd/Service errors (150-154)
|
|
# * Python/pip/uv errors (160-162)
|
|
# * PostgreSQL errors (170-173)
|
|
# * MySQL/MariaDB errors (180-183)
|
|
# * MongoDB errors (190-193)
|
|
# * Proxmox custom codes (200-231)
|
|
# * Tools & Addon Scripts (232-238)
|
|
# * Node.js/npm errors (239, 243, 245-249)
|
|
# - Returns description string for given exit code
|
|
# ------------------------------------------------------------------------------
|
|
explain_exit_code() {
|
|
local code="$1"
|
|
case "$code" in
|
|
# --- 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) ---
|
|
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: 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)" ;;
|
|
101) echo "APT: Configuration error (bad sources.list, malformed config)" ;;
|
|
102) echo "APT: Lock held by another process (dpkg/apt still running)" ;;
|
|
|
|
# --- Script Validation & Setup (103-123) ---
|
|
103) echo "Validation: Shell is not Bash" ;;
|
|
104) echo "Validation: Not running as root (or invoked via sudo)" ;;
|
|
105) echo "Validation: Proxmox VE version not supported" ;;
|
|
106) echo "Validation: Architecture not supported (ARM / PiMox)" ;;
|
|
107) echo "Validation: Kernel key parameters unreadable" ;;
|
|
108) echo "Validation: Kernel key limits exceeded" ;;
|
|
109) echo "Proxmox: No available container ID after max attempts" ;;
|
|
110) echo "Proxmox: Failed to apply default.vars" ;;
|
|
111) echo "Proxmox: App defaults file not available" ;;
|
|
112) echo "Proxmox: Invalid install menu option" ;;
|
|
113) echo "LXC: Under-provisioned — user aborted update" ;;
|
|
114) echo "LXC: Storage too low — user aborted update" ;;
|
|
115) echo "Download: install.func download failed or incomplete" ;;
|
|
116) echo "Proxmox: Default bridge vmbr0 not found" ;;
|
|
117) echo "LXC: Container did not reach running state" ;;
|
|
118) echo "LXC: No IP assigned to container after timeout" ;;
|
|
119) echo "Proxmox: No valid storage for rootdir content" ;;
|
|
120) echo "Proxmox: No valid storage for vztmpl content" ;;
|
|
121) echo "LXC: Container network not ready (no IP after retries)" ;;
|
|
122) echo "LXC: No internet connectivity — user declined to continue" ;;
|
|
123) echo "LXC: Local IP detection failed" ;;
|
|
|
|
# --- BSD sysexits.h (64-78) ---
|
|
64) echo "Usage error (wrong arguments)" ;;
|
|
65) echo "Data format error (bad input data)" ;;
|
|
66) echo "Input file not found (cannot open input)" ;;
|
|
67) echo "User not found (addressee unknown)" ;;
|
|
68) echo "Host not found (hostname unknown)" ;;
|
|
69) echo "Service unavailable" ;;
|
|
70) echo "Internal software error" ;;
|
|
71) echo "System error (OS-level failure)" ;;
|
|
72) echo "Critical OS file missing" ;;
|
|
73) echo "Cannot create output file" ;;
|
|
74) echo "I/O error" ;;
|
|
76) echo "Remote protocol error" ;;
|
|
77) echo "Permission denied" ;;
|
|
|
|
# --- 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" ;;
|
|
151) echo "Systemd: Service unit not found" ;;
|
|
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" ;;
|
|
162) echo "Python: Installation aborted (permissions or EXTERNALLY-MANAGED)" ;;
|
|
|
|
# --- PostgreSQL (170-173) ---
|
|
170) echo "PostgreSQL: Connection failed (server not running / wrong socket)" ;;
|
|
171) echo "PostgreSQL: Authentication failed (bad user/password)" ;;
|
|
172) echo "PostgreSQL: Database does not exist" ;;
|
|
173) echo "PostgreSQL: Fatal error in query / syntax" ;;
|
|
|
|
# --- MySQL / MariaDB (180-183) ---
|
|
180) echo "MySQL/MariaDB: Connection failed (server not running / wrong socket)" ;;
|
|
181) echo "MySQL/MariaDB: Authentication failed (bad user/password)" ;;
|
|
182) echo "MySQL/MariaDB: Database does not exist" ;;
|
|
183) echo "MySQL/MariaDB: Fatal error in query / syntax" ;;
|
|
|
|
# --- MongoDB (190-193) ---
|
|
190) echo "MongoDB: Connection failed (server not running)" ;;
|
|
191) echo "MongoDB: Authentication failed (bad user/password)" ;;
|
|
192) echo "MongoDB: Database not found" ;;
|
|
193) echo "MongoDB: Fatal query error" ;;
|
|
|
|
# --- Proxmox Custom Codes (200-231) ---
|
|
200) echo "Proxmox: Failed to create lock file" ;;
|
|
203) echo "Proxmox: Missing CTID variable" ;;
|
|
204) echo "Proxmox: Missing PCT_OSTYPE variable" ;;
|
|
205) echo "Proxmox: Invalid CTID (<100)" ;;
|
|
206) echo "Proxmox: CTID already in use" ;;
|
|
207) echo "Proxmox: Password contains unescaped special characters" ;;
|
|
208) echo "Proxmox: Invalid configuration (DNS/MAC/Network format)" ;;
|
|
209) echo "Proxmox: Container creation failed" ;;
|
|
210) echo "Proxmox: Cluster not quorate" ;;
|
|
211) echo "Proxmox: Timeout waiting for template lock" ;;
|
|
212) echo "Proxmox: Storage type 'iscsidirect' does not support containers (VMs only)" ;;
|
|
213) echo "Proxmox: Storage type does not support 'rootdir' content" ;;
|
|
214) echo "Proxmox: Not enough storage space" ;;
|
|
215) echo "Proxmox: Container created but not listed (ghost state)" ;;
|
|
216) echo "Proxmox: RootFS entry missing in config" ;;
|
|
217) echo "Proxmox: Storage not accessible" ;;
|
|
218) echo "Proxmox: Template file corrupted or incomplete" ;;
|
|
219) echo "Proxmox: CephFS does not support containers - use RBD" ;;
|
|
220) echo "Proxmox: Unable to resolve template path" ;;
|
|
221) echo "Proxmox: Template file not readable" ;;
|
|
222) echo "Proxmox: Template download failed" ;;
|
|
223) echo "Proxmox: Template not available after download" ;;
|
|
224) echo "Proxmox: PBS storage is for backups only" ;;
|
|
225) echo "Proxmox: No template available for OS/Version" ;;
|
|
226) echo "Proxmox: VM disk import or post-creation setup failed" ;;
|
|
231) echo "Proxmox: LXC stack upgrade failed" ;;
|
|
|
|
# --- Tools & Addon Scripts (232-238) ---
|
|
232) echo "Tools: Wrong execution environment (run on PVE host, not inside LXC)" ;;
|
|
233) echo "Tools: Application not installed (update prerequisite missing)" ;;
|
|
234) echo "Tools: No LXC containers found or available" ;;
|
|
235) echo "Tools: Backup or restore operation failed" ;;
|
|
236) echo "Tools: Required hardware not detected" ;;
|
|
237) echo "Tools: Dependency package installation failed" ;;
|
|
238) echo "Tools: OS or distribution not supported for this addon" ;;
|
|
|
|
# --- Node.js / npm / pnpm / yarn (239-249) ---
|
|
239) echo "npm/Node.js: Unexpected runtime error or dependency failure" ;;
|
|
243) echo "Node.js: Out of memory (JavaScript heap out of memory)" ;;
|
|
245) echo "Node.js: Invalid command-line option" ;;
|
|
246) echo "Node.js: Internal JavaScript Parse Error" ;;
|
|
247) echo "Node.js: Fatal internal error" ;;
|
|
248) echo "Node.js: Invalid C++ addon / N-API failure" ;;
|
|
249) echo "npm/pnpm/yarn: Unknown fatal error" ;;
|
|
|
|
# --- DPKG ---
|
|
255) echo "DPKG: Fatal internal error" ;;
|
|
|
|
# --- Default ---
|
|
*) echo "Unknown error" ;;
|
|
esac
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# json_escape()
|
|
#
|
|
# - Escapes a string for safe JSON embedding
|
|
# - Strips ANSI escape sequences and non-printable control characters
|
|
# - Handles backslashes, quotes, newlines, tabs, and carriage returns
|
|
# ------------------------------------------------------------------------------
|
|
json_escape() {
|
|
local s="$1"
|
|
# Strip ANSI escape sequences (color codes etc.)
|
|
s=$(printf '%s' "$s" | sed 's/\x1b\[[0-9;]*[a-zA-Z]//g')
|
|
s=${s//\\/\\\\}
|
|
s=${s//"/\\"/}
|
|
s=${s//$'\n'/\\n}
|
|
s=${s//$'\r'/}
|
|
s=${s//$'\t'/\\t}
|
|
# Remove any remaining control characters (0x00-0x1F except those already handled)
|
|
# Also remove DEL (0x7F) and invalid high bytes that break JSON parsers
|
|
s=$(printf '%s' "$s" | tr -d '\000-\010\013\014\016-\037\177')
|
|
printf '%s' "$s"
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# get_error_text()
|
|
#
|
|
# - Returns last 20 lines of the active log (INSTALL_LOG or BUILD_LOG)
|
|
# - Falls back to combined log or BUILD_LOG if primary is not accessible
|
|
# - Handles container paths that don't exist on the host
|
|
# ------------------------------------------------------------------------------
|
|
get_error_text() {
|
|
local logfile=""
|
|
if declare -f get_active_logfile >/dev/null 2>&1; then
|
|
logfile=$(get_active_logfile)
|
|
elif [[ -n "${INSTALL_LOG:-}" ]]; then
|
|
logfile="$INSTALL_LOG"
|
|
elif [[ -n "${BUILD_LOG:-}" ]]; then
|
|
logfile="$BUILD_LOG"
|
|
fi
|
|
|
|
# If logfile is inside container (e.g. /root/.install-*), try the host copy
|
|
if [[ -n "$logfile" && ! -s "$logfile" ]]; then
|
|
# Try combined log: /tmp/<app>-<CTID>-<SESSION_ID>.log
|
|
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
|
|
fi
|
|
|
|
# Also try BUILD_LOG as fallback if primary log is empty/missing
|
|
if [[ -z "$logfile" || ! -s "$logfile" ]] && [[ -n "${BUILD_LOG:-}" && -s "${BUILD_LOG}" ]]; then
|
|
logfile="$BUILD_LOG"
|
|
fi
|
|
|
|
if [[ -n "$logfile" && -s "$logfile" ]]; then
|
|
tail -n 20 "$logfile" 2>/dev/null | sed 's/\r$//' | sed 's/\x1b\[[0-9;]*[a-zA-Z]//g'
|
|
fi
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# 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()
|
|
#
|
|
# - Builds a structured error string for telemetry reporting
|
|
# - Format: "exit_code=<N> | <explanation>\n---\n<last 20 log lines>"
|
|
# - If no log lines available, returns just the explanation
|
|
# - Arguments:
|
|
# * $1: exit_code (numeric)
|
|
# * $2: log_text (optional, output from get_error_text)
|
|
# - Returns structured error string via stdout
|
|
# ------------------------------------------------------------------------------
|
|
build_error_string() {
|
|
local exit_code="${1:-1}"
|
|
local log_text="${2:-}"
|
|
local explanation
|
|
explanation=$(explain_exit_code "$exit_code")
|
|
|
|
if [[ -n "$log_text" ]]; then
|
|
# Structured format: header + separator + log lines
|
|
printf 'exit_code=%s | %s\n---\n%s' "$exit_code" "$explanation" "$log_text"
|
|
else
|
|
# No log available - just the explanation with exit code
|
|
printf 'exit_code=%s | %s' "$exit_code" "$explanation"
|
|
fi
|
|
}
|
|
|
|
# ==============================================================================
|
|
# SECTION 2: TELEMETRY FUNCTIONS
|
|
# ==============================================================================
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# detect_gpu()
|
|
#
|
|
# - Detects GPU vendor, model, and passthrough type
|
|
# - Sets GPU_VENDOR, GPU_MODEL, and GPU_PASSTHROUGH globals
|
|
# - Used for GPU analytics
|
|
# ------------------------------------------------------------------------------
|
|
detect_gpu() {
|
|
GPU_VENDOR="unknown"
|
|
GPU_MODEL=""
|
|
GPU_PASSTHROUGH="unknown"
|
|
|
|
local gpu_line
|
|
gpu_line=$(lspci 2>/dev/null | grep -iE "VGA|3D|Display" | head -1)
|
|
|
|
if [[ -n "$gpu_line" ]]; then
|
|
# Extract model: everything after the colon, clean up
|
|
GPU_MODEL=$(echo "$gpu_line" | sed 's/.*: //' | sed 's/ (rev .*)$//' | cut -c1-64)
|
|
|
|
# Detect vendor and passthrough type
|
|
if echo "$gpu_line" | grep -qi "Intel"; then
|
|
GPU_VENDOR="intel"
|
|
GPU_PASSTHROUGH="igpu"
|
|
elif echo "$gpu_line" | grep -qi "AMD\|ATI"; then
|
|
GPU_VENDOR="amd"
|
|
if echo "$gpu_line" | grep -qi "Radeon RX\|Radeon Pro"; then
|
|
GPU_PASSTHROUGH="dgpu"
|
|
else
|
|
GPU_PASSTHROUGH="igpu"
|
|
fi
|
|
elif echo "$gpu_line" | grep -qi "NVIDIA"; then
|
|
GPU_VENDOR="nvidia"
|
|
GPU_PASSTHROUGH="dgpu"
|
|
fi
|
|
fi
|
|
|
|
export GPU_VENDOR GPU_MODEL GPU_PASSTHROUGH
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# detect_cpu()
|
|
#
|
|
# - Detects CPU vendor and model
|
|
# - Sets CPU_VENDOR (intel/amd/arm/unknown) and CPU_MODEL globals
|
|
# - Used for CPU analytics
|
|
# ------------------------------------------------------------------------------
|
|
detect_cpu() {
|
|
CPU_VENDOR="unknown"
|
|
CPU_MODEL=""
|
|
|
|
if [[ -f /proc/cpuinfo ]]; then
|
|
local vendor_id
|
|
vendor_id=$(grep -m1 "vendor_id" /proc/cpuinfo 2>/dev/null | cut -d: -f2 | tr -d ' ')
|
|
|
|
case "$vendor_id" in
|
|
GenuineIntel) CPU_VENDOR="intel" ;;
|
|
AuthenticAMD) CPU_VENDOR="amd" ;;
|
|
*)
|
|
# ARM doesn't have vendor_id, check for CPU implementer
|
|
if grep -qi "CPU implementer" /proc/cpuinfo 2>/dev/null; then
|
|
CPU_VENDOR="arm"
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
# Extract model name and clean it up
|
|
CPU_MODEL=$(grep -m1 "model name" /proc/cpuinfo 2>/dev/null | cut -d: -f2 | sed 's/^ *//' | sed 's/(R)//g' | sed 's/(TM)//g' | sed 's/ */ /g' | cut -c1-64)
|
|
fi
|
|
|
|
export CPU_VENDOR CPU_MODEL
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# detect_ram()
|
|
#
|
|
# - Detects RAM speed using dmidecode
|
|
# - Sets RAM_SPEED global (e.g., "4800" for DDR5-4800)
|
|
# - Requires root access for dmidecode
|
|
# - Returns empty if not available or if speed is "Unknown" (nested VMs)
|
|
# ------------------------------------------------------------------------------
|
|
detect_ram() {
|
|
RAM_SPEED=""
|
|
|
|
if command -v dmidecode &>/dev/null; then
|
|
# Get configured memory speed (actual running speed)
|
|
# Use || true to handle "Unknown" values in nested VMs (no numeric match)
|
|
RAM_SPEED=$(dmidecode -t memory 2>/dev/null | grep -m1 "Configured Memory Speed:" | grep -oE "[0-9]+" | head -1) || true
|
|
|
|
# Fallback to Speed: if Configured not available
|
|
if [[ -z "$RAM_SPEED" ]]; then
|
|
RAM_SPEED=$(dmidecode -t memory 2>/dev/null | grep -m1 "Speed:" | grep -oE "[0-9]+" | head -1) || true
|
|
fi
|
|
fi
|
|
|
|
export RAM_SPEED
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# post_to_api()
|
|
#
|
|
# - Sends LXC container creation statistics to telemetry ingest service
|
|
# - Only executes if:
|
|
# * curl is available
|
|
# * DIAGNOSTICS=yes
|
|
# * RANDOM_UUID is set
|
|
# - Payload includes:
|
|
# * Container type, disk size, CPU cores, RAM
|
|
# * OS type and version
|
|
# * Application name (NSAPP)
|
|
# * Installation method
|
|
# * PVE version
|
|
# * Status: "installing"
|
|
# * Random UUID for session tracking
|
|
# - Anonymous telemetry (no personal data)
|
|
# - Never blocks or fails script execution
|
|
# ------------------------------------------------------------------------------
|
|
post_to_api() {
|
|
# Prevent duplicate submissions (post_to_api is called from multiple places)
|
|
[[ "${POST_TO_API_DONE:-}" == "true" ]] && return 0
|
|
|
|
# Silent fail - telemetry should never break scripts
|
|
command -v curl &>/dev/null || {
|
|
[[ "${DEV_MODE:-}" == "true" ]] && echo "[DEBUG] curl not found, skipping" >&2
|
|
return 0
|
|
}
|
|
[[ "${DIAGNOSTICS:-no}" == "no" ]] && {
|
|
[[ "${DEV_MODE:-}" == "true" ]] && echo "[DEBUG] DIAGNOSTICS=no, skipping" >&2
|
|
return 0
|
|
}
|
|
[[ -z "${RANDOM_UUID:-}" ]] && {
|
|
[[ "${DEV_MODE:-}" == "true" ]] && echo "[DEBUG] RANDOM_UUID empty, skipping" >&2
|
|
return 0
|
|
}
|
|
|
|
[[ "${DEV_MODE:-}" == "true" ]] && echo "[DEBUG] post_to_api() DIAGNOSTICS=$DIAGNOSTICS RANDOM_UUID=$RANDOM_UUID NSAPP=$NSAPP" >&2
|
|
|
|
# Set type for later status updates
|
|
TELEMETRY_TYPE="lxc"
|
|
|
|
local pve_version=""
|
|
if command -v pveversion &>/dev/null; then
|
|
pve_version=$(pveversion 2>/dev/null | awk -F'[/ ]' '{print $2}') || true
|
|
fi
|
|
|
|
# Detect GPU if not already set
|
|
if [[ -z "${GPU_VENDOR:-}" ]]; then
|
|
detect_gpu
|
|
fi
|
|
local gpu_vendor="${GPU_VENDOR:-unknown}"
|
|
local gpu_model
|
|
gpu_model=$(json_escape "${GPU_MODEL:-}")
|
|
local gpu_passthrough="${GPU_PASSTHROUGH:-unknown}"
|
|
|
|
# Detect CPU if not already set
|
|
if [[ -z "${CPU_VENDOR:-}" ]]; then
|
|
detect_cpu
|
|
fi
|
|
local cpu_vendor="${CPU_VENDOR:-unknown}"
|
|
local cpu_model
|
|
cpu_model=$(json_escape "${CPU_MODEL:-}")
|
|
|
|
# Detect RAM if not already set
|
|
if [[ -z "${RAM_SPEED:-}" ]]; then
|
|
detect_ram
|
|
fi
|
|
local ram_speed="${RAM_SPEED:-}"
|
|
|
|
local JSON_PAYLOAD
|
|
JSON_PAYLOAD=$(
|
|
cat <<EOF
|
|
{
|
|
"random_id": "${RANDOM_UUID}",
|
|
"execution_id": "${EXECUTION_ID:-${RANDOM_UUID}}",
|
|
"type": "lxc",
|
|
"nsapp": "${NSAPP:-unknown}",
|
|
"status": "installing",
|
|
"ct_type": ${CT_TYPE:-1},
|
|
"disk_size": ${DISK_SIZE:-0},
|
|
"core_count": ${CORE_COUNT:-0},
|
|
"ram_size": ${RAM_SIZE:-0},
|
|
"os_type": "${var_os:-}",
|
|
"os_version": "${var_version:-}",
|
|
"pve_version": "${pve_version}",
|
|
"method": "${METHOD:-default}",
|
|
"cpu_vendor": "${cpu_vendor}",
|
|
"cpu_model": "${cpu_model}",
|
|
"gpu_vendor": "${gpu_vendor}",
|
|
"gpu_model": "${gpu_model}",
|
|
"gpu_passthrough": "${gpu_passthrough}",
|
|
"ram_speed": "${ram_speed}",
|
|
"repo_source": "${REPO_SOURCE}"
|
|
}
|
|
EOF
|
|
)
|
|
|
|
[[ "${DEV_MODE:-}" == "true" ]] && echo "[DEBUG] Sending to: $TELEMETRY_URL" >&2
|
|
[[ "${DEV_MODE:-}" == "true" ]] && echo "[DEBUG] Payload: $JSON_PAYLOAD" >&2
|
|
|
|
# Fire-and-forget: never block, never fail
|
|
local http_code
|
|
if [[ "${DEV_MODE:-}" == "true" ]]; then
|
|
http_code=$(curl -sS -w "%{http_code}" -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$JSON_PAYLOAD" -o /dev/stderr 2>&1) || true
|
|
echo "[DEBUG] HTTP response code: $http_code" >&2
|
|
else
|
|
curl -fsS -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$JSON_PAYLOAD" &>/dev/null || true
|
|
fi
|
|
|
|
POST_TO_API_DONE=true
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# post_to_api_vm()
|
|
#
|
|
# - Sends VM creation statistics to telemetry ingest service
|
|
# - Reads DIAGNOSTICS from /usr/local/community-scripts/diagnostics file
|
|
# - Payload differences from LXC:
|
|
# * ct_type=2 (VM instead of LXC)
|
|
# * type="vm"
|
|
# * Disk size without 'G' suffix
|
|
# - Includes hardware detection: CPU, GPU, RAM speed
|
|
# - Only executes if DIAGNOSTICS=yes and RANDOM_UUID is set
|
|
# - Never blocks or fails script execution
|
|
# ------------------------------------------------------------------------------
|
|
post_to_api_vm() {
|
|
# Read diagnostics setting from file
|
|
if [[ -f /usr/local/community-scripts/diagnostics ]]; then
|
|
DIAGNOSTICS=$(grep -i "^DIAGNOSTICS=" /usr/local/community-scripts/diagnostics 2>/dev/null | awk -F'=' '{print $2}') || true
|
|
fi
|
|
|
|
# Silent fail - telemetry should never break scripts
|
|
command -v curl &>/dev/null || return 0
|
|
[[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0
|
|
[[ -z "${RANDOM_UUID:-}" ]] && return 0
|
|
|
|
# Set type for later status updates
|
|
TELEMETRY_TYPE="vm"
|
|
|
|
local pve_version=""
|
|
if command -v pveversion &>/dev/null; then
|
|
pve_version=$(pveversion 2>/dev/null | awk -F'[/ ]' '{print $2}') || true
|
|
fi
|
|
|
|
# Detect GPU if not already set
|
|
if [[ -z "${GPU_VENDOR:-}" ]]; then
|
|
detect_gpu
|
|
fi
|
|
local gpu_vendor="${GPU_VENDOR:-unknown}"
|
|
local gpu_model
|
|
gpu_model=$(json_escape "${GPU_MODEL:-}")
|
|
local gpu_passthrough="${GPU_PASSTHROUGH:-unknown}"
|
|
|
|
# Detect CPU if not already set
|
|
if [[ -z "${CPU_VENDOR:-}" ]]; then
|
|
detect_cpu
|
|
fi
|
|
local cpu_vendor="${CPU_VENDOR:-unknown}"
|
|
local cpu_model
|
|
cpu_model=$(json_escape "${CPU_MODEL:-}")
|
|
|
|
# Detect RAM if not already set
|
|
if [[ -z "${RAM_SPEED:-}" ]]; then
|
|
detect_ram
|
|
fi
|
|
local ram_speed="${RAM_SPEED:-}"
|
|
|
|
# Remove 'G' suffix from disk size
|
|
local DISK_SIZE_API="${DISK_SIZE%G}"
|
|
|
|
local JSON_PAYLOAD
|
|
JSON_PAYLOAD=$(
|
|
cat <<EOF
|
|
{
|
|
"random_id": "${RANDOM_UUID}",
|
|
"execution_id": "${EXECUTION_ID:-${RANDOM_UUID}}",
|
|
"type": "vm",
|
|
"nsapp": "${NSAPP:-unknown}",
|
|
"status": "installing",
|
|
"ct_type": 2,
|
|
"disk_size": ${DISK_SIZE_API:-0},
|
|
"core_count": ${CORE_COUNT:-0},
|
|
"ram_size": ${RAM_SIZE:-0},
|
|
"os_type": "${var_os:-}",
|
|
"os_version": "${var_version:-}",
|
|
"pve_version": "${pve_version}",
|
|
"method": "${METHOD:-default}",
|
|
"cpu_vendor": "${cpu_vendor}",
|
|
"cpu_model": "${cpu_model}",
|
|
"gpu_vendor": "${gpu_vendor}",
|
|
"gpu_model": "${gpu_model}",
|
|
"gpu_passthrough": "${gpu_passthrough}",
|
|
"ram_speed": "${ram_speed}",
|
|
"repo_source": "${REPO_SOURCE}"
|
|
}
|
|
EOF
|
|
)
|
|
|
|
# Fire-and-forget: never block, never fail
|
|
curl -fsS -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$JSON_PAYLOAD" &>/dev/null || true
|
|
|
|
POST_TO_API_DONE=true
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# post_progress_to_api()
|
|
#
|
|
# - Lightweight progress ping from host or container
|
|
# - Updates the existing telemetry record status
|
|
# - Arguments:
|
|
# * $1: status (optional, default: "configuring")
|
|
# Valid values: "validation", "configuring"
|
|
# - Signals that the installation is actively progressing (not stuck)
|
|
# - Fire-and-forget: never blocks or fails the script
|
|
# - Only executes if DIAGNOSTICS=yes and RANDOM_UUID is set
|
|
# - Can be called multiple times safely
|
|
# ------------------------------------------------------------------------------
|
|
post_progress_to_api() {
|
|
command -v curl &>/dev/null || return 0
|
|
[[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0
|
|
[[ -z "${RANDOM_UUID:-}" ]] && return 0
|
|
|
|
local progress_status="${1:-configuring}"
|
|
local app_name="${NSAPP:-${app:-unknown}}"
|
|
local telemetry_type="${TELEMETRY_TYPE:-lxc}"
|
|
|
|
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}\",\"nsapp\":\"${app_name}\",\"status\":\"${progress_status}\"}" &>/dev/null || true
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# post_update_to_api()
|
|
#
|
|
# - Reports installation completion status to telemetry ingest service
|
|
# - Prevents duplicate submissions via POST_UPDATE_DONE flag
|
|
# - Arguments:
|
|
# * $1: status ("done" or "failed")
|
|
# * $2: exit_code (numeric, default: 1 for failed, 0 for done)
|
|
# - Payload includes:
|
|
# * Final status (mapped: "done"→"success", "failed"→"failed")
|
|
# * Error description via explain_exit_code()
|
|
# * Numeric exit code
|
|
# - Only executes once per session
|
|
# - Never blocks or fails script execution
|
|
# ------------------------------------------------------------------------------
|
|
post_update_to_api() {
|
|
# Silent fail - telemetry should never break scripts
|
|
command -v curl &>/dev/null || return 0
|
|
|
|
# Support "force" mode (3rd arg) to bypass duplicate check for retries after cleanup
|
|
local force="${3:-}"
|
|
POST_UPDATE_DONE=${POST_UPDATE_DONE:-false}
|
|
if [[ "$POST_UPDATE_DONE" == "true" && "$force" != "force" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
[[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0
|
|
[[ -z "${RANDOM_UUID:-}" ]] && return 0
|
|
|
|
local status="${1:-failed}"
|
|
local raw_exit_code="${2:-1}"
|
|
local exit_code=0 error="" pb_status error_category=""
|
|
|
|
# Get GPU info (if detected)
|
|
local gpu_vendor="${GPU_VENDOR:-unknown}"
|
|
local gpu_model
|
|
gpu_model=$(json_escape "${GPU_MODEL:-}")
|
|
local gpu_passthrough="${GPU_PASSTHROUGH:-unknown}"
|
|
|
|
# Get CPU info (if detected)
|
|
local cpu_vendor="${CPU_VENDOR:-unknown}"
|
|
local cpu_model
|
|
cpu_model=$(json_escape "${CPU_MODEL:-}")
|
|
|
|
# Get RAM info (if detected)
|
|
local ram_speed="${RAM_SPEED:-}"
|
|
|
|
# Map status to telemetry values: installing, success, failed, unknown
|
|
case "$status" in
|
|
done | success)
|
|
pb_status="success"
|
|
exit_code=0
|
|
error=""
|
|
error_category=""
|
|
;;
|
|
failed)
|
|
pb_status="failed"
|
|
;;
|
|
*)
|
|
pb_status="unknown"
|
|
;;
|
|
esac
|
|
|
|
# For failed/unknown status, resolve exit code and error description
|
|
local short_error=""
|
|
if [[ "$pb_status" == "failed" ]] || [[ "$pb_status" == "unknown" ]]; then
|
|
if [[ "$raw_exit_code" =~ ^[0-9]+$ ]]; then
|
|
exit_code="$raw_exit_code"
|
|
else
|
|
exit_code=1
|
|
fi
|
|
# Get full installation log for error field
|
|
local log_text=""
|
|
log_text=$(get_full_log 122880) || true # 120KB max
|
|
if [[ -z "$log_text" ]]; then
|
|
# Fallback to last 20 lines
|
|
log_text=$(get_error_text)
|
|
fi
|
|
local full_error
|
|
full_error=$(build_error_string "$exit_code" "$log_text")
|
|
error=$(json_escape "$full_error")
|
|
short_error=$(json_escape "$(explain_exit_code "$exit_code")")
|
|
error_category=$(categorize_error "$exit_code")
|
|
[[ -z "$error" ]] && error="Unknown error"
|
|
fi
|
|
|
|
# Calculate duration if timer was started
|
|
local duration=0
|
|
if [[ -n "${INSTALL_START_TIME:-}" ]]; then
|
|
duration=$(($(date +%s) - INSTALL_START_TIME))
|
|
fi
|
|
|
|
# Get PVE version
|
|
local pve_version=""
|
|
if command -v pveversion &>/dev/null; then
|
|
pve_version=$(pveversion 2>/dev/null | awk -F'[/ ]' '{print $2}') || true
|
|
fi
|
|
|
|
local http_code=""
|
|
|
|
# ── Attempt 1: Full payload with complete error text (includes full log) ──
|
|
local JSON_PAYLOAD
|
|
JSON_PAYLOAD=$(
|
|
cat <<EOF
|
|
{
|
|
"random_id": "${RANDOM_UUID}",
|
|
"execution_id": "${EXECUTION_ID:-${RANDOM_UUID}}",
|
|
"type": "${TELEMETRY_TYPE:-lxc}",
|
|
"nsapp": "${NSAPP:-unknown}",
|
|
"status": "${pb_status}",
|
|
"ct_type": ${CT_TYPE:-1},
|
|
"disk_size": ${DISK_SIZE:-0},
|
|
"core_count": ${CORE_COUNT:-0},
|
|
"ram_size": ${RAM_SIZE:-0},
|
|
"os_type": "${var_os:-}",
|
|
"os_version": "${var_version:-}",
|
|
"pve_version": "${pve_version}",
|
|
"method": "${METHOD:-default}",
|
|
"exit_code": ${exit_code},
|
|
"error": "${error}",
|
|
"error_category": "${error_category}",
|
|
"install_duration": ${duration},
|
|
"cpu_vendor": "${cpu_vendor}",
|
|
"cpu_model": "${cpu_model}",
|
|
"gpu_vendor": "${gpu_vendor}",
|
|
"gpu_model": "${gpu_model}",
|
|
"gpu_passthrough": "${gpu_passthrough}",
|
|
"ram_speed": "${ram_speed}",
|
|
"repo_source": "${REPO_SOURCE}"
|
|
}
|
|
EOF
|
|
)
|
|
|
|
http_code=$(curl -sS -w "%{http_code}" -m "${STATUS_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$JSON_PAYLOAD" -o /dev/null 2>/dev/null) || http_code="000"
|
|
|
|
if [[ "$http_code" =~ ^2[0-9]{2}$ ]]; then
|
|
POST_UPDATE_DONE=true
|
|
return 0
|
|
fi
|
|
|
|
# ── Attempt 2: Short error text (no full log) ──
|
|
sleep 1
|
|
local RETRY_PAYLOAD
|
|
RETRY_PAYLOAD=$(
|
|
cat <<EOF
|
|
{
|
|
"random_id": "${RANDOM_UUID}",
|
|
"execution_id": "${EXECUTION_ID:-${RANDOM_UUID}}",
|
|
"type": "${TELEMETRY_TYPE:-lxc}",
|
|
"nsapp": "${NSAPP:-unknown}",
|
|
"status": "${pb_status}",
|
|
"ct_type": ${CT_TYPE:-1},
|
|
"disk_size": ${DISK_SIZE:-0},
|
|
"core_count": ${CORE_COUNT:-0},
|
|
"ram_size": ${RAM_SIZE:-0},
|
|
"os_type": "${var_os:-}",
|
|
"os_version": "${var_version:-}",
|
|
"pve_version": "${pve_version}",
|
|
"method": "${METHOD:-default}",
|
|
"exit_code": ${exit_code},
|
|
"error": "${short_error}",
|
|
"error_category": "${error_category}",
|
|
"install_duration": ${duration},
|
|
"cpu_vendor": "${cpu_vendor}",
|
|
"cpu_model": "${cpu_model}",
|
|
"gpu_vendor": "${gpu_vendor}",
|
|
"gpu_model": "${gpu_model}",
|
|
"gpu_passthrough": "${gpu_passthrough}",
|
|
"ram_speed": "${ram_speed}",
|
|
"repo_source": "${REPO_SOURCE}"
|
|
}
|
|
EOF
|
|
)
|
|
|
|
http_code=$(curl -sS -w "%{http_code}" -m "${STATUS_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$RETRY_PAYLOAD" -o /dev/null 2>/dev/null) || http_code="000"
|
|
|
|
if [[ "$http_code" =~ ^2[0-9]{2}$ ]]; then
|
|
POST_UPDATE_DONE=true
|
|
return 0
|
|
fi
|
|
|
|
# ── Attempt 3: Minimal payload (bare minimum to set status) ──
|
|
sleep 2
|
|
local MINIMAL_PAYLOAD
|
|
MINIMAL_PAYLOAD=$(
|
|
cat <<EOF
|
|
{
|
|
"random_id": "${RANDOM_UUID}",
|
|
"execution_id": "${EXECUTION_ID:-${RANDOM_UUID}}",
|
|
"type": "${TELEMETRY_TYPE:-lxc}",
|
|
"nsapp": "${NSAPP:-unknown}",
|
|
"status": "${pb_status}",
|
|
"exit_code": ${exit_code},
|
|
"error": "${short_error}",
|
|
"error_category": "${error_category}",
|
|
"install_duration": ${duration}
|
|
}
|
|
EOF
|
|
)
|
|
|
|
http_code=$(curl -sS -w "%{http_code}" -m "${STATUS_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$MINIMAL_PAYLOAD" -o /dev/null 2>/dev/null) || http_code="000"
|
|
|
|
if [[ "$http_code" =~ ^2[0-9]{2}$ ]]; then
|
|
POST_UPDATE_DONE=true
|
|
return 0
|
|
fi
|
|
|
|
# All 3 attempts failed — do NOT set POST_UPDATE_DONE=true.
|
|
# This allows the EXIT trap (on_exit in error_handler.func) to retry.
|
|
# No infinite loop risk: EXIT trap fires exactly once.
|
|
}
|
|
|
|
# ==============================================================================
|
|
# SECTION 3: EXTENDED TELEMETRY FUNCTIONS
|
|
# ==============================================================================
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# categorize_error()
|
|
#
|
|
# - Maps exit codes to error categories for better analytics
|
|
# - Categories: network, storage, dependency, permission, timeout, config, resource, unknown
|
|
# - Used to group errors in dashboard
|
|
# ------------------------------------------------------------------------------
|
|
categorize_error() {
|
|
local code="$1"
|
|
case "$code" in
|
|
# Network errors (curl/wget)
|
|
6 | 7 | 22 | 35) echo "network" ;;
|
|
|
|
# Docker / Privileged mode required
|
|
10) echo "config" ;;
|
|
|
|
# Timeout errors
|
|
28 | 124 | 211) echo "timeout" ;;
|
|
|
|
# Storage errors (Proxmox storage)
|
|
214 | 217 | 219 | 224) echo "storage" ;;
|
|
|
|
# Dependency/Package errors (APT, DPKG, pip, commands)
|
|
100 | 101 | 102 | 127 | 160 | 161 | 162 | 255) echo "dependency" ;;
|
|
|
|
# Permission errors
|
|
126 | 152) echo "permission" ;;
|
|
|
|
# Configuration errors (Proxmox config, invalid args)
|
|
128 | 203 | 204 | 205 | 206 | 207 | 208) echo "config" ;;
|
|
|
|
# Proxmox container/template errors
|
|
200 | 209 | 210 | 212 | 213 | 215 | 216 | 218 | 220 | 221 | 222 | 223 | 225 | 231) echo "proxmox" ;;
|
|
|
|
# Service/Systemd errors
|
|
150 | 151 | 153 | 154) echo "service" ;;
|
|
|
|
# Database errors (PostgreSQL, MySQL, MongoDB)
|
|
170 | 171 | 172 | 173 | 180 | 181 | 182 | 183 | 190 | 191 | 192 | 193) echo "database" ;;
|
|
|
|
# Node.js / JavaScript runtime errors
|
|
243 | 245 | 246 | 247 | 248 | 249) echo "runtime" ;;
|
|
|
|
# Python environment errors
|
|
# (already covered: 160-162 under dependency)
|
|
|
|
# Aborted by user (SIGHUP=terminal closed, SIGINT=Ctrl+C, SIGTERM=killed)
|
|
129 | 130 | 143) echo "user_aborted" ;;
|
|
|
|
# Resource errors (OOM, SIGKILL, SIGABRT)
|
|
134 | 137) echo "resource" ;;
|
|
|
|
# Signal/Process errors (SIGPIPE, SIGSEGV)
|
|
139 | 141) echo "signal" ;;
|
|
|
|
# Shell errors (general error, syntax error)
|
|
1 | 2) echo "shell" ;;
|
|
|
|
# Default - truly unknown
|
|
*) echo "unknown" ;;
|
|
esac
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# start_install_timer()
|
|
#
|
|
# - Captures start time for installation duration tracking
|
|
# - Call at the beginning of installation
|
|
# - Sets INSTALL_START_TIME global variable
|
|
# ------------------------------------------------------------------------------
|
|
start_install_timer() {
|
|
INSTALL_START_TIME=$(date +%s)
|
|
export INSTALL_START_TIME
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# get_install_duration()
|
|
#
|
|
# - Returns elapsed seconds since start_install_timer() was called
|
|
# - Returns 0 if timer was not started
|
|
# ------------------------------------------------------------------------------
|
|
get_install_duration() {
|
|
if [[ -z "${INSTALL_START_TIME:-}" ]]; then
|
|
echo "0"
|
|
return
|
|
fi
|
|
local now=$(date +%s)
|
|
echo $((now - INSTALL_START_TIME))
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# _telemetry_report_exit()
|
|
#
|
|
# - Internal handler called by EXIT trap set in init_tool_telemetry()
|
|
# - Determines success/failure from exit code and reports via appropriate API
|
|
# - Arguments:
|
|
# * $1: exit_code from the script
|
|
# ------------------------------------------------------------------------------
|
|
_telemetry_report_exit() {
|
|
local ec="${1:-0}"
|
|
local status="success"
|
|
[[ "$ec" -ne 0 ]] && status="failed"
|
|
|
|
# Lazy name resolution: use explicit name, fall back to $APP, then "unknown"
|
|
local name="${TELEMETRY_TOOL_NAME:-${APP:-unknown}}"
|
|
|
|
if [[ "${TELEMETRY_TOOL_TYPE:-pve}" == "addon" ]]; then
|
|
post_addon_to_api "$name" "$status" "$ec"
|
|
else
|
|
post_tool_to_api "$name" "$status" "$ec"
|
|
fi
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# init_tool_telemetry()
|
|
#
|
|
# - One-line telemetry setup for tools/addon scripts
|
|
# - Reads DIAGNOSTICS from /usr/local/community-scripts/diagnostics
|
|
# (persisted on PVE host during first build, and inside containers by install.func)
|
|
# - Starts install timer for duration tracking
|
|
# - Sets EXIT trap to automatically report success/failure on script exit
|
|
# - Arguments:
|
|
# * $1: tool_name (optional, falls back to $APP at exit time)
|
|
# * $2: type ("pve" for PVE host scripts, "addon" for container addons)
|
|
# - Usage:
|
|
# source <(curl -fsSL .../misc/api.func) 2>/dev/null || true
|
|
# init_tool_telemetry "post-pve-install" "pve"
|
|
# init_tool_telemetry "" "addon" # uses $APP at exit time
|
|
# ------------------------------------------------------------------------------
|
|
init_tool_telemetry() {
|
|
local name="${1:-}"
|
|
local type="${2:-pve}"
|
|
|
|
[[ -n "$name" ]] && TELEMETRY_TOOL_NAME="$name"
|
|
TELEMETRY_TOOL_TYPE="$type"
|
|
|
|
# Read diagnostics opt-in/opt-out
|
|
if [[ -f /usr/local/community-scripts/diagnostics ]]; then
|
|
DIAGNOSTICS=$(grep -i "^DIAGNOSTICS=" /usr/local/community-scripts/diagnostics 2>/dev/null | awk -F'=' '{print $2}') || true
|
|
fi
|
|
|
|
start_install_timer
|
|
|
|
# EXIT trap: automatically report telemetry when script ends
|
|
trap '_telemetry_report_exit "$?"' EXIT
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# post_tool_to_api()
|
|
#
|
|
# - Reports tool usage to telemetry
|
|
# - Arguments:
|
|
# * $1: tool_name (e.g., "microcode", "lxc-update", "post-pve-install")
|
|
# * $2: status ("success" or "failed")
|
|
# * $3: exit_code (optional, default: 0 for success, 1 for failed)
|
|
# - For PVE host tools, not container installations
|
|
# ------------------------------------------------------------------------------
|
|
post_tool_to_api() {
|
|
command -v curl &>/dev/null || return 0
|
|
[[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0
|
|
|
|
local tool_name="${1:-unknown}"
|
|
local status="${2:-success}"
|
|
local exit_code="${3:-0}"
|
|
local error="" error_category=""
|
|
local uuid duration
|
|
|
|
# Generate UUID for this tool execution
|
|
uuid=$(cat /proc/sys/kernel/random/uuid 2>/dev/null || uuidgen 2>/dev/null || echo "tool-$(date +%s)")
|
|
duration=$(get_install_duration)
|
|
|
|
# Map status
|
|
[[ "$status" == "done" ]] && status="success"
|
|
|
|
if [[ "$status" == "failed" ]]; then
|
|
[[ ! "$exit_code" =~ ^[0-9]+$ ]] && exit_code=1
|
|
local error_text=""
|
|
error_text=$(get_error_text)
|
|
local full_error
|
|
full_error=$(build_error_string "$exit_code" "$error_text")
|
|
error=$(json_escape "$full_error")
|
|
error_category=$(categorize_error "$exit_code")
|
|
fi
|
|
|
|
local pve_version=""
|
|
if command -v pveversion &>/dev/null; then
|
|
pve_version=$(pveversion 2>/dev/null | awk -F'[/ ]' '{print $2}') || true
|
|
fi
|
|
|
|
local JSON_PAYLOAD
|
|
JSON_PAYLOAD=$(
|
|
cat <<EOF
|
|
{
|
|
"random_id": "${uuid}",
|
|
"execution_id": "${EXECUTION_ID:-${uuid}}",
|
|
"type": "pve",
|
|
"nsapp": "${tool_name}",
|
|
"status": "${status}",
|
|
"exit_code": ${exit_code},
|
|
"error": "${error}",
|
|
"error_category": "${error_category}",
|
|
"install_duration": ${duration:-0},
|
|
"pve_version": "${pve_version}",
|
|
"repo_source": "${REPO_SOURCE}"
|
|
}
|
|
EOF
|
|
)
|
|
|
|
curl -fsS -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$JSON_PAYLOAD" &>/dev/null || true
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# post_addon_to_api()
|
|
#
|
|
# - Reports addon installation to telemetry
|
|
# - Arguments:
|
|
# * $1: addon_name (e.g., "filebrowser", "netdata")
|
|
# * $2: status ("success" or "failed")
|
|
# * $3: exit_code (optional)
|
|
# - For addons installed inside containers
|
|
# ------------------------------------------------------------------------------
|
|
post_addon_to_api() {
|
|
command -v curl &>/dev/null || return 0
|
|
[[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0
|
|
|
|
local addon_name="${1:-unknown}"
|
|
local status="${2:-success}"
|
|
local exit_code="${3:-0}"
|
|
local error="" error_category=""
|
|
local uuid duration
|
|
|
|
# Generate UUID for this addon installation
|
|
uuid=$(cat /proc/sys/kernel/random/uuid 2>/dev/null || uuidgen 2>/dev/null || echo "addon-$(date +%s)")
|
|
duration=$(get_install_duration)
|
|
|
|
# Map status
|
|
[[ "$status" == "done" ]] && status="success"
|
|
|
|
if [[ "$status" == "failed" ]]; then
|
|
[[ ! "$exit_code" =~ ^[0-9]+$ ]] && exit_code=1
|
|
local error_text=""
|
|
error_text=$(get_error_text)
|
|
local full_error
|
|
full_error=$(build_error_string "$exit_code" "$error_text")
|
|
error=$(json_escape "$full_error")
|
|
error_category=$(categorize_error "$exit_code")
|
|
fi
|
|
|
|
# Detect OS info
|
|
local os_type="" os_version=""
|
|
if [[ -f /etc/os-release ]]; then
|
|
os_type=$(grep "^ID=" /etc/os-release | cut -d= -f2 | tr -d '"')
|
|
os_version=$(grep "^VERSION_ID=" /etc/os-release | cut -d= -f2 | tr -d '"')
|
|
fi
|
|
|
|
local JSON_PAYLOAD
|
|
JSON_PAYLOAD=$(
|
|
cat <<EOF
|
|
{
|
|
"random_id": "${uuid}",
|
|
"execution_id": "${EXECUTION_ID:-${uuid}}",
|
|
"type": "addon",
|
|
"nsapp": "${addon_name}",
|
|
"status": "${status}",
|
|
"exit_code": ${exit_code},
|
|
"error": "${error}",
|
|
"error_category": "${error_category}",
|
|
"install_duration": ${duration:-0},
|
|
"os_type": "${os_type}",
|
|
"os_version": "${os_version}",
|
|
"repo_source": "${REPO_SOURCE}"
|
|
}
|
|
EOF
|
|
)
|
|
|
|
curl -fsS -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$JSON_PAYLOAD" &>/dev/null || true
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# post_update_to_api_extended()
|
|
#
|
|
# - Extended version of post_update_to_api with duration, GPU, and error category
|
|
# - Same arguments as post_update_to_api:
|
|
# * $1: status ("done" or "failed")
|
|
# * $2: exit_code (numeric)
|
|
# - Automatically includes:
|
|
# * Install duration (if start_install_timer was called)
|
|
# * Error category (for failed status)
|
|
# * GPU info (if detect_gpu was called)
|
|
# ------------------------------------------------------------------------------
|
|
post_update_to_api_extended() {
|
|
# Silent fail - telemetry should never break scripts
|
|
command -v curl &>/dev/null || return 0
|
|
|
|
# Prevent duplicate submissions
|
|
POST_UPDATE_DONE=${POST_UPDATE_DONE:-false}
|
|
[[ "$POST_UPDATE_DONE" == "true" ]] && return 0
|
|
|
|
[[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0
|
|
[[ -z "${RANDOM_UUID:-}" ]] && return 0
|
|
|
|
local status="${1:-failed}"
|
|
local raw_exit_code="${2:-1}"
|
|
local exit_code=0 error="" pb_status error_category=""
|
|
local duration gpu_vendor gpu_passthrough
|
|
|
|
# Get duration
|
|
duration=$(get_install_duration)
|
|
|
|
# Get GPU info (if detected)
|
|
gpu_vendor="${GPU_VENDOR:-}"
|
|
gpu_passthrough="${GPU_PASSTHROUGH:-}"
|
|
|
|
# Map status to telemetry values
|
|
case "$status" in
|
|
done | success)
|
|
pb_status="success"
|
|
exit_code=0
|
|
error=""
|
|
error_category=""
|
|
;;
|
|
failed)
|
|
pb_status="failed"
|
|
;;
|
|
*)
|
|
pb_status="unknown"
|
|
;;
|
|
esac
|
|
|
|
# For failed/unknown status, resolve exit code and error description
|
|
if [[ "$pb_status" == "failed" ]] || [[ "$pb_status" == "unknown" ]]; then
|
|
if [[ "$raw_exit_code" =~ ^[0-9]+$ ]]; then
|
|
exit_code="$raw_exit_code"
|
|
else
|
|
exit_code=1
|
|
fi
|
|
local error_text=""
|
|
error_text=$(get_error_text)
|
|
local full_error
|
|
full_error=$(build_error_string "$exit_code" "$error_text")
|
|
error=$(json_escape "$full_error")
|
|
error_category=$(categorize_error "$exit_code")
|
|
[[ -z "$error" ]] && error="Unknown error"
|
|
fi
|
|
|
|
local JSON_PAYLOAD
|
|
JSON_PAYLOAD=$(
|
|
cat <<EOF
|
|
{
|
|
"random_id": "${RANDOM_UUID}",
|
|
"execution_id": "${EXECUTION_ID:-${RANDOM_UUID}}",
|
|
"type": "${TELEMETRY_TYPE:-lxc}",
|
|
"nsapp": "${NSAPP:-unknown}",
|
|
"status": "${pb_status}",
|
|
"exit_code": ${exit_code},
|
|
"error": "${error}",
|
|
"error_category": "${error_category}",
|
|
"install_duration": ${duration:-0},
|
|
"gpu_vendor": "${gpu_vendor}",
|
|
"gpu_passthrough": "${gpu_passthrough}",
|
|
"repo_source": "${REPO_SOURCE}"
|
|
}
|
|
EOF
|
|
)
|
|
|
|
local http_code
|
|
http_code=$(curl -sS -w "%{http_code}" -m "${STATUS_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$JSON_PAYLOAD" -o /dev/null 2>/dev/null) || http_code="000"
|
|
|
|
if [[ "$http_code" =~ ^2[0-9]{2}$ ]]; then
|
|
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
|
|
}
|