#!/bin/bash
#
# LibrePortal Initialization Script
#
# Usage: ./init.sh [OPTIONS] init [password] [git_user] [git_token] [git_url] [unattended] [install_mode]
#
# OPTIONS:
#   --random-password    Generate a random password automatically
#   --local             Use local folder installation automatically
#   --unattended        Run in unattended mode (skip confirmations)
#   --skip-os-update    Skip operating system update
#   --skip-prereqs      Skip installing prerequisite apps
#   --skip-docker-images On UNINSTALL: keep the rootless docker daemon, the
#                        docker-install user, and the image/build cache instead
#                        of tearing them down — so a following reinstall rebuilds
#                        the WebUI image from cache (fast) instead of from
#                        scratch. (No effect on install.)
#   --system-dir=PATH    Install root for the control plane (configs/logs/install/
#                        db). Default /libreportal-system. (Also: LP_SYSTEM_DIR.)
#   --containers-dir=PATH  Root for live app data. Default /libreportal-containers.
#                        (Also: LP_CONTAINERS_DIR.) Put on its own disk if wanted.
#   --backups-dir=PATH   Root for backup repos. Default /libreportal-backups.
#                        (Also: LP_BACKUPS_DIR.) Point at a separate disk/mount.
#   --manager-user=NAME  Control-plane manager user (owns the install, runs the
#                        runtime). Default libreportal. (Also: LP_MANAGER_USER.)
#   --allow-home         Permit a containers/backups root inside /home/<user>
#                        (needs rootless o+x traversal of that home — a privacy
#                        trade-off; refused without this flag).
#
# Examples:
#   ./init.sh --random-password --local init
#   ./init.sh --random-password --local --unattended init
#   ./init.sh --random-password --local --skip-os-update --skip-prereqs init
#   ./init.sh --unattended --skip-docker-images uninstall   # keep docker layer
#   ./init.sh init mypassword myuser mytoken https://github.com/user/repo.git
#

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
NC='\033[0m'

isSuccessful() { echo -e "${GREEN}✓ Success${NC} $1"; }
isError()      { echo -e "${RED}✗ Error${NC} $1"; }
isNotice()     { echo -e "${YELLOW}! Notice${NC} $1"; }
isQuestion()   { echo -e -n "${BLUE}❯ Question${NC} $1 "; }

displayLibrePortalLogo() {
    local hbar; hbar=$(printf '═%.0s' $(seq 1 50))
    printf '\n╔%s╗\n' "$hbar"
    printf '║%6s%s%8s║\n' '' '╦  ┬┌┐ ┬─┐┌─┐  ╭─╮  ╔═╗┌─┐┬─┐┌┬┐┌─┐┬' ''
    printf '║%6s%s%8s║\n' '' '║  │├┴┐├┬┘├┤   │◉│  ╠═╝│ │├┬┘ │ ├─┤│' ''
    printf '║%6s%s%6s║\n' '' '╩═╝┴└─┘┴└─└─┘  ╨─╨  ╩  └─┘┴└─ ┴ ┴ ┴┴─┘' ''
    printf '╚%s╝\n\n' "$hbar"
}

init_action="$1"
for arg in "$@"; do
    case "$arg" in --*) ;; *) init_action="$arg"; break ;;
    esac
done
if [[ "${BASH_SOURCE[0]}" == "$0" ]] \
    && [[ "$init_action" == "init" ]] \
    && [[ "$LIBREPORTAL_SKIP_LOGO" != "1" ]]; then
    displayLibrePortalLogo
fi
unset init_action

checkSuccess() {
    if [ $? -eq 0 ]; then
        isSuccessful "$1"
    else
        isError "$1"
        exit 1
    fi
}

