mirror of
https://github.com/community-scripts/ProxmoxVE.git
synced 2026-03-03 18:35:55 +00:00
* fix: send telemetry BEFORE log collection in signal handlers - Swap ensure_log_on_host/post_update_to_api order in on_interrupt, on_terminate, api_exit_script, and inline SIGHUP/SIGINT/SIGTERM traps - For signal exits (>128): send telemetry immediately, then best-effort log collection - Add 2>/dev/null || true to all I/O in signal handlers to prevent SIGPIPE - Fix on_exit: exit_code=0 now reports 'done' instead of 'failed 1' - Root cause: pct pull hangs on dying containers blocked telemetry updates, leaving 595+ records stuck in 'installing' daily * feat: add execution_id to all telemetry payloads - Generate EXECUTION_ID from RANDOM_UUID in variables() - Export EXECUTION_ID to container environment - Add execution_id field to all 8 API payloads in api.func - Add execution_id to post_progress_to_api in install.func and alpine-install.func - Fallback to RANDOM_UUID when EXECUTION_ID not set (backward compat) * fix: correct telemetry type values for PVE and addon scripts - PVE scripts (tools/pve/*): change type 'tool' -> 'pve' - Addon scripts (tools/addon/*): fix 4 scripts that wrongly used 'tool' -> 'addon' (netdata, add-tailscale-lxc, add-netbird-lxc, all-templates) - api.func: post_tool_to_api sends type='pve', default fallback 'pve' - Aligns with PocketBase categories: lxc, vm, pve, addon * fix: persist diagnostics opt-in inside containers for addon telemetry - install.func + alpine-install.func: create /usr/local/community-scripts/diagnostics inside the container when DIAGNOSTICS=yes (from build.func export) - Enables addon scripts running later inside containers to find the opt-in - Update init_tool_telemetry default type from 'tool' to 'pve' * refactor: clean up diagnostics/telemetry opt-in system - diagnostics_check(): deduplicate heredoc (was 2x 22 lines), improve whiptail text with clear what/what-not collected, add telemetry + privacy links - diagnostics_menu(): better UX with current status, clear enable/disable buttons, note about existing containers - variables(): change DIAGNOSTICS default from 'yes' to 'no' (safe: no telemetry before user consents via diagnostics_check) - install.func + alpine-install.func: persist BOTH yes AND no in container so opt-out is explicit (not just missing file = no) - Fix typo 'menue' -> 'menu' in config file comments * fix: no pre-selection in telemetry dialog, link to telemetry-service README - Add --defaultno so 'No, opt out' is focused by default (user must Tab to Yes) - Change privacy link from discussions/1836 to telemetry-service#privacy--compliance * fix: use radiolist for telemetry dialog (no pre-selection) - Replace --yesno with --radiolist: user must actively SPACE-select an option - Both options start as OFF (no pre-selection) - Cancel/Exit defaults to 'no' (opt-out) * simplify: inline telemetry dialog text like other whiptail dialogs * improve: telemetry dialog with more detail, link to PRIVACY.md - Add what we collect / don't collect sections back to dialog - Link to telemetry-service/docs/PRIVACY.md instead of README anchor - Update config file comment with same link
199 lines
5.9 KiB
Bash
199 lines
5.9 KiB
Bash
#!/usr/bin/env bash
|
||
|
||
# Copyright (c) 2021-2026 community-scripts ORG
|
||
# Author: MickLesk
|
||
# Adapted from onethree7 (https://github.com/onethree7/proxmox-lxc-privilege-converter)
|
||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||
|
||
if ! command -v curl >/dev/null 2>&1; 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://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/misc/core.func)
|
||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||
load_functions
|
||
declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "pve-privilege-converter" "pve"
|
||
|
||
set -euo pipefail
|
||
shopt -s inherit_errexit nullglob
|
||
|
||
APP="PVE-Privilege-Converter"
|
||
APP_TYPE="tools"
|
||
header_info "$APP"
|
||
|
||
check_root() {
|
||
if [[ $EUID -ne 0 ]]; then
|
||
msg_error "Script must be run as root"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
select_target_storage_and_container_id() {
|
||
echo -e "\nSelect target storage for restored container:\n"
|
||
mapfile -t target_storages < <(pvesm status --content images | awk 'NR > 1 {print $1}')
|
||
for i in "${!target_storages[@]}"; do
|
||
printf "%s) %s\n" "$((i + 1))" "${target_storages[$i]}"
|
||
done
|
||
|
||
while true; do
|
||
read -rp "Enter number of target storage: " choice
|
||
if [[ "$choice" =~ ^[0-9]+$ ]] && ((choice >= 1 && choice <= ${#target_storages[@]})); then
|
||
TARGET_STORAGE="${target_storages[$((choice - 1))]}"
|
||
break
|
||
else
|
||
echo "Invalid selection. Try again."
|
||
fi
|
||
done
|
||
|
||
next_free_id=$(pvesh get /cluster/nextid 2>/dev/null || echo 999)
|
||
[[ "$next_free_id" =~ ^[0-9]+$ ]] || next_free_id=999
|
||
|
||
echo ""
|
||
read -rp "Suggested next free container ID: $next_free_id. Enter new container ID [default: $next_free_id]: " NEW_CONTAINER_ID
|
||
NEW_CONTAINER_ID="${NEW_CONTAINER_ID:-$next_free_id}"
|
||
}
|
||
|
||
select_container() {
|
||
mapfile -t lxc_list_raw < <(pct list | awk 'NR > 1 {print $1, $3}')
|
||
lxc_list=()
|
||
for entry in "${lxc_list_raw[@]}"; do
|
||
[[ -n "$entry" ]] && lxc_list+=("$entry")
|
||
done
|
||
|
||
if [[ ${#lxc_list[@]} -eq 0 ]]; then
|
||
msg_error "No containers found"
|
||
exit 1
|
||
fi
|
||
|
||
PS3="Enter number of container to convert: "
|
||
select opt in "${lxc_list[@]}"; do
|
||
if [[ -n "$opt" ]]; then
|
||
read -r CONTAINER_ID CONTAINER_NAME <<<"$opt"
|
||
CONTAINER_NAME="${CONTAINER_NAME:-}"
|
||
break
|
||
else
|
||
echo "Invalid selection. Try again."
|
||
fi
|
||
done
|
||
}
|
||
|
||
select_backup_storage() {
|
||
echo -e "Select backup storage (temporary vzdump location):"
|
||
mapfile -t backup_storages < <(pvesm status --content backup | awk 'NR > 1 {print $1}')
|
||
local PS3="Enter number of backup storage: "
|
||
|
||
select opt in "${backup_storages[@]}"; do
|
||
if [[ -n "$opt" ]]; then
|
||
BACKUP_STORAGE="$opt"
|
||
break
|
||
else
|
||
echo "Invalid selection. Try again."
|
||
fi
|
||
done
|
||
}
|
||
|
||
backup_container() {
|
||
msg_custom "📦" "\e[36m" "Backing up container $CONTAINER_ID"
|
||
vzdump_output=$(mktemp)
|
||
vzdump "$CONTAINER_ID" --compress zstd --storage "$BACKUP_STORAGE" --mode snapshot | tee "$vzdump_output"
|
||
BACKUP_PATH=$(awk '/tar.zst/ {print $NF}' "$vzdump_output" | tr -d "'")
|
||
if [ -z "$BACKUP_PATH" ] || ! grep -q "Backup job finished successfully" "$vzdump_output"; then
|
||
rm "$vzdump_output"
|
||
msg_error "Backup failed"
|
||
exit 1
|
||
fi
|
||
rm "$vzdump_output"
|
||
msg_ok "Backup complete: $BACKUP_PATH"
|
||
}
|
||
|
||
perform_conversion() {
|
||
if pct config "$CONTAINER_ID" | grep -q 'unprivileged: 1'; then
|
||
UNPRIVILEGED=true
|
||
else
|
||
UNPRIVILEGED=false
|
||
fi
|
||
|
||
msg_custom "🛠️" "\e[36m" "Restoring as $(if $UNPRIVILEGED; then echo privileged; else echo unprivileged; fi) container"
|
||
restore_opts=("$NEW_CONTAINER_ID" "$BACKUP_PATH" --storage "$TARGET_STORAGE")
|
||
if $UNPRIVILEGED; then
|
||
restore_opts+=(--unprivileged false)
|
||
else
|
||
restore_opts+=(--unprivileged)
|
||
fi
|
||
|
||
if pct restore "${restore_opts[@]}" -ignore-unpack-errors 1; then
|
||
msg_ok "Conversion successful"
|
||
else
|
||
msg_error "Conversion failed"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
manage_states() {
|
||
read -rp "Shutdown source and start new container? [Y/n]: " answer
|
||
answer=${answer:-Y}
|
||
if [[ $answer =~ ^[Yy] ]]; then
|
||
pct shutdown "$CONTAINER_ID"
|
||
for i in {1..36}; do
|
||
sleep 5
|
||
! pct status "$CONTAINER_ID" | grep -q running && break
|
||
done
|
||
if pct status "$CONTAINER_ID" | grep -q running; then
|
||
read -rp "Timeout reached. Force shutdown? [Y/n]: " force
|
||
if [[ ${force:-Y} =~ ^[Yy] ]]; then
|
||
pkill -9 -f "lxc-start -F -n $CONTAINER_ID"
|
||
fi
|
||
fi
|
||
pct start "$NEW_CONTAINER_ID"
|
||
msg_ok "New container started"
|
||
else
|
||
msg_custom "ℹ️" "\e[36m" "Skipped container state change"
|
||
fi
|
||
}
|
||
|
||
cleanup_files() {
|
||
read -rp "Delete backup archive? [$BACKUP_PATH] [Y/n]: " cleanup
|
||
if [[ ${cleanup:-Y} =~ ^[Yy] ]]; then
|
||
rm -f "$BACKUP_PATH" && msg_ok "Removed backup archive"
|
||
else
|
||
msg_custom "💾" "\e[36m" "Retained backup archive"
|
||
fi
|
||
}
|
||
|
||
summary() {
|
||
local conversion="Unknown"
|
||
if [[ -n "${UNPRIVILEGED:-}" ]]; then
|
||
if $UNPRIVILEGED; then
|
||
conversion="Unprivileged → Privileged"
|
||
else
|
||
conversion="Privileged → Unprivileged"
|
||
fi
|
||
fi
|
||
|
||
echo
|
||
msg_custom "📄" "\e[36m" "Summary:"
|
||
msg_custom " " "\e[36m" "$(printf "%-22s %s" "Original Container:" "$CONTAINER_ID ($CONTAINER_NAME)")"
|
||
msg_custom " " "\e[36m" "$(printf "%-22s %s" "Backup Storage:" "$BACKUP_STORAGE")"
|
||
msg_custom " " "\e[36m" "$(printf "%-22s %s" "Target Storage:" "$TARGET_STORAGE")"
|
||
msg_custom " " "\e[36m" "$(printf "%-22s %s" "Backup Path:" "$BACKUP_PATH")"
|
||
msg_custom " " "\e[36m" "$(printf "%-22s %s" "New Container ID:" "$NEW_CONTAINER_ID")"
|
||
msg_custom " " "\e[36m" "$(printf "%-22s %s" "Privilege Conversion:" "$conversion")"
|
||
echo
|
||
}
|
||
|
||
main() {
|
||
header_info
|
||
check_root
|
||
select_container
|
||
select_backup_storage
|
||
backup_container
|
||
select_target_storage_and_container_id
|
||
perform_conversion
|
||
manage_states
|
||
cleanup_files
|
||
summary
|
||
}
|
||
|
||
main
|