222 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
			
		
		
	
	
			222 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
| #!/usr/bin/env bash
 | ||
| 
 | ||
| # Copyright (c) 2021-2025 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/ProxmoxVED/raw/branch/main/misc/core.func)
 | ||
| load_functions
 | ||
| 
 | ||
| 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"
 | ||
| 
 | ||
|   if ! grep -q "Backup job finished successfully" "$vzdump_output"; then
 | ||
|     rm "$vzdump_output"
 | ||
|     msg_error "Backup failed"
 | ||
|     exit 1
 | ||
|   fi
 | ||
| 
 | ||
|   if grep -q "creating Proxmox Backup Server archive" "$vzdump_output"; then
 | ||
|     BACKUP_PATH=$(awk -F"'" '/creating Proxmox Backup Server archive/ {print $2}' "$vzdump_output")
 | ||
|     BACKUP_TYPE="pbs"
 | ||
|   else
 | ||
|     BACKUP_PATH=$(awk '/tar\.zst/ {print $NF}' "$vzdump_output" | tr -d "'")
 | ||
|     BACKUP_TYPE="file"
 | ||
|   fi
 | ||
| 
 | ||
|   rm "$vzdump_output"
 | ||
| 
 | ||
|   if [[ -z "$BACKUP_PATH" ]]; then
 | ||
|     msg_error "Could not determine backup path"
 | ||
|     exit 1
 | ||
|   fi
 | ||
| 
 | ||
|   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"
 | ||
|   if [[ "$BACKUP_TYPE" == "pbs" ]]; then
 | ||
|     restore_opts=("$NEW_CONTAINER_ID" "$BACKUP_PATH" --repository "$BACKUP_STORAGE")
 | ||
|   else
 | ||
|     restore_opts=("$NEW_CONTAINER_ID" "$BACKUP_PATH" --storage "$TARGET_STORAGE")
 | ||
|   fi
 | ||
|   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
 | ||
|     if [[ "$BACKUP_TYPE" == "file" && -f "$BACKUP_PATH" ]]; then
 | ||
|       rm -f "$BACKUP_PATH" && msg_ok "Removed backup archive"
 | ||
|     elif [[ "$BACKUP_TYPE" == "pbs" ]]; then
 | ||
|       msg_info "Cannot delete PBS backups via script – use GUI or $(proxmox-backup-client prune)"
 | ||
|     else
 | ||
|       msg_warn "Unrecognized backup type – skipping deletion"
 | ||
|     fi
 | ||
|   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
 | 