isHeader() {
    local title="$1"
    local width=52
    local inner=$((width - 2))
    local title_len=${#title}
    local total_pad=$((inner - title_len))
    if (( total_pad < 0 )); then total_pad=0; fi
    local left_pad=$((total_pad / 2))
    local right_pad=$((total_pad - left_pad))
    local hbar
    hbar=$(printf '═%.0s' $(seq 1 "$inner"))
    printf '\n╔%s╗\n' "$hbar"
    printf '║%*s%s%*s║\n' "$left_pad" '' "$title" "$right_pad" ''
    printf '╚%s╝\n\n' "$hbar"
}

# Original parameters for backward compatibility
param1="$1" # init to start script
param2="$2" # password
param3="$3" # git user
param4="$4" # git token
param5="$5" # git url
param6="$6" # unattended
param7="$7" # install mode (git/local)

# Parse command line arguments
init_random_password=false
init_local_install=false
init_unattended_mode=false
init_skip_os_update=false
init_skip_prereqs=false
init_skip_docker_images=false
init_allow_home=false

install_param="init"
# Control-plane manager user — configurable via --manager-user= / LP_MANAGER_USER
# (default libreportal). Resolved early here (sudo_bashrc needs it); re-resolved
# after flag parsing in libreportalDerivePaths, and baked into the helpers/unit/
# wrapper at install (the __MANAGER__ placeholder).
sudo_user_name="${LP_MANAGER_USER:-libreportal}"
sshd_config="/etc/ssh/sshd_config"
sudo_bashrc="/home/$sudo_user_name/.bashrc"
hosts_file="/etc/hosts"
hostname_file="/etc/hostname"
# All LibrePortal executables installed outside /docker live together here
# (root-owned). The user-facing CLI is symlinked into $PATH from /usr/local/bin.
lp_lib_dir="/usr/local/lib/libreportal"
command_script="$lp_lib_dir/libreportal"
command_symlink="/usr/local/bin/libreportal"
# Version of the ROOT-OWNED footprint (helpers, CLI wrapper, uninstall launcher,
# systemd unit, sudoers). BUMP THIS whenever you change any of those — a plain
# `update apply` runs as the manager and CANNOT rewrite root-owned files, so a bump
# tells the updater the new release needs a root re-install (which re-bakes them).
# Recorded at install in $lp_lib_dir/.footprint_version. See docs/DEVELOPMENT.md.
footprint_version=2
footprint_marker="$lp_lib_dir/.footprint_version"

# Directories — three independently-relocatable roots (see scripts/source/paths.sh
# for the canonical description). Defaults below; overridden by --system-dir /
# --containers-dir / --backups-dir (parsed in the flag loop) via the LP_*_DIR
# vars. init.sh derives inline (kept in sync with paths.sh) because the bare
# /root/init.sh reinstall copy has no scripts/ alongside to source.
#   LP_SYSTEM_DIR     — manager-owned control plane (configs/logs/install/db/…)
#   LP_CONTAINERS_DIR — container-user-owned live app data
#   LP_BACKUPS_DIR    — container-user-owned backup repos (own mount-able)
libreportalDerivePaths() {
    # Transitional compat: an existing install (legacy single /docker tree,
    # identified by its config marker) keeps using /docker until a deliberate
    # reinstall — so deploying new code never strands a running box.
    if [[ -z "${LP_SYSTEM_DIR:-}" ]]; then
        if [[ ! -e /libreportal-system && -f /docker/configs/general/general_docker_install ]]; then
            LP_SYSTEM_DIR=/docker
            : "${LP_CONTAINERS_DIR:=/docker/containers}"
            : "${LP_BACKUPS_DIR:=/docker/backups}"
        else
            LP_SYSTEM_DIR=/libreportal-system
        fi
    fi
    : "${LP_CONTAINERS_DIR:=/libreportal-containers}"
    : "${LP_BACKUPS_DIR:=/libreportal-backups}"

    docker_dir="$LP_SYSTEM_DIR"
    system_dir="$LP_SYSTEM_DIR"
    configs_dir="$LP_SYSTEM_DIR/configs/"
    logs_dir="$LP_SYSTEM_DIR/logs/"
    ssl_dir="$LP_SYSTEM_DIR/ssl/"
    ssh_dir="$LP_SYSTEM_DIR/ssh/"
    wireguard_dir="$LP_SYSTEM_DIR/wireguard/"
    migrate_dir="$LP_SYSTEM_DIR/migrate"
    restore_dir="$LP_SYSTEM_DIR/restore"
    script_dir="$LP_SYSTEM_DIR/install"
    install_configs_dir="$script_dir/configs/"
    install_containers_dir="$script_dir/containers/"
    install_scripts_dir="$script_dir/scripts/"
    containers_dir="$LP_CONTAINERS_DIR/"
    backup_dir="$LP_BACKUPS_DIR"

    # Control-plane manager user (configurable; default libreportal).
    sudo_user_name="${LP_MANAGER_USER:-${sudo_user_name:-libreportal}}"
    sudo_bashrc="/home/$sudo_user_name/.bashrc"
}
libreportalDerivePaths

# Validate the chosen roots before anything is created/baked. Called from the
# install flow only (NOT at source time — the CLI sources init.sh too). Aborts on
# an unsafe choice; the root helpers also re-check at runtime (defence in depth).
libreportalValidatePaths() {
    local pair name d
    # Manager username: must be a valid Linux username (it becomes a real account,
    # a sudoers drop-in name, and the baked __MANAGER__ in the root helpers).
    if [[ ! "$sudo_user_name" =~ ^[a-z_][a-z0-9_-]*$ ]]; then
        isError "Invalid manager user '$sudo_user_name' — use lowercase letters, digits, '_' or '-' (must start with a letter or '_')."
        exit 1
    fi
    for pair in "system:$LP_SYSTEM_DIR" "containers:$LP_CONTAINERS_DIR" "backups:$LP_BACKUPS_DIR"; do
        name="${pair%%:*}"; d="${pair#*:}"
        case "$d" in
            ""|/) isError "The $name root must be a non-root absolute path (got '$d')."; exit 1 ;;
            /usr|/usr/*|/etc|/etc/*|/bin|/bin/*|/sbin|/sbin/*|/lib|/lib/*|/lib64|/lib64/*|/boot|/boot/*|/proc|/proc/*|/sys|/sys/*|/dev|/dev/*|/run|/run/*|/root|/root/*)
                isError "Refusing $name root '$d' — inside a protected system path."; exit 1 ;;
            /*) ;;  # absolute, allowed
            *) isError "The $name root must be an absolute path (got '$d')."; exit 1 ;;
        esac
    done
    # The three must not nest inside one another — EXCEPT the legacy single-tree
    # compat layout (/docker + /docker/{containers,backups}), which nests by design.
    if [[ "$LP_SYSTEM_DIR" != "/docker" ]]; then
        local a b
        for a in "$LP_SYSTEM_DIR" "$LP_CONTAINERS_DIR" "$LP_BACKUPS_DIR"; do
            for b in "$LP_SYSTEM_DIR" "$LP_CONTAINERS_DIR" "$LP_BACKUPS_DIR"; do
                [[ "$a" == "$b" ]] && continue
                if [[ "$a" == "$b"/* ]]; then
                    isError "Roots must not nest: '$a' is inside '$b'."; exit 1
                fi
            done
        done
    fi
    # containers/backups inside a human home need the rootless user to traverse it
    # (o+x up the chain) — a privacy trade-off. Require an explicit opt-in.
    for pair in "containers:$LP_CONTAINERS_DIR" "backups:$LP_BACKUPS_DIR"; do
        name="${pair%%:*}"; d="${pair#*:}"
        if [[ "$d" == /home/* && "$init_allow_home" != "true" ]]; then
            isError "The $name root '$d' is inside a user home; the rootless container user would need o+x traversal of that home (a privacy trade-off)."
            isNotice "Re-run with --allow-home to accept, or choose a dedicated path/mount."
            exit 1
        fi
    done
}

# Parse flags
init_shift_count=0
for ((i=1; i<=$#; i++)); do
    case "${!i}" in
        --random-password)
            init_random_password=true
            ((init_shift_count++))
            ;;
        --local)
            init_local_install=true
            ((init_shift_count++))
            ;;
        --unattended)
            init_unattended_mode=true
            ((init_shift_count++))
            ;;
        --skip-os-update)
            init_skip_os_update=true
            ((init_shift_count++))
            ;;
        --skip-prereqs)
            init_skip_prereqs=true
            ((init_shift_count++))
            ;;
        --skip-docker-images)
            # On uninstall: preserve the rootless docker layer (daemon +
            # docker-install user + image/build cache) so the next reinstall's
            # `docker build` is cache-fast. Honored in runFullUninstall.
            init_skip_docker_images=true
            ((init_shift_count++))
            ;;
        # Relocatable roots (=form keeps the single-token shift logic). Validated
        # by libreportalValidatePaths before any folder is created. Can also be set
        # via the LP_*_DIR environment.
        --system-dir=*)     LP_SYSTEM_DIR="${!i#*=}";     ((init_shift_count++)) ;;
        --containers-dir=*) LP_CONTAINERS_DIR="${!i#*=}"; ((init_shift_count++)) ;;
        --backups-dir=*)    LP_BACKUPS_DIR="${!i#*=}";    ((init_shift_count++)) ;;
        --manager-user=*)   LP_MANAGER_USER="${!i#*=}";   ((init_shift_count++)) ;;
        --allow-home)       init_allow_home=true;         ((init_shift_count++)) ;;
    esac
done

# Re-derive after flags (flags/env set LP_*_DIR; libreportalDerivePaths keeps any
# value already set and only fills the rest).
libreportalDerivePaths

# Shift parsed flags to get positional parameters
if [ $init_shift_count -gt 0 ]; then
    for ((i=1; i<=init_shift_count; i++)); do
        shift
done
    # Reset positional parameters after shifting flags
    param1="$1" # init to start script
    param2="$2" # password
    param3="$3" # git user
    param4="$4" # git token
    param5="$5" # git url
    param6="$6" # unattended
    param7="$7" # install mode (git/local)
fi

# Apply flag logic
if [ "$init_random_password" = true ]; then
    param2=$(openssl rand -base64 12 | tr -d '\n')
    isSuccessful "Generated password: $param2"
fi

if [ "$init_local_install" = true ]; then
    param7="local"
fi

# Auto-detect installation mode based on provided parameters. Only when init.sh
# is EXECUTED directly (install time) — start.sh sources init.sh for its function
# definitions at runtime (Model A, as the manager), and this block must not run
# then (it would print a spurious "Auto-detected ..." and rewrite the config).
if [[ "${BASH_SOURCE[0]}" == "$0" && "$param1" != "uninstall" && -z "$param7" ]]; then
    # A reinstall that doesn't re-pass the git args must not silently
    # downgrade an existing git install to local (that disables the updater
    # and blanks the saved creds). Honor a git URL already saved from a
    # prior install — only fall back to local when there's no git history.
    saved_git_url=$(grep -E '^CFG_GIT_URL=' "${configs_dir}general/general_install" 2>/dev/null \
                    | sed -E 's/^[^=]+=([^[:space:]#]*).*/\1/')
    if [[ -n "$param3" || -n "$param4" || -n "$param5" ]]; then
        # Git parameters provided, set to git mode
        param7="git"
        isSuccessful "Auto-detected Git installation mode (git args provided)"
    elif [[ -n "$saved_git_url" && "$saved_git_url" != "changeme" && "$saved_git_url" != "empty" ]]; then
        # No git args this run, but a prior git install is on disk — keep it
        param7="git"
        isSuccessful "Auto-detected Git installation mode (existing git config detected)"
    else
        # No git parameters and no prior git config, set to local mode
        param7="local"
        isSuccessful "Auto-detected Local installation mode"
    fi
fi


initUpdateConfigOption() {
    local config_option="$1"
    local config_value="$2"
    local config_file=""

    for category_dir in "$configs_dir"/*; do
        if [ -d "$category_dir" ] && [ -f "$category_dir/.category" ]; then
            for config_file in "$category_dir"/*; do
                if [ -f "$config_file" ] && [[ ! "$config_file" =~ \.category$ ]]; then
                    if grep -q "^$config_option=" "$config_file"; then
                        local escaped_value=$(printf '%s\n' "$config_value" | sed -e 's/[\/&]/\\&/g')
                        local original_line=$(grep "^$config_option=" "$config_file")
                        local comment_part=$(echo "$original_line" | sed -n "s|^$config_option=[^#]*\(#.*\)|\1|p")

                        if [[ -n "$comment_part" ]]; then
                            sed -i "s|^$config_option=.*|$config_option=$escaped_value                     $comment_part|" "$config_file"
                        else
                            sed -i "s|^$config_option=.*|$config_option=$escaped_value|" "$config_file"
                        fi
                        source "$config_file"
                        return 0
                    fi
                fi
            done
        fi
    done

    return 1
}

if [[ "${BASH_SOURCE[0]}" == "$0" && -n "$param7" && -f "${configs_dir}general/general_install" ]]; then
    initUpdateConfigOption "CFG_INSTALL_MODE" "$param7"
fi

if [ "$init_unattended_mode" = true ]; then
    param6="true"
fi

if [ "$init_skip_os_update" = true ]; then
    isNotice "Skipping operating system update"
fi

if [ "$init_skip_prereqs" = true ]; then
    isNotice "Skipping prerequisite apps installation"
fi

# Get script directory for local installation (only if needed)
if [ "$init_local_install" = true ]; then
    init_script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
    isNotice "Using script directory for local installation: $init_script_dir"
fi

detectLocalLibrePortal() {
    local check_dir
    
    # If --local flag is used, check script directory, otherwise check current directory
    if [ "$init_local_install" = true ]; then
        check_dir="$init_script_dir"
        echo "Checking script directory for LibrePortal structure..."
    else
        check_dir="$(pwd)"
        echo "Checking current directory for LibrePortal structure..."
    fi
    
    local required_dirs=("configs" "scripts" "containers")
    local required_files=("init.sh" "start.sh")
    local missing_count=0
    
    # Check for required directories
    for dir in "${required_dirs[@]}"; do
        if [[ ! -d "$check_dir/$dir" ]]; then
            echo "  Missing directory: $dir"
            ((missing_count++))
        fi
    done
    
    # Check for required files
    for file in "${required_files[@]}"; do
        if [[ ! -f "$check_dir/$file" ]]; then
            echo "  Missing file: $file"
            ((missing_count++))
        fi
    done
    
    if [[ $missing_count -eq 0 ]]; then
        echo "Valid LibrePortal structure detected in $check_dir"
		echo ""
        return 0
    else
        isNotice "Warning: $missing_count required items missing. Not a valid LibrePortal source."
        return 1
    fi
}

copyFilesFromLocal() {
    local source_dir
    
    # If --local flag is used, use script directory, otherwise use current directory
    if [ "$init_local_install" = true ]; then
        source_dir="$init_script_dir"
    else
        source_dir="$(pwd)"
    fi
    
    isHeader "Copying from Local Directory"
    
    # Remove existing install directory
    sudo rm -rf "$script_dir"
    
    # Create install directory
    sudo mkdir -p "$script_dir"
    
    isNotice "Copying files from $source_dir to $script_dir..."
    
    # Copy all files while preserving structure
    if sudo cp -r "$source_dir"/* "$script_dir/" 2>/dev/null; then
        sudo cp -f "$script_dir/init.sh" /root/
        setupConfigsFromRepo
        sudo chown -R $sudo_user_name:$sudo_user_name "$script_dir"
        isSuccessful "Files copied from local directory to '$script_dir'."
    else
        isError "Failed to copy files from local directory."
        exit 1
    fi
}

resetConfigVars() {
    param2=""
    param3=""
    param4=""
    param5=""
    param7=""
    GIT_USER=""
    GIT_TOKEN=""
    GIT_URL=""
    INSTALL_MODE=""
}

initCheckConfigs() {
    # Check if any config files exist (new structure) or old config files
    local config_found=false
    
    # Check for new structure
    if [ -d "$configs_dir" ]; then
        for category_dir in "$configs_dir"/*; do
            if [ -d "$category_dir" ] && [ -f "$category_dir/.category" ]; then
                # Load new structure config files
                for config_file in "$category_dir"/*; do
                    local should_load=true
                    local filename=$(basename "$config_file")
                    # Skip .category files and excluded files (hardcoded)
                    if [[ "$config_file" =~ \.category$ ]]; then
                        should_load=false
                    fi
                    
                    if [ "$should_load" = true ]; then
                        source "$config_file"
                        config_found=true
                    fi
                done
            fi
        done
    fi
    
    if [ "$config_found" = false ]; then
        isNotice "Configuration files do not exist, skipping init check"
        return
    fi

    get_cfg() {
        local var_name="$1"
        local var_value="${!var_name}"
        echo "$var_value"
    }

    [[ -z "$param2" ]] && param2=$(get_cfg CFG_LIBREPORTAL_USER_PASS)
    [[ -z "$param3" ]] && param3=$(get_cfg CFG_GIT_USER)
    [[ -z "$param4" ]] && param4=$(get_cfg CFG_GIT_KEY)
    [[ -z "$param5" ]] && param5=$(get_cfg CFG_GIT_URL)
    [[ -z "$param7" ]] && param7=$(get_cfg CFG_INSTALL_MODE)

    [[ "$param2" == "changeme" ]] && param2=""
    [[ "$param3" == "changeme" ]] && param3=""
    [[ "$param4" == "changeme" ]] && param4=""
    [[ "$param5" == "changeme" ]] && param5=""
    [[ "$param7" == "changeme" ]] && param7=""
}

validateUnattended() {
    if [[ -z "$param2" ]]; then
        isError "Password is required in unattended mode"
        exit 1
    fi

    # If install mode is not specified, default to git
    if [[ -z "$param7" ]]; then
        param7="git"
    fi

    # For git installation, validate git parameters
    if [[ "$param7" == "git" ]]; then
        if [[ -z "$param5" ]]; then
            isError "Git repository URL is required in unattended mode"
            exit 1
        fi

        if [[ -z "$param3" ]]; then
            param3="empty"
            param4="empty"
        fi

        if [[ "$param3" != "empty" && -z "$param4" ]]; then
            param4="empty"
        fi
    fi
}

initInputQuestions() {
    ### PASSWORD
    if [[ -z "$param2" ]]; then
        while true; do
            echo ""
            echo "LibrePortal User Password"
            read -p "Use custom (c) or randomized (r) password? (c/r): " choice

            case "$choice" in
                c)
                    read -p "Enter custom password: " param2
                    [[ -z "$param2" ]] && echo "Password cannot be empty." || break
                    ;;
                r)
                    param2=$(openssl rand -base64 12)
                    isSuccessful "Generated password: $param2"
                    break
                    ;;
                *)
                    echo "Invalid option. Enter c or r."
                    ;;
            esac
        done
    fi

    ### INSTALLATION METHOD
    if [[ -z "$param7" ]]; then
        echo ""
        echo "Installation Method:"
        
        # Check if local installation is available
        if detectLocalLibrePortal; then
            echo "1) Install from Git repository"
            echo "2) Install from local folder (current directory)"
            echo ""
            while true; do
                read -p "Choose option [1-2]: " install_choice
                case "$install_choice" in
                    1)
                        param7="git"
                        echo "Using Git repository installation."
                        break
                        ;;
                    2)
                        param7="local"
                        echo "Using local folder installation."
                        break
                        ;;
                    *)
                        echo "Invalid option. Choose 1 or 2."
                        ;;
                esac
            done
        else
            echo "Local installation not available - missing required files/directories."
            echo "Defaulting to Git repository installation."
            param7="git"
        fi
    fi

    ### GIT USER (only for git installation)
    if [[ "$param7" == "git" && -z "$param3" ]]; then
        echo ""
        echo "Git Authentication Method:"
        echo "1) Login required (username + token)"
        echo "2) Authenticationless (public repos / SSH keys)"
        echo ""
        while true; do
            read -p "Choose option [1-2]: " git_auth_choice
            case "$git_auth_choice" in
                1)
                    read -p "Enter Git username: " param3
                    if [[ -z "$param3" ]]; then
                        echo "Username cannot be empty for login authentication."
                    else
                        break
                    fi
                    ;;
                2)
                    param3="empty"
                    param4="empty"
                    echo "Using authenticationless Git access."
                    break
                    ;;
                *)
                    echo "Invalid option. Choose 1 or 2."
                    ;;
            esac
        done
    fi

    ### GIT TOKEN (only for git installation)
    if [[ "$param7" == "git" && "$param3" != "empty" && -z "$param4" ]]; then
        read -p "Enter Git token: " param4
        [[ -z "$param4" ]] && param4="empty"
    fi

    ### GIT URL (only for git installation)
    if [[ "$param7" == "git" && -z "$param5" ]]; then
        while true; do
            echo ""
            read -p "Enter Git repository URL: " param5
            [[ -z "$param5" ]] && echo "Git repository URL is required." || {
                # Validate and fix Git URL
                param5=$(validateAndFixGitUrl "$param5")
                echo "Using Git URL: $param5"
                break
            }
        done
    elif [[ "$param7" == "git" ]]; then
        # Also validate URL if provided as parameter
        param5=$(validateAndFixGitUrl "$param5")
        echo "Using Git URL: $param5"
    fi
}

cleanGitUrl() {
    local url="$1"
    url="${url#"${url%%[![:space:]]*}"}"
    url="${url%"${url##*[![:space:]]}"}"
    url="${url#https://}"
    url="${url#http://}"
    while [[ "$url" == */ ]]; do url="${url%/}"; done
    url="${url%.git}"
    echo "$url"
}

