21 Commits

Author SHA1 Message Date
Sam Heinz
61446fb0fa arm64: move tools.func changes from VE to VED 2026-03-07 20:00:37 +10:00
Sam Heinz
e2425b5eb6 arm64: add navidrome script 2026-03-07 19:49:56 +10:00
Sam Heinz
c14b73d346 rdtclient: change dotnet runtime to 10.0 2026-03-07 15:27:21 +10:00
Sam Heinz
20ae91a95c arm64-rdtclient: update dotnet to 10.0 2026-03-07 15:18:51 +10:00
Sam Heinz
53d84b11fe arm64-rdtclient: remove libicu72 dependency 2026-03-07 15:04:16 +10:00
Sam Heinz
26054d7c1b arm64: ensure both install scripts are in same bash process 2026-03-07 14:57:26 +10:00
Sam Heinz
e515b844c3 arm64: add support for update_script_arm64 2026-03-07 14:09:39 +10:00
Sam Heinz
fa78e82800 arm64: remove temp arm scripts and add rdtclient script 2026-03-07 13:54:29 +10:00
Sam Heinz
cb413b48a6 arm64: grab arm64 lxc install script if on arm64 2026-03-07 13:50:07 +10:00
Sam Heinz
70ef0b792c arm: add support for arm hosts in misc/ scripts 2026-03-07 13:34:20 +10:00
vhsdream
77e85aa26d remove oxicloud user 2026-03-06 17:59:19 -05:00
vhsdream
bfefe51761 Try to fix permission issues 2026-03-06 17:51:35 -05:00
vhsdream
c0b7c6c2ff sediting 2026-03-06 14:18:07 -05:00
vhsdream
db6c5d2b4f more sed changes 2026-03-06 14:05:45 -05:00
vhsdream
05426a7735 fix sed redux 2026-03-06 12:52:41 -05:00
vhsdream
74da15f98f fix sed 2026-03-06 11:47:23 -05:00
vhsdream
f5a9954484 delete env file 2026-03-06 11:13:17 -05:00
vhsdream
7399402e30 Add OxiCloud 2026-03-06 10:57:09 -05:00
CanbiZ (MickLesk)
2f5560c975 move jsons 2026-03-06 11:37:20 +01:00
Sam Heinz
34f0284c09 arm64 scripts test
Adguard, Bazarr, Homeassistant, jellyfin, pihole, rdtclient, vaultwarden are working
kima-hub is unported
bentopdf is untested
lubelogger is broken
2026-03-06 20:35:01 +10:00
Michel Roegl-Brunner
dfe47a1ece Create arm-test.sh 2026-03-06 11:13:23 +01:00
31 changed files with 1982 additions and 2938 deletions

1
.gitignore vendored
View File

@@ -13,3 +13,4 @@ vm/debian-13-vm.sh
vm/vm-manager.sh
vm/vm-manager.sh
vm/debian-13-vm.sh
.DS_Store

53
ct/navidrome.sh Normal file
View File

@@ -0,0 +1,53 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/arm64-dev-build/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/navidrome/navidrome
APP="Navidrome"
var_tags="${var_tags:-music}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-1024}"
var_disk="${var_disk:-4}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /var/lib/navidrome ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "navidrome" "navidrome/navidrome"; then
msg_info "Stopping Services"
systemctl stop navidrome
msg_ok "Services Stopped"
fetch_and_deploy_gh_release "navidrome" "navidrome/navidrome" "binary"
msg_info "Starting Services"
systemctl start navidrome
msg_ok "Started Services"
msg_ok "Updated successfully!"
fi
exit
}
start
build_container
description
msg_ok "Completed successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:4533${CL}"

64
ct/oxicloud.sh Normal file
View File

@@ -0,0 +1,64 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: vhsdream
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
# Source: https://github.com/DioCrafts/OxiCloud
APP="OxiCloud"
var_tags="${var_tags:-files;documents}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-3072}"
var_disk="${var_disk:-20}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /opt/oxicloud ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "OxiCloud" "DioCrafts/OxiCloud"; then
msg_info "Stopping OxiCloud"
$STD systemctl stop oxicloud
msg_ok "Stopped OxiCloud"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "OxiCloud" "DioCrafts/OxiCloud" "tarball" "latest" "/opt/oxicloud"
TOOLCHAIN="$(sed -n '2s/[^:]*://p' /opt/oxicloud/Dockerfile | awk -F- '{print $1}')"
RUST_TOOLCHAIN=$TOOLCHAIN setup_rust
msg_info "Updating OxiCloud"
cd /opt/oxicloud
export DATABASE_URL="postgres://${PG_DB_USER}:${PG_DB_PASS}@localhost/${PG_DB_NAME}"
$STD cargo build --release
mv target/release/oxicloud /usr/bin/oxicloud && chmod +x /usr/bin/oxicloud
msg_ok "Updated OxiCloud"
msg_info "Starting OxiCloud"
$STD systemctl start oxicloud
msg_ok "Started OxiCloud"
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}:8086${CL}"

87
ct/rdtclient.sh Normal file
View File

@@ -0,0 +1,87 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/arm64-dev-build/misc/build.func)
# Copyright (c) 2021-2026 tteck
# Author: tteck (tteckster)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/rogerfar/rdt-client
APP="RDTClient"
var_tags="${var_tags:-torrent}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-1024}"
var_disk="${var_disk:-4}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /opt/rdtc/ ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "rdt-client" "rogerfar/rdt-client"; then
msg_info "Stopping Service"
systemctl stop rdtc
msg_ok "Stopped Service"
msg_info "Creating backup"
mkdir -p /opt/rdtc-backup
cp -R /opt/rdtc/appsettings.json /opt/rdtc-backup/
msg_ok "Backup created"
fetch_and_deploy_gh_release "rdt-client" "rogerfar/rdt-client" "prebuild" "latest" "/opt/rdtc" "RealDebridClient.zip"
cp -R /opt/rdtc-backup/appsettings.json /opt/rdtc/
if dpkg-query -W aspnetcore-runtime-9.0 >/dev/null 2>&1; then
$STD apt remove --purge -y aspnetcore-runtime-9.0
ensure_dependencies aspnetcore-runtime-10.0
fi
rm -rf /opt/rdtc-backup
msg_info "Starting Service"
systemctl start rdtc
msg_ok "Started Service"
msg_ok "Updated successfully!"
fi
exit
}
function update_script_arm64() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /opt/rdtc/ ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "rdt-client" "rogerfar/rdt-client"; then
msg_info "Stopping Service"
systemctl stop rdtc
msg_ok "Stopped Service"
msg_info "Updating .NET Runtime"
rm -rf /usr/share/dotnet /usr/bin/dotnet
$STD apt-get install -y libc6 libgcc-s1 libgssapi-krb5-2 liblttng-ust1 libssl3 libstdc++6 zlib1g libicu76
curl -fsSL -o /tmp/dotnet.tar.gz "https://builds.dotnet.microsoft.com/dotnet/Sdk/10.0.103/dotnet-sdk-10.0.103-linux-arm64.tar.gz"
tar -zxf /tmp/dotnet.tar.gz -C /usr/share/dotnet
ln -sf /usr/share/dotnet/dotnet /usr/bin/dotnet
rm -f /tmp/dotnet.tar.gz
msg_ok "Updated .NET Runtime"
fi
}
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}:6500${CL}"

View File

@@ -0,0 +1,36 @@
{
"name": "OxiCloud",
"slug": "oxicloud",
"categories": [
11,
12
],
"date_created": "2026-03-11",
"type": "ct",
"updateable": true,
"privileged": false,
"interface_port": 8086,
"documentation": "https://github.com/DioCrafts/OxiCloud/tree/main/doc",
"website": "https://github.com/DioCrafts/OxiCloud",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/oxicloud.webp",
"config_path": "/etc/oxicloud/.env",
"description": "Ultra-fast, secure & lightweight self-hosted cloud storage — your files, photos, calendars & contacts, all in one place. Built in Rust.",
"install_methods": [
{
"type": "default",
"script": "ct/oxicloud.sh",
"resources": {
"cpu": 2,
"ram": 3072,
"hdd": 20,
"os": "Debian",
"version": "13"
}
}
],
"default_credentials": {
"username": null,
"password": null
},
"notes": []
}

View File

@@ -1 +0,0 @@

View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
install_dotnet_runtime() {
msg_info "Installing Dependencies"
$STD apt-get install -y unzip
msg_ok "Installed Dependencies"
msg_info "Installing ASP.NET Core Runtime"
$STD apt-get install -y libc6 libgcc-s1 libgssapi-krb5-2 liblttng-ust1 libssl3 libstdc++6 zlib1g libicu76
curl -fsSL -o /tmp/dotnet.tar.gz "https://builds.dotnet.microsoft.com/dotnet/Sdk/10.0.103/dotnet-sdk-10.0.103-linux-arm64.tar.gz"
mkdir -p /usr/share/dotnet
tar -zxf /tmp/dotnet.tar.gz -C /usr/share/dotnet
ln -sf /usr/share/dotnet/dotnet /usr/bin/dotnet
rm -f /tmp/dotnet.tar.gz
msg_ok "Installed ASP.NET Core Runtime"
}

View File

@@ -0,0 +1,33 @@
#!/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/navidrome/navidrome
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
msg_info "Installing Dependencies (Patience)"
$STD apt install -y ffmpeg
msg_ok "Installed Dependencies"
fetch_and_deploy_gh_release "navidrome" "navidrome/navidrome" "binary"
msg_info "Starting Navidrome"
systemctl enable -q --now navidrome
msg_ok "Started Navidrome"
read -p "${TAB3}Do you want to install filebrowser addon? (y/n) " -n 1 -r
if [[ $REPLY =~ ^[Yy]$ ]]; then
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/filebrowser.sh)"
fi
motd_ssh
customize
cleanup_lxc

View File

@@ -0,0 +1,71 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: vhsdream
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
# Source: https://github.com/DioCrafts/OxiCloud
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
msg_info "Installing Dependencies"
$STD apt install -y \
build-essential
msg_ok "Installed Dependencies"
PG_VERSION="17" setup_postgresql
PG_DB_NAME="oxicloud" PG_DB_USER="oxicloud" setup_postgresql_db
fetch_and_deploy_gh_release "OxiCloud" "DioCrafts/OxiCloud" "tarball" "latest" "/opt/oxicloud"
TOOLCHAIN="$(sed -n '2s/[^:]*://p' /opt/oxicloud/Dockerfile | awk -F- '{print $1}')"
RUST_TOOLCHAIN=$TOOLCHAIN setup_rust
msg_info "Building OxiCloud"
cd /opt/oxicloud
export DATABASE_URL="postgres://${PG_DB_USER}:${PG_DB_PASS}@localhost/${PG_DB_NAME}"
$STD cargo build --release
mv target/release/oxicloud /usr/bin/oxicloud && chmod +x /usr/bin/oxicloud
msg_ok "Built OxiCloud"
msg_info "Configuring OxiCloud"
mkdir -p {/mnt/oxicloud,/etc/oxicloud}
sed -e 's|_STORAGE_PATH=.*|_STORAGE_PATH=/mnt/oxicloud|' \
-e 's|_SERVER_HOST=.*|_SERVER_HOST=0.0.0.0|' \
-e "s|^#OXICLOUD_BASE_URL=.*|OXICLOUD_BASE_URL=${LOCAL_IP}:8086|" \
-e "s|_STRING=.*|_STRING=${DATABASE_URL}|" \
-e "s|DATABASE_URL=.*|DATABASE_URL=${DATABASE_URL}|" \
-e "s|^#OXICLOUD_JWT_SECRET=.*|OXICLOUD_JWT_SECRET=$(openssl rand -hex 32)|" \
-e 's|^#OXICLOUD_ENABLE|OXICLOUD_ENABLE|g' \
/opt/oxicloud/example.env >/etc/oxicloud/.env
chmod 600 /etc/oxicloud/.env
msg_ok "Configured OxiCloud"
msg_info "Creating OxiCloud Service"
cat <<EOF >/etc/systemd/system/oxicloud.service
[Unit]
Description=OxiCloud Service
After=network.target
[Service]
Type=simple
User=root
EnvironmentFile=/etc/oxicloud/.env
ExecStart=/usr/bin/oxicloud
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
$STD systemctl enable -q --now oxicloud
msg_ok "Created OxiCloud Service"
motd_ssh
customize
cleanup_lxc

