mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-09-26 16:46:56 +00:00
ProxMenux modifies the host: it rewrites configuration files, installs packages, enables services. Until now nobody could say afterwards what had changed, and showing the script does not answer that question — a four-hundred-line function may alter two values, and the reader has no way to know which two. This adds the two halves of an answer. The change journal records what ProxMenux does as it does it. Eleven bash primitives capture the previous state, apply the change and record it in the same step, writing to a spool that the Monitor reads back. One hundred and thirteen functions across twenty-five scripts are instrumented, covering post-install, shared storage, security tooling, container conversions, disk operations and the PVE 8 to 9 upgrade path. The page shows the difference — rotate 7 becoming rotate 14 — and never the script. Restore and backup scripts are deliberately left out: a restore puts the host back to a state some other script already recorded. The Audit and reports page answers the other half: what state is this host in, regardless of who put it there. Forty-three checks across seven areas read the host and classify each result as critical, warning, observation, conformant, unverified or not applicable, with the evidence they read attached to each one. A declared policy lets the reader say what this particular host is expected to do — which guests must have a backup, which storages are essential — so the report judges the host against its own intent rather than a generic template. An inventory records the hardware, network and guest topology behind those readings, a comparison shows what moved between two runs, and six report profiles produce a printable document scoped to what the reader needs. Everything is available in the eight supported languages. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
288 lines
9.3 KiB
Bash
288 lines
9.3 KiB
Bash
#!/bin/bash
|
|
|
|
# ==========================================================
|
|
# ProxMenu - LXC Privileged to Unprivileged Converter
|
|
# ==========================================================
|
|
# Author : MacRimi
|
|
# Copyright : (c) 2024 MacRimi
|
|
# License : (GPL-3.0) (https://github.com/MacRimi/ProxMenux/blob/main/LICENSE)
|
|
# Version : 1.1
|
|
# Last Updated: 19/08/2025
|
|
# ==========================================================
|
|
# Description:
|
|
# This script converts a privileged LXC container to an unprivileged one
|
|
# using the direct conversion method (mount and chown).
|
|
# ==========================================================
|
|
|
|
# Configuration ============================================
|
|
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
|
|
BASE_DIR="/usr/local/share/proxmenux"
|
|
UTILS_FILE="$BASE_DIR/utils.sh"
|
|
|
|
if [[ -f "$UTILS_FILE" ]]; then
|
|
source "$UTILS_FILE"
|
|
fi
|
|
|
|
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
|
|
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
|
|
fi
|
|
|
|
load_language
|
|
initialize_cache
|
|
|
|
# ==========================================================
|
|
|
|
select_privileged_container() {
|
|
|
|
CONTAINERS=$(pct list | awk 'NR>1 {print $1, $3}' | while read id name; do
|
|
if pct config "$id" | grep -q "^unprivileged: 0" || ! pct config "$id" | grep -q "^unprivileged:"; then
|
|
echo "$id" "$name"
|
|
fi
|
|
done | xargs -n2)
|
|
|
|
if [ -z "$CONTAINERS" ]; then
|
|
msg_error "$(translate 'No privileged containers available in Proxmox.')"
|
|
exit 1
|
|
fi
|
|
cleanup
|
|
CONTAINER_ID=$(whiptail --title "$(translate 'Select Privileged Container')" \
|
|
--menu "$(translate 'Select the privileged LXC container to convert:')" 20 70 10 $CONTAINERS 3>&1 1>&2 2>&3)
|
|
|
|
if [ -z "$CONTAINER_ID" ]; then
|
|
msg_error "$(translate 'No container selected. Exiting.')"
|
|
exit 1
|
|
fi
|
|
|
|
msg_ok "$(translate 'Privileged container selected:') $CONTAINER_ID"
|
|
}
|
|
|
|
validate_container_id() {
|
|
local FUNC_VERSION="1.1"
|
|
pmx_journal_context "validate_container_id" "$FUNC_VERSION"
|
|
|
|
if [ -z "$CONTAINER_ID" ]; then
|
|
msg_error "$(translate 'Container ID not defined. Make sure to select a container first.')"
|
|
exit 1
|
|
fi
|
|
|
|
|
|
if pct config "$CONTAINER_ID" | grep -q "^unprivileged: 1"; then
|
|
msg_error "$(translate 'Container') $CONTAINER_ID $(translate 'is already unprivileged.')"
|
|
exit 1
|
|
fi
|
|
|
|
if pct status "$CONTAINER_ID" | grep -q "running"; then
|
|
msg_info "$(translate 'Stopping the container before conversion...')"
|
|
pmx_record_execution "stop CT ${CONTAINER_ID} for privileged-to-unprivileged conversion" \
|
|
"pct stop ${CONTAINER_ID}"
|
|
pct stop "$CONTAINER_ID"
|
|
msg_ok "$(translate 'Container stopped.')"
|
|
fi
|
|
}
|
|
|
|
show_backup_warning() {
|
|
local message="$(translate 'It is strongly recommended to create a backup of your container before proceeding with the conversion.')"
|
|
message="$message\n\n$(translate 'Do you want to continue with the conversion now, or exit to create a backup first?')"
|
|
message="$message\n\n$(translate 'Continue: Proceed with conversion')"
|
|
message="$message\n$(translate 'Exit: Stop to create backup manually')"
|
|
|
|
if whiptail --title "$(translate 'Backup Recommendation')" \
|
|
--yes-button "$(translate 'Continue')" \
|
|
--no-button "$(translate 'Exit')" \
|
|
--yesno "$message" 18 80; then
|
|
return 0
|
|
else
|
|
msg_info2 "$(translate 'User chose to exit for manual backup creation.')"
|
|
exit 0
|
|
fi
|
|
}
|
|
|
|
convert_direct_method() {
|
|
local FUNC_VERSION="1.1"
|
|
pmx_journal_context "convert_direct_method" "$FUNC_VERSION"
|
|
|
|
msg_info2 "$(translate 'Starting direct conversion of container') $CONTAINER_ID..."
|
|
pmx_record_execution "convert CT ${CONTAINER_ID} filesystem ownership to unprivileged IDs" \
|
|
"mount rootfs, remap ownership by 100000, and update CT configuration"
|
|
|
|
TEMP_DIR="/tmp/lxc_convert_$CONTAINER_ID"
|
|
mkdir -p "$TEMP_DIR"
|
|
|
|
|
|
ROOTFS_CONFIG=$(pct config "$CONTAINER_ID" | grep "^rootfs:")
|
|
if [ -z "$ROOTFS_CONFIG" ]; then
|
|
msg_error "$(translate 'Could not find rootfs configuration for container.')"
|
|
exit 1
|
|
fi
|
|
|
|
|
|
STORAGE_DISK=$(echo "$ROOTFS_CONFIG" | awk '{print $2}' | cut -d, -f1)
|
|
|
|
msg_ok "$(translate 'Storage disk identifier:') $STORAGE_DISK"
|
|
|
|
|
|
DISK_PATH=$(pvesm path "$STORAGE_DISK" 2>/dev/null)
|
|
|
|
if [ -n "$DISK_PATH" ] && [ -e "$DISK_PATH" ]; then
|
|
msg_ok "$(translate 'Disk path resolved via pvesm:') $DISK_PATH"
|
|
else
|
|
|
|
STORAGE_NAME=$(echo "$STORAGE_DISK" | cut -d: -f1)
|
|
DISK_NAME=$(echo "$STORAGE_DISK" | cut -d: -f2)
|
|
|
|
msg_info2 "$(translate 'pvesm path failed, trying manual detection...')"
|
|
msg_info2 "$(translate 'Storage:') $STORAGE_NAME, $(translate 'Disk:') $DISK_NAME"
|
|
|
|
|
|
for vg in pve $(vgs --noheadings -o vg_name 2>/dev/null | tr -d ' '); do
|
|
if [ -e "/dev/$vg/$DISK_NAME" ]; then
|
|
DISK_PATH="/dev/$vg/$DISK_NAME"
|
|
break
|
|
fi
|
|
done
|
|
|
|
|
|
if [ -z "$DISK_PATH" ] || [ ! -e "$DISK_PATH" ]; then
|
|
ZFS_PATH="/dev/zvol/$STORAGE_NAME/$DISK_NAME"
|
|
if [ -e "$ZFS_PATH" ]; then
|
|
DISK_PATH="$ZFS_PATH"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
|
|
if [ -z "$DISK_PATH" ] || [ ! -e "$DISK_PATH" ]; then
|
|
msg_error "$(translate 'Could not determine disk path for:') $STORAGE_DISK"
|
|
msg_error "$(translate 'Tried pvesm path and manual detection methods')"
|
|
msg_info2 "$(translate 'Available storage information:')"
|
|
pvesm status 2>/dev/null || msg_error "$(translate 'pvesm status failed')"
|
|
rmdir "$TEMP_DIR"
|
|
exit 1
|
|
fi
|
|
|
|
|
|
msg_ok "$(translate 'Mounting container filesystem')"
|
|
if ! mount "$DISK_PATH" "$TEMP_DIR" 2>/dev/null; then
|
|
|
|
if ! mount -o loop "$DISK_PATH" "$TEMP_DIR" 2>/dev/null; then
|
|
msg_error "$(translate 'Failed to mount container filesystem.')"
|
|
msg_error "$(translate 'Disk path:') $DISK_PATH"
|
|
msg_success "$(translate "Press Enter to return")"
|
|
read -r
|
|
rmdir "$TEMP_DIR"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
msg_info "$(translate 'Converting file ownership (this may take several minutes)...')"
|
|
|
|
|
|
find "$TEMP_DIR" -type f -print0 | while IFS= read -r -d '' S; do
|
|
|
|
if [ ! -e "$S" ] || [ ! -r "$S" ]; then
|
|
continue
|
|
fi
|
|
|
|
|
|
if STAT_OUTPUT=$(stat -c "%u %g" "$S" 2>/dev/null); then
|
|
U=$(echo "$STAT_OUTPUT" | awk '{print $1}')
|
|
G=$(echo "$STAT_OUTPUT" | awk '{print $2}')
|
|
F=100000
|
|
|
|
|
|
NEW_UID=$((F + U))
|
|
NEW_GID=$((F + G))
|
|
|
|
|
|
if ! chown "$NEW_UID:$NEW_GID" "$S" 2>/dev/null; then
|
|
|
|
continue
|
|
fi
|
|
fi
|
|
done
|
|
|
|
|
|
find "$TEMP_DIR" -type d -print0 | while IFS= read -r -d '' S; do
|
|
|
|
if [ ! -e "$S" ] || [ ! -r "$S" ]; then
|
|
continue
|
|
fi
|
|
|
|
|
|
if STAT_OUTPUT=$(stat -c "%u %g" "$S" 2>/dev/null); then
|
|
U=$(echo "$STAT_OUTPUT" | awk '{print $1}')
|
|
G=$(echo "$STAT_OUTPUT" | awk '{print $2}')
|
|
F=100000
|
|
|
|
|
|
NEW_UID=$((F + U))
|
|
NEW_GID=$((F + G))
|
|
|
|
|
|
if ! chown "$NEW_UID:$NEW_GID" "$S" 2>/dev/null; then
|
|
|
|
continue
|
|
fi
|
|
fi
|
|
done
|
|
|
|
|
|
[ -e "$TEMP_DIR/var/spool/postfix/dev/-random" ] && rm -f "$TEMP_DIR/var/spool/postfix/dev/-random"
|
|
[ -e "$TEMP_DIR/var/spool/postfix/dev/-urandom" ] && rm -f "$TEMP_DIR/var/spool/postfix/dev/-urandom"
|
|
|
|
|
|
[ -e "$TEMP_DIR/usr/bin/sudo" ] && chmod u+s "$TEMP_DIR/usr/bin/sudo"
|
|
|
|
umount "$TEMP_DIR"
|
|
rmdir "$TEMP_DIR"
|
|
|
|
|
|
CONFIG_FILE="/etc/pve/lxc/$CONTAINER_ID.conf"
|
|
if ! grep -q "^unprivileged:" "$CONFIG_FILE"; then
|
|
echo "unprivileged: 1" | pmx_append_file "$CONFIG_FILE"
|
|
else
|
|
pmx_edit_file "$CONFIG_FILE" 's/^unprivileged:.*/unprivileged: 1/'
|
|
fi
|
|
|
|
msg_ok "$(translate 'Direct conversion completed for container') $CONTAINER_ID"
|
|
|
|
echo -e
|
|
msg_success "Press Enter to continue..."
|
|
read -r
|
|
}
|
|
|
|
cleanup_and_finalize() {
|
|
local FUNC_VERSION="1.1"
|
|
pmx_journal_context "cleanup_and_finalize" "$FUNC_VERSION"
|
|
|
|
if whiptail --yesno "$(translate 'Do you want to start the converted unprivileged container') $CONTAINER_ID $(translate 'now?')" 10 60; then
|
|
msg_info2 "$(translate 'Starting unprivileged container...')"
|
|
pmx_record_execution "start converted unprivileged CT ${CONTAINER_ID}" "pct start ${CONTAINER_ID}"
|
|
pct start "$CONTAINER_ID"
|
|
msg_ok "$(translate 'Unprivileged container') $CONTAINER_ID $(translate 'started successfully.')"
|
|
fi
|
|
}
|
|
|
|
main() {
|
|
show_proxmenux_logo
|
|
msg_title "$(translate "LXC Privileged to Unprivileged conversion")"
|
|
msg_info "$(translate 'Starting LXC Privileged to Unprivileged conversion process...')"
|
|
|
|
select_privileged_container
|
|
validate_container_id
|
|
show_backup_warning
|
|
|
|
convert_direct_method
|
|
cleanup_and_finalize
|
|
|
|
msg_ok "$(translate 'Converted container ID:') $CONTAINER_ID"
|
|
msg_ok "$(translate 'LXC conversion from privileged to unprivileged completed successfully!')"
|
|
echo -e
|
|
msg_success "$(translate "Press Enter to return to menu...")"
|
|
read -r
|
|
exit 0
|
|
}
|
|
|
|
|
|
main
|