validateAndFixGitUrl() {
    local url="$1"
    url="${url#"${url%%[![:space:]]*}"}"
    url="${url%"${url##*[![:space:]]}"}"
    echo "$url"
}

initReloadConfigs() {
    for category_dir in "$configs_dir"/*; do
        if [ -d "$category_dir" ] && [ -f "$category_dir/.category" ]; then
            for config_file in "$category_dir"/*; do
                if [ -f "$config_file" ] && [[ ! "$config_file" =~ \.category$ ]]; then
                    source "$config_file"
                fi
            done
        fi
    done
    
    # Check old config files (backward compatibility)
    for config_file in "$configs_dir"/config_*; do
        if [ -f "$config_file" ]; then
            source "$config_file"
        fi
    done
}

writeConfig()
{
    # Defer config writes until the repo is cloned and /docker/configs is
    # populated by setupConfigsFromRepo. On a fresh install the file
    # below doesn't exist yet — initUpdateConfigs at the end of init.sh
    # is what actually persists everything.
    if [[ ! -f "${configs_dir}general/general_install" ]]; then
        return 0
    fi

    [[ -n "$param2" ]] && initUpdateConfigOption "CFG_LIBREPORTAL_USER_PASS" "$param2"
    [[ -n "$param3" ]] && initUpdateConfigOption "CFG_GIT_USER"             "$param3"
    [[ -n "$param4" ]] && initUpdateConfigOption "CFG_GIT_KEY"              "$param4"
    [[ -n "$param5" ]] && initUpdateConfigOption "CFG_GIT_URL"              "$param5"
    [[ -n "$param7" ]] && initUpdateConfigOption "CFG_INSTALL_MODE"         "$param7"
}


initDisplayConfig() 
{
    GIT_USER="$param3"
    GIT_TOKEN="$param4"
    GIT_URL="$param5"
    INSTALL_MODE="$param7"

    isHeader "Configuration Summary"
    isNotice "LibrePortal User Password: [HIDDEN]"

    if [[ "$INSTALL_MODE" == "local" ]]; then
        isNotice "Git Username: [NOT APPLICABLE]"
        isNotice "Git Token: [NOT APPLICABLE]"
    else
        [[ "$GIT_USER" == "empty" ]] \
            && isNotice "Git Username: [DISABLED]" \
            || isNotice "Git Username: $GIT_USER"

        [[ "$GIT_TOKEN" == "empty" ]] \
            && isNotice "Git Token: [DISABLED]" \
            || isNotice "Git Token: [HIDDEN]"
    fi

    [[ "$INSTALL_MODE" == "local" ]] \
        && isNotice "Installation Mode: Local Folder" \
        || isNotice "Installation Mode: Git Repository"

    if [[ "$INSTALL_MODE" != "local" ]]; then
        isNotice "Git URL: $GIT_URL"
    fi
    echo ""

    if [[ "$param6" == "true" || "$param6" == "1" ]]; then
        isNotice "Unattended mode enabled — auto-accepting configuration"
        writeConfig
    fi

    if [[ "$INSTALL_MODE" != "local" ]]; then
        read -p "Are these details correct? (y/n): " confirm
    else
        confirm=y
    fi

    if [[ "$confirm" != "y" ]]; then
        echo "Restarting configuration.."
        resetConfigVars
        initCheckConfigs
        initInputQuestions
        initDisplayConfig
    else
        writeConfig
        isSuccessful "Configuration saved."
    fi
}

initOS()
{
	isHeader "Updating Operating System"
	apt-get install sudo -y
	sudo apt-get update
	sudo apt-get dist-upgrade -y
	echo ""
	isSuccessful "OS Updated"
}

initPrerequires()
{
	isHeader "Installing Prerequired Apps"
	# apache2-utils → htpasswd, used by hashPassword for fast local bcrypt.
	sudo apt-get install git zip curl sshpass dos2unix dnsutils apt-transport-https ca-certificates software-properties-common uidmap adduser apache2-utils restic -y
	TARGET_PATH="/usr/local/bin"
	CONFIG_FILE="$HOME/.bashrc"
	if ! echo "$PATH" | grep -q "$TARGET_PATH"; then
		echo "Adding $TARGET_PATH to PATH..."
		echo "export PATH=\$PATH:$TARGET_PATH" >> "$CONFIG_FILE"
		source "$CONFIG_FILE"
		echo "PATH updated successfully!"
	else
		echo "$TARGET_PATH is already in PATH."
	fi
	isSuccessful "Prerequisite apps installed."
}

initDocker()
{
	isHeader "Installing Docker"
	if command -v docker &> /dev/null; then
		isSuccessful "Docker is already installed."
	else
		curl -fsSL https://get.docker.com | sh
		systemctl start docker
		systemctl enable docker
		isSuccessful "Docker has been installed successfully."
	fi
}

initUsers()
{
	isHeader "Creating User Accounts"
	if id "$sudo_user_name" &>/dev/null; then
		isSuccessful "User $sudo_user_name already exists."
	else
		# No -G sudo: the manager's privileges come from the scoped /etc/sudoers.d
		# drop-in below (user-specific), not blanket sudo-group membership.
		sudo useradd -s /bin/bash -d "/home/$sudo_user_name" -m "$sudo_user_name" 2>/dev/null
		isNotice "Setting password for $sudo_user_name user."
		echo "$sudo_user_name:$param2" | sudo chpasswd
		sudo usermod -aG docker "$sudo_user_name"
		sudo systemctl restart docker
		isSuccessful "User $sudo_user_name created successfully."
	fi

	# Drop a stale cron spool from a prior install. userdel doesn't remove
	# /var/spool/cron/crontabs/<user>, so if the manager's uid was recycled the
	# leftover (owned by the dead uid) can't be replaced by the new user in the
	# sticky spool dir → "crontab: rename: Operation not permitted". Remove it
	# only when it's owned by a different uid (a current-uid spool is valid), plus
	# the legacy easydocker artifact, so the manager-run crontab setup writes a
	# clean, correctly-owned spool.
	local spool_dir="/var/spool/cron/crontabs"
	local mgr_uid; mgr_uid=$(id -u "$sudo_user_name" 2>/dev/null)
	if [[ -f "$spool_dir/$sudo_user_name" \
	   && "$(stat -c %u "$spool_dir/$sudo_user_name" 2>/dev/null)" != "$mgr_uid" ]]; then
		sudo rm -f "$spool_dir/$sudo_user_name"
		isNotice "Removed a stale cron spool for $sudo_user_name (recycled uid)."
	fi
	sudo rm -f "$spool_dir/easydocker"

	# /home/$sudo_user_name may be owned by a stale uid from a previous install
	# (e.g. the EasyDocker rename): useradd doesn't reclaim an existing home dir,
	# so files inside — incl. restic's cache dir under ~/.cache/restic — end up
	# unreadable by the new manager. restic then logs `mkdir: permission denied`
	# every backup (non-fatal but slows them). Same recycled-uid pattern as the
	# cron spool above. Idempotent: no-op when ownership already matches.
	if [[ -d "/home/$sudo_user_name" \
	   && "$(stat -c %u "/home/$sudo_user_name" 2>/dev/null)" != "$mgr_uid" ]]; then
		sudo chown -R "$sudo_user_name":"$sudo_user_name" "/home/$sudo_user_name"
		isNotice "Reclaimed /home/$sudo_user_name from a stale uid (recycled or rename)."
	fi
	# Install-phase sudo: the heavy install runs AS this user (see the handoff in
	# completeInitMessage) and needs BROAD root — useradd for the docker-install
	# user, rootless setup, apt, sysctl, etc. So grant a temporary validated
	# NOPASSWD: ALL drop-in now (never appended to /etc/sudoers — a malformed main
	# file locks out sudo entirely); completeInitMessage calls initScopedSudoers
	# to tighten it to the scoped RUNTIME allowlist once the install succeeds.
	local sudoers_dropin="/etc/sudoers.d/${sudo_user_name}"
	local sudoers_tmp
	sudoers_tmp=$(mktemp)
	printf '%s ALL=(ALL) NOPASSWD: ALL\n' "$sudo_user_name" > "$sudoers_tmp"
	if sudo visudo -cf "$sudoers_tmp" >/dev/null 2>&1; then
		sudo install -m 0440 -o root -g root "$sudoers_tmp" "$sudoers_dropin"
		isSuccessful "Configured install-phase sudo for $sudo_user_name (tightened after install)."
	else
		isError "Refusing to install an invalid sudoers drop-in for $sudo_user_name."
	fi
	rm -f "$sudoers_tmp"
}

# Tighten the manager's sudo from the install-phase NOPASSWD: ALL down to the
# scoped RUNTIME allowlist. Called AFTER the (manager-run) install phase, which
# needs the broad root this deliberately withholds. The runtime then reaches root
# ONLY via: the unprivileged docker-install user (data plane, rootless-confined),
# the root-owned /usr/local/lib/libreportal/ helpers (each a fixed, self-validated
# op the manager can't modify), and a fixed system-binary set. Excluded:
# bash/su + tee/cp/chmod/chown/sed/mv/rm/install (each root-equivalent). Also
# clears legacy broad grants (a NOPASSWD: ALL in the main /etc/sudoers, sudo-group
# membership). See docs/FOOTPRINT.md.
initScopedSudoers()
{
	local sudoers_dropin="/etc/sudoers.d/${sudo_user_name}"
	local install_user
	install_user=$(grep -h '^CFG_DOCKER_INSTALL_USER=' "$configs_dir/general/general_docker_install" 2>/dev/null | head -1 | cut -d= -f2 | awk '{print $1}')
	install_user="${install_user:-${CFG_DOCKER_INSTALL_USER:-dockerinstall}}"
	local sudoers_tmp
	sudoers_tmp=$(mktemp)
	cat > "$sudoers_tmp" <<EOF
# Scoped least-privilege grant for the LibrePortal manager. Generated by init.sh.
Cmnd_Alias LP_HELPERS = ${lp_lib_dir}/libreportal-ownership, \\
                        ${lp_lib_dir}/libreportal-dns, \\
                        ${lp_lib_dir}/libreportal-ssh-access, \\
                        ${lp_lib_dir}/libreportal-socket, \\
                        ${lp_lib_dir}/libreportal-svc, \\
                        ${lp_lib_dir}/libreportal-bininstall, \\
                        ${lp_lib_dir}/libreportal-appcfg
Cmnd_Alias LP_SYSTEM = /usr/bin/systemctl, /usr/sbin/ufw, /usr/local/bin/ufw-docker, \\
                       /usr/sbin/nft, /usr/sbin/sysctl, /sbin/sysctl, \\
                       /usr/bin/loginctl, /usr/sbin/service
${sudo_user_name} ALL=(${install_user}) NOPASSWD:SETENV: ALL
${sudo_user_name} ALL=(root) NOPASSWD: LP_HELPERS, LP_SYSTEM
EOF
	if sudo visudo -cf "$sudoers_tmp" >/dev/null 2>&1; then
		sudo install -m 0440 -o root -g root "$sudoers_tmp" "$sudoers_dropin"
		# Clear legacy broad grants from older installs.
		if sudo grep -qE "^${sudo_user_name}[[:space:]]+ALL=\(ALL\)[[:space:]]+NOPASSWD:[[:space:]]+ALL" /etc/sudoers 2>/dev/null; then
			local main_tmp; main_tmp=$(mktemp)
			sudo grep -vE "^${sudo_user_name}[[:space:]]+ALL=\(ALL\)[[:space:]]+NOPASSWD:[[:space:]]+ALL$" /etc/sudoers > "$main_tmp"
			sudo visudo -cf "$main_tmp" >/dev/null 2>&1 && sudo cp "$main_tmp" /etc/sudoers
			rm -f "$main_tmp"
		fi
		sudo gpasswd -d "$sudo_user_name" sudo >/dev/null 2>&1 || true
		isSuccessful "Tightened $sudo_user_name sudo to the scoped runtime allowlist."
	else
		isError "Invalid scoped sudoers — left install-phase sudo in place for $sudo_user_name."
	fi
	rm -f "$sudoers_tmp"
}

# Install the root-owned privilege helpers. Under Model A the runtime runs AS the
# manager, so the genuine-root operations it can't drop (the /docker ownership
# model, /etc/resolv.conf edits, host SSH access) need root — but granting the
# manager blanket `sudo chown/chmod/tee/sed/cp` would be root-equivalent. Each
# helper does a FIXED, self-validated set of operations and lives root:root 0755
# where the manager can't edit it, so the scoped sudoers can allow each wholesale.
# The manager name is baked in here (the manager can't change it); the sed is a
# no-op on helpers without the placeholder.
initRootHelpers()
{
	# One root-owned home for every LibrePortal executable installed outside
	# /docker (the helpers here; the CLI wrapper lands here too, see the command
	# setup). Root-owned 0755 = the manager can't tamper with the helpers it
	# sudo's (the trust boundary the scoped sudoers relies on).
	sudo install -d -m 0755 -o root -g root "$lp_lib_dir"
	local helper helper_src helper_dst helper_tmp
	for helper in libreportal-ownership libreportal-dns libreportal-ssh-access libreportal-socket libreportal-svc libreportal-bininstall libreportal-appcfg; do
		helper_src="$script_dir/scripts/system/$helper"
		helper_dst="$lp_lib_dir/$helper"
		if [[ ! -f "$helper_src" ]]; then
			isError "Root helper source missing ($helper_src) — skipping."
			continue
		fi
		helper_tmp=$(mktemp)
		# Bake the manager name + the three relocatable roots into the installed
		# (root-owned, manager-immutable) helper. This is the trust boundary: the
		# helpers operate on FIXED paths chosen at install by root, never read from
		# manager-writable config. '#' delimiter since the values are paths.
		sed -e "s/__MANAGER__/${sudo_user_name}/g" \
		    -e "s#__SYSTEM_DIR__#${LP_SYSTEM_DIR}#g" \
		    -e "s#__CONTAINERS_DIR__#${LP_CONTAINERS_DIR}#g" \
		    -e "s#__BACKUPS_DIR__#${LP_BACKUPS_DIR}#g" \
		    "$helper_src" > "$helper_tmp"
		if bash -n "$helper_tmp" 2>/dev/null; then
			sudo install -m 0755 -o root -g root "$helper_tmp" "$helper_dst"
			isSuccessful "Installed root-owned helper ($helper)."
		else
			isError "Refusing to install a malformed root helper ($helper)."
		fi
		rm -f "$helper_tmp"
	done

	# Install the release-signing PUBLIC key into the root-owned footprint, so the
	# runtime updater verifies signatures against a key the manager can't swap.
	if [[ -f "$script_dir/libreportal.pub" ]]; then
		sudo install -m 0644 -o root -g root "$script_dir/libreportal.pub" "$lp_lib_dir/libreportal.pub"
	fi

	# Record the footprint version now installed (root-owned, world-readable) so the
	# manager-run updater can tell when a release bumps it and a root re-install is
	# needed. Written here because this re-runs on every install/reinstall.
	echo "$footprint_version" | sudo tee "$footprint_marker" >/dev/null
	sudo chmod 0644 "$footprint_marker"
}

initFolders()
{
	isHeader "LibrePortal Folder Creation"
	folders=("$docker_dir" "$containers_dir" "$ssl_dir" "$ssh_dir" "$wireguard_dir" "$logs_dir" "$configs_dir" "$backup_dir" "$restore_dir" "$migrate_dir" "$script_dir")
	for folder in "${folders[@]}"; do
		if [ ! -d "$folder" ]; then
			sudo mkdir "$folder"
			sudo chown $sudo_user_name:$sudo_user_name "$folder"
			sudo chmod 750 "$folder"
			isSuccessful "Folder '$folder' created."
		fi
	done
	isSuccessful "All folders have been created."
}

# Establish the rootless container layer — the docker-install user and ownership
# of /docker/containers — during the ROOT phase, BEFORE the manager-run install
# boots. That boot scans app configs under /docker/containers AS the container
# user (runFileOp), so if the dir is still manager-owned (as initFolders leaves
# it) the scan errors with "Permission denied". Handing the dir to the container
# user here means the scan reads a dir it owns. Rootless-only — rooted keeps
# containers manager/root-owned, which the manager reads fine. Idempotent: the
# later rootless setup finds the user existing and just (re)asserts its password
# + daemon config. Runs after initGIT (config present) + initFolders (dir present).
initContainerLayer()
{
	local cfg="$configs_dir/general/general_docker_install"
	local dtype duser
	dtype=$(grep -h '^CFG_DOCKER_INSTALL_TYPE=' "$cfg" 2>/dev/null | head -1 | cut -d= -f2 | awk '{print $1}')
	[[ "$dtype" != "rootless" ]] && return 0
	duser=$(grep -h '^CFG_DOCKER_INSTALL_USER=' "$cfg" 2>/dev/null | head -1 | cut -d= -f2 | awk '{print $1}')
	duser="${duser:-dockerinstall}"

	isHeader "Container Layer Setup"
	if id "$duser" &>/dev/null; then
		isSuccessful "Container user '$duser' already exists."
	else
		# -m + the system login.defs SUB_UID/GID defaults assign its subordinate
		# uid/gid ranges (needed for rootless). The later rootless setup sees it
		# existing and configures the daemon/linger/password.
		sudo useradd -m -s /bin/bash -d "/home/$duser" "$duser" 2>/dev/null
		isSuccessful "Created container user '$duser'."
	fi

	# The system root is manager-owned and initFolders makes it 750; give it the
	# rootless traversal bit (o+x → 751) so the container user can reach the few
	# bind-mount sources it must read there (configs/webui/*). The container + backup
	# roots are SEPARATE roots now, so the container user no longer traverses the
	# system tree to reach its own data.
	[[ -d "$docker_dir" ]] && sudo chmod o+x "$docker_dir"

	# Hand the container + backup roots to the container user — it owns per-app data
	# in rootless, and restic (which runs AS that user) writes the backup repos.
	# 751: owner full; the manager (other) can traverse in to known paths.
	local d
	for d in "$containers_dir" "$backup_dir"; do
		if [[ -d "$d" ]]; then
			sudo chown "$duser:$duser" "$d"
			sudo chmod 751 "$d"
		fi
	done
	isSuccessful "containers/ + backups/ handed to '$duser' (system root traversable)."
}

setupConfigsFromRepo()
{
    isNotice "Setting up configuration files from repository..."

    local src="$script_dir/configs"
    local dst="${configs_dir%/}"

    if [[ ! -d "$src" ]]; then
        isError "Source configs directory missing: $src"
        isError "The clone in $script_dir didn't include a configs/ tree — aborting."
        exit 1
    fi

    sudo mkdir -p "$dst"

    # No-clobber: only seed config files that don't already exist. On a fresh
    # install this copies the whole template; on a re-run/deploy it preserves the
    # user's live values (a plain cp -a here silently reset e.g. the Docker
    # install type back to the template default). New *keys* in existing files
    # are added separately by the add-only reconcile pass.
    if ! sudo cp -an "$src"/. "$dst"/; then
        isError "Failed to copy configs from $src to $dst — aborting."
        exit 1
    fi
    sudo chown -R "$sudo_user_name":"$sudo_user_name" "$dst"

    if [[ ! -f "$dst/general/general_install" ]]; then
        isError "Configs were copied but $dst/general/general_install is missing."
        isError "Repository layout may have changed — fix and re-run."
        exit 1
    fi
    isSuccessful "Configuration files copied from repository."

    isNotice "Applying initial configuration values..."
    [[ -n "$param2" ]] && initUpdateConfigOption "CFG_LIBREPORTAL_USER_PASS" "$param2"
    [[ -n "$param3" ]] && initUpdateConfigOption "CFG_GIT_USER"             "$param3"
    [[ -n "$param4" ]] && initUpdateConfigOption "CFG_GIT_KEY"              "$param4"
    [[ -n "$param5" ]] && initUpdateConfigOption "CFG_GIT_URL"              "$param5"
    [[ -n "$param7" ]] && initUpdateConfigOption "CFG_INSTALL_MODE"         "$param7"

    isSuccessful "Configuration setup complete."
}

initGIT()
{
    isHeader "Git Clone / Update"
    
    # Handle local installation
    if [[ "$param7" == "local" ]]; then
        isNotice "Using local folder installation."
        copyFilesFromLocal
        return
    fi

    # Handle release installation — a verified tarball, not a clone. The bootstrap
    # (install.sh) usually staged + verified the code already (LP_ALREADY_FETCHED);
    # otherwise fetch it via the sourced helper (only possible when run from a
    # populated install tree — a bare /root reinstall must go through install.sh).
    if [[ "$param7" == "release" ]]; then
        if [[ "${LP_ALREADY_FETCHED:-0}" == "1" ]]; then
            isNotice "Using the release staged + verified by the bootstrap installer."
        elif [[ -f "$script_dir/scripts/source/fetch.sh" ]]; then
            source "$script_dir/scripts/docker/command/run_privileged.sh"
            source "$script_dir/scripts/source/fetch.sh"
            lpFetchRelease || { isError "Release fetch failed."; exit 1; }
        else
            isError "Release-mode (re)install must be run via install.sh:  curl -fsSL <host>/install.sh | sudo bash"
            exit 1
        fi
        sudo cp -f "$script_dir/init.sh" /root/ 2>/dev/null || true
        setupConfigsFromRepo
        sudo chown -R "$sudo_user_name":"$sudo_user_name" "$script_dir"
        return
    fi

    GIT_USER="$param3"
    GIT_TOKEN="$param4"
    GIT_URL="$param5"

    if [[ -z "$GIT_URL" ]]; then
        isError "Git URL is empty. Please provide a valid Git repository URL."
        exit 1
    fi

    local clean_url
    clean_url=$(cleanGitUrl "$GIT_URL")
    if [[ -z "$clean_url" ]]; then
        isError "Could not normalize Git URL: $GIT_URL"
        exit 1
    fi

    local auth=""
    if [[ -n "$GIT_USER" && "$GIT_USER" != "empty" ]]; then
        auth="${GIT_USER}:${GIT_TOKEN}@"
    fi

    isNotice "Cloning $clean_url into $script_dir ..."
    sudo rm -rf "$script_dir"

    local scheme cloned=false
    for scheme in https http; do
        if sudo -u "$sudo_user_name" git clone -q "${scheme}://${auth}${clean_url}.git" "$script_dir" 2>/dev/null; then
            cloned=true
            isSuccessful "Cloned via ${scheme^^}."
            break
        fi
    done

    if ! $cloned; then
        isError "Failed to clone $clean_url over HTTPS or HTTP."
        isError "Check the URL, credentials, and that the server is reachable."
        exit 1
    fi

    sudo cp -f "$script_dir/init.sh" /root/
    setupConfigsFromRepo
    sudo chown -R "$sudo_user_name":"$sudo_user_name" "$script_dir"
}

initLibrePortalCommand()
{
	isHeader "Custom Command Setup"
	if ! grep -q "LibrePortal Command Start" $sudo_bashrc; then
		isNotice "Command maker not found. Removing old LibrePortal command."
		sed -i '/^libreportal() {$/,/^}$/d' $sudo_bashrc
	else
		isNotice "Command maker found. Removing old LibrePortal command."
		sed -i '/# LibrePortal Command Start/,/# LibrePortal Command End/d' $sudo_bashrc
	fi
	isNotice "Custom command 'libreportal' is not installed. Installing..."

	# The CLI wrapper lives alongside the root helpers in $lp_lib_dir; a symlink
	# in /usr/local/bin keeps `libreportal` on $PATH.
	sudo install -d -m 0755 -o root -g root "$lp_lib_dir"
	sudo rm -rf $command_script
sudo tee -a "$command_script" >/dev/null <<'EOF'
#!/usr/bin/env bash
# LibrePortal Command Start
# LibrePortal Command Version 1.4

# Manager user baked at install (the __MANAGER__ placeholder); unbaked keeps "__".
CHECK_USER="__MANAGER__"; [[ "$CHECK_USER" == *"__"* ]] && CHECK_USER="libreportal"
LP_MANAGER_USER="$CHECK_USER"; export LP_MANAGER_USER
CURRENT_USER=$(whoami)

# Check if the script is run by the specified user
if [ "$CURRENT_USER" != "$CHECK_USER" ]; then
    echo "Script is NOT able to run from this user."
    echo "This script should ONLY be run as: $CHECK_USER"
    exit 1
fi

# --- Relocatable roots (baked into this wrapper at install by init.sh) --------
# An unbaked copy keeps the "__" sentinel, which no real absolute path has.
# Exported so start.sh (and paths.sh) inherit them authoritatively.
LP_SYSTEM_DIR="__SYSTEM_DIR__";         [[ "$LP_SYSTEM_DIR" == *"__"* ]]     && LP_SYSTEM_DIR="/libreportal-system"
LP_CONTAINERS_DIR="__CONTAINERS_DIR__"; [[ "$LP_CONTAINERS_DIR" == *"__"* ]] && LP_CONTAINERS_DIR="/libreportal-containers"
LP_BACKUPS_DIR="__BACKUPS_DIR__";       [[ "$LP_BACKUPS_DIR" == *"__"* ]]    && LP_BACKUPS_DIR="/libreportal-backups"
export LP_SYSTEM_DIR LP_CONTAINERS_DIR LP_BACKUPS_DIR
docker_dir="$LP_SYSTEM_DIR"
configs_dir="$LP_SYSTEM_DIR/configs"
script_dir="$LP_SYSTEM_DIR/install"
install_scripts_dir="$script_dir/scripts"

command1="${1:-empty}"
command2="${2:-empty}"
command3="${3:-empty}"
command4="${4:-empty}"
command5="${5:-empty}"
command6="${6:-empty}"
command7="${7:-empty}"
command8="${8:-empty}"
command9="${9:-empty}"

path="$PWD"

reset_git_config() {
	echo ""
	echo "Resetting Git configuration for re-entry..."

	# Use dynamic config update
	commandUpdateConfigOption "CFG_GIT_USER" "changeme"
	commandUpdateConfigOption "CFG_GIT_KEY" "changeme"
	commandUpdateConfigOption "CFG_GIT_URL" "changeme"
}

# Helper function to load config files in the libreportal command
commandReloadConfigs() {
	# Load new structure config files only
	for category_dir in "$configs_dir"/*; do
		if [ -d "$category_dir" ] && [ -f "$category_dir/.category" ]; then
			for config_file in "$category_dir"/*; do
				local should_load=true
				local filename=$(basename "$config_file")
				# Skip .category files and excluded files (hardcoded for now)
				if [[ "$config_file" =~ \.category$ ]]; then
					should_load=false
				fi
				
				if [ "$should_load" = true ]; then
					source "$config_file"
				fi
			done
		fi
	done
}

# Helper function to update config files in the libreportal command
commandUpdateConfigOption() {
	local config_option="$1"
	local config_value="$2"
	for category_dir in "$configs_dir"/*; do
		if [ -d "$category_dir" ] && [ -f "$category_dir/.category" ]; then
			for config_file in "$category_dir"/*; do
				if [ -f "$config_file" ] && [[ ! "$config_file" =~ \.category$ ]]; then
					if grep -q "^$config_option=" "$config_file"; then
						# Escape special characters in the config value to prevent sed issues
						local escaped_value=$(printf '%s\n' "$config_value" | sed -e 's/[\/&]/\\&/g')
						
						# Extract the comment part first (everything after the first #)
						local original_line=$(grep "^$config_option=" "$config_file")
						local comment_part=$(echo "$original_line" | sed -n "s|^$config_option=[^#]*\(#.*\)|\1|p")
						
						# Replace the value, preserving comment if it existed
						if [[ -n "$comment_part" ]]; then
							sed -i "s|^$config_option=.*|$config_option=$escaped_value                     $comment_part|" "$config_file"
						else
							sed -i "s|^$config_option=.*|$config_option=$escaped_value|" "$config_file"
						fi
						source "$config_file"
						return 0
					fi
				fi
			done
		fi
	done
}

update_config_values() {
	# Reload all config files to get current values
	commandReloadConfigs

	if [[ "$CFG_INSTALL_MODE" == "local" ]]; then
		[[ "$CFG_GIT_USER" != "empty" ]] && commandUpdateConfigOption "CFG_GIT_USER" "empty"
		[[ "$CFG_GIT_KEY"  != "empty" ]] && commandUpdateConfigOption "CFG_GIT_KEY"  "empty"
		[[ "$CFG_GIT_URL"  != "empty" ]] && commandUpdateConfigOption "CFG_GIT_URL"  "empty"
		commandReloadConfigs
		return 0
	fi

	CFG_GIT_USER="${CFG_GIT_USER:-changeme}"
	if [[ -z "$CFG_GIT_USER" ]] || [[ "$CFG_GIT_USER" == "changeme" ]]; then
		while true; do
			echo "Please enter your Git username (Press Enter to set to 'empty'):"
			read -r NEW_GIT_USER
			if [[ -n "$NEW_GIT_USER" || "$NEW_GIT_USER" == "" ]]; then
				if [[ -z "$NEW_GIT_USER" ]]; then
					NEW_GIT_USER="empty"
				fi
				commandUpdateConfigOption "CFG_GIT_USER" "$NEW_GIT_USER"
				commandReloadConfigs
				echo "Updating Git Username to '$NEW_GIT_USER'"
				break
			fi
		done
	fi

	CFG_GIT_KEY="${CFG_GIT_KEY:-changeme}"
	# If Git user is disabled, force token to empty and skip prompt
	if [[ "$CFG_GIT_USER" == "empty" ]]; then
		commandUpdateConfigOption "CFG_GIT_KEY" "empty"
		commandReloadConfigs
		echo "Git authentication disabled; skipping Git token."
	else
		# Only prompt if token is unset or changeme
		if [[ -z "$CFG_GIT_KEY" || "$CFG_GIT_KEY" == "changeme" ]]; then
			while true; do
				echo "Please enter your Git access token (Press Enter to set to 'empty'):"
				read -rs NEW_GIT_KEY
				echo ""

				if [[ -z "$NEW_GIT_KEY" ]]; then
					NEW_GIT_KEY="empty"
				fi

				commandUpdateConfigOption "CFG_GIT_KEY" "$NEW_GIT_KEY"
				commandReloadConfigs
				echo "Git token updated."
				break
			done
		fi
	fi

	CFG_GIT_URL="${CFG_GIT_URL:-changeme}"
	if [[ -z "$CFG_GIT_URL" ]] || [[ "$CFG_GIT_URL" == "changeme" ]]; then
		while true; do
			echo "Please enter your Git repository URL:"
			read -rs NEW_GIT_URL
			if [[ -n "$NEW_GIT_URL" ]]; then
				commandUpdateConfigOption "CFG_GIT_URL" "$NEW_GIT_URL"
				commandReloadConfigs
				echo "Updating Git URL"
				break
			fi
			echo "Error: Git URL cannot be empty"
		done
	fi
}

commandCleanGitUrl() {
	local url="$1"
	url="${url#"${url%%[![:space:]]*}"}"
	url="${url%"${url##*[![:space:]]}"}"
	url="${url#https://}"
	url="${url#http://}"
	while [[ "$url" == */ ]]; do url="${url%/}"; done
	url="${url%.git}"
	echo "$url"
}