View File

@@ -0,0 +1,59 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 tteck
# Author: tteck (tteckster)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/rogerfar/rdt-client
if ! declare -F install_dotnet_runtime >/dev/null; then
install_dotnet_runtime() {
msg_info "Installing Dependencies"
setup_deb822_repo \
"microsoft" \
"https://packages.microsoft.com/keys/microsoft-2025.asc" \
"https://packages.microsoft.com/debian/13/prod/" \
"trixie"
$STD apt-get install -y aspnetcore-runtime-10.0 unzip
msg_ok "Installed Dependencies"
}
fi
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
install_dotnet_runtime
fetch_and_deploy_gh_release "rdt-client" "rogerfar/rdt-client" "prebuild" "latest" "/opt/rdtc" "RealDebridClient.zip"
msg_info "Setting up rdtclient"
cd /opt/rdtc
mkdir -p data/{db,downloads}
sed -i 's#/data/db/#/opt/rdtc&#g' /opt/rdtc/appsettings.json
msg_ok "Configured rdtclient"
msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/rdtc.service
[Unit]
Description=RdtClient Service
[Service]
WorkingDirectory=/opt/rdtc
ExecStart=/usr/bin/dotnet RdtClient.Web.dll
SyslogIdentifier=RdtClient
User=root
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now rdtc
msg_ok "Created Service"
motd_ssh
customize
cleanup_lxc

View File

@@ -1,22 +1,54 @@
# Copyright (c) 2021-2026 community-scripts ORG
# Author: tteck (tteckster)
# Co-Author: MickLesk
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/arm64-dev-build/LICENSE
if ! command -v curl >/dev/null 2>&1; then
apk update && apk add curl >/dev/null 2>&1
fi
COMMUNITY_SCRIPTS_URL="${COMMUNITY_SCRIPTS_URL:-https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main}"
source <(curl -fsSL "$COMMUNITY_SCRIPTS_URL/misc/core.func")
source <(curl -fsSL "$COMMUNITY_SCRIPTS_URL/misc/error_handler.func")
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/arm64-dev-build/misc/core.func)
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/arm64-dev-build/misc/error_handler.func)
load_functions
catch_errors
# Persist diagnostics setting inside container (exported from build.func)
# so addon scripts running later can find the user's choice
if [[ ! -f /usr/local/community-scripts/diagnostics ]]; then
mkdir -p /usr/local/community-scripts
echo "DIAGNOSTICS=${DIAGNOSTICS:-no}" >/usr/local/community-scripts/diagnostics
fi
# Get LXC IP address (must be called INSIDE container, after network is up)
get_lxc_ip
# ------------------------------------------------------------------------------
# post_progress_to_api()
#
# - Lightweight progress ping from inside the container
# - Updates the existing telemetry record status
# - Arguments:
# * $1: status (optional, default: "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
# ------------------------------------------------------------------------------
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}"
curl -fsS -m 5 -X POST "https://telemetry.community-scripts.org/telemetry" \
-H "Content-Type: application/json" \
-d "{\"random_id\":\"${RANDOM_UUID}\",\"execution_id\":\"${EXECUTION_ID:-${RANDOM_UUID}}\",\"type\":\"lxc\",\"nsapp\":\"${app:-unknown}\",\"status\":\"${progress_status}\"}" &>/dev/null || true
}
# This function enables IPv6 if it's not disabled and sets verbose mode
verb_ip6() {
set_std_mode # Set STD mode based on VERBOSE
if [ "$IPV6_METHOD" == "disable" ]; then
if [ "${IPV6_METHOD:-}" = "disable" ]; then
msg_info "Disabling IPv6 (this may affect some services)"
$STD sysctl -w net.ipv6.conf.all.disable_ipv6=1
$STD sysctl -w net.ipv6.conf.default.disable_ipv6=1
@@ -32,43 +64,6 @@ EOF
fi
}
set -Eeuo pipefail
trap 'error_handler $? $LINENO "$BASH_COMMAND"' ERR
trap on_exit EXIT
trap on_interrupt INT
trap on_terminate TERM
error_handler() {
local exit_code="$1"
local line_number="$2"
local command="$3"
# Exitcode 0 = kein Fehler → ignorieren
if [[ "$exit_code" -eq 0 ]]; then
return 0
fi
printf "\e[?25h"
echo -e "\n${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}\n"
exit "$exit_code"
}
on_exit() {
local exit_code="$?"
[[ -n "${lockfile:-}" && -e "$lockfile" ]] && rm -f "$lockfile"
exit "$exit_code"
}
on_interrupt() {
echo -e "\n${RD}Interrupted by user (SIGINT)${CL}"
exit 130
}
on_terminate() {
echo -e "\n${RD}Terminated by signal (SIGTERM)${CL}"
exit 143
}
# This function sets up the Container OS by generating the locale, setting the timezone, and checking the network connection
setting_up_container() {
msg_info "Setting up Container OS"
@@ -84,10 +79,11 @@ setting_up_container() {
if [ "$(ip addr show | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}' | cut -d'/' -f1)" = "" ]; then
echo 1>&2 -e "\n${CROSS}${RD} No Network After $RETRY_NUM Tries${CL}"
echo -e "${NETWORK}Check Network Settings"
exit 1
exit 121
fi
msg_ok "Set up Container OS"
msg_ok "Network Connected: ${BL}$(ip addr show | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1 | tail -n1)${CL}"
post_progress_to_api
}
# This function checks the network connection by pinging a known IP address and prompts the user to continue if the internet is not connected
@@ -103,7 +99,7 @@ network_check() {
echo -e "${INFO}${RD}Expect Issues Without Internet${CL}"
else
echo -e "${NETWORK}Check Network Settings"
exit 1
exit 122
fi
fi
RESOLVEDIP=$(getent hosts github.com | awk '{ print $1 }')
@@ -120,38 +116,43 @@ network_check() {
update_os() {
msg_info "Updating Container OS"
$STD apk -U upgrade
source <(curl -fsSL "$COMMUNITY_SCRIPTS_URL/misc/alpine-tools.func")
local tools_content
tools_content=$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/arm64-dev-build/misc/tools.func) || {
msg_error "Failed to download tools.func"
exit 115
}
source /dev/stdin <<<"$tools_content"
if ! declare -f fetch_and_deploy_gh_release >/dev/null 2>&1; then
msg_error "tools.func loaded but incomplete — missing expected functions"
exit 115
fi
msg_ok "Updated Container OS"
post_progress_to_api
}
# This function modifies the message of the day (motd) and SSH settings
motd_ssh() {
echo "export TERM='xterm-256color'" >>/root/.bashrc
IP=$(ip -4 addr show eth0 | awk '/inet / {print $2}' | cut -d/ -f1 | head -n 1)
if [ -f "/etc/os-release" ]; then
OS_NAME=$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '"')
OS_VERSION=$(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '"')
else
OS_NAME="Alpine Linux"
OS_VERSION="Unknown"
fi
PROFILE_FILE="/etc/profile.d/00_lxc-details.sh"
echo "echo -e \"\"" >"$PROFILE_FILE"
echo -e "echo -e \"${BOLD}${YW}${APPLICATION} LXC Container - DEV Repository${CL}\"" >>"$PROFILE_FILE"
echo -e "echo -e \"${RD}WARNING: This is a DEVELOPMENT version (ProxmoxVED). Do NOT use in production!${CL}\"" >>"$PROFILE_FILE"
echo -e "echo -e \"${YW} OS: ${GN}${OS_NAME} - Version: ${OS_VERSION}${CL}\"" >>"$PROFILE_FILE"
echo -e "echo -e \"${YW} Hostname: ${GN}\$(hostname)${CL}\"" >>"$PROFILE_FILE"
echo -e "echo -e \"${YW} IP Address: ${GN}${IP}${CL}\"" >>"$PROFILE_FILE"
echo -e "echo -e \"${YW} Repository: ${GN}https://github.com/community-scripts/ProxmoxVED${CL}\"" >>"$PROFILE_FILE"
echo -e "echo -e \"${BOLD}${APPLICATION} LXC Container${CL}"\" >>"$PROFILE_FILE"
echo -e "echo -e \"${TAB}${GATEWAY}${YW} Provided by: ${GN}community-scripts ORG ${YW}| GitHub: ${GN}https://github.com/community-scripts/ProxmoxVED${CL}\"" >>"$PROFILE_FILE"
echo "echo \"\"" >>"$PROFILE_FILE"
echo -e "echo -e \"${TAB}${OS}${YW} OS: ${GN}\$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '\"') - Version: \$(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '\"')${CL}\"" >>"$PROFILE_FILE"
echo -e "echo -e \"${TAB}${HOSTNAME}${YW} Hostname: ${GN}\$(hostname)${CL}\"" >>"$PROFILE_FILE"
echo -e "echo -e \"${TAB}${INFO}${YW} IP Address: ${GN}\$(ip -4 addr show eth0 | awk '/inet / {print \$2}' | cut -d/ -f1 | head -n 1)${CL}\"" >>"$PROFILE_FILE"
# Configure SSH if enabled
if [[ "${SSH_ROOT}" == "yes" ]]; then
# Enable sshd service
$STD rc-update add sshd
# Allow root login via SSH
sed -i "s/#PermitRootLogin prohibit-password/PermitRootLogin yes/g" /etc/ssh/sshd_config
# Start the sshd service
$STD /etc/init.d/sshd start
fi
post_progress_to_api
}
# Validate Timezone for some LXC's
@@ -186,6 +187,7 @@ EOF
msg_ok "Customized Container"
fi
echo "bash -c \"\$(curl -fsSL https://github.com/community-scripts/ProxmoxVED/raw/main/ct/${app}.sh)\"" >/usr/bin/update
echo "bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/arm64-dev-build/ct/${app}.sh)\"" >/usr/bin/update
chmod +x /usr/bin/update
post_progress_to_api
}

View File

