mirror of
https://github.com/community-scripts/ProxmoxVE.git
synced 2026-04-05 18:13:50 +00:00
Merge branch 'main' into refactor/turnkey-modernize
This commit is contained in:
27
CHANGELOG.md
27
CHANGELOG.md
@@ -426,35 +426,20 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## 2026-03-23
|
## 2026-03-24
|
||||||
|
|
||||||
### 🆕 New Scripts
|
### 🆕 New Scripts
|
||||||
|
|
||||||
- Alpine-Borgbackup-Server ([#13219](https://github.com/community-scripts/ProxmoxVE/pull/13219))
|
- Homebrew (Addon) ([#13249](https://github.com/community-scripts/ProxmoxVE/pull/13249))
|
||||||
|
- NextExplorer ([#13252](https://github.com/community-scripts/ProxmoxVE/pull/13252))
|
||||||
|
|
||||||
|
## 2026-03-23
|
||||||
|
|
||||||
### 🚀 Updated Scripts
|
### 🚀 Updated Scripts
|
||||||
|
|
||||||
- NginxProxyManager: build OpenResty from source via GitHub releases [@MickLesk](https://github.com/MickLesk) ([#13134](https://github.com/community-scripts/ProxmoxVE/pull/13134))
|
|
||||||
|
|
||||||
- #### 🐞 Bug Fixes
|
|
||||||
|
|
||||||
- Tracearr: modify service restart and modify build ressources [@MickLesk](https://github.com/MickLesk) ([#13230](https://github.com/community-scripts/ProxmoxVE/pull/13230))
|
|
||||||
|
|
||||||
- #### ✨ New Features
|
|
||||||
|
|
||||||
- Kometa: optimize config.yml sed patterns, add Quickstart integration [@MickLesk](https://github.com/MickLesk) ([#13198](https://github.com/community-scripts/ProxmoxVE/pull/13198))
|
|
||||||
|
|
||||||
- #### 🔧 Refactor
|
- #### 🔧 Refactor
|
||||||
|
|
||||||
- Refactor: nginxproxymanager update and OpenResty flow [@MickLesk](https://github.com/MickLesk) ([#13216](https://github.com/community-scripts/ProxmoxVE/pull/13216))
|
- core: harden shell scripts against injection and insecure permissions [@MickLesk](https://github.com/MickLesk) ([#13239](https://github.com/community-scripts/ProxmoxVE/pull/13239))
|
||||||
- Refactor: PartDB [@MickLesk](https://github.com/MickLesk) ([#13229](https://github.com/community-scripts/ProxmoxVE/pull/13229))
|
|
||||||
|
|
||||||
### 💾 Core
|
|
||||||
|
|
||||||
- #### 🔧 Refactor
|
|
||||||
|
|
||||||
- core: alpine - Improve network connectivity and DNS checks [@MickLesk](https://github.com/MickLesk) ([#13222](https://github.com/community-scripts/ProxmoxVE/pull/13222))
|
|
||||||
- core: allow /31 and /32 CIDR with out-of-subnet gateway [@MickLesk](https://github.com/MickLesk) ([#13231](https://github.com/community-scripts/ProxmoxVE/pull/13231))
|
|
||||||
|
|
||||||
## 2026-03-22
|
## 2026-03-22
|
||||||
|
|
||||||
|
|||||||
6
ct/headers/nextexplorer
Normal file
6
ct/headers/nextexplorer
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
__ ______ __
|
||||||
|
____ ___ _ __/ /_/ ____/ ______ / /___ ________ _____
|
||||||
|
/ __ \/ _ \| |/_/ __/ __/ | |/_/ __ \/ / __ \/ ___/ _ \/ ___/
|
||||||
|
/ / / / __/> </ /_/ /____> </ /_/ / / /_/ / / / __/ /
|
||||||
|
/_/ /_/\___/_/|_|\__/_____/_/|_/ .___/_/\____/_/ \___/_/
|
||||||
|
/_/
|
||||||
76
ct/nextexplorer.sh
Normal file
76
ct/nextexplorer.sh
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
|
||||||
|
|
||||||
|
# Copyright (c) 2021-2026 community-scripts ORG
|
||||||
|
# Author: vhsdream
|
||||||
|
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||||
|
# Source: https://github.com/nxzai/nextExplorer
|
||||||
|
|
||||||
|
APP="nextExplorer"
|
||||||
|
var_tags="${var_tags:-files;documents}"
|
||||||
|
var_cpu="${var_cpu:-2}"
|
||||||
|
var_ram="${var_ram:-3072}"
|
||||||
|
var_disk="${var_disk:-8}"
|
||||||
|
var_os="${var_os:-debian}"
|
||||||
|
var_version="${var_version:-13}"
|
||||||
|
var_unprivileged="${var_unprivileged:-1}"
|
||||||
|
|
||||||
|
header_info "$APP"
|
||||||
|
variables
|
||||||
|
color
|
||||||
|
catch_errors
|
||||||
|
|
||||||
|
function update_script() {
|
||||||
|
header_info
|
||||||
|
check_container_storage
|
||||||
|
check_container_resources
|
||||||
|
|
||||||
|
if [[ ! -d /opt/nextExplorer ]]; then
|
||||||
|
msg_error "No ${APP} Installation Found!"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
NODE_VERSION="24" setup_nodejs
|
||||||
|
|
||||||
|
if check_for_gh_release "nextExplorer" "nxzai/nextExplorer"; then
|
||||||
|
msg_info "Stopping nextExplorer"
|
||||||
|
$STD systemctl stop nextexplorer
|
||||||
|
msg_ok "Stopped nextExplorer"
|
||||||
|
|
||||||
|
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "nextExplorer" "nxzai/nextExplorer" "tarball" "latest" "/opt/nextExplorer"
|
||||||
|
|
||||||
|
msg_info "Updating nextExplorer"
|
||||||
|
APP_DIR="/opt/nextExplorer/app"
|
||||||
|
mkdir -p "$APP_DIR"
|
||||||
|
cd /opt/nextExplorer
|
||||||
|
export NODE_ENV=production
|
||||||
|
$STD npm ci --omit=dev --workspace backend
|
||||||
|
mv node_modules "$APP_DIR"
|
||||||
|
mv backend/{src,package.json} "$APP_DIR"
|
||||||
|
unset NODE_ENV
|
||||||
|
export NODE_ENV=development
|
||||||
|
$STD npm ci --workspace frontend
|
||||||
|
$STD npm run -w frontend build -- --sourcemap false
|
||||||
|
unset NODE_ENV
|
||||||
|
mv frontend/dist/ "$APP_DIR"/src/public
|
||||||
|
chown -R explorer:explorer "$APP_DIR" /etc/nextExplorer
|
||||||
|
sed -i "\|version|s|$(jq -cr '.version' ${APP_DIR}/package.json)|$(cat ~/.nextexplorer)|" "$APP_DIR"/package.json
|
||||||
|
sed -i 's/app.js/server.js/' /etc/systemd/system/nextexplorer.service && systemctl daemon-reload
|
||||||
|
msg_ok "Updated nextExplorer"
|
||||||
|
|
||||||
|
msg_info "Starting nextExplorer"
|
||||||
|
$STD systemctl start nextexplorer
|
||||||
|
msg_ok "Started nextExplorer"
|
||||||
|
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}:3000${CL}"
|
||||||
@@ -102,6 +102,7 @@ EOF
|
|||||||
msg_ok "Built OpenResty"
|
msg_ok "Built OpenResty"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
cd /root
|
||||||
if [ -d /opt/certbot ]; then
|
if [ -d /opt/certbot ]; then
|
||||||
msg_info "Updating Certbot"
|
msg_info "Updating Certbot"
|
||||||
$STD /opt/certbot/bin/pip install --upgrade pip setuptools wheel
|
$STD /opt/certbot/bin/pip install --upgrade pip setuptools wheel
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ fetch_and_deploy_gh_release "kometa-quickstart" "Kometa-Team/Quickstart" "tarbal
|
|||||||
msg_info "Installing Kometa Quickstart"
|
msg_info "Installing Kometa Quickstart"
|
||||||
cd /opt/kometa-quickstart
|
cd /opt/kometa-quickstart
|
||||||
$STD uv venv /opt/kometa-quickstart/.venv
|
$STD uv venv /opt/kometa-quickstart/.venv
|
||||||
$STD /opt/kometa-quickstart/.venv/bin/python -m pip install -r requirements.txt
|
$STD uv pip install -r requirements.txt -p /opt/kometa-quickstart/.venv/bin/python
|
||||||
msg_ok "Installed Kometa Quickstart"
|
msg_ok "Installed Kometa Quickstart"
|
||||||
|
|
||||||
msg_info "Creating Service"
|
msg_info "Creating Service"
|
||||||
|
|||||||
164
install/nextexplorer-install.sh
Normal file
164
install/nextexplorer-install.sh
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Copyright (c) 2021-2026 community-scripts ORG
|
||||||
|
# Author: vhsdream
|
||||||
|
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||||
|
# Source: https://github.com/nxzai/nextExplorer
|
||||||
|
|
||||||
|
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 \
|
||||||
|
ripgrep \
|
||||||
|
imagemagick \
|
||||||
|
ffmpeg \
|
||||||
|
libva-drm2 \
|
||||||
|
libva2 \
|
||||||
|
mesa-va-drivers \
|
||||||
|
vainfo
|
||||||
|
msg_ok "Installed Dependencies"
|
||||||
|
|
||||||
|
NODE_VERSION="24" setup_nodejs
|
||||||
|
|
||||||
|
fetch_and_deploy_gh_release "nextExplorer" "nxzai/nextExplorer" "tarball" "latest" "/opt/nextExplorer"
|
||||||
|
|
||||||
|
msg_info "Building nextExplorer"
|
||||||
|
APP_DIR="/opt/nextExplorer/app"
|
||||||
|
LOCAL_IP="$(hostname -I | awk '{print $1}')"
|
||||||
|
mkdir -p "$APP_DIR"
|
||||||
|
mkdir -p /etc/nextExplorer
|
||||||
|
cd /opt/nextExplorer
|
||||||
|
export NODE_ENV=production
|
||||||
|
$STD npm ci --omit=dev --workspace backend
|
||||||
|
mv node_modules "$APP_DIR"
|
||||||
|
mv backend/{src,package.json} "$APP_DIR"
|
||||||
|
unset NODE_ENV
|
||||||
|
|
||||||
|
export NODE_ENV=development
|
||||||
|
export NODE_OPTIONS="--max-old-space-size=2048"
|
||||||
|
$STD npm ci --workspace frontend
|
||||||
|
$STD npm run -w frontend build -- --sourcemap false
|
||||||
|
unset NODE_ENV
|
||||||
|
mv frontend/dist/ "$APP_DIR"/src/public
|
||||||
|
msg_ok "Built nextExplorer"
|
||||||
|
|
||||||
|
msg_info "Configuring nextExplorer"
|
||||||
|
SECRET=$(openssl rand -hex 32)
|
||||||
|
cat <<EOF >/etc/nextExplorer/.env
|
||||||
|
NODE_ENV=production
|
||||||
|
PORT=3000
|
||||||
|
|
||||||
|
VOLUME_ROOT=/mnt
|
||||||
|
CONFIG_DIR=/etc/nextExplorer
|
||||||
|
CACHE_DIR=/etc/nextExplorer/cache
|
||||||
|
# USER_ROOT=
|
||||||
|
|
||||||
|
PUBLIC_URL=${LOCAL_IP}:3000
|
||||||
|
# TRUST_PROXY=
|
||||||
|
# CORS_ORIGINS=
|
||||||
|
|
||||||
|
TERMINAL_ENABLED=false
|
||||||
|
|
||||||
|
LOG_LEVEL=info
|
||||||
|
DEBUG=false
|
||||||
|
ENABLE_HTTP_LOGGING=false
|
||||||
|
|
||||||
|
AUTH_ENABLED=true
|
||||||
|
AUTH_MODE=both
|
||||||
|
SESSION_SECRET="${SECRET}"
|
||||||
|
# AUTH_MAX_FAILED=
|
||||||
|
# AUTH_LOCK_MINUTES=
|
||||||
|
# AUTH_USER_EMAIL=
|
||||||
|
# AUTH_USER_PASSWORD=
|
||||||
|
|
||||||
|
# OIDC_ENABLED=
|
||||||
|
# OIDC_ISSUER=
|
||||||
|
# OIDC_AUTHORIZATION_URL=
|
||||||
|
# OIDC_TOKEN_URL=
|
||||||
|
# OIDC_USERINFO_URL=
|
||||||
|
# OIDC_CLIENT_ID=
|
||||||
|
# OIDC_CLIENT_SECRET=
|
||||||
|
# OIDC_CALLBACK_URL=
|
||||||
|
# OIDC_LOGOUT_URL=
|
||||||
|
# OIDC_SCOPES=
|
||||||
|
# OIDC_AUTO_CREATE_USERS=true
|
||||||
|
|
||||||
|
# SEARCH_DEEP=
|
||||||
|
# SEARCH_RIPGREP=
|
||||||
|
# SEARCH_MAX_FILESIZE=
|
||||||
|
|
||||||
|
# ONLYOFFICE_URL=
|
||||||
|
# ONLYOFFICE_SECRET=
|
||||||
|
# ONLYOFFICE_LANG=
|
||||||
|
# ONLYOFFICE_FORCE_SAVE=
|
||||||
|
# ONLYOFFICE_FILE_EXTENSIONS=
|
||||||
|
|
||||||
|
# COLLABORA_URL=
|
||||||
|
# COLLABORA_DISCOVERY_URL=
|
||||||
|
# COLLABORA_SECRET=
|
||||||
|
# COLLABORA_LANG=
|
||||||
|
# COLLABORA_FILE_EXTENSIONS=
|
||||||
|
|
||||||
|
SHOW_VOLUME_USAGE=true
|
||||||
|
# USER_DIR_ENABLED=
|
||||||
|
# SKIP_HOME=
|
||||||
|
|
||||||
|
# EDITOR_EXTENSIONS=
|
||||||
|
|
||||||
|
# FFMPEG_PATH=
|
||||||
|
# FFPROBE_PATH=
|
||||||
|
|
||||||
|
## Hardware acceleration
|
||||||
|
# FFMPEG_HWACCEL=vaapi
|
||||||
|
# FFMPEG_HWACCEL_DEVICE=/dev/dri/renderD128
|
||||||
|
# FFMPEG_HWACCEL_OUTPUT_FORMAT=nv12
|
||||||
|
|
||||||
|
FAVORITES_DEFAULT_ICON=outline.StarIcon
|
||||||
|
|
||||||
|
SHARES_ENABLED=true
|
||||||
|
# SHARES_TOKEN_LENGTH=10
|
||||||
|
# SHARES_MAX_PER_USER=100
|
||||||
|
# SHARES_DEFAULT_EXPIRY_DAYS=30
|
||||||
|
# SHARES_GUEST_SESSION_HOURS=24
|
||||||
|
# SHARES_ALLOW_PASSWORD=true
|
||||||
|
# SHARES_ALLOW_ANONYMOUS=true
|
||||||
|
EOF
|
||||||
|
chmod 600 /etc/nextExplorer/.env
|
||||||
|
$STD useradd -U -s /usr/sbin/nologin -m -d /home/explorer explorer
|
||||||
|
chown -R explorer:explorer "$APP_DIR" /etc/nextExplorer
|
||||||
|
sed -i "\|version|s|$(jq -cr '.version' ${APP_DIR}/package.json)|$(cat ~/.nextexplorer)|" "$APP_DIR"/package.json
|
||||||
|
msg_ok "Configured nextExplorer"
|
||||||
|
|
||||||
|
msg_info "Creating nextExplorer Service"
|
||||||
|
cat <<EOF >/etc/systemd/system/nextexplorer.service
|
||||||
|
[Unit]
|
||||||
|
Description=nextExplorer Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=explorer
|
||||||
|
Group=explorer
|
||||||
|
WorkingDirectory=/opt/nextExplorer/app
|
||||||
|
EnvironmentFile=/etc/nextExplorer/.env
|
||||||
|
ExecStart=/usr/bin/node ./src/server.js
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
$STD systemctl enable -q --now nextexplorer
|
||||||
|
msg_ok "Created nextExplorer Service"
|
||||||
|
|
||||||
|
motd_ssh
|
||||||
|
customize
|
||||||
|
cleanup_lxc
|
||||||
@@ -35,7 +35,7 @@ cd Shinobi
|
|||||||
gitVersionNumber=$(git rev-parse HEAD)
|
gitVersionNumber=$(git rev-parse HEAD)
|
||||||
theDateRightNow=$(date)
|
theDateRightNow=$(date)
|
||||||
touch version.json
|
touch version.json
|
||||||
chmod 777 version.json
|
chmod 644 version.json
|
||||||
echo '{"Product" : "'"Shinobi"'" , "Branch" : "'"master"'" , "Version" : "'"$gitVersionNumber"'" , "Date" : "'"$theDateRightNow"'" , "Repository" : "'"https://gitlab.com/Shinobi-Systems/Shinobi.git"'"}' >version.json
|
echo '{"Product" : "'"Shinobi"'" , "Branch" : "'"master"'" , "Version" : "'"$gitVersionNumber"'" , "Date" : "'"$theDateRightNow"'" , "Repository" : "'"https://gitlab.com/Shinobi-Systems/Shinobi.git"'"}' >version.json
|
||||||
msg_ok "Cloned Shinobi"
|
msg_ok "Cloned Shinobi"
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ fetch_and_deploy_gh_release "tasmoadmin" "TasmoAdmin/TasmoAdmin" "prebuild" "lat
|
|||||||
msg_info "Configuring TasmoAdmin"
|
msg_info "Configuring TasmoAdmin"
|
||||||
rm -rf /etc/php/8.4/apache2/conf.d/10-opcache.ini
|
rm -rf /etc/php/8.4/apache2/conf.d/10-opcache.ini
|
||||||
chown -R www-data:www-data /var/www/tasmoadmin
|
chown -R www-data:www-data /var/www/tasmoadmin
|
||||||
chmod 777 /var/www/tasmoadmin/tmp /var/www/tasmoadmin/data
|
chmod 775 /var/www/tasmoadmin/tmp /var/www/tasmoadmin/data
|
||||||
cat <<EOF >/etc/apache2/sites-available/tasmoadmin.conf
|
cat <<EOF >/etc/apache2/sites-available/tasmoadmin.conf
|
||||||
<VirtualHost *:9999>
|
<VirtualHost *:9999>
|
||||||
ServerName tasmoadmin
|
ServerName tasmoadmin
|
||||||
|
|||||||
@@ -348,10 +348,10 @@ explain_exit_code() {
|
|||||||
json_escape() {
|
json_escape() {
|
||||||
# Escape a string for safe JSON embedding using awk (handles any input size).
|
# 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
|
# Pipeline: strip ANSI → remove control chars → escape \ " TAB → join lines with \n
|
||||||
printf '%s' "$1" \
|
printf '%s' "$1" |
|
||||||
| sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' \
|
sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' |
|
||||||
| tr -d '\000-\010\013\014\016-\037\177\r' \
|
tr -d '\000-\010\013\014\016-\037\177\r' |
|
||||||
| awk '
|
awk '
|
||||||
BEGIN { ORS = "" }
|
BEGIN { ORS = "" }
|
||||||
{
|
{
|
||||||
gsub(/\\/, "\\\\") # backslash → \\
|
gsub(/\\/, "\\\\") # backslash → \\
|
||||||
@@ -627,7 +627,7 @@ post_to_api() {
|
|||||||
|
|
||||||
[[ "${DEV_MODE:-}" == "true" ]] && echo "[DEBUG] post_to_api() DIAGNOSTICS=$DIAGNOSTICS RANDOM_UUID=$RANDOM_UUID NSAPP=$NSAPP" >&2
|
[[ "${DEV_MODE:-}" == "true" ]] && echo "[DEBUG] post_to_api() DIAGNOSTICS=$DIAGNOSTICS RANDOM_UUID=$RANDOM_UUID NSAPP=$NSAPP" >&2
|
||||||
|
|
||||||
# Set type for later status updates (respect pre-set value, e.g. "turnkey")
|
# Set type for later status updates (preserve if already set, e.g. turnkey)
|
||||||
TELEMETRY_TYPE="${TELEMETRY_TYPE:-lxc}"
|
TELEMETRY_TYPE="${TELEMETRY_TYPE:-lxc}"
|
||||||
|
|
||||||
local pve_version=""
|
local pve_version=""
|
||||||
@@ -692,6 +692,7 @@ EOF
|
|||||||
# Send initial "installing" record with retry.
|
# Send initial "installing" record with retry.
|
||||||
# This record MUST exist for all subsequent updates to succeed.
|
# This record MUST exist for all subsequent updates to succeed.
|
||||||
local http_code="" attempt
|
local http_code="" attempt
|
||||||
|
local _post_success=false
|
||||||
for attempt in 1 2 3; do
|
for attempt in 1 2 3; do
|
||||||
if [[ "${DEV_MODE:-}" == "true" ]]; then
|
if [[ "${DEV_MODE:-}" == "true" ]]; then
|
||||||
http_code=$(curl -sS -w "%{http_code}" -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
|
http_code=$(curl -sS -w "%{http_code}" -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
|
||||||
@@ -703,11 +704,19 @@ EOF
|
|||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "$JSON_PAYLOAD" -o /dev/null 2>/dev/null) || http_code="000"
|
-d "$JSON_PAYLOAD" -o /dev/null 2>/dev/null) || http_code="000"
|
||||||
fi
|
fi
|
||||||
[[ "$http_code" =~ ^2[0-9]{2}$ ]] && break
|
if [[ "$http_code" =~ ^2[0-9]{2}$ ]]; then
|
||||||
|
_post_success=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
[[ "$attempt" -lt 3 ]] && sleep 1
|
[[ "$attempt" -lt 3 ]] && sleep 1
|
||||||
done
|
done
|
||||||
|
|
||||||
POST_TO_API_DONE=true
|
# Only mark done if at least one attempt succeeded.
|
||||||
|
# If all 3 failed, POST_TO_API_DONE stays false so post_update_to_api
|
||||||
|
# and on_exit() know the initial record was never created.
|
||||||
|
# The server has fallback logic to create a new record on status updates,
|
||||||
|
# so subsequent calls can still succeed even without the initial record.
|
||||||
|
POST_TO_API_DONE=${_post_success}
|
||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
@@ -798,15 +807,19 @@ EOF
|
|||||||
|
|
||||||
# Send initial "installing" record with retry (must succeed for updates to work)
|
# Send initial "installing" record with retry (must succeed for updates to work)
|
||||||
local http_code="" attempt
|
local http_code="" attempt
|
||||||
|
local _post_success=false
|
||||||
for attempt in 1 2 3; do
|
for attempt in 1 2 3; do
|
||||||
http_code=$(curl -sS -w "%{http_code}" -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
|
http_code=$(curl -sS -w "%{http_code}" -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "$JSON_PAYLOAD" -o /dev/null 2>/dev/null) || http_code="000"
|
-d "$JSON_PAYLOAD" -o /dev/null 2>/dev/null) || http_code="000"
|
||||||
[[ "$http_code" =~ ^2[0-9]{2}$ ]] && break
|
if [[ "$http_code" =~ ^2[0-9]{2}$ ]]; then
|
||||||
|
_post_success=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
[[ "$attempt" -lt 3 ]] && sleep 1
|
[[ "$attempt" -lt 3 ]] && sleep 1
|
||||||
done
|
done
|
||||||
|
|
||||||
POST_TO_API_DONE=true
|
POST_TO_API_DONE=${_post_success}
|
||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
@@ -1083,6 +1096,12 @@ EOF
|
|||||||
# - Used to group errors in dashboard
|
# - Used to group errors in dashboard
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
categorize_error() {
|
categorize_error() {
|
||||||
|
# Allow build.func to override category based on log analysis (exit code 1 subclassification)
|
||||||
|
if [[ -n "${ERROR_CATEGORY_OVERRIDE:-}" ]]; then
|
||||||
|
echo "$ERROR_CATEGORY_OVERRIDE"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
local code="$1"
|
local code="$1"
|
||||||
case "$code" in
|
case "$code" in
|
||||||
# Network errors (curl/wget)
|
# Network errors (curl/wget)
|
||||||
|
|||||||
105
misc/build.func
105
misc/build.func
@@ -221,6 +221,14 @@ update_motd_ip() {
|
|||||||
local current_hostname="$(hostname)"
|
local current_hostname="$(hostname)"
|
||||||
local current_ip="$(hostname -I | awk '{print $1}')"
|
local current_ip="$(hostname -I | awk '{print $1}')"
|
||||||
|
|
||||||
|
# Escape sed special chars in replacement strings (& \ |)
|
||||||
|
current_os="${current_os//\\/\\\\}"
|
||||||
|
current_os="${current_os//&/\\&}"
|
||||||
|
current_hostname="${current_hostname//\\/\\\\}"
|
||||||
|
current_hostname="${current_hostname//&/\\&}"
|
||||||
|
current_ip="${current_ip//\\/\\\\}"
|
||||||
|
current_ip="${current_ip//&/\\&}"
|
||||||
|
|
||||||
# Update only if values actually changed
|
# Update only if values actually changed
|
||||||
if ! grep -q "OS:.*$current_os" "$PROFILE_FILE" 2>/dev/null; then
|
if ! grep -q "OS:.*$current_os" "$PROFILE_FILE" 2>/dev/null; then
|
||||||
sed -i "s|OS:.*|OS: \${GN}$current_os\${CL}\\\"|" "$PROFILE_FILE"
|
sed -i "s|OS:.*|OS: \${GN}$current_os\${CL}\\\"|" "$PROFILE_FILE"
|
||||||
@@ -4076,8 +4084,8 @@ EOF
|
|||||||
if [ "$var_os" == "alpine" ]; then
|
if [ "$var_os" == "alpine" ]; then
|
||||||
sleep 3
|
sleep 3
|
||||||
pct exec "$CTID" -- /bin/sh -c 'cat <<EOF >/etc/apk/repositories
|
pct exec "$CTID" -- /bin/sh -c 'cat <<EOF >/etc/apk/repositories
|
||||||
http://dl-cdn.alpinelinux.org/alpine/latest-stable/main
|
https://dl-cdn.alpinelinux.org/alpine/latest-stable/main
|
||||||
http://dl-cdn.alpinelinux.org/alpine/latest-stable/community
|
https://dl-cdn.alpinelinux.org/alpine/latest-stable/community
|
||||||
EOF'
|
EOF'
|
||||||
pct exec "$CTID" -- ash -c "apk add bash newt curl openssh nano mc ncurses jq" >>"$BUILD_LOG" 2>&1 || {
|
pct exec "$CTID" -- ash -c "apk add bash newt curl openssh nano mc ncurses jq" >>"$BUILD_LOG" 2>&1 || {
|
||||||
msg_error "Failed to install base packages in Alpine container"
|
msg_error "Failed to install base packages in Alpine container"
|
||||||
@@ -4086,7 +4094,9 @@ EOF'
|
|||||||
else
|
else
|
||||||
sleep 3
|
sleep 3
|
||||||
LANG=${LANG:-en_US.UTF-8}
|
LANG=${LANG:-en_US.UTF-8}
|
||||||
pct exec "$CTID" -- bash -c "sed -i \"/$LANG/ s/^# //\" /etc/locale.gen"
|
local LANG_ESC="${LANG//./\\.}"
|
||||||
|
LANG_ESC="${LANG_ESC//|/\\|}"
|
||||||
|
pct exec "$CTID" -- bash -c "sed -i \"/$LANG_ESC/ s/^# //\" /etc/locale.gen"
|
||||||
pct exec "$CTID" -- bash -c "locale_line=\$(grep -v '^#' /etc/locale.gen | grep -E '^[a-zA-Z]' | awk '{print \$1}' | head -n 1) && \
|
pct exec "$CTID" -- bash -c "locale_line=\$(grep -v '^#' /etc/locale.gen | grep -E '^[a-zA-Z]' | awk '{print \$1}' | head -n 1) && \
|
||||||
echo LANG=\$locale_line >/etc/default/locale && \
|
echo LANG=\$locale_line >/etc/default/locale && \
|
||||||
locale-gen >/dev/null && \
|
locale-gen >/dev/null && \
|
||||||
@@ -4216,6 +4226,53 @@ EOF'
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Defense-in-depth: Ensure error handling stays disabled during recovery.
|
||||||
|
# Some functions (e.g. silent/$STD) unconditionally re-enable set -Eeuo pipefail
|
||||||
|
# and trap 'error_handler' ERR. If any code path above called such a function,
|
||||||
|
# the grep/sed pipelines below would trigger error_handler on non-match (exit 1).
|
||||||
|
set +Eeuo pipefail
|
||||||
|
trap - ERR
|
||||||
|
|
||||||
|
# --- Exit code 1 subclassification: analyze logs BEFORE telemetry call ---
|
||||||
|
# Exit code 1 is generic ("General error"). Analyze logs to determine the
|
||||||
|
# real error category so telemetry gets a useful classification instead of "shell".
|
||||||
|
local is_oom=false
|
||||||
|
local is_network_issue=false
|
||||||
|
local is_apt_issue=false
|
||||||
|
local is_cmd_not_found=false
|
||||||
|
local is_disk_full=false
|
||||||
|
|
||||||
|
if [[ $install_exit_code -eq 1 && -f "$combined_log" ]]; then
|
||||||
|
if grep -qiE 'E: Unable to|E: Package|E: Failed to fetch|dpkg.*error|broken packages|unmet dependencies|dpkg --configure -a' "$combined_log"; then
|
||||||
|
is_apt_issue=true
|
||||||
|
fi
|
||||||
|
if grep -qiE 'Cannot allocate memory|Out of memory|oom-killer|Killed process|JavaScript heap' "$combined_log"; then
|
||||||
|
is_oom=true
|
||||||
|
fi
|
||||||
|
if grep -qiE 'Could not resolve|DNS|Connection refused|Network is unreachable|No route to host|Temporary failure resolving|Failed to fetch' "$combined_log"; then
|
||||||
|
is_network_issue=true
|
||||||
|
fi
|
||||||
|
if grep -qiE ': command not found|No such file or directory.*/s?bin/' "$combined_log"; then
|
||||||
|
is_cmd_not_found=true
|
||||||
|
fi
|
||||||
|
if grep -qiE 'ENOSPC|no space left on device|Disk quota exceeded|errno -28' "$combined_log"; then
|
||||||
|
is_disk_full=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set override for categorize_error() so telemetry gets the real category
|
||||||
|
if [[ "$is_apt_issue" == true ]]; then
|
||||||
|
export ERROR_CATEGORY_OVERRIDE="dependency"
|
||||||
|
elif [[ "$is_oom" == true ]]; then
|
||||||
|
export ERROR_CATEGORY_OVERRIDE="resource"
|
||||||
|
elif [[ "$is_network_issue" == true ]]; then
|
||||||
|
export ERROR_CATEGORY_OVERRIDE="network"
|
||||||
|
elif [[ "$is_disk_full" == true ]]; then
|
||||||
|
export ERROR_CATEGORY_OVERRIDE="storage"
|
||||||
|
elif [[ "$is_cmd_not_found" == true ]]; then
|
||||||
|
export ERROR_CATEGORY_OVERRIDE="dependency"
|
||||||
|
fi
|
||||||
|
|
||||||
# Report failure to telemetry API (now with log available on host)
|
# Report failure to telemetry API (now with log available on host)
|
||||||
# NOTE: Do NOT use msg_info/spinner here — the background spinner process
|
# NOTE: Do NOT use msg_info/spinner here — the background spinner process
|
||||||
# causes SIGTSTP in non-interactive shells (bash -c "$(curl ...)"), which
|
# causes SIGTSTP in non-interactive shells (bash -c "$(curl ...)"), which
|
||||||
@@ -4224,13 +4281,6 @@ EOF'
|
|||||||
post_update_to_api "failed" "$install_exit_code"
|
post_update_to_api "failed" "$install_exit_code"
|
||||||
$STD echo -e "${TAB}${CM:-✔} Failure reported"
|
$STD echo -e "${TAB}${CM:-✔} Failure reported"
|
||||||
|
|
||||||
# Defense-in-depth: Ensure error handling stays disabled during recovery.
|
|
||||||
# Some functions (e.g. silent/$STD) unconditionally re-enable set -Eeuo pipefail
|
|
||||||
# and trap 'error_handler' ERR. If any code path above called such a function,
|
|
||||||
# the grep/sed pipelines below would trigger error_handler on non-match (exit 1).
|
|
||||||
set +Eeuo pipefail
|
|
||||||
trap - ERR
|
|
||||||
|
|
||||||
# Show combined log location
|
# Show combined log location
|
||||||
if [[ -n "$CTID" && -n "${SESSION_ID:-}" ]]; then
|
if [[ -n "$CTID" && -n "${SESSION_ID:-}" ]]; then
|
||||||
msg_custom "📋" "${YW}" "Installation log: ${combined_log}"
|
msg_custom "📋" "${YW}" "Installation log: ${combined_log}"
|
||||||
@@ -4259,12 +4309,9 @@ EOF'
|
|||||||
# Prompt user for cleanup with 60s timeout
|
# Prompt user for cleanup with 60s timeout
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Detect error type for smart recovery options
|
# Extend error detection for non-exit-1 codes (exit 1 was already analyzed above)
|
||||||
local is_oom=false
|
# The is_* flags were set above for exit code 1 log analysis; here we add
|
||||||
local is_network_issue=false
|
# exit-code-specific detections for other codes.
|
||||||
local is_apt_issue=false
|
|
||||||
local is_cmd_not_found=false
|
|
||||||
local is_disk_full=false
|
|
||||||
local error_explanation=""
|
local error_explanation=""
|
||||||
if declare -f explain_exit_code >/dev/null 2>&1; then
|
if declare -f explain_exit_code >/dev/null 2>&1; then
|
||||||
error_explanation="$(explain_exit_code "$install_exit_code")"
|
error_explanation="$(explain_exit_code "$install_exit_code")"
|
||||||
@@ -4314,26 +4361,6 @@ EOF'
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Exit 1 subclassification: analyze logs to identify actual root cause
|
|
||||||
# Many exit 1 errors are actually APT, OOM, network, or command-not-found issues
|
|
||||||
if [[ $install_exit_code -eq 1 && -f "$combined_log" ]]; then
|
|
||||||
if grep -qiE 'E: Unable to|E: Package|E: Failed to fetch|dpkg.*error|broken packages|unmet dependencies|dpkg --configure -a' "$combined_log"; then
|
|
||||||
is_apt_issue=true
|
|
||||||
fi
|
|
||||||
if grep -qiE 'Cannot allocate memory|Out of memory|oom-killer|Killed process|JavaScript heap' "$combined_log"; then
|
|
||||||
is_oom=true
|
|
||||||
fi
|
|
||||||
if grep -qiE 'Could not resolve|DNS|Connection refused|Network is unreachable|No route to host|Temporary failure resolving|Failed to fetch' "$combined_log"; then
|
|
||||||
is_network_issue=true
|
|
||||||
fi
|
|
||||||
if grep -qiE ': command not found|No such file or directory.*/s?bin/' "$combined_log"; then
|
|
||||||
is_cmd_not_found=true
|
|
||||||
fi
|
|
||||||
if grep -qiE 'ENOSPC|no space left on device|Disk quota exceeded|errno -28' "$combined_log"; then
|
|
||||||
is_disk_full=true
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Show error explanation if available
|
# Show error explanation if available
|
||||||
if [[ -n "$error_explanation" ]]; then
|
if [[ -n "$error_explanation" ]]; then
|
||||||
echo -e "${TAB}${RD}Error: ${error_explanation}${CL}"
|
echo -e "${TAB}${RD}Error: ${error_explanation}${CL}"
|
||||||
@@ -4535,6 +4562,7 @@ EOF'
|
|||||||
|
|
||||||
if [[ $apt_retry_code -eq 0 ]]; then
|
if [[ $apt_retry_code -eq 0 ]]; then
|
||||||
msg_ok "Installation completed successfully after APT repair!"
|
msg_ok "Installation completed successfully after APT repair!"
|
||||||
|
INSTALL_COMPLETE=true
|
||||||
post_update_to_api "done" "0" "force"
|
post_update_to_api "done" "0" "force"
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
@@ -4759,6 +4787,10 @@ fix_gpu_gids() {
|
|||||||
pct stop "$CTID" >/dev/null 2>&1
|
pct stop "$CTID" >/dev/null 2>&1
|
||||||
sleep 1
|
sleep 1
|
||||||
|
|
||||||
|
# Validate GIDs are numeric before sed
|
||||||
|
[[ "$render_gid" =~ ^[0-9]+$ ]] || render_gid="104"
|
||||||
|
[[ "$video_gid" =~ ^[0-9]+$ ]] || video_gid="44"
|
||||||
|
|
||||||
# Update dev entries with correct GIDs
|
# Update dev entries with correct GIDs
|
||||||
sed -i.bak -E "s|(dev[0-9]+: /dev/dri/renderD[0-9]+),gid=[0-9]+|\1,gid=${render_gid}|g" "$LXC_CONFIG"
|
sed -i.bak -E "s|(dev[0-9]+: /dev/dri/renderD[0-9]+),gid=[0-9]+|\1,gid=${render_gid}|g" "$LXC_CONFIG"
|
||||||
sed -i -E "s|(dev[0-9]+: /dev/dri/card[0-9]+),gid=[0-9]+|\1,gid=${video_gid}|g" "$LXC_CONFIG"
|
sed -i -E "s|(dev[0-9]+: /dev/dri/card[0-9]+),gid=[0-9]+|\1,gid=${video_gid}|g" "$LXC_CONFIG"
|
||||||
@@ -5705,6 +5737,7 @@ EOF
|
|||||||
systemctl start ping-instances.service
|
systemctl start ping-instances.service
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
INSTALL_COMPLETE=true
|
||||||
post_update_to_api "done" "none"
|
post_update_to_api "done" "none"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -507,14 +507,23 @@ _stop_container_if_installing() {
|
|||||||
on_exit() {
|
on_exit() {
|
||||||
local exit_code=$?
|
local exit_code=$?
|
||||||
|
|
||||||
# Report orphaned "installing" records to telemetry API
|
# Report orphaned telemetry records
|
||||||
# Catches ALL exit paths: errors, signals, AND clean exits where
|
# Two scenarios handled:
|
||||||
# post_to_api was called but post_update_to_api was never called
|
# 1. POST_TO_API_DONE=true but POST_UPDATE_DONE=false: Record was created but
|
||||||
if [[ "${POST_TO_API_DONE:-}" == "true" && "${POST_UPDATE_DONE:-}" != "true" ]]; then
|
# never got a final status update → send abort/done now.
|
||||||
if [[ $exit_code -ne 0 ]]; then
|
# 2. POST_TO_API_DONE=false but DIAGNOSTICS=yes: Initial post failed (server
|
||||||
_send_abort_telemetry "$exit_code"
|
# unreachable/timeout), but the server has fallback create-on-update logic,
|
||||||
elif declare -f post_update_to_api >/dev/null 2>&1; then
|
# so a status update can still create the record. Worth one last try.
|
||||||
post_update_to_api "done" "0" 2>/dev/null || true
|
if [[ "${POST_UPDATE_DONE:-}" != "true" ]]; then
|
||||||
|
if [[ "${POST_TO_API_DONE:-}" == "true" || "${DIAGNOSTICS:-no}" == "yes" ]]; then
|
||||||
|
if [[ $exit_code -ne 0 ]]; then
|
||||||
|
_send_abort_telemetry "$exit_code"
|
||||||
|
elif [[ "${INSTALL_COMPLETE:-}" == "true" ]] && declare -f post_update_to_api >/dev/null 2>&1; then
|
||||||
|
# Only report success if the install was explicitly marked complete.
|
||||||
|
# Without this guard, early bailouts (e.g. user cancelled) with exit 0
|
||||||
|
# would be falsely reported as successful installations.
|
||||||
|
post_update_to_api "done" "0" 2>/dev/null || true
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -309,14 +309,14 @@ customize() {
|
|||||||
if [[ "$PASSWORD" == "" ]]; then
|
if [[ "$PASSWORD" == "" ]]; then
|
||||||
msg_info "Customizing Container"
|
msg_info "Customizing Container"
|
||||||
GETTY_OVERRIDE="/etc/systemd/system/container-getty@1.service.d/override.conf"
|
GETTY_OVERRIDE="/etc/systemd/system/container-getty@1.service.d/override.conf"
|
||||||
mkdir -p $(dirname $GETTY_OVERRIDE)
|
mkdir -p "$(dirname "$GETTY_OVERRIDE")"
|
||||||
cat <<EOF >$GETTY_OVERRIDE
|
cat <<EOF >"$GETTY_OVERRIDE"
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=
|
ExecStart=
|
||||||
ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud tty%I 115200,38400,9600 \$TERM
|
ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud tty%I 115200,38400,9600 \$TERM
|
||||||
EOF
|
EOF
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl restart $(basename $(dirname $GETTY_OVERRIDE) | sed 's/\.d//')
|
systemctl restart "$(basename "$(dirname "$GETTY_OVERRIDE")" | sed 's/\.d//')"
|
||||||
msg_ok "Customized Container"
|
msg_ok "Customized Container"
|
||||||
fi
|
fi
|
||||||
echo "bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)\"" >/usr/bin/update
|
echo "bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)\"" >/usr/bin/update
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ download_gpg_key() {
|
|||||||
|
|
||||||
# Process based on mode
|
# Process based on mode
|
||||||
if [[ "$mode" == "dearmor" ]]; then
|
if [[ "$mode" == "dearmor" ]]; then
|
||||||
if gpg --dearmor --yes -o "$output" <"$temp_key" 2>/dev/null; then
|
if gpg --dearmor --yes -o "$output" <"$temp_key" 2>/dev/null && [[ -s "$output" ]]; then
|
||||||
rm -f "$temp_key"
|
rm -f "$temp_key"
|
||||||
debug_log "GPG key installed (dearmored): $output"
|
debug_log "GPG key installed (dearmored): $output"
|
||||||
return 0
|
return 0
|
||||||
@@ -5192,7 +5192,7 @@ _setup_gpu_permissions() {
|
|||||||
for nvidia_dev in /dev/nvidia*; do
|
for nvidia_dev in /dev/nvidia*; do
|
||||||
[[ -e "$nvidia_dev" ]] && {
|
[[ -e "$nvidia_dev" ]] && {
|
||||||
chgrp video "$nvidia_dev" 2>/dev/null || true
|
chgrp video "$nvidia_dev" 2>/dev/null || true
|
||||||
chmod 666 "$nvidia_dev" 2>/dev/null || true
|
chmod 660 "$nvidia_dev" 2>/dev/null || true
|
||||||
}
|
}
|
||||||
done
|
done
|
||||||
if [[ -d /dev/nvidia-caps ]]; then
|
if [[ -d /dev/nvidia-caps ]]; then
|
||||||
@@ -5200,7 +5200,7 @@ _setup_gpu_permissions() {
|
|||||||
for caps_dev in /dev/nvidia-caps/*; do
|
for caps_dev in /dev/nvidia-caps/*; do
|
||||||
[[ -e "$caps_dev" ]] && {
|
[[ -e "$caps_dev" ]] && {
|
||||||
chgrp video "$caps_dev" 2>/dev/null || true
|
chgrp video "$caps_dev" 2>/dev/null || true
|
||||||
chmod 666 "$caps_dev" 2>/dev/null || true
|
chmod 660 "$caps_dev" 2>/dev/null || true
|
||||||
}
|
}
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
@@ -5217,7 +5217,8 @@ _setup_gpu_permissions() {
|
|||||||
|
|
||||||
# /dev/kfd permissions (AMD ROCm)
|
# /dev/kfd permissions (AMD ROCm)
|
||||||
if [[ -e /dev/kfd ]]; then
|
if [[ -e /dev/kfd ]]; then
|
||||||
chmod 666 /dev/kfd 2>/dev/null || true
|
chgrp render /dev/kfd 2>/dev/null || true
|
||||||
|
chmod 660 /dev/kfd 2>/dev/null || true
|
||||||
msg_info "AMD ROCm compute device configured"
|
msg_info "AMD ROCm compute device configured"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
173
tools/addon/homebrew.sh
Normal file
173
tools/addon/homebrew.sh
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Copyright (c) 2021-2026 community-scripts ORG
|
||||||
|
# Author: MorganCSIT | MickLesk (CanbiZ)
|
||||||
|
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||||
|
# Source: https://brew.sh | Github: https://github.com/Homebrew/brew
|
||||||
|
|
||||||
|
if ! command -v curl &>/dev/null; then
|
||||||
|
printf "\r\e[2K%b" '\033[93m Setup Source \033[m' >&2
|
||||||
|
apt-get update >/dev/null 2>&1
|
||||||
|
apt-get install -y curl >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
|
||||||
|
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
|
||||||
|
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
|
||||||
|
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||||
|
|
||||||
|
# Enable error handling
|
||||||
|
set -Eeuo pipefail
|
||||||
|
trap 'error_handler' ERR
|
||||||
|
load_functions
|
||||||
|
init_tool_telemetry "" "addon"
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# CONFIGURATION
|
||||||
|
# ==============================================================================
|
||||||
|
VERBOSE=${var_verbose:-no}
|
||||||
|
APP="homebrew"
|
||||||
|
APP_TYPE="tools"
|
||||||
|
INSTALL_PATH="/home/linuxbrew/.linuxbrew"
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# OS DETECTION
|
||||||
|
# ==============================================================================
|
||||||
|
if [[ -f "/etc/alpine-release" ]]; then
|
||||||
|
echo -e "${CROSS} Alpine is not supported by Homebrew. Exiting."
|
||||||
|
exit 1
|
||||||
|
elif grep -qE 'ID=debian|ID=ubuntu' /etc/os-release; then
|
||||||
|
OS="Debian"
|
||||||
|
else
|
||||||
|
echo -e "${CROSS} Unsupported OS detected. Exiting."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# UNINSTALL
|
||||||
|
# ==============================================================================
|
||||||
|
function uninstall() {
|
||||||
|
msg_info "Uninstalling Homebrew"
|
||||||
|
|
||||||
|
BREW_USER=$(awk -F: '$3 >= 1000 && $3 < 65534 { print $1; exit }' /etc/passwd)
|
||||||
|
if [[ -n "$BREW_USER" ]]; then
|
||||||
|
BREW_USER_HOME=$(getent passwd "$BREW_USER" | cut -d: -f6)
|
||||||
|
for rc_file in "$BREW_USER_HOME/.bashrc" "$BREW_USER_HOME/.profile"; do
|
||||||
|
if [[ -f "$rc_file" ]]; then
|
||||||
|
sed -i '/# Homebrew (Linuxbrew)/,/^fi$/d' "$rc_file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf /home/linuxbrew
|
||||||
|
rm -f /etc/profile.d/homebrew.sh
|
||||||
|
groupdel linuxbrew &>/dev/null || true
|
||||||
|
|
||||||
|
msg_ok "Homebrew has been uninstalled"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# INSTALL
|
||||||
|
# ==============================================================================
|
||||||
|
function install() {
|
||||||
|
msg_info "Detecting Non-Root User"
|
||||||
|
BREW_USER=$(awk -F: '$3 >= 1000 && $3 < 65534 { print $1; exit }' /etc/passwd)
|
||||||
|
if [[ -z "$BREW_USER" ]]; then
|
||||||
|
msg_warn "No non-root user found (uid >= 1000). Homebrew cannot run as root."
|
||||||
|
read -r -p "${TAB}Create a 'brew' user automatically? (y/N): " create_user_prompt
|
||||||
|
if [[ "${create_user_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||||
|
msg_info "Creating user 'brew'"
|
||||||
|
useradd -m -s /bin/bash brew
|
||||||
|
BREW_USER="brew"
|
||||||
|
msg_ok "Created user 'brew'"
|
||||||
|
else
|
||||||
|
msg_error "Cannot install Homebrew without a non-root user. Exiting."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
msg_ok "Detected User: $BREW_USER"
|
||||||
|
|
||||||
|
msg_info "Installing Dependencies"
|
||||||
|
$STD apt update
|
||||||
|
$STD apt install -y build-essential git file procps
|
||||||
|
msg_ok "Installed Dependencies"
|
||||||
|
|
||||||
|
msg_info "Setting Up Homebrew Prefix"
|
||||||
|
export PATH="/usr/sbin:$PATH"
|
||||||
|
groupadd -f linuxbrew
|
||||||
|
mkdir -p /home/linuxbrew/.linuxbrew
|
||||||
|
chown -R "$BREW_USER":linuxbrew /home/linuxbrew
|
||||||
|
chmod 2775 /home/linuxbrew
|
||||||
|
chmod 2775 /home/linuxbrew/.linuxbrew
|
||||||
|
usermod -aG linuxbrew "$BREW_USER"
|
||||||
|
msg_ok "Set Up Homebrew Prefix"
|
||||||
|
|
||||||
|
msg_info "Installing Homebrew"
|
||||||
|
$STD su - "$BREW_USER" -c 'NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
|
||||||
|
msg_ok "Installed Homebrew"
|
||||||
|
|
||||||
|
msg_info "Configuring Shell Integration"
|
||||||
|
cat <<'EOF' >/etc/profile.d/homebrew.sh
|
||||||
|
#!/bin/bash
|
||||||
|
if [ -d "/home/linuxbrew/.linuxbrew" ]; then
|
||||||
|
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
chmod +x /etc/profile.d/homebrew.sh
|
||||||
|
|
||||||
|
BREW_USER_HOME=$(getent passwd "$BREW_USER" | cut -d: -f6)
|
||||||
|
BREW_SHELL_BLOCK='\n# Homebrew (Linuxbrew)\nif [ -d "/home/linuxbrew/.linuxbrew" ]; then\n eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"\nfi'
|
||||||
|
for rc_file in "$BREW_USER_HOME/.bashrc" "$BREW_USER_HOME/.profile"; do
|
||||||
|
if ! grep -q 'linuxbrew' "$rc_file" 2>/dev/null; then
|
||||||
|
echo -e "$BREW_SHELL_BLOCK" >>"$rc_file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
msg_ok "Configured Shell Integration"
|
||||||
|
|
||||||
|
msg_info "Verifying Installation"
|
||||||
|
$STD su - "$BREW_USER" -c 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" && brew --version'
|
||||||
|
msg_ok "Homebrew Verified"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
msg_ok "Homebrew installed successfully"
|
||||||
|
msg_ok "Ready for user: ${BL}${BREW_USER}${CL}"
|
||||||
|
echo ""
|
||||||
|
echo -e "${TAB}${INFO} Usage: Switch to the brew user with a login shell:"
|
||||||
|
echo -e "${TAB} ${BL}su - ${BREW_USER}${CL}"
|
||||||
|
echo -e "${TAB} Then run: ${BL}brew install <package>${CL}"
|
||||||
|
echo -e "${TAB} Update with: ${BL}brew update${CL}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# MAIN
|
||||||
|
# ==============================================================================
|
||||||
|
header_info
|
||||||
|
|
||||||
|
if [[ -d "$INSTALL_PATH" ]]; then
|
||||||
|
msg_warn "Homebrew is already installed."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
read -r -p "${TAB}Uninstall Homebrew? (y/N): " uninstall_prompt
|
||||||
|
if [[ "${uninstall_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||||
|
uninstall
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg_warn "No action selected. Exiting."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fresh installation
|
||||||
|
msg_warn "Homebrew is not installed."
|
||||||
|
echo ""
|
||||||
|
echo -e "${TAB}${INFO} This will install:"
|
||||||
|
echo -e "${TAB} - Homebrew (Linuxbrew) package manager"
|
||||||
|
echo -e "${TAB} - Shell integration for the detected non-root user"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
read -r -p "${TAB}Install Homebrew? (y/N): " install_prompt
|
||||||
|
if [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||||
|
install
|
||||||
|
else
|
||||||
|
msg_warn "Installation cancelled. Exiting."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
@@ -150,7 +150,7 @@ function install() {
|
|||||||
curl -fsSL "https://raw.githubusercontent.com/runtipi/runtipi/master/scripts/install.sh" -o "install.sh"
|
curl -fsSL "https://raw.githubusercontent.com/runtipi/runtipi/master/scripts/install.sh" -o "install.sh"
|
||||||
chmod +x install.sh
|
chmod +x install.sh
|
||||||
$STD ./install.sh
|
$STD ./install.sh
|
||||||
chmod 666 /opt/runtipi/state/settings.json 2>/dev/null || true
|
chmod 660 /opt/runtipi/state/settings.json 2>/dev/null || true
|
||||||
rm -f /opt/install.sh
|
rm -f /opt/install.sh
|
||||||
msg_ok "Installed ${APP}"
|
msg_ok "Installed ${APP}"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user