setup_repo() {
	while true; do
		update_config_values
		commandReloadConfigs

		CLEAN_GIT_URL=$(commandCleanGitUrl "$CFG_GIT_URL")

		local auth=""
		if [[ "$CFG_GIT_USER" != "empty" && -n "$CFG_GIT_USER" ]]; then
			auth="${CFG_GIT_USER}:${CFG_GIT_KEY}@"
		fi
		AUTH_HTTPS_REPO_URL="https://${auth}${CLEAN_GIT_URL}.git"
		AUTH_HTTP_REPO_URL="http://${auth}${CLEAN_GIT_URL}.git"

		echo ""
		echo "Configuration Summary:"
		echo ""
		
		if [[ "$CFG_INSTALL_MODE" == "local" ]]; then
			echo "Git Username: [NOT APPLICABLE]"
			echo "Git Token: [NOT APPLICABLE]"
			echo "Git URL: [NOT APPLICABLE]"
		else
			echo "Git Username: $CFG_GIT_USER"
			echo "Git Token: [HIDDEN]"
			echo "Git URL: $CLEAN_GIT_URL"
		fi
		echo ""

		read -p "Are these details correct? (y/n): " confirm_config

		if [[ "$confirm_config" == "y" ]]; then
			echo "Configuration confirmed."
			break
		else
			reset_git_config
		fi
	done
}