@@ -1,21 +1,7 @@
#!/bin/ash
# shellcheck shell=ash
# Erwartet vorhandene msg_* und optional $STD aus deinem Framework.
# Fallbacks, wenn core.func nicht geladen wurde (Alpine/ash-safe)
if ! command -v msg_info >/dev/null 2>&1; then
msg_info() { echo "[INFO] $*"; }
fi
if ! command -v msg_ok >/dev/null 2>&1; then
msg_ok() { echo "[OK] $*"; }
fi
if ! command -v msg_warn >/dev/null 2>&1; then
msg_warn() { echo "[WARN] $*"; }
fi
if ! command -v msg_error >/dev/null 2>&1; then
msg_error() { echo "[ERROR] $*" >&2; }
fi
# Expects existing msg_* functions and optional $STD from the framework.
# ------------------------------
# helpers
@@ -23,145 +9,6 @@ fi
lower() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]'; }
has() { command -v "$1" >/dev/null 2>&1; }
# tools.func compatibility helpers (Alpine-safe)
cache_installed_version() {
local app="$1" version="$2"
mkdir -p /var/cache/app-versions
echo "$version" >"/var/cache/app-versions/${app}_version.txt"
}
get_cached_version() {
local app="$1"
mkdir -p /var/cache/app-versions
if [ -f "/var/cache/app-versions/${app}_version.txt" ]; then
cat "/var/cache/app-versions/${app}_version.txt"
return 0
fi
return 0
}
version_gt() {
# returns 0 if $1 > $2
# BusyBox-safe version compare
awk -v a="$1" -v b="$2" '
function splitver(v, arr) { n=split(v, arr, /\./); return n }
BEGIN {
na=splitver(a, A); nb=splitver(b, B);
n=(na>nb?na:nb);
for (i=1;i<=n;i++) {
va=(A[i]==""?0:A[i]); vb=(B[i]==""?0:B[i]);
if (va+0 > vb+0) exit 0;
if (va+0 < vb+0) exit 1;
}
exit 1;
}'
}
get_system_arch() {
local arch
arch=$(uname -m 2>/dev/null || echo "")
[ "$arch" = "x86_64" ] && arch="amd64"
[ "$arch" = "aarch64" ] && arch="arm64"
echo "$arch"
}
create_temp_dir() {
mktemp -d
}
get_os_info() {
local field="${1:-all}"
[ -z "${_OS_ID:-}" ] && _OS_ID=$(awk -F= '/^ID=/{gsub(/"/,"",$2); print $2}' /etc/os-release 2>/dev/null)
[ -z "${_OS_CODENAME:-}" ] && _OS_CODENAME=$(awk -F= '/^VERSION_CODENAME=/{gsub(/"/,"",$2); print $2}' /etc/os-release 2>/dev/null)
[ -z "${_OS_VERSION:-}" ] && _OS_VERSION=$(awk -F= '/^VERSION_ID=/{gsub(/"/,"",$2); print $2}' /etc/os-release 2>/dev/null)
case "$field" in
id) echo "$_OS_ID" ;;
codename) echo "$_OS_CODENAME" ;;
version | version_id) echo "$_OS_VERSION" ;;
all) echo "ID=$_OS_ID CODENAME=$_OS_CODENAME VERSION=$_OS_VERSION" ;;
*) echo "$_OS_ID" ;;
esac
}
is_alpine() { [ "$(get_os_info id)" = "alpine" ]; }
get_os_version_major() {
local v
v=$(get_os_info version)
echo "${v%%.*}"
}
ensure_dependencies() {
need_tool "$@"
}
download_file() {
local url="$1" output="$2" max_retries="${3:-3}" show_progress="${4:-false}"
local i=1 curl_opts="-fsSL"
[ "$show_progress" = "true" ] && curl_opts="-fL#"
while [ $i -le "$max_retries" ]; do
if curl $curl_opts -o "$output" "$url"; then
return 0
fi
i=$((i + 1))
[ $i -le "$max_retries" ] && sleep 2
done
msg_error "Failed to download: $url"
return 1
}
github_api_call() {
local url="$1" output_file="${2:-/dev/stdout}"
local max_retries=3 retry_delay=2 attempt=1
local header=""
[ -n "${GITHUB_TOKEN:-}" ] && header="-H Authorization:Bearer\ ${GITHUB_TOKEN}"
while [ $attempt -le $max_retries ]; do
http_code=$(curl -fsSL -w "%{http_code}" -o "$output_file" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
$header "$url" 2>/dev/null || echo 000)
case "$http_code" in
200) return 0 ;;
403) [ $attempt -lt $max_retries ] && sleep "$retry_delay" || {
msg_error "GitHub API rate limit exceeded"
return 1
} ;;
404)
msg_error "GitHub API endpoint not found: $url"
return 1
;;
*) [ $attempt -lt $max_retries ] && sleep "$retry_delay" || {
msg_error "GitHub API call failed with HTTP $http_code"
return 1
} ;;
esac
retry_delay=$((retry_delay * 2))
attempt=$((attempt + 1))
done
return 1
}
extract_version_from_json() {
local json="$1" field="${2:-tag_name}" strip_v="${3:-true}" version
need_tool jq || return 1
version=$(printf '%s' "$json" | jq -r ".${field} // empty")
[ -z "$version" ] && return 1
[ "$strip_v" = "true" ] && printf '%s' "${version#v}" || printf '%s' "$version"
}
get_latest_github_release() {
local repo="$1" strip_v="${2:-true}" tmp
tmp=$(mktemp) || return 1
github_api_call "https://api.github.com/repos/${repo}/releases/latest" "$tmp" || {
rm -f "$tmp"
return 1
}
extract_version_from_json "$(cat "$tmp")" "tag_name" "$strip_v"
rc=$?
rm -f "$tmp"
return $rc
}
need_tool() {
# usage: need_tool curl jq unzip ...
# setup missing tools via apk
@@ -187,26 +34,28 @@ net_resolves() {
}
ensure_usr_local_bin_persist() {
# Login shells: /etc/profile.d/
local PROFILE_FILE="/etc/profile.d/10-localbin.sh"
if [ ! -f "$PROFILE_FILE" ]; then
echo 'case ":$PATH:" in *:/usr/local/bin:*) ;; *) export PATH="/usr/local/bin:$PATH";; esac' >"$PROFILE_FILE"
chmod +x "$PROFILE_FILE"
fi
# Non-login shells (pct enter): /root/.profile and /root/.bashrc
for rc_file in /root/.profile /root/.bashrc; do
if [ -f "$rc_file" ] && ! grep -q '/usr/local/bin' "$rc_file"; then
echo 'export PATH="/usr/local/bin:$PATH"' >>"$rc_file"
fi
done
}
download_with_progress() {
# $1 url, $2 dest
local url="$1" out="$2" content_length
local url="$1" out="$2" cl
need_tool curl pv || return 1
content_length=$(
curl -fsSLI "$url" 2>/dev/null |
awk '(tolower($1) ~ /^content-length:/) && ($2 + 0 > 0) {print $2+0}' |
tail -1 | tr -cd '[:digit:]' || true
)
if [ -n "$content_length" ]; then
curl -fsSL "$url" | pv -s "$content_length" >"$out" || {
cl=$(curl -fsSLI "$url" 2>/dev/null | awk 'tolower($0) ~ /^content-length:/ {print $2}' | tr -d '\r')
if [ -n "$cl" ]; then
curl -fsSL "$url" | pv -s "$cl" >"$out" || {
msg_error "Download failed: $url"
return 1
}
@@ -237,12 +86,7 @@ check_for_gh_release() {
}
need_tool curl jq || return 1
github_api_call "https://api.github.com/repos/${source}/releases/latest" "/tmp/${app_lc}-release.json" || {
msg_error "Unable to fetch latest tag for $app"
return 1
}
tag=$(cat "/tmp/${app_lc}-release.json" | jq -r '.tag_name // empty')
rm -f "/tmp/${app_lc}-release.json"
tag=$(curl -fsSL "https://api.github.com/repos/${source}/releases/latest" | jq -r '.tag_name // empty')
[ -z "$tag" ] && {
msg_error "Unable to fetch latest tag for $app"
return 1
@@ -276,14 +120,14 @@ check_for_gh_release() {
}
# ------------------------------
# GitHub: get Release & deployen (Alpine)
# GitHub: get Release & deploy (Alpine)
# modes: tarball | prebuild | singlefile
# ------------------------------
fetch_and_deploy_gh_release() {
fetch_and_deploy_gh() {
# $1 app, $2 repo, [$3 mode], [$4 version], [$5 target], [$6 asset_pattern
local app="$1" repo="$2" mode="${3:-tarball}" version="${4:-latest}" target="${5:-/opt/$1}" pattern="${6:-}"
local app_lc
app_lc=$(lower "$app" | tr -d ' ')
app_lc="$(lower "$app" | tr -d ' ')"
local vfile="$HOME/.${app_lc}"
local json url filename tmpd unpack
@@ -294,27 +138,26 @@ fetch_and_deploy_gh_release() {
need_tool curl jq tar || return 1
[ "$mode" = "prebuild" ] || [ "$mode" = "singlefile" ] && need_tool unzip >/dev/null 2>&1 || true
tmpd=$(mktemp -d) || return 1
tmpd="$(mktemp -d)" || return 1
mkdir -p "$target"
# Release JSON (with token/rate-limit handling)
# Release JSON
if [ "$version" = "latest" ]; then
github_api_call "https://api.github.com/repos/$repo/releases/latest" "$tmpd/release.json" || {
json="$(curl -fsSL "https://api.github.com/repos/$repo/releases/latest")" || {
msg_error "GitHub API failed"
rm -rf "$tmpd"
return 1
}
else
github_api_call "https://api.github.com/repos/$repo/releases/tags/$version" "$tmpd/release.json" || {
json="$(curl -fsSL "https://api.github.com/repos/$repo/releases/tags/$version")" || {
msg_error "GitHub API failed"
rm -rf "$tmpd"
return 1
}
fi
json=$(cat "$tmpd/release.json")
# correct Version
version=$(printf '%s' "$json" | jq -r '.tag_name // empty')
version="$(printf '%s' "$json" | jq -r '.tag_name // empty')"
version="${version#v}"
[ -z "$version" ] && {
@@ -323,15 +166,9 @@ fetch_and_deploy_gh_release() {
return 1
}
get_url() {
printf '%s' "$json" | jq -r '.assets[].browser_download_url' |
awk -v p="$pattern" 'BEGIN{IGNORECASE=1} $0 ~ p {print; exit}' |
tr -d '[:cntrl:]'
}
case "$mode" in
tarball | source)
url=$(printf '%s' "$json" | jq -r '.tarball_url // empty')
url="$(printf '%s' "$json" | jq -r '.tarball_url // empty')"
[ -z "$url" ] && url="https://github.com/$repo/archive/refs/tags/v$version.tar.gz"
filename="${app_lc}-${version}.tar.gz"
download_with_progress "$url" "$tmpd/$filename" || {
@@ -343,8 +180,7 @@ fetch_and_deploy_gh_release() {
rm -rf "$tmpd"
return 1
}
unpack=$(find "$tmpd" -mindepth 1 -maxdepth 1 -type d | head -n1)
[ "${CLEAN_INSTALL:-0}" = "1" ] && rm -rf "${target:?}/"*
unpack="$(find "$tmpd" -mindepth 1 -maxdepth 1 -type d | head -n1)"
# copy content of unpack to target
(cd "$unpack" && tar -cf - .) | (cd "$target" && tar -xf -) || {
msg_error "copy failed"
@@ -352,41 +188,16 @@ fetch_and_deploy_gh_release() {
return 1
}
;;
binary)
[ -n "$pattern" ] || pattern="*.apk"
url=$(get_url)
[ -z "$url" ] && {
msg_error "binary asset not found for pattern: $pattern"
rm -rf "$tmpd"
return 1
}
filename="${url##*/}"
download_with_progress "$url" "$tmpd/$filename" || {
rm -rf "$tmpd"
return 1
}
case "$filename" in
*.apk)
apk add --no-cache --allow-untrusted "$tmpd/$filename" >/dev/null 2>&1 || {
msg_error "apk install failed: $filename"
rm -rf "$tmpd"
return 1
}
;;
*)
msg_error "Unsupported binary asset on Alpine: $filename"
rm -rf "$tmpd"
return 1
;;
esac
;;
prebuild)
[ -n "$pattern" ] || {
msg_error "prebuild requires asset pattern"
rm -rf "$tmpd"
return 1
}
url=$(get_url)
url="$(printf '%s' "$json" | jq -r '.assets[].browser_download_url' | awk -v p="$pattern" '
BEGIN{IGNORECASE=1}
$0 ~ p {print; exit}
')"
[ -z "$url" ] && {
msg_error "asset not found for pattern: $pattern"
rm -rf "$tmpd"
@@ -417,10 +228,9 @@ fetch_and_deploy_gh_release() {
return 1
;;
esac
[ "${CLEAN_INSTALL:-0}" = "1" ] && rm -rf "${target:?}/"*
# top-level folder strippen
if [ "$(find "$tmpd/unp" -mindepth 1 -maxdepth 1 -type d | wc -l)" -eq 1 ] && [ -z "$(find "$tmpd/unp" -mindepth 1 -maxdepth 1 -type f | head -n1)" ]; then
unpack=$(find "$tmpd/unp" -mindepth 1 -maxdepth 1 -type d)
unpack="$(find "$tmpd/unp" -mindepth 1 -maxdepth 1 -type d)"
(cd "$unpack" && tar -cf - .) | (cd "$target" && tar -xf -) || {
msg_error "copy failed"
rm -rf "$tmpd"
@@ -440,20 +250,21 @@ fetch_and_deploy_gh_release() {
rm -rf "$tmpd"
return 1
}
url=$(get_url)
url="$(printf '%s' "$json" | jq -r '.assets[].browser_download_url' | awk -v p="$pattern" '
BEGIN{IGNORECASE=1}
$0 ~ p {print; exit}
')"
[ -z "$url" ] && {
msg_error "asset not found for pattern: $pattern"
rm -rf "$tmpd"
return 1
}
filename="${url##*/}"
local target_file="$app"
[ "${USE_ORIGINAL_FILENAME:-false}" = "true" ] && target_file="$filename"
download_with_progress "$url" "$target/$target_file" || {
download_with_progress "$url" "$target/$app" || {
rm -rf "$tmpd"
return 1
}
chmod +x "$target/$target_file"
chmod +x "$target/$app"
;;
*)
msg_error "Unknown mode: $mode"
@@ -468,11 +279,6 @@ fetch_and_deploy_gh_release() {
msg_ok "Deployed $app ($version) → $target"
}
# tools.func compatibility alias
fetch_and_deploy_gh() {
fetch_and_deploy_gh_release "$@"
}
# ------------------------------
# yq (mikefarah) Alpine
# ------------------------------

View File

@@ -1,6 +1,6 @@
# Copyright (c) 2021-2026 community-scripts ORG
# Author: michelroegl-brunner | MickLesk
# License: MIT | https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/LICENSE
# License: MIT | https://raw.githubusercontent.com/community-scripts/ProxmoxVED/arm64-dev-build/LICENSE
# ==============================================================================
# API.FUNC - TELEMETRY & DIAGNOSTICS API
@@ -12,7 +12,6 @@
# Features:
# - Container/VM creation statistics
# - Installation success/failure tracking
# - Pre-flight abort tracking ("aborted" status)
# - Error code mapping and reporting
# - Privacy-respecting anonymous telemetry
#
@@ -97,7 +96,7 @@ detect_repo_source() {
"")
# No URL detected — use hardcoded fallback
# This value must match the repo: ProxmoxVE for production, ProxmoxVED for dev
REPO_SOURCE="ProxmoxVED"
REPO_SOURCE="ProxmoxVE"
;;
*)
# Fork or unknown repo
@@ -125,6 +124,7 @@ detect_repo_source
# * 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)
@@ -132,7 +132,9 @@ detect_repo_source
# * 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)
# * Application Install/Update errors (250-254)
# - Returns description string for given exit code
# ------------------------------------------------------------------------------
explain_exit_code() {
@@ -190,7 +192,7 @@ explain_exit_code() {
101) echo "APT: Configuration error (bad sources.list, malformed config)" ;;
102) echo "APT: Lock held by another process (dpkg/apt still running)" ;;
# --- Validation / Preflight (103-108) ---
# --- 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" ;;
@@ -199,6 +201,19 @@ explain_exit_code() {
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)" ;;
@@ -287,8 +302,18 @@ explain_exit_code() {
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)" ;;
@@ -298,6 +323,13 @@ explain_exit_code() {
248) echo "Node.js: Invalid C++ addon / N-API failure" ;;
249) echo "npm/pnpm/yarn: Unknown fatal error" ;;
# --- Application Install/Update Errors (250-254) ---
250) echo "App: Download failed or version not determined" ;;
251) echo "App: File extraction failed (corrupt or incomplete archive)" ;;
252) echo "App: Required file or resource not found" ;;
253) echo "App: Data migration required — update aborted" ;;
254) echo "App: User declined prompt or input timed out" ;;
# --- DPKG ---
255) echo "DPKG: Fatal internal error" ;;
@@ -314,17 +346,20 @@ explain_exit_code() {
# - 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)
s=$(printf '%s' "$s" | tr -d '\000-\010\013\014\016-\037')
printf '%s' "$s"
# Escape a string for safe JSON embedding using awk (handles any input size).
# Pipeline: strip ANSI → remove control chars → escape \ " TAB → join lines with \n
printf '%s' "$1" \
| sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' \
| tr -d '\000-\010\013\014\016-\037\177\r' \
| awk '
BEGIN { ORS = "" }
{
gsub(/\\/, "\\\\") # backslash → \\
gsub(/"/, "\\\"") # double quote → \"
gsub(/\t/, "\\t") # tab → \t
if (NR > 1) printf "\\n"
printf "%s", $0
}'
}
# ------------------------------------------------------------------------------
@@ -360,6 +395,11 @@ get_error_text() {
logfile="$BUILD_LOG"
fi
# Try SILENT_LOGFILE as last resort (captures $STD command output)
if [[ -z "$logfile" || ! -s "$logfile" ]] && [[ -n "${SILENT_LOGFILE:-}" && -s "${SILENT_LOGFILE}" ]]; then
logfile="$SILENT_LOGFILE"
fi
if [[ -n "$logfile" && -s "$logfile" ]]; then
tail -n 20 "$logfile" 2>/dev/null | sed 's/\r$//' | sed 's/\x1b\[[0-9;]*[a-zA-Z]//g'
fi
@@ -405,6 +445,13 @@ get_full_log() {
fi
fi
# Fall back to SILENT_LOGFILE (captures $STD command output)
if [[ -z "$logfile" || ! -s "$logfile" ]]; then
if [[ -n "${SILENT_LOGFILE:-}" && -s "${SILENT_LOGFILE}" ]]; then
logfile="$SILENT_LOGFILE"
fi
fi
if [[ -n "$logfile" && -s "$logfile" ]]; then
# Strip ANSI codes, carriage returns, and anonymize IP addresses (GDPR)
sed 's/\r$//' "$logfile" 2>/dev/null |
@@ -642,18 +689,23 @@ 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
# Send initial "installing" record with retry.
# This record MUST exist for all subsequent updates to succeed.
local http_code="" attempt
for attempt in 1 2 3; do
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) || http_code="000"
echo "[DEBUG] post_to_api attempt $attempt HTTP=$http_code" >&2
else
http_code=$(curl -sS -w "%{http_code}" -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
-H "Content-Type: application/json" \
-d "$JSON_PAYLOAD" -o /dev/null 2>/dev/null) || http_code="000"
fi
[[ "$http_code" =~ ^2[0-9]{2}$ ]] && break
[[ "$attempt" -lt 3 ]] && sleep 1
done
POST_TO_API_DONE=true
}
@@ -744,10 +796,15 @@ post_to_api_vm() {
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
# Send initial "installing" record with retry (must succeed for updates to work)
local http_code="" attempt
for attempt in 1 2 3; do
http_code=$(curl -sS -w "%{http_code}" -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
-H "Content-Type: application/json" \
-d "$JSON_PAYLOAD" -o /dev/null 2>/dev/null) || http_code="000"
[[ "$http_code" =~ ^2[0-9]{2}$ ]] && break
[[ "$attempt" -lt 3 ]] && sleep 1
done
POST_TO_API_DONE=true
}
@@ -779,76 +836,6 @@ post_progress_to_api() {
-d "{\"random_id\":\"${RANDOM_UUID}\",\"execution_id\":\"${EXECUTION_ID:-${RANDOM_UUID}}\",\"type\":\"${telemetry_type}\",\"nsapp\":\"${app_name}\",\"status\":\"${progress_status}\"}" &>/dev/null || true
}
# ------------------------------------------------------------------------------
# post_preflight_to_api()
#
# - Reports preflight failure to telemetry with "aborted" status
# - Uses PREFLIGHT_FAILURES array from build.func for error details
# - Sends error_category "preflight" for analytics grouping
# - Fire-and-forget: never blocks or fails script execution
# ------------------------------------------------------------------------------
post_preflight_to_api() {
# 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 telemetry
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
# Build error summary from preflight failures
local error_summary=""
for failure in "${PREFLIGHT_FAILURES[@]}"; do
local code="${failure%%|*}"
local msg="${failure#*|}"
if [[ -n "$error_summary" ]]; then
error_summary="${error_summary}\n"
fi
error_summary="${error_summary}[${code}] ${msg}"
done
error_summary=$(json_escape "$error_summary")
local exit_code="${PREFLIGHT_EXIT_CODE:-1}"
local short_error
short_error=$(json_escape "$(explain_exit_code "$exit_code")")
local error_category="preflight"
local JSON_PAYLOAD
JSON_PAYLOAD=$(
cat <<EOF
{
"random_id": "${RANDOM_UUID}",
"execution_id": "${EXECUTION_ID:-${RANDOM_UUID}}",
"type": "lxc",
"nsapp": "${NSAPP:-unknown}",
"status": "aborted",
"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_summary}",
"error_category": "${error_category}",
"repo_source": "${REPO_SOURCE}"
}
EOF
)
# Fire-and-forget with short timeout
curl -fsS -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
-H "Content-Type: application/json" \
-d "$JSON_PAYLOAD" &>/dev/null || true
}
# ------------------------------------------------------------------------------
# post_update_to_api()
#
@@ -896,7 +883,7 @@ post_update_to_api() {
# Get RAM info (if detected)
local ram_speed="${RAM_SPEED:-}"
# Map status to telemetry values: installing, success, failed, aborted, unknown
# Map status to telemetry values: installing, success, failed, unknown
case "$status" in
done | success)
pb_status="success"
@@ -907,17 +894,14 @@ post_update_to_api() {
failed)
pb_status="failed"
;;
aborted)
pb_status="aborted"
;;
*)
pb_status="unknown"
;;
esac
# For failed/aborted/unknown status, resolve exit code and error description
local short_error=""
if [[ "$pb_status" == "failed" ]] || [[ "$pb_status" == "aborted" ]] || [[ "$pb_status" == "unknown" ]]; then
# For failed/unknown status, resolve exit code and error description
local short_error="" medium_error=""
if [[ "$pb_status" == "failed" ]] || [[ "$pb_status" == "unknown" ]]; then
if [[ "$raw_exit_code" =~ ^[0-9]+$ ]]; then
exit_code="$raw_exit_code"
else
@@ -936,6 +920,18 @@ post_update_to_api() {
short_error=$(json_escape "$(explain_exit_code "$exit_code")")
error_category=$(categorize_error "$exit_code")
[[ -z "$error" ]] && error="Unknown error"
# Build medium error for attempt 2: explanation + last 100 log lines (≤16KB)
# This is the critical middle ground between full 120KB log and generic-only description
local medium_log=""
medium_log=$(get_full_log 16384) || true # 16KB max
if [[ -z "$medium_log" ]]; then
medium_log=$(get_error_text) || true
fi
local medium_full
medium_full=$(build_error_string "$exit_code" "$medium_log")
medium_error=$(json_escape "$medium_full")
[[ -z "$medium_error" ]] && medium_error="$short_error"
fi
# Calculate duration if timer was started
@@ -952,6 +948,11 @@ post_update_to_api() {
local http_code=""
# Strip 'G' suffix from disk size (VMs set DISK_SIZE=32G)
local DISK_SIZE_API="${DISK_SIZE:-0}"
DISK_SIZE_API="${DISK_SIZE_API%G}"
[[ ! "$DISK_SIZE_API" =~ ^[0-9]+$ ]] && DISK_SIZE_API=0
# ── Attempt 1: Full payload with complete error text (includes full log) ──
local JSON_PAYLOAD
JSON_PAYLOAD=$(
@@ -963,7 +964,7 @@ post_update_to_api() {
"nsapp": "${NSAPP:-unknown}",
"status": "${pb_status}",
"ct_type": ${CT_TYPE:-1},
"disk_size": ${DISK_SIZE:-0},
"disk_size": ${DISK_SIZE_API},
"core_count": ${CORE_COUNT:-0},
"ram_size": ${RAM_SIZE:-0},
"os_type": "${var_os:-}",
@@ -994,7 +995,7 @@ EOF
return 0
fi
# ── Attempt 2: Short error text (no full log) ──
# ── Attempt 2: Medium error text (truncated log ≤16KB instead of full 120KB) ──
sleep 1
local RETRY_PAYLOAD
RETRY_PAYLOAD=$(
@@ -1006,7 +1007,7 @@ EOF
"nsapp": "${NSAPP:-unknown}",
"status": "${pb_status}",
"ct_type": ${CT_TYPE:-1},
"disk_size": ${DISK_SIZE:-0},
"disk_size": ${DISK_SIZE_API},
"core_count": ${CORE_COUNT:-0},
"ram_size": ${RAM_SIZE:-0},
"os_type": "${var_os:-}",
@@ -1014,7 +1015,7 @@ EOF
"pve_version": "${pve_version}",
"method": "${METHOD:-default}",
"exit_code": ${exit_code},
"error": "${short_error}",
"error": "${medium_error}",
"error_category": "${error_category}",
"install_duration": ${duration},
"cpu_vendor": "${cpu_vendor}",
@@ -1037,7 +1038,7 @@ EOF
return 0
fi
# ── Attempt 3: Minimal payload (bare minimum to set status) ──
# ── Attempt 3: Minimal payload with medium error (bare minimum to set status) ──
sleep 2
local MINIMAL_PAYLOAD
MINIMAL_PAYLOAD=$(
@@ -1049,7 +1050,7 @@ EOF
"nsapp": "${NSAPP:-unknown}",
"status": "${pb_status}",
"exit_code": ${exit_code},
"error": "${short_error}",
"error": "${medium_error}",
"error_category": "${error_category}",
"install_duration": ${duration}
}
@@ -1066,7 +1067,7 @@ EOF
fi
# All 3 attempts failed — do NOT set POST_UPDATE_DONE=true.
# This allows the EXIT trap (api_exit_script) to retry with 3 fresh attempts.
# This allows the EXIT trap (on_exit in error_handler.func) to retry.
# No infinite loop risk: EXIT trap fires exactly once.
}
@@ -1078,7 +1079,7 @@ EOF
# categorize_error()
#
# - Maps exit codes to error categories for better analytics
# - Categories: network, storage, dependency, permission, timeout, config, resource, preflight, unknown
# - Categories: network, storage, dependency, permission, timeout, config, resource, unknown
# - Used to group errors in dashboard
# ------------------------------------------------------------------------------
categorize_error() {
@@ -1102,9 +1103,6 @@ categorize_error() {
# Permission errors
126 | 152) echo "permission" ;;
# Validation / Preflight errors
103 | 104 | 105 | 106 | 107 | 108) echo "preflight" ;;
# Configuration errors (Proxmox config, invalid args)
128 | 203 | 204 | 205 | 206 | 207 | 208) echo "config" ;;
@@ -1405,16 +1403,13 @@ post_update_to_api_extended() {
failed)
pb_status="failed"
;;
aborted)
pb_status="aborted"
;;
*)
pb_status="unknown"
;;
esac
# For failed/aborted/unknown status, resolve exit code and error description
if [[ "$pb_status" == "failed" ]] || [[ "$pb_status" == "aborted" ]] || [[ "$pb_status" == "unknown" ]]; then
# 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

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: community-scripts ORG
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/branch/main/LICENSE
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/arm64-dev-build/LICENSE
# Revision: 1
# ==============================================================================
@@ -17,7 +17,7 @@
# - Cloud-Init status monitoring and waiting
#
# Usage:
# source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/cloud-init.func)
# source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/arm64-dev-build/misc/cloud-init.func)
# setup_cloud_init "$VMID" "$STORAGE" "$HN" "yes"
#
# Compatible with: Debian, Ubuntu, and all Cloud-Init enabled distributions

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# License: MIT | https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/LICENSE
# License: MIT | https://raw.githubusercontent.com/community-scripts/ProxmoxVED/arm64-dev-build/LICENSE
# ==============================================================================
# CORE FUNCTIONS - LXC CONTAINER UTILITIES
@@ -276,7 +276,7 @@ shell_check() {
msg_error "Your default shell is currently not set to Bash. To use these scripts, please switch to the Bash shell."
echo -e "\nExiting..."
sleep 2
exit
exit 103
fi
}
@@ -293,7 +293,7 @@ root_check() {
msg_error "Please run this script as root."
echo -e "\nExiting..."
sleep 2
exit
exit 104
fi
}
@@ -314,7 +314,7 @@ pve_check() {
if ((MINOR < 0 || MINOR > 9)); then
msg_error "This version of Proxmox VE is not supported."
msg_error "Supported: Proxmox VE version 8.0 8.9"
exit 1
exit 105
fi
return 0
fi
@@ -325,7 +325,7 @@ pve_check() {
if ((MINOR < 0 || MINOR > 1)); then
msg_error "This version of Proxmox VE is not yet supported."
msg_error "Supported: Proxmox VE version 9.0 9.1"
exit 1
exit 105
fi
return 0
fi
@@ -333,7 +333,7 @@ pve_check() {
# All other unsupported versions
msg_error "This version of Proxmox VE is not supported."
msg_error "Supported versions: Proxmox VE 8.0 8.9 or 9.0 9.1"
exit 1
exit 105
}
# ------------------------------------------------------------------------------
@@ -344,12 +344,12 @@ pve_check() {
# - Provides link to ARM64-compatible scripts
# ------------------------------------------------------------------------------
arch_check() {
if [ "$(dpkg --print-architecture)" != "amd64" ]; then
echo -e "\n ${INFO}${YWB}This script will not work with PiMox! \n"
echo -e "\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \n"
echo -e "Exiting..."
local arch
arch="$(dpkg --print-architecture)"
if [[ "$arch" != "amd64" && "$arch" != "arm64" ]]; then
msg_error "This script requires amd64 or arm64 (detected: $arch)."
sleep 2
exit
exit 106
fi
}
@@ -399,7 +399,6 @@ ssh_check() {
# even after INSTALL_LOG is exported for the container)
# - INSTALL_LOG: Container operations (application installation)
# - BUILD_LOG: Host operations (container creation)
# - Fallback to BUILD_LOG if neither is set
# ------------------------------------------------------------------------------
get_active_logfile() {
@@ -490,9 +489,9 @@ log_section() {
#
# - Executes command with output redirected to active log file
# - On error: displays last 20 lines of log and exits with original exit code
# - Temporarily disables error trap to capture exit code correctly
# - Saves and restores previous error handling state (so callers that
# intentionally disabled error handling aren't silently re-enabled)
# - Sources explain_exit_code() for detailed error messages
# ------------------------------------------------------------------------------
silent() {
@@ -510,19 +509,30 @@ silent() {
return 0
fi
# Save current error handling state before disabling
# This prevents re-enabling error handling when the caller intentionally
# disabled it (e.g. build_container recovery section)
local _restore_errexit=false
[[ "$-" == *e* ]] && _restore_errexit=true
set +Eeuo pipefail
trap - ERR
"$@" >>"$logfile" 2>&1
local rc=$?
set -Eeuo pipefail
trap 'error_handler' ERR
# Restore error handling ONLY if it was active before this call
if $_restore_errexit; then
set -Eeuo pipefail
trap 'error_handler' ERR
fi
if [[ $rc -ne 0 ]]; then
# Source explain_exit_code if needed
if ! declare -f explain_exit_code >/dev/null 2>&1; then
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
if ! source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/arm64-dev-build/misc/error_handler.func); then
explain_exit_code() { echo "unknown (error_handler.func download failed)"; }
fi
fi
local explanation
@@ -543,6 +553,53 @@ silent() {
fi
}
# ------------------------------------------------------------------------------
# apt_update_safe()
#
# - Runs apt-get update with graceful error handling
# - On failure: shows warning with common causes instead of aborting
# - Logs full output to active log file
# - Returns 0 even on failure so the caller can continue
# - Typical cause: enterprise repos returning 401 Unauthorized
#
# Usage:
# apt_update_safe # Warn on failure, continue without aborting
# ------------------------------------------------------------------------------
apt_update_safe() {
local logfile
logfile="$(get_active_logfile)"
local _restore_errexit=false
[[ "$-" == *e* ]] && _restore_errexit=true
set +Eeuo pipefail
trap - ERR
apt-get update >>"$logfile" 2>&1
local rc=$?
if $_restore_errexit; then
set -Eeuo pipefail
trap 'error_handler' ERR
fi
if [[ $rc -ne 0 ]]; then
msg_warn "apt-get update exited with code ${rc} — some repositories may have failed."
# Check log for common 401/403 enterprise repo issues
if grep -qiE '401\s*Unauthorized|403\s*Forbidden|enterprise\.proxmox\.com' "$logfile" 2>/dev/null; then
echo -e "${TAB}${INFO} ${YWB}Hint: Proxmox enterprise repository returned an auth error.${CL}"
echo -e "${TAB} If you don't have a subscription, you can disable the enterprise"
echo -e "${TAB} repo and use the no-subscription repo instead."
fi
echo -e "${TAB}${INFO} ${YWB}Continuing despite partial update failure — packages may still be installable.${CL}"
echo ""
fi
return 0
}
# ------------------------------------------------------------------------------
# spinner()
#
@@ -599,6 +656,7 @@ stop_spinner() {
unset SPINNER_PID SPINNER_MSG
stty sane 2>/dev/null || true
stty -tostop 2>/dev/null || true
}
# ==============================================================================
@@ -776,8 +834,8 @@ fatal() {
# ------------------------------------------------------------------------------
exit_script() {
clear
echo -e "\n${CROSS}${RD}User exited script${CL}\n"
exit
msg_error "User exited script"
exit 0
}
# ------------------------------------------------------------------------------
@@ -791,13 +849,16 @@ exit_script() {
get_header() {
local app_name=$(echo "${APP,,}" | tr -d ' ')
local app_type=${APP_TYPE:-ct} # Default to 'ct' if not set
local header_url="https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/${app_type}/headers/${app_name}"
local header_dir="${app_type}"
[[ "$app_type" == "addon" ]] && header_dir="tools"
local header_url="https://raw.githubusercontent.com/community-scripts/ProxmoxVED/arm64-dev-build/${header_dir}/headers/${app_name}"
local local_header_path="/usr/local/community-scripts/headers/${app_type}/${app_name}"
mkdir -p "$(dirname "$local_header_path")"
if [ ! -s "$local_header_path" ]; then
if ! curl -fsSL "$header_url" -o "$local_header_path"; then
msg_warn "Failed to download header: $header_url"
return 1
fi
fi
@@ -838,10 +899,10 @@ header_info() {
ensure_tput() {
if ! command -v tput >/dev/null 2>&1; then
if grep -qi 'alpine' /etc/os-release; then
apk add --no-cache ncurses >/dev/null 2>&1
apk add --no-cache ncurses >/dev/null 2>&1 || msg_warn "Failed to install ncurses (tput may be unavailable)"
elif command -v apt-get >/dev/null 2>&1; then
apt-get update -qq >/dev/null
apt-get install -y -qq ncurses-bin >/dev/null 2>&1
apt-get install -y -qq ncurses-bin >/dev/null 2>&1 || msg_warn "Failed to install ncurses-bin (tput may be unavailable)"
fi
fi
}
@@ -872,18 +933,13 @@ is_alpine() {
#
# - Determines if script should run in verbose mode
# - Checks VERBOSE and var_verbose variables
# - Also returns true if not running in TTY (pipe/redirect scenario)
# - Used by msg_info() to decide between spinner and static output
# - Note: Non-TTY (pipe) scenarios are handled separately in msg_info()
# to allow spinner output to pass through pipes (e.g. lxc-attach | tee)
# ------------------------------------------------------------------------------
is_verbose_mode() {
local verbose="${VERBOSE:-${var_verbose:-no}}"
local tty_status
if [[ -t 2 ]]; then
tty_status="interactive"
else
tty_status="not-a-tty"
fi
[[ "$verbose" != "no" || ! -t 2 ]]
[[ "$verbose" != "no" ]]
}
# ------------------------------------------------------------------------------
@@ -1301,6 +1357,7 @@ prompt_select() {
# Validate options
if [[ $num_options -eq 0 ]]; then
msg_warn "prompt_select called with no options"
echo "" >&2
return 1
fi
@@ -1514,6 +1571,7 @@ cleanup_lxc() {
post_progress_to_api
fi
}
# ------------------------------------------------------------------------------
# check_or_create_swap()
#
@@ -1542,22 +1600,30 @@ check_or_create_swap() {
local swap_size_mb
swap_size_mb=$(prompt_input "Enter swap size in MB (e.g., 2048 for 2GB):" "2048" 60)
if ! [[ "$swap_size_mb" =~ ^[0-9]+$ ]]; then
msg_error "Invalid size input. Aborting."
msg_error "Invalid swap size: '${swap_size_mb}' (must be a number in MB)"
return 1
fi
local swap_file="/swapfile"
msg_info "Creating ${swap_size_mb}MB swap file at $swap_file"
if dd if=/dev/zero of="$swap_file" bs=1M count="$swap_size_mb" status=progress &&
chmod 600 "$swap_file" &&
mkswap "$swap_file" &&
swapon "$swap_file"; then
msg_ok "Swap file created and activated successfully"
else
msg_error "Failed to create or activate swap"
if ! dd if=/dev/zero of="$swap_file" bs=1M count="$swap_size_mb" status=progress; then
msg_error "Failed to allocate swap file (dd failed)"
return 1
fi
if ! chmod 600 "$swap_file"; then
msg_error "Failed to set permissions on $swap_file"
return 1
fi
if ! mkswap "$swap_file"; then
msg_error "Failed to format swap file (mkswap failed)"
return 1
fi
if ! swapon "$swap_file"; then
msg_error "Failed to activate swap (swapon failed)"
return 1
fi
msg_ok "Swap file created and activated successfully"
}
# ------------------------------------------------------------------------------
@@ -1639,7 +1705,7 @@ function get_lxc_ip() {
LOCAL_IP="$(get_current_ip || true)"
if [[ -z "$LOCAL_IP" ]]; then
msg_error "Could not determine LOCAL_IP"
msg_error "Could not determine LOCAL_IP (checked: eth0, hostname -I, ip route, IPv6 targets)"
return 1
fi
fi
@@ -1651,17 +1717,4 @@ function get_lxc_ip() {
# SIGNAL TRAPS
# ==============================================================================
# ------------------------------------------------------------------------------
# on_hup_keepalive()
#
# - SIGHUP (terminal hangup) trap handler
# - Keeps long-running scripts alive if terminal/SSH session disconnects
# - Stops spinner safely and writes warning to active log
# ------------------------------------------------------------------------------
on_hup_keepalive() {
stop_spinner
log_msg "[WARN] Received SIGHUP (terminal hangup). Continuing execution in background."
}
trap 'on_hup_keepalive' HUP
trap 'stop_spinner' EXIT INT TERM

View File

@@ -4,7 +4,7 @@
# ------------------------------------------------------------------------------
# Copyright (c) 2021-2026 community-scripts ORG
# Author: MickLesk (CanbiZ)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/arm64-dev-build/LICENSE
# ------------------------------------------------------------------------------
#
# Provides comprehensive error handling and signal management for all scripts.
@@ -94,6 +94,29 @@ if ! declare -f explain_exit_code &>/dev/null; then
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" ;;
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?)" ;;
@@ -155,6 +178,16 @@ 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" ;;
# --- Tools & Addon Scripts (232-238) ---
232) echo "Tools: Wrong execution environment (run on PVE host, not inside LXC)" ;;
233) echo "Tools: Application not installed (update prerequisite missing)" ;;
234) echo "Tools: No LXC containers found or available" ;;
235) echo "Tools: Backup or restore operation failed" ;;
236) echo "Tools: Required hardware not detected" ;;
237) echo "Tools: Dependency package installation failed" ;;
238) echo "Tools: OS or distribution not supported for this addon" ;;
239) echo "npm/Node.js: Unexpected runtime error or dependency failure" ;;
243) echo "Node.js: Out of memory (JavaScript heap out of memory)" ;;
245) echo "Node.js: Invalid command-line option" ;;
@@ -162,6 +195,14 @@ if ! declare -f explain_exit_code &>/dev/null; then
247) echo "Node.js: Fatal internal error" ;;
248) echo "Node.js: Invalid C++ addon / N-API failure" ;;
249) echo "npm/pnpm/yarn: Unknown fatal error" ;;
# --- Application Install/Update Errors (250-254) ---
250) echo "App: Download failed or version not determined" ;;
251) echo "App: File extraction failed (corrupt or incomplete archive)" ;;
252) echo "App: Required file or resource not found" ;;
253) echo "App: Data migration required — update aborted" ;;
254) echo "App: User declined prompt or input timed out" ;;
255) echo "DPKG: Fatal internal error" ;;
*) echo "Unknown error" ;;
esac
@@ -199,11 +240,16 @@ error_handler() {
return 0
fi
# Stop spinner and restore cursor FIRST — before any output
# This prevents spinner text overlapping with error messages
if declare -f stop_spinner >/dev/null 2>&1; then
stop_spinner 2>/dev/null || true
fi
printf "\e[?25h"
local explanation
explanation="$(explain_exit_code "$exit_code")"
printf "\e[?25h"
# ALWAYS report failure to API immediately - don't wait for container checks
# This ensures we capture failures that occur before/after container exists
if declare -f post_update_to_api &>/dev/null; then
@@ -281,6 +327,8 @@ error_handler() {
echo -en "${YW}Remove broken container ${CTID}? (Y/n) [auto-remove in 60s]: ${CL}"
fi
# Read user response
local response=""
if read -t 60 -r response; then
if [[ -z "$response" || "$response" =~ ^[Yy]$ ]]; then
echo ""
@@ -359,9 +407,65 @@ _send_abort_telemetry() {
command -v curl &>/dev/null || return 0
[[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0
[[ -z "${RANDOM_UUID:-}" ]] && return 0
curl -fsS -m 5 -X POST "${TELEMETRY_URL:-https://telemetry.community-scripts.org/telemetry}" \
-H "Content-Type: application/json" \
-d "{\"random_id\":\"${RANDOM_UUID}\",\"execution_id\":\"${EXECUTION_ID:-${RANDOM_UUID}}\",\"type\":\"${TELEMETRY_TYPE:-lxc}\",\"nsapp\":\"${NSAPP:-${app:-unknown}}\",\"status\":\"failed\",\"exit_code\":${exit_code}}" &>/dev/null || true
# Collect last 200 log lines for error diagnosis (best-effort)
# Container context has no get_full_log(), so we gather as much as possible
local error_text=""
local logfile=""
if [[ -n "${INSTALL_LOG:-}" && -s "${INSTALL_LOG}" ]]; then
logfile="${INSTALL_LOG}"
elif [[ -n "${SILENT_LOGFILE:-}" && -s "${SILENT_LOGFILE}" ]]; then
logfile="${SILENT_LOGFILE}"
fi
if [[ -n "$logfile" ]]; then
error_text=$(tail -n 200 "$logfile" 2>/dev/null | sed 's/\x1b\[[0-9;]*[a-zA-Z]//g; s/\\/\\\\/g; s/"/\\"/g; s/\r//g' | tr '\n' '|' | sed 's/|$//' | head -c 16384 | tr -d '\000-\010\013\014\016-\037\177') || true
fi
# Prepend exit code explanation header (like build_error_string does on host)
local explanation=""
if declare -f explain_exit_code &>/dev/null; then
explanation=$(explain_exit_code "$exit_code" 2>/dev/null) || true
fi
if [[ -n "$explanation" && -n "$error_text" ]]; then
error_text="exit_code=${exit_code} | ${explanation}|---|${error_text}"
elif [[ -n "$explanation" && -z "$error_text" ]]; then
error_text="exit_code=${exit_code} | ${explanation}"
fi
# Calculate duration if start time is available
local duration=""
if [[ -n "${DIAGNOSTICS_START_TIME:-}" ]]; then
duration=$(($(date +%s) - DIAGNOSTICS_START_TIME))
fi
# Categorize error if function is available (may not be in minimal container context)
local error_category=""
if declare -f categorize_error &>/dev/null; then
error_category=$(categorize_error "$exit_code" 2>/dev/null) || true
fi
# Build JSON payload with error context
local payload
payload="{\"random_id\":\"${RANDOM_UUID}\",\"execution_id\":\"${EXECUTION_ID:-${RANDOM_UUID}}\",\"type\":\"${TELEMETRY_TYPE:-lxc}\",\"nsapp\":\"${NSAPP:-${app:-unknown}}\",\"status\":\"failed\",\"exit_code\":${exit_code}"
[[ -n "$error_text" ]] && payload="${payload},\"error\":\"${error_text}\""
[[ -n "$error_category" ]] && payload="${payload},\"error_category\":\"${error_category}\""
[[ -n "$duration" ]] && payload="${payload},\"duration\":${duration}"
payload="${payload}}"
local api_url="${TELEMETRY_URL:-https://telemetry.community-scripts.org/telemetry}"
# 2 attempts (retry once on failure) — original had no retry
local attempt
for attempt in 1 2; do
if curl -fsS -m 5 -X POST "$api_url" \
-H "Content-Type: application/json" \
-d "$payload" &>/dev/null; then
return 0
fi
[[ $attempt -eq 1 ]] && sleep 1
done
return 0
}
# ------------------------------------------------------------------------------
@@ -437,6 +541,12 @@ on_exit() {
# - Exits with code 130 (128 + SIGINT=2)
# ------------------------------------------------------------------------------
on_interrupt() {
# Stop spinner and restore cursor before any output
if declare -f stop_spinner >/dev/null 2>&1; then
stop_spinner 2>/dev/null || true
fi
printf "\e[?25h" 2>/dev/null || true
_send_abort_telemetry "130"
_stop_container_if_installing
if declare -f msg_error >/dev/null 2>&1; then
@@ -456,6 +566,12 @@ on_interrupt() {
# - Exits with code 143 (128 + SIGTERM=15)
# ------------------------------------------------------------------------------
on_terminate() {
# Stop spinner and restore cursor before any output
if declare -f stop_spinner >/dev/null 2>&1; then
stop_spinner 2>/dev/null || true
fi
printf "\e[?25h" 2>/dev/null || true
_send_abort_telemetry "143"
_stop_container_if_installing
if declare -f msg_error >/dev/null 2>&1; then
@@ -478,6 +594,11 @@ on_terminate() {
# - Exits with code 129 (128 + SIGHUP=1)
# ------------------------------------------------------------------------------
on_hangup() {
# Stop spinner (no cursor restore needed — terminal is already gone)
if declare -f stop_spinner >/dev/null 2>&1; then
stop_spinner 2>/dev/null || true
fi
_send_abort_telemetry "129"
_stop_container_if_installing
exit 129

File diff suppressed because it is too large Load Diff

View File

@@ -934,7 +934,11 @@ upgrade_package() {
# ------------------------------------------------------------------------------
# Repository availability check with caching
# ------------------------------------------------------------------------------
declare -A _REPO_CACHE 2>/dev/null || true
# Note: Must use -gA (global) because tools.func is sourced inside update_os()
# function scope. Plain 'declare -A' would create a local variable that gets
# destroyed when update_os() returns, causing "unbound variable" errors later
# when setup_postgresql/verify_repo_available tries to access the cache key.
declare -gA _REPO_CACHE 2>/dev/null || declare -A _REPO_CACHE 2>/dev/null || true
verify_repo_available() {
local repo_url="$1"
@@ -1732,6 +1736,13 @@ setup_deb822_repo() {
rm -f "$tmp_gpg"
return 1
}
else
# Already binary — copy directly
cp -f "$tmp_gpg" "/etc/apt/keyrings/${name}.gpg" || {
msg_error "Failed to install GPG key for ${name}"
rm -f "$tmp_gpg"
return 1
}
fi
rm -f "$tmp_gpg"
chmod 644 "/etc/apt/keyrings/${name}.gpg"
@@ -2724,10 +2735,13 @@ function fetch_and_deploy_codeberg_release() {
# Fall back to architecture heuristic
if [[ -z "$url_match" ]]; then
for u in $assets; do
if [[ "$u" =~ ($arch|amd64|x86_64|aarch64|arm64).*\.deb$ ]]; then
url_match="$u"
break
if [[ "${arch,,}" =~ ^(amd64|x86_64)$ ]]; then
[[ "$u" =~ (amd64|x86_64).*\.deb$ ]] || continue
elif [[ "${arch,,}" =~ ^(arm64|aarch64)$ ]]; then
[[ "$u" =~ (arm64|aarch64).*\.deb$ ]] || continue
fi
url_match="$u"
break
done
fi
@@ -3024,7 +3038,11 @@ _gh_scan_older_releases() {
done)
fi
if [[ "$has_match" != "true" ]]; then
has_match=$(echo "$releases_list" | jq -r ".[$i].assets[].browser_download_url" | grep -qE "($arch|amd64|x86_64|aarch64|arm64).*\.deb$" && echo true)
if [[ "${arch,,}" =~ ^(amd64|x86_64)$ ]]; then
has_match=$(echo "$releases_list" | jq -r ".[$i].assets[].browser_download_url" | grep -qE '(amd64|x86_64).*\.deb$' && echo true)
elif [[ "${arch,,}" =~ ^(arm64|aarch64)$ ]]; then
has_match=$(echo "$releases_list" | jq -r ".[$i].assets[].browser_download_url" | grep -qE '(arm64|aarch64).*\.deb$' && echo true)
fi
fi
if [[ "$has_match" != "true" ]]; then
has_match=$(echo "$releases_list" | jq -r ".[$i].assets[].browser_download_url" | grep -qE '\.deb$' && echo true)
@@ -3222,10 +3240,13 @@ function fetch_and_deploy_gh_release() {
# If no match via explicit pattern, fall back to architecture heuristic
if [[ -z "$url_match" ]]; then
for u in $assets; do
if [[ "$u" =~ ($arch|amd64|x86_64|aarch64|arm64).*\.deb$ ]]; then
url_match="$u"
break
if [[ "${arch,,}" =~ ^(amd64|x86_64)$ ]]; then
[[ "$u" =~ (amd64|x86_64).*\.deb$ ]] || continue
elif [[ "${arch,,}" =~ ^(arm64|aarch64)$ ]]; then
[[ "$u" =~ (arm64|aarch64).*\.deb$ ]] || continue
fi
url_match="$u"
break
done
fi
@@ -3256,10 +3277,13 @@ function fetch_and_deploy_gh_release() {
fi
if [[ -z "$url_match" ]]; then
for u in $assets; do
if [[ "$u" =~ ($arch|amd64|x86_64|aarch64|arm64).*\.deb$ ]]; then
url_match="$u"
break
if [[ "${arch,,}" =~ ^(amd64|x86_64)$ ]]; then
[[ "$u" =~ (amd64|x86_64).*\.deb$ ]] || continue
elif [[ "${arch,,}" =~ ^(arm64|aarch64)$ ]]; then
[[ "$u" =~ (arm64|aarch64).*\.deb$ ]] || continue
fi
url_match="$u"
break
done
fi
if [[ -z "$url_match" ]]; then
@@ -5142,7 +5166,7 @@ current_ip="$(get_current_ip)"
if [[ -z "$current_ip" ]]; then
echo "[ERROR] Could not detect local IP" >&2
exit 1
exit 123
fi
if [[ -f "$IP_FILE" ]]; then
@@ -5643,20 +5667,20 @@ function setup_mongodb() {
# - Handles Debian Trixie libaio1t64 transition
#
# Variables:
# USE_MYSQL_REPO - Set to "true" to use official MySQL repository
# (default: false, uses distro packages)
# USE_MYSQL_REPO - Use official MySQL repository (default: true)
# Set to "false" to use distro packages instead
# MYSQL_VERSION - MySQL version to install when using official repo
# (e.g. 8.0, 8.4) (default: 8.0)
#
# Examples:
# setup_mysql # Uses distro package (recommended)
# USE_MYSQL_REPO=true setup_mysql # Uses official MySQL repo
# USE_MYSQL_REPO=true MYSQL_VERSION="8.4" setup_mysql # Specific version
# setup_mysql # Uses official MySQL repo, 8.0
# MYSQL_VERSION="8.4" setup_mysql # Specific version from MySQL repo
# USE_MYSQL_REPO=false setup_mysql # Uses distro package instead
# ------------------------------------------------------------------------------
function setup_mysql() {
local MYSQL_VERSION="${MYSQL_VERSION:-8.0}"
local USE_MYSQL_REPO="${USE_MYSQL_REPO:-false}"
local USE_MYSQL_REPO="${USE_MYSQL_REPO:-true}"
local DISTRO_ID DISTRO_CODENAME
DISTRO_ID=$(awk -F= '/^ID=/{print $2}' /etc/os-release | tr -d '"')
DISTRO_CODENAME=$(awk -F= '/^VERSION_CODENAME=/{print $2}' /etc/os-release)
@@ -6357,21 +6381,21 @@ EOF
# - Restores dumped data post-upgrade
#
# Variables:
# USE_PGDG_REPO - Set to "true" to use official PGDG repository
# (default: false, uses distro packages)
# USE_PGDG_REPO - Use official PGDG repository (default: true)
# Set to "false" to use distro packages instead
# PG_VERSION - Major PostgreSQL version (e.g. 15, 16) (default: 16)
# PG_MODULES - Comma-separated list of modules (e.g. "postgis,contrib")
#
# Examples:
# setup_postgresql # Uses distro package (recommended)
# USE_PGDG_REPO=true setup_postgresql # Uses official PGDG repo
# USE_PGDG_REPO=true PG_VERSION="17" setup_postgresql # Specific version from PGDG
# setup_postgresql # Uses PGDG repo, PG 16
# PG_VERSION="17" setup_postgresql # Specific version from PGDG
# USE_PGDG_REPO=false setup_postgresql # Uses distro package instead
# ------------------------------------------------------------------------------
function setup_postgresql() {
local PG_VERSION="${PG_VERSION:-16}"
local PG_MODULES="${PG_MODULES:-}"
local USE_PGDG_REPO="${USE_PGDG_REPO:-false}"
local USE_PGDG_REPO="${USE_PGDG_REPO:-true}"
local DISTRO_ID DISTRO_CODENAME
DISTRO_ID=$(awk -F= '/^ID=/{print $2}' /etc/os-release | tr -d '"')
DISTRO_CODENAME=$(awk -F= '/^VERSION_CODENAME=/{print $2}' /etc/os-release)
@@ -6489,14 +6513,13 @@ function setup_postgresql() {
local SUITE
case "$DISTRO_CODENAME" in
trixie | forky | sid)
if verify_repo_available "https://apt.postgresql.org/pub/repos/apt" "trixie-pgdg"; then
SUITE="trixie-pgdg"
else
SUITE="bookworm-pgdg"
msg_warn "PGDG repo not available for ${DISTRO_CODENAME}, falling back to distro packages"
USE_PGDG_REPO=false setup_postgresql
return $?
fi
;;
*)
SUITE=$(get_fallback_suite "$DISTRO_ID" "$DISTRO_CODENAME" "https://apt.postgresql.org/pub/repos/apt")
@@ -7989,7 +8012,7 @@ EOF
# ------------------------------------------------------------------------------
function fetch_and_deploy_from_url() {
local url="$1"
local directory="$2"
local directory="${2:-}"
if [[ -z "$url" ]]; then
msg_error "URL parameter is required"
@@ -8121,422 +8144,3 @@ function fetch_and_deploy_from_url() {
msg_ok "Successfully deployed archive to $directory"
return 0
}
function fetch_and_deploy_gl_release() {
local app="$1"
local repo="$2"
local mode="${3:-tarball}"
local version="${var_appversion:-${4:-latest}}"
local target="${5:-/opt/$app}"
local asset_pattern="${6:-}"
if [[ -z "$app" ]]; then
app="${repo##*/}"
if [[ -z "$app" ]]; then
msg_error "fetch_and_deploy_gl_release requires app name or valid repo"
return 1
fi
fi
local app_lc=$(echo "${app,,}" | tr -d ' ')
local version_file="$HOME/.${app_lc}"
local api_timeout="--connect-timeout 10 --max-time 60"
local download_timeout="--connect-timeout 15 --max-time 900"
local current_version=""
[[ -f "$version_file" ]] && current_version=$(<"$version_file")
ensure_dependencies jq
local repo_encoded
repo_encoded=$(python3 -c "import urllib.parse, sys; print(urllib.parse.quote(sys.argv[1], safe=''))" "$repo" 2>/dev/null \
|| echo "$repo" | sed 's|/|%2F|g')
local api_base="https://gitlab.com/api/v4/projects/$repo_encoded/releases"
local api_url
if [[ "$version" != "latest" ]]; then
api_url="$api_base/$version"
else
api_url="$api_base?per_page=1&order_by=released_at&sort=desc"
fi
local header=()
[[ -n "${GITLAB_TOKEN:-}" ]] && header=(-H "PRIVATE-TOKEN: $GITLAB_TOKEN")
local max_retries=3 retry_delay=2 attempt=1 success=false http_code
while ((attempt <= max_retries)); do
http_code=$(curl $api_timeout -sSL -w "%{http_code}" -o /tmp/gl_rel.json "${header[@]}" "$api_url" 2>/dev/null) || true
if [[ "$http_code" == "200" ]]; then
success=true
break
elif [[ "$http_code" == "429" ]]; then
if ((attempt < max_retries)); then
msg_warn "GitLab API rate limit hit, retrying in ${retry_delay}s... (attempt $attempt/$max_retries)"
sleep "$retry_delay"
retry_delay=$((retry_delay * 2))
fi
else
sleep "$retry_delay"
fi
((attempt++))
done
if ! $success; then
if [[ "$http_code" == "401" ]]; then
msg_error "GitLab API authentication failed (HTTP 401)."
if [[ -n "${GITLAB_TOKEN:-}" ]]; then
msg_error "Your GITLAB_TOKEN appears to be invalid or expired."
else
msg_error "The repository may require authentication. Try: export GITLAB_TOKEN=\"glpat-your_token\""
fi
elif [[ "$http_code" == "404" ]]; then
msg_error "GitLab project or release not found (HTTP 404)."
msg_error "Ensure '$repo' is correct and the project is accessible."
elif [[ "$http_code" == "429" ]]; then
msg_error "GitLab API rate limit exceeded (HTTP 429)."
msg_error "To increase the limit, export a GitLab token before running the script:"
msg_error " export GITLAB_TOKEN=\"glpat-your_token_here\""
elif [[ "$http_code" == "000" || -z "$http_code" ]]; then
msg_error "GitLab API connection failed (no response)."
msg_error "Check your network/DNS: curl -sSL https://gitlab.com/api/v4/version"
else
msg_error "Failed to fetch release metadata (HTTP $http_code)"
fi
return 1
fi
local json tag_name
json=$(</tmp/gl_rel.json)
if [[ "$version" == "latest" ]]; then
json=$(echo "$json" | jq '.[0] // empty')
if [[ -z "$json" || "$json" == "null" ]]; then
msg_error "No releases found for $repo on GitLab"
return 1
fi
fi
tag_name=$(echo "$json" | jq -r '.tag_name // empty')
if [[ -z "$tag_name" ]]; then
msg_error "Could not determine tag name from release metadata"
return 1
fi
[[ "$tag_name" =~ ^v[0-9] ]] && version="${tag_name:1}" || version="$tag_name"
local version_safe="${version//\//-}"
if [[ "$current_version" == "$version" ]]; then
$STD msg_ok "$app is already up-to-date (v$version)"
return 0
fi
local tmpdir
tmpdir=$(mktemp -d) || return 1
local filename=""
msg_info "Fetching GitLab release: $app ($version)"
_gl_asset_urls() {
local release_json="$1"
echo "$release_json" | jq -r '
(.assets.links // [])[] | .direct_asset_url // .url
'
}
### Tarball Mode ###
if [[ "$mode" == "tarball" || "$mode" == "source" ]]; then
local direct_tarball_url="https://gitlab.com/$repo/-/archive/$tag_name/${app_lc}-${version_safe}.tar.gz"
filename="${app_lc}-${version_safe}.tar.gz"
curl $download_timeout -fsSL "${header[@]}" -o "$tmpdir/$filename" "$direct_tarball_url" || {
msg_error "Download failed: $direct_tarball_url"
rm -rf "$tmpdir"
return 1
}
mkdir -p "$target"
if [[ "${CLEAN_INSTALL:-0}" == "1" ]]; then
rm -rf "${target:?}/"*
fi
tar --no-same-owner -xzf "$tmpdir/$filename" -C "$tmpdir" || {
msg_error "Failed to extract tarball"
rm -rf "$tmpdir"
return 1
}
local unpack_dir
unpack_dir=$(find "$tmpdir" -mindepth 1 -maxdepth 1 -type d | head -n1)
shopt -s dotglob nullglob
cp -r "$unpack_dir"/* "$target/"
shopt -u dotglob nullglob
### Binary Mode ###
elif [[ "$mode" == "binary" ]]; then
local arch
arch=$(dpkg --print-architecture 2>/dev/null || uname -m)
[[ "$arch" == "x86_64" ]] && arch="amd64"
[[ "$arch" == "aarch64" ]] && arch="arm64"
local assets url_match=""
assets=$(_gl_asset_urls "$json")
if [[ -n "$asset_pattern" ]]; then
for u in $assets; do
case "${u##*/}" in
$asset_pattern)
url_match="$u"
break
;;
esac
done
fi
if [[ -z "$url_match" ]]; then
for u in $assets; do
if [[ "$u" =~ ($arch|amd64|x86_64|aarch64|arm64).*\.deb$ ]]; then
url_match="$u"
break
fi
done
fi
if [[ -z "$url_match" ]]; then
for u in $assets; do
[[ "$u" =~ \.deb$ ]] && url_match="$u" && break
done
fi
if [[ -z "$url_match" ]]; then
local fallback_json
if fallback_json=$(_gl_scan_older_releases "$repo" "$repo_encoded" "https://gitlab.com" "binary" "$asset_pattern" "$tag_name"); then
json="$fallback_json"
tag_name=$(echo "$json" | jq -r '.tag_name // empty')
[[ "$tag_name" =~ ^v[0-9] ]] && version="${tag_name:1}" || version="$tag_name"
msg_info "Fetching GitLab release: $app ($version)"
assets=$(_gl_asset_urls "$json")
if [[ -n "$asset_pattern" ]]; then
for u in $assets; do
case "${u##*/}" in $asset_pattern)
url_match="$u"; break ;;
esac
done
fi
if [[ -z "$url_match" ]]; then
for u in $assets; do
[[ "$u" =~ ($arch|amd64|x86_64|aarch64|arm64).*\.deb$ ]] && url_match="$u" && break
done
fi
if [[ -z "$url_match" ]]; then
for u in $assets; do
[[ "$u" =~ \.deb$ ]] && url_match="$u" && break
done
fi
fi
fi
if [[ -z "$url_match" ]]; then
msg_error "No suitable .deb asset found for $app"
rm -rf "$tmpdir"
return 1
fi
filename="${url_match##*/}"
curl $download_timeout -fsSL "${header[@]}" -o "$tmpdir/$filename" "$url_match" || {
msg_error "Download failed: $url_match"
rm -rf "$tmpdir"
return 1
}
chmod 644 "$tmpdir/$filename"
local dpkg_opts=""
[[ "${DPKG_FORCE_CONFOLD:-}" == "1" ]] && dpkg_opts="-o Dpkg::Options::=--force-confold"
[[ "${DPKG_FORCE_CONFNEW:-}" == "1" ]] && dpkg_opts="-o Dpkg::Options::=--force-confnew"
DEBIAN_FRONTEND=noninteractive SYSTEMD_OFFLINE=1 $STD apt install -y $dpkg_opts "$tmpdir/$filename" || {
SYSTEMD_OFFLINE=1 $STD dpkg -i "$tmpdir/$filename" || {
msg_error "Both apt and dpkg installation failed"
rm -rf "$tmpdir"
return 1
}
}
### Prebuild Mode ###
elif [[ "$mode" == "prebuild" ]]; then
local pattern="${6%\"}"
pattern="${pattern#\"}"
[[ -z "$pattern" ]] && {
msg_error "Mode 'prebuild' requires 6th parameter (asset filename pattern)"
rm -rf "$tmpdir"
return 1
}
local asset_url=""
for u in $(_gl_asset_urls "$json"); do
filename_candidate="${u##*/}"
case "$filename_candidate" in
$pattern)
asset_url="$u"
break
;;
esac
done
if [[ -z "$asset_url" ]]; then
local fallback_json
if fallback_json=$(_gl_scan_older_releases "$repo" "$repo_encoded" "https://gitlab.com" "prebuild" "$pattern" "$tag_name"); then
json="$fallback_json"
tag_name=$(echo "$json" | jq -r '.tag_name // empty')
[[ "$tag_name" =~ ^v[0-9] ]] && version="${tag_name:1}" || version="$tag_name"
msg_info "Fetching GitLab release: $app ($version)"
for u in $(_gl_asset_urls "$json"); do
filename_candidate="${u##*/}"
case "$filename_candidate" in $pattern)
asset_url="$u"; break ;;
esac
done
fi
fi
[[ -z "$asset_url" ]] && {
msg_error "No asset matching '$pattern' found"
rm -rf "$tmpdir"
return 1
}
filename="${asset_url##*/}"
curl $download_timeout -fsSL "${header[@]}" -o "$tmpdir/$filename" "$asset_url" || {
msg_error "Download failed: $asset_url"
rm -rf "$tmpdir"
return 1
}
local unpack_tmp
unpack_tmp=$(mktemp -d)
mkdir -p "$target"
if [[ "${CLEAN_INSTALL:-0}" == "1" ]]; then
rm -rf "${target:?}/"*
fi
if [[ "$filename" == *.zip ]]; then
ensure_dependencies unzip
unzip -q "$tmpdir/$filename" -d "$unpack_tmp" || {
msg_error "Failed to extract ZIP archive"
rm -rf "$tmpdir" "$unpack_tmp"
return 1
}
elif [[ "$filename" == *.tar.* || "$filename" == *.tgz || "$filename" == *.txz ]]; then
tar --no-same-owner -xf "$tmpdir/$filename" -C "$unpack_tmp" || {
msg_error "Failed to extract TAR archive"
rm -rf "$tmpdir" "$unpack_tmp"
return 1
}
else
msg_error "Unsupported archive format: $filename"
rm -rf "$tmpdir" "$unpack_tmp"
return 1
fi
local top_entries inner_dir
top_entries=$(find "$unpack_tmp" -mindepth 1 -maxdepth 1)
if [[ "$(echo "$top_entries" | wc -l)" -eq 1 && -d "$top_entries" ]]; then
inner_dir="$top_entries"
shopt -s dotglob nullglob
if compgen -G "$inner_dir/*" >/dev/null; then
cp -r "$inner_dir"/* "$target/" || {
msg_error "Failed to copy contents from $inner_dir to $target"
rm -rf "$tmpdir" "$unpack_tmp"
return 1
}
else
msg_error "Inner directory is empty: $inner_dir"
rm -rf "$tmpdir" "$unpack_tmp"
return 1
fi
shopt -u dotglob nullglob
else
shopt -s dotglob nullglob
if compgen -G "$unpack_tmp/*" >/dev/null; then
cp -r "$unpack_tmp"/* "$target/" || {
msg_error "Failed to copy contents to $target"
rm -rf "$tmpdir" "$unpack_tmp"
return 1
}
else
msg_error "Unpacked archive is empty"
rm -rf "$tmpdir" "$unpack_tmp"
return 1
fi
shopt -u dotglob nullglob
fi
### Singlefile Mode ###
elif [[ "$mode" == "singlefile" ]]; then
local pattern="${6%\"}"
pattern="${pattern#\"}"
[[ -z "$pattern" ]] && {
msg_error "Mode 'singlefile' requires 6th parameter (asset filename pattern)"
rm -rf "$tmpdir"
return 1
}
local asset_url=""
for u in $(_gl_asset_urls "$json"); do
filename_candidate="${u##*/}"
case "$filename_candidate" in
$pattern)
asset_url="$u"
break
;;
esac
done
if [[ -z "$asset_url" ]]; then
local fallback_json
if fallback_json=$(_gl_scan_older_releases "$repo" "$repo_encoded" "https://gitlab.com" "singlefile" "$pattern" "$tag_name"); then
json="$fallback_json"
tag_name=$(echo "$json" | jq -r '.tag_name // empty')
[[ "$tag_name" =~ ^v[0-9] ]] && version="${tag_name:1}" || version="$tag_name"
msg_info "Fetching GitLab release: $app ($version)"
for u in $(_gl_asset_urls "$json"); do
filename_candidate="${u##*/}"
case "$filename_candidate" in $pattern)
asset_url="$u"; break ;;
esac
done
fi
fi
[[ -z "$asset_url" ]] && {
msg_error "No asset matching '$pattern' found"
rm -rf "$tmpdir"
return 1
}
filename="${asset_url##*/}"
mkdir -p "$target"
local use_filename="${USE_ORIGINAL_FILENAME:-false}"
local target_file="$app"
[[ "$use_filename" == "true" ]] && target_file="$filename"
curl $download_timeout -fsSL "${header[@]}" -o "$target/$target_file" "$asset_url" || {
msg_error "Download failed: $asset_url"
rm -rf "$tmpdir"
return 1
}
if [[ "$target_file" != *.jar && -f "$target/$target_file" ]]; then
chmod +x "$target/$target_file"
fi
else
msg_error "Unknown mode: $mode"
rm -rf "$tmpdir"
return 1
fi
echo "$version" >"$version_file"
msg_ok "Deployed: $app ($version)"
rm -rf "$tmpdir"
}

View File

@@ -1,5 +1,5 @@
# Copyright (c) 2021-2026 community-scripts ORG
# License: MIT | https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/LICENSE
# License: MIT | https://raw.githubusercontent.com/community-scripts/ProxmoxVED/arm64-dev-build/LICENSE
set -euo pipefail
SPINNER_PID=""
@@ -35,7 +35,7 @@ load_functions() {
get_header() {
local app_name=$(echo "${APP,,}" | tr ' ' '-')
local app_type=${APP_TYPE:-vm}
local header_url="https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/${app_type}/headers/${app_name}"
local header_url="https://raw.githubusercontent.com/community-scripts/ProxmoxVED/arm64-dev-build/${app_type}/headers/${app_name}"
local local_header_path="/usr/local/community-scripts/headers/${app_type}/${app_name}"
mkdir -p "$(dirname "$local_header_path")"
@@ -190,7 +190,7 @@ silent() {
if [[ $rc -ne 0 ]]; then
# Source explain_exit_code if needed
if ! declare -f explain_exit_code >/dev/null 2>&1; then
source <(curl -fsSL "$COMMUNITY_SCRIPTS_URL/misc/error_handler.func") 2>/dev/null || true
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/arm64-dev-build/misc/error_handler.func) 2>/dev/null || true
fi
local explanation=""
@@ -244,7 +244,7 @@ curl_handler() {
if [[ -z "$url" ]]; then
msg_error "no valid url or option entered for curl_handler"
exit 1
exit 64
fi
$STD msg_info "Fetching: $url"
@@ -273,7 +273,7 @@ curl_handler() {
rm -f /tmp/curl_error.log
fi
__curl_err_handler "$exit_code" "$url" "$curl_stderr"
exit 1 # hard exit if exit_code is not 0
exit "$exit_code"
fi
$STD printf "\r\033[K${INFO}${YW}Retry $attempt/$max_retries in ${delay}s...${CL}" >&2
@@ -316,7 +316,7 @@ __curl_err_handler() {
esac
[[ -n "$curl_msg" ]] && printf "%s\n" "$curl_msg" >&2
exit 1
exit "$exit_code"
}
# ------------------------------------------------------------------------------
@@ -331,7 +331,7 @@ shell_check() {
msg_error "Your default shell is currently not set to Bash. To use these scripts, please switch to the Bash shell."
echo -e "\nExiting..."
sleep 2
exit
exit 103
fi
}
@@ -352,11 +352,11 @@ clear_line() {
#
# - Determines if script should run in verbose mode
# - Checks VERBOSE and var_verbose variables
# - Also returns true if not running in TTY (pipe/redirect scenario)
# - Note: Non-TTY (pipe) scenarios are handled separately in msg_info()
# ------------------------------------------------------------------------------
is_verbose_mode() {
local verbose="${VERBOSE:-${var_verbose:-no}}"
[[ "$verbose" != "no" || ! -t 2 ]]
[[ "$verbose" != "no" ]]
}
### dev spinner ###
@@ -552,7 +552,7 @@ check_root() {
msg_error "Please run this script as root."
echo -e "\nExiting..."
sleep 2
exit
exit 104
fi
}
@@ -562,7 +562,7 @@ pve_check() {
echo -e "Requires Proxmox Virtual Environment Version 8.1 - 8.4 or 9.0 - 9.1."
echo -e "Exiting..."
sleep 2
exit
exit 105
fi
}
@@ -572,21 +572,21 @@ arch_check() {
echo -e "\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \n"
echo -e "Exiting..."
sleep 2
exit
exit 106
fi
}
exit_script() {
clear
echo -e "\n${CROSS}${RD}User exited script${CL}\n"
exit
exit 0
}
check_hostname_conflict() {
local hostname="$1"
if qm list | awk '{print $2}' | grep -qx "$hostname"; then
msg_error "Hostname $hostname already in use by another VM."
exit 1
exit 206
fi
}
@@ -595,7 +595,7 @@ set_description() {
cat <<EOF
<div align='center'>
<a href='https://Helper-Scripts.com' target='_blank' rel='noopener noreferrer'>
<img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
<img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVED/arm64-dev-build/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
</a>
<h2 style='font-size: 24px; margin: 20px 0;'>${NSAPP} VM</h2>