sync_configs_from_install() {
	local src="$script_dir/configs"
	local dst="$configs_dir"
	if [ ! -d "$src" ]; then
		echo "ERROR: $src missing — clone broken."
		return 1
	fi
	mkdir -p "$dst"
	# No-clobber: preserve the user's live config values; only add new files.
	if ! cp -an "$src"/. "$dst"/; then
		echo "ERROR: Failed to sync configs from $src to $dst."
		return 1
	fi
	chown -R "$CHECK_USER:$CHECK_USER" "$dst"
	if [ ! -f "$dst/general/general_install" ]; then
		echo "ERROR: $dst/general/general_install missing after sync."
		return 1
	fi
	[ -n "$CFG_GIT_USER" ] && commandUpdateConfigOption "CFG_GIT_USER"     "$CFG_GIT_USER"
	[ -n "$CFG_GIT_KEY"  ] && commandUpdateConfigOption "CFG_GIT_KEY"      "$CFG_GIT_KEY"
	[ -n "$CFG_GIT_URL"  ] && commandUpdateConfigOption "CFG_GIT_URL"      "$CFG_GIT_URL"
	commandUpdateConfigOption "CFG_INSTALL_MODE" "git"
	echo "SUCCESS: Configs synced and credentials re-applied."
}

clone_repo() {
	rm -rf "$script_dir"
	local clone_url
	if [ "$CFG_GIT_USER" != "empty" ]; then
		for clone_url in "$AUTH_HTTPS_REPO_URL" "$AUTH_HTTP_REPO_URL"; do
			if git clone -q "$clone_url" "$script_dir" 2>/dev/null; then
				sudo cp -f "$script_dir/init.sh" /root/
				sync_configs_from_install || return 1
				echo "SUCCESS: Clone complete. Run 'libreportal run' to continue."
				return 0
			fi
		done
		echo "ERROR: Authentication failed. Please check your credentials."
		return 1
	fi
	for clone_url in "https://${CLEAN_GIT_URL}.git" "http://${CLEAN_GIT_URL}.git"; do
		if git clone -q "$clone_url" "$script_dir" 2>/dev/null; then
			sudo cp -f "$script_dir/init.sh" /root/
			sync_configs_from_install || return 1
			echo "SUCCESS: Clone complete. Run 'libreportal run' to continue."
			return 0
		fi
	done
	echo "ERROR: Anonymous clone failed."
	return 1
}

clone_and_install() {
	commandReloadConfigs
	if [[ "$CFG_INSTALL_MODE" == "local" ]]; then
		echo "NOTICE: Local install detected — no Git remote to clone."
		return 0
	fi
	if [[ "$CFG_INSTALL_MODE" == "release" ]]; then
		if [ -f "$install_scripts_dir/source/fetch.sh" ] && [ -f "$install_scripts_dir/docker/command/run_privileged.sh" ]; then
			source "$install_scripts_dir/docker/command/run_privileged.sh"
			source "$install_scripts_dir/source/fetch.sh"
			if lpFetchRelease; then
				echo "SUCCESS: Re-fetched the verified release. Run 'libreportal run' to continue."
			else
				echo "ERROR: release re-fetch failed."
			fi
		else
			echo "NOTICE: Release install — reinstall via the bootstrap:  curl -fsSL <host>/install.sh | sudo bash"
		fi
		return 0
	fi
	update_config_values;
	setup_repo;
	clone_repo;
}

cd "$docker_dir/" 2>/dev/null || cd /
if [[ $command1 == "reset" ]]; then
	clone_and_install
elif [ -f "$script_dir/start.sh" ]; then
	chmod 0755 "$script_dir"/*
	cd "$script_dir"
	./start.sh "$command1" "$command2" "$command3" "$command4" "$command5" "$command6" "$command7" "$command8" "$command9"
else
	clone_and_install
fi
# LibrePortal Command End
EOF
	# Bake the manager name + three roots into the (root-owned) wrapper, same as
	# the helpers.
	sudo sed -i \
	    -e "s/__MANAGER__/${sudo_user_name}/g" \
	    -e "s#__SYSTEM_DIR__#${LP_SYSTEM_DIR}#g" \
	    -e "s#__CONTAINERS_DIR__#${LP_CONTAINERS_DIR}#g" \
	    -e "s#__BACKUPS_DIR__#${LP_BACKUPS_DIR}#g" \
	    "$command_script"
	sudo chmod +x $command_script
	sudo chown root:root $command_script
	# Put it on $PATH via a symlink (replaces any older real file at this path).
	sudo ln -sfn "$command_script" "$command_symlink"

	# Generate the uninstall command at the fixed footprint + put it on $PATH as
	# `libreportal-uninstall` (same idea as the CLI wrapper above — generated by
	# init.sh, not a separate repo file). It just runs the engine's uninstall, so
	# users never type a data path. $script_dir is baked in; /root/init.sh is a
	# fallback if the install tree is already gone.
	sudo tee "$lp_lib_dir/uninstall.sh" >/dev/null <<EOF
#!/usr/bin/env bash
# LibrePortal uninstall command — generated by init.sh. Runs the engine's uninstall.
[[ \$EUID -eq 0 ]] || { echo "libreportal-uninstall must run as root (try: sudo)"; exit 1; }
_init="$script_dir/init.sh"; [[ -f "\$_init" ]] || _init="/root/init.sh"
[[ -f "\$_init" ]] || { echo "Cannot find init.sh to run the uninstall."; exit 1; }
exec bash "\$_init" "\$@" uninstall
EOF
	sudo chmod 0755 "$lp_lib_dir/uninstall.sh"
	sudo chown root:root "$lp_lib_dir/uninstall.sh"
	sudo ln -sfn "$lp_lib_dir/uninstall.sh" /usr/local/bin/libreportal-uninstall
	source $sudo_bashrc
}

initUpdateConfigs()
{
    isHeader "Updating Configs"

    initUpdateConfigOption "CFG_LIBREPORTAL_USER_PASS" "$param2" && isSuccessful "Updated Docker user password"
    initUpdateConfigOption "CFG_GIT_USER"             "$param3" && isSuccessful "Updated Git Username"
    initUpdateConfigOption "CFG_GIT_KEY"              "$param4" && isSuccessful "Updated Git Token"
    initUpdateConfigOption "CFG_GIT_URL"              "$param5" && isSuccessful "Updated Git URL"
    initUpdateConfigOption "CFG_INSTALL_MODE"         "$param7" && isSuccessful "Updated Installation Mode"

    isHeader "Verifying Saved Configuration"
    local cfg_file="${configs_dir}general/general_install"
    if [[ ! -f "$cfg_file" ]]; then
        isError "Expected $cfg_file is missing — install cannot proceed."
        exit 1
    fi

    local saved_mode saved_user saved_url saved_key
    saved_mode=$(grep -E '^CFG_INSTALL_MODE=' "$cfg_file" | sed -E 's/^[^=]+=([^[:space:]#]*).*/\1/')
    saved_user=$(grep -E '^CFG_GIT_USER=' "$cfg_file"     | sed -E 's/^[^=]+=([^[:space:]#]*).*/\1/')
    saved_url=$(grep -E '^CFG_GIT_URL=' "$cfg_file"       | sed -E 's/^[^=]+=([^[:space:]#]*).*/\1/')
    saved_key=$(grep -E '^CFG_GIT_KEY=' "$cfg_file"       | sed -E 's/^[^=]+=([^[:space:]#]*).*/\1/')

    isNotice "Mode:  $saved_mode"
    isNotice "User:  $saved_user"
    isNotice "URL:   $saved_url"
    [[ -n "$saved_key" && "$saved_key" != "changeme" ]] \
        && isNotice "Token: [SET]" \
        || isNotice "Token: [EMPTY]"

    if [[ "$saved_mode" == "git" ]]; then
        local fail=0
        if [[ -z "$saved_url" || "$saved_url" == "changeme" ]]; then
            isError "CFG_GIT_URL didn't persist — installer will re-prompt."
            fail=1
        fi
        if [[ "$saved_user" != "empty" && ( -z "$saved_user" || "$saved_user" == "changeme" ) ]]; then
            isError "CFG_GIT_USER didn't persist — installer will re-prompt."
            fail=1
        fi
        if (( fail )); then
            isError "Aborting before handoff so you can fix this once instead of retyping every install run."
            exit 1
        fi
    fi
    isSuccessful "Configuration verified."
}

completeInitMessage()
{
	isHeader "LibrePortal Initilization Complete"

	# Run LibrePortal install as the libreportal user
	isNotice "Starting LibrePortal installation as $sudo_user_name user..."
	
	# Switch to libreportal user and run the install command
	# LIBREPORTAL_INITIAL_INSTALL=1 tells the runtime entry (check_files.sh) to
	# skip the routine update check on this very first run — we just installed
	# the latest code from this very tree, and the update path would otherwise
	# try to git-pull (no .git in the deployed install dir) or lpFetchRelease
	# (no release manifest reachable yet), failing in ways that leave the
	# WebUI / task-processor uninstalled.
	if sudo -u "$sudo_user_name" LIBREPORTAL_SKIP_LOGO=1 LIBREPORTAL_INITIAL_INSTALL=1 bash -c "libreportal run install"; then
		# Install done — tighten the manager's broad install-phase sudo down to
		# the scoped runtime allowlist.
		initScopedSudoers
	else
		echo ""
		echo "⚠️  LibrePortal installation encountered issues."
		echo "   You can manually run the installation with:"
		echo "   sudo -u $sudo_user_name bash -c 'libreportal run install'"
		echo ""
	fi
}

# FULL uninstall: permanently remove everything LibrePortal placed on the host.
# Runs as root (the entrypoint root-check below enforces it). Order matters —
# containers run AS the docker-install user and the rootless daemon is that user's
# systemd --user service, so stop those BEFORE removing the users. Self-contained:
# uses only init.sh's inline helpers, so it still works as it deletes /docker.
# Keep in sync with docs/FOOTPRINT.md.
# Discover where THIS box was actually installed — custom installs put the roots
# anywhere, and a bare `init.sh uninstall` has no LP_*_DIR in scope. The systemd
# unit is the authoritative baked record (Environment=LP_*_DIR + User=<manager>).
# Sets LP_*_DIR + sudo_user_name so the following libreportalDerivePaths resolves
# the real locations; silently no-ops on a legacy unit (then the derive defaults /
# /docker compat shim apply).
libreportalReadBakedRoots() {
	local unit=/etc/systemd/system/libreportal.service
	[[ -f "$unit" ]] || return 0
	local s c b m
	s=$(grep -oE 'LP_SYSTEM_DIR=\S+'     "$unit" | head -1 | cut -d= -f2)
	c=$(grep -oE 'LP_CONTAINERS_DIR=\S+' "$unit" | head -1 | cut -d= -f2)
	b=$(grep -oE 'LP_BACKUPS_DIR=\S+'    "$unit" | head -1 | cut -d= -f2)
	m=$(grep -oE '^User=\S+'             "$unit" | head -1 | cut -d= -f2)
	[[ -n "$s" ]] && LP_SYSTEM_DIR="$s"
	[[ -n "$c" ]] && LP_CONTAINERS_DIR="$c"
	[[ -n "$b" ]] && LP_BACKUPS_DIR="$b"
	[[ -n "$m" ]] && { LP_MANAGER_USER="$m"; sudo_user_name="$m"; }
}

runFullUninstall()
{
	# Resolve the ACTUAL install roots/manager for this box before removing anything.
	libreportalReadBakedRoots
	libreportalDerivePaths

	local mgr="${sudo_user_name:-libreportal}"
	local iuser
	iuser=$(grep -h '^CFG_DOCKER_INSTALL_USER=' "${configs_dir}general/general_docker_install" 2>/dev/null | head -1 | cut -d= -f2 | awk '{print $1}')
	iuser="${iuser:-dockerinstall}"

	# --skip-docker-images: keep the rootless docker layer (the daemon, the
	# "$iuser" user, the image/build cache + the rootless sysctl drop-in) instead
	# of tearing it down, so a following reinstall's `docker build` is cache-fast
	# instead of from-scratch. Everything else (control plane, manager, footprint,
	# /docker) is still removed.
	local keep_docker="${init_skip_docker_images:-false}"

	isHeader "LibrePortal — FULL Uninstall"
	isError "This PERMANENTLY removes EVERYTHING — there is no undo:"
	echo "    - all containers + images + the rootless docker setup"
	echo "    - $docker_dir  (system: configs, database, install)"
	echo "    - $containers_dir  (live app data)"
	echo "    - $backup_dir  (backup repos)"
	echo "    - the '$mgr' and '$iuser' users + their home directories"
	echo "    - /usr/local/lib/libreportal/ + the /usr/local/bin/libreportal command"
	echo "    - /etc/sudoers.d/$mgr, the systemd service, the sysctl drop-ins"
	echo "    - the restic / kopia / ufw-docker binaries LibrePortal installed"
	echo ""
	isNotice "LEFT IN PLACE: the docker engine, docker-compose, apt-installed deps,"
	isNotice "and your SSH config (so you can't get locked out)."
	echo ""
	if [[ "$keep_docker" == "true" ]]; then
		isNotice "--skip-docker-images: KEEPING the rootless docker daemon, the '$iuser' user, and the image/build cache (for a faster reinstall)."
		echo ""
	fi
	if [[ "$init_unattended_mode" == true ]]; then
		isNotice "Unattended mode — proceeding without the DELETE LIBREPORTAL prompt."
	else
		isQuestion "Type exactly  DELETE LIBREPORTAL  to confirm:"
		local confirm; read -r confirm
		if [[ "$confirm" != "DELETE LIBREPORTAL" ]]; then
			isNotice "Aborted — nothing was removed."
			return 0
		fi
	fi

	isHeader "Tearing down LibrePortal"

	# 1. Stop + remove the task-processor service.
	systemctl disable --now libreportal.service >/dev/null 2>&1
	rm -f /etc/systemd/system/libreportal.service
	systemctl daemon-reload >/dev/null 2>&1
	isSuccessful "Stopped + removed the task-processor service"

	# 2. Best-effort graceful container removal, then tear down the rootless
	#    user's session (stops the rootless dockerd + any survivors).
	local uid; uid=$(id -u "$iuser" 2>/dev/null)
	if [[ -n "$uid" ]]; then
		# Remove stale containers either way — they bind-mount the about-to-be-
		# wiped /docker. The images + build cache live in the user's home and are
		# untouched by `docker rm`.
		sudo -u "$iuser" env XDG_RUNTIME_DIR="/run/user/$uid" \
			DOCKER_HOST="unix:///run/user/$uid/docker.sock" \
			bash -c 'docker ps -aq | xargs -r docker rm -f' >/dev/null 2>&1 || true
		if [[ "$keep_docker" == "true" ]]; then
			isSuccessful "Removed containers; kept the rootless docker daemon + images for '$iuser'"
		else
			sudo -u "$iuser" env XDG_RUNTIME_DIR="/run/user/$uid" \
				dockerd-rootless-setuptool.sh uninstall >/dev/null 2>&1 || true
			loginctl disable-linger "$iuser" >/dev/null 2>&1 || true
			loginctl terminate-user "$iuser" >/dev/null 2>&1 || true
			pkill -9 -u "$iuser" >/dev/null 2>&1 || true
			isSuccessful "Stopped containers + rootless docker for '$iuser'"
		fi
	fi

	# 3. Remove the out-of-/docker footprint (see docs/FOOTPRINT.md).
	rm -f /usr/local/bin/libreportal /usr/local/bin/libreportal-uninstall
	rm -rf /usr/local/lib/libreportal
	rm -f "/etc/sudoers.d/$mgr"
	# Keep the sysctl drop-ins when preserving docker — removing + reloading them
	# would reset ip_unprivileged_port_start etc. out from under the still-running
	# rootless daemon. (The reinstall re-adds them idempotently.)
	if [[ "$keep_docker" != "true" ]]; then
		rm -f /etc/sysctl.d/99-libreportal-hardening.conf /etc/sysctl.d/99-libreportal-rootless.conf
		sysctl --system >/dev/null 2>&1
	fi
	rm -f /usr/local/bin/restic /usr/local/bin/kopia /usr/local/bin/ufw-docker
	rm -f /root/init.sh
	isSuccessful "Removed the system-integration footprint"

	# 4. Remove all app data — the three roots (on a legacy single-tree install the
	#    container/backup roots are subdirs of the system root, so this is safe and
	#    idempotent either way).
	rm -rf "$docker_dir" "$containers_dir" "$backup_dir"
	isSuccessful "Removed $docker_dir, $containers_dir, $backup_dir"

	# 5. Remove the LibrePortal users + their subuid/subgid ranges + home dirs.
	#    Terminate each user's session/linger and kill its processes first, or
	#    `userdel -r` leaves the home behind ("user currently used"); rm -rf the
	#    home afterwards as a backstop.
	local u
	local users_to_remove=("$mgr")
	[[ "$keep_docker" == "true" ]] || users_to_remove+=("$iuser")
	for u in "${users_to_remove[@]}"; do
		loginctl disable-linger "$u" >/dev/null 2>&1 || true
		loginctl terminate-user "$u" >/dev/null 2>&1 || true
		pkill -9 -u "$u" >/dev/null 2>&1 || true
		userdel -r "$u" >/dev/null 2>&1 || true
		[[ -n "$u" ]] && rm -rf "/home/$u"
		# userdel does NOT remove the cron spool; a leftover (owned by this
		# now-removed uid) blocks the next install's user from replacing it in the
		# sticky spool dir → "crontab: rename: Operation not permitted".
		[[ -n "$u" ]] && rm -f "/var/spool/cron/crontabs/$u"
	done
	rm -f /var/spool/cron/crontabs/easydocker   # legacy pre-rename artifact
	if [[ "$keep_docker" == "true" ]]; then
		sed -i "/^${mgr}:/d" /etc/subuid /etc/subgid 2>/dev/null || true
		isSuccessful "Removed user '$mgr' (+ home); kept '$iuser' + its docker image store"
	else
		sed -i "/^${mgr}:/d;/^${iuser}:/d" /etc/subuid /etc/subgid 2>/dev/null || true
		isSuccessful "Removed users '$mgr' + '$iuser' (+ home dirs)"
	fi

	isHeader "LibrePortal uninstalled"
	isNotice "Left in place: docker engine, docker-compose, apt deps, SSH config."
}

# Only run the installer entrypoint (root check + init flow) when init.sh is
# EXECUTED directly. When it's SOURCED — start.sh loads init.sh for its function
# defs at runtime, and under Model A start.sh runs as the manager, not root — the
# defs above are all that's wanted and this root check must NOT fire.
[[ "${BASH_SOURCE[0]}" != "${0}" ]] && return 0 2>/dev/null

if [[ $EUID -ne 0 ]]; then
	echo "This script must be run as root."
	exit 1
else
	if [[ "$param1" == "init" ]]; then
		# Validate the chosen install roots before creating/baking anything.
		libreportalValidatePaths

		# Always check existing config first
		initCheckConfigs

		if [[ "$param6" == "true" || "$param6" == "1" ]]; then
			# Validate unattended params
			validateUnattended
			initDisplayConfig # in unattended mode, it will auto-accept
		else
			# Interactive mode
			initInputQuestions
			initDisplayConfig
		fi

		# Common steps (run in both interactive and unattended after confirmation)
		if [ "$init_skip_os_update" != true ]; then
			initOS
		fi
		
		if [ "$init_skip_prereqs" != true ]; then
			initPrerequires
		fi
		
		initDocker
		initUsers
		initFolders
		initGIT
		initRootHelpers
		initLibrePortalCommand
		initUpdateConfigs
		initContainerLayer
		completeInitMessage
	elif [[ "$param1" == "uninstall" ]]; then
		runFullUninstall
	fi
fi
