# Copyright (c) 2021-2026 community-scripts ORG # Author: tteck (tteckster) # Co-Author: MickLesk # Co-Author: michelroegl-brunner # License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE # ============================================================================== # INSTALL.FUNC - UNIFIED CONTAINER INSTALLATION & SETUP # ============================================================================== # # All-in-One install.func supporting official Proxmox LXC templates: # - Debian, Ubuntu, Devuan (apt, systemd/sysvinit) # - Alpine (apk, OpenRC) # - Fedora, Rocky, AlmaLinux, CentOS Stream (dnf, systemd) # - openSUSE (zypper, systemd) # - Gentoo (emerge, OpenRC) # - openEuler (dnf, systemd) # # Supported templates (pveam available): # almalinux-9/10, alpine-3.x, centos-9-stream, debian-12/13, # devuan-5, fedora-42, gentoo-current, openeuler-25, # opensuse-15.x, rockylinux-9/10, ubuntu-22.04/24.04/25.04 # # Features: # - Automatic OS detection # - Unified package manager abstraction # - Init system abstraction (systemd/OpenRC/sysvinit) # - Network connectivity verification # - MOTD and SSH configuration # - Container customization # # ============================================================================== # ============================================================================== # SECTION 1: INITIALIZATION & OS DETECTION # ============================================================================== # Global variables for OS detection OS_TYPE="" # debian, ubuntu, devuan, alpine, fedora, rocky, alma, centos, opensuse, gentoo, openeuler OS_FAMILY="" # debian, alpine, rhel, suse, gentoo OS_VERSION="" # Version number PKG_MANAGER="" # apt, apk, dnf, yum, zypper, emerge INIT_SYSTEM="" # systemd, openrc, sysvinit # ------------------------------------------------------------------------------ # detect_os() # # Detects the operating system and sets global variables: # OS_TYPE, OS_FAMILY, OS_VERSION, PKG_MANAGER, INIT_SYSTEM # ------------------------------------------------------------------------------ detect_os() { if [[ -f /etc/os-release ]]; then # shellcheck disable=SC1091 . /etc/os-release OS_TYPE="${ID:-unknown}" OS_VERSION="${VERSION_ID:-unknown}" elif [[ -f /etc/alpine-release ]]; then OS_TYPE="alpine" OS_VERSION=$(cat /etc/alpine-release) elif [[ -f /etc/debian_version ]]; then OS_TYPE="debian" OS_VERSION=$(cat /etc/debian_version) elif [[ -f /etc/redhat-release ]]; then OS_TYPE="centos" OS_VERSION=$(grep -oE '[0-9]+\.[0-9]+' /etc/redhat-release | head -1) elif [[ -f /etc/arch-release ]]; then OS_TYPE="arch" OS_VERSION="rolling" elif [[ -f /etc/gentoo-release ]]; then OS_TYPE="gentoo" OS_VERSION=$(cat /etc/gentoo-release | grep -oE '[0-9.]+') else OS_TYPE="unknown" OS_VERSION="unknown" fi # Normalize OS type and determine family case "$OS_TYPE" in debian) OS_FAMILY="debian" PKG_MANAGER="apt" ;; ubuntu) OS_FAMILY="debian" PKG_MANAGER="apt" ;; devuan) OS_FAMILY="debian" PKG_MANAGER="apt" ;; alpine) OS_FAMILY="alpine" PKG_MANAGER="apk" ;; fedora) OS_FAMILY="rhel" PKG_MANAGER="dnf" ;; rocky | rockylinux) OS_TYPE="rocky" OS_FAMILY="rhel" PKG_MANAGER="dnf" ;; alma | almalinux) OS_TYPE="alma" OS_FAMILY="rhel" PKG_MANAGER="dnf" ;; centos) OS_FAMILY="rhel" # CentOS 7 uses yum, 8+ uses dnf if [[ "${OS_VERSION%%.*}" -ge 8 ]]; then PKG_MANAGER="dnf" else PKG_MANAGER="yum" fi ;; rhel) OS_FAMILY="rhel" PKG_MANAGER="dnf" ;; openeuler) OS_FAMILY="rhel" PKG_MANAGER="dnf" ;; opensuse* | sles) OS_TYPE="opensuse" OS_FAMILY="suse" PKG_MANAGER="zypper" ;; gentoo) OS_FAMILY="gentoo" PKG_MANAGER="emerge" ;; *) OS_FAMILY="unknown" PKG_MANAGER="unknown" ;; esac # Detect init system if command -v systemctl &>/dev/null && [[ -d /run/systemd/system ]]; then INIT_SYSTEM="systemd" elif command -v rc-service &>/dev/null || [[ -d /etc/init.d && -f /sbin/openrc ]]; then INIT_SYSTEM="openrc" elif [[ -f /etc/inittab ]]; then INIT_SYSTEM="sysvinit" else INIT_SYSTEM="unknown" fi } # ------------------------------------------------------------------------------ # Bootstrap: Ensure curl is available and source core functions # ------------------------------------------------------------------------------ _bootstrap() { # Minimal bootstrap to get curl installed if ! command -v curl &>/dev/null; then printf "\r\e[2K%b" '\033[93m Setup Source \033[m' >&2 if command -v apt-get &>/dev/null; then apt-get update &>/dev/null && apt-get install -y curl &>/dev/null elif command -v apk &>/dev/null; then apk update &>/dev/null && apk add curl &>/dev/null elif command -v dnf &>/dev/null; then dnf install -y curl &>/dev/null elif command -v yum &>/dev/null; then yum install -y curl &>/dev/null elif command -v zypper &>/dev/null; then zypper install -y curl &>/dev/null elif command -v emerge &>/dev/null; then emerge --quiet net-misc/curl &>/dev/null fi fi # Configurable base URL for development — override with COMMUNITY_SCRIPTS_URL COMMUNITY_SCRIPTS_URL="${COMMUNITY_SCRIPTS_URL:-https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main}" # Source core functions source <(curl -fsSL "$COMMUNITY_SCRIPTS_URL/misc/core.func") source <(curl -fsSL "$COMMUNITY_SCRIPTS_URL/misc/error_handler.func") load_functions catch_errors get_lxc_ip } # Run bootstrap and OS detection _bootstrap detect_os # ============================================================================== # SECTION 2: PACKAGE MANAGER ABSTRACTION # ============================================================================== # ------------------------------------------------------------------------------ # pkg_update() # # Updates package manager cache/database # ------------------------------------------------------------------------------ pkg_update() { case "$PKG_MANAGER" in apt) if ! $STD apt-get update; then local failed_mirror failed_mirror=$(grep -m1 -oP '(?<=URIs: https?://)[^/]+' /etc/apt/sources.list.d/debian.sources 2>/dev/null || grep -m1 -oP '(?<=deb https?://)[^/]+' /etc/apt/sources.list 2>/dev/null || echo "unknown") msg_warn "apt-get update failed (${failed_mirror}), trying alternate mirrors..." local eu_mirrors="ftp.de.debian.org ftp.fr.debian.org ftp.nl.debian.org ftp.uk.debian.org ftp.ch.debian.org ftp.se.debian.org ftp.it.debian.org ftp.fau.de ftp.halifax.rwth-aachen.de debian.mirror.lrz.de mirror.init7.net debian.ethz.ch mirrors.dotsrc.org debian.mirrors.ovh.net" local us_mirrors="ftp.us.debian.org ftp.ca.debian.org debian.csail.mit.edu mirrors.ocf.berkeley.edu mirrors.wikimedia.org debian.osuosl.org mirror.cogentco.com" local ap_mirrors="ftp.au.debian.org ftp.jp.debian.org ftp.tw.debian.org ftp.kr.debian.org ftp.hk.debian.org ftp.sg.debian.org mirror.aarnet.edu.au mirror.nitc.ac.in" local tz regional others tz=$(cat /etc/timezone 2>/dev/null || echo "UTC") case "$tz" in Europe/* | Arctic/*) regional="$eu_mirrors" others="$us_mirrors $ap_mirrors" ;; America/*) regional="$us_mirrors" others="$eu_mirrors $ap_mirrors" ;; Asia/* | Australia/* | Pacific/*) regional="$ap_mirrors" others="$eu_mirrors $us_mirrors" ;; *) regional="" others="$eu_mirrors $us_mirrors $ap_mirrors" ;; esac echo 'Acquire::By-Hash "no";' >/etc/apt/apt.conf.d/99no-by-hash _try_apt_mirror() { local m=$1 for src in /etc/apt/sources.list.d/debian.sources /etc/apt/sources.list; do [[ -f "$src" ]] && sed -i "s|URIs: http[s]*://[^/]*/|URIs: http://${m}/|g; s|deb http[s]*://[^/]*/|deb http://${m}/|g" "$src" done rm -rf /var/lib/apt/lists/* local out out=$(apt-get update 2>&1) if echo "$out" | grep -qi "hashsum\|hash sum"; then msg_warn "Mirror ${m} failed (hash mismatch)" return 1 elif echo "$out" | grep -qi "SSL\|certificate"; then msg_warn "Mirror ${m} failed (SSL/certificate error)" return 1 elif echo "$out" | grep -q "^E:"; then msg_warn "Mirror ${m} failed (apt-get update error)" return 1 else msg_ok "CDN set to ${m}: tests passed" return 0 fi } _scan_reachable() { local result="" for m in $1; do if timeout 2 bash -c "echo >/dev/tcp/$m/80" 2>/dev/null; then result="$result $m" fi done echo "$result" | xargs } local apt_ok=false # Phase 1: Scan global mirrors first (independent of local CDN issues) local others_ok others_ok=$(_scan_reachable "$others") local others_pick others_pick=$(printf '%s\n' $others_ok | shuf | head -3 | xargs) for mirror in $others_pick; do msg_info "Attempting mirror: ${mirror}" if _try_apt_mirror "$mirror"; then apt_ok=true break fi done # Phase 2: Try ftp.debian.org if [[ "$apt_ok" != true ]]; then if timeout 2 bash -c "echo >/dev/tcp/ftp.debian.org/80" 2>/dev/null; then msg_info "Attempting mirror: ftp.debian.org" if _try_apt_mirror "ftp.debian.org"; then apt_ok=true fi fi fi # Phase 3: Fall back to regional mirrors if [[ "$apt_ok" != true ]]; then local regional_ok regional_ok=$(_scan_reachable "$regional") local regional_pick regional_pick=$(printf '%s\n' $regional_ok | shuf | head -3 | xargs) for mirror in $regional_pick; do msg_info "Attempting mirror: ${mirror}" if _try_apt_mirror "$mirror"; then apt_ok=true break fi done fi # Phase 4: All auto mirrors failed, prompt user if [[ "$apt_ok" != true ]]; then msg_warn "Multiple mirrors failed (possible CDN synchronization issue)." msg_info "Find Debian mirrors at: https://www.debian.org/mirror/list" while true; do read -rp " Enter a Debian mirror hostname (or 'skip' to abort): " custom_mirror /dev/null; then $STD update-rc.d "$service" defaults elif command -v chkconfig &>/dev/null; then $STD chkconfig "$service" on fi ;; *) msg_warn "Unknown init system, cannot enable $service" return 1 ;; esac } # ------------------------------------------------------------------------------ # svc_disable(service) # # Disables a service from starting at boot # ------------------------------------------------------------------------------ svc_disable() { local service="$1" [[ -z "$service" ]] && return 1 case "$INIT_SYSTEM" in systemd) $STD systemctl disable "$service" ;; openrc) $STD rc-update del "$service" default 2>/dev/null || true ;; sysvinit) if command -v update-rc.d &>/dev/null; then $STD update-rc.d "$service" remove elif command -v chkconfig &>/dev/null; then $STD chkconfig "$service" off fi ;; *) return 1 ;; esac } # ------------------------------------------------------------------------------ # svc_start(service) # # Starts a service immediately # ------------------------------------------------------------------------------ svc_start() { local service="$1" [[ -z "$service" ]] && return 1 case "$INIT_SYSTEM" in systemd) $STD systemctl start "$service" ;; openrc) $STD rc-service "$service" start ;; sysvinit) $STD /etc/init.d/"$service" start ;; *) return 1 ;; esac } # ------------------------------------------------------------------------------ # svc_stop(service) # # Stops a running service # ------------------------------------------------------------------------------ svc_stop() { local service="$1" [[ -z "$service" ]] && return 1 case "$INIT_SYSTEM" in systemd) $STD systemctl stop "$service" ;; openrc) $STD rc-service "$service" stop ;; sysvinit) $STD /etc/init.d/"$service" stop ;; *) return 1 ;; esac } # ------------------------------------------------------------------------------ # svc_restart(service) # # Restarts a service # ------------------------------------------------------------------------------ svc_restart() { local service="$1" [[ -z "$service" ]] && return 1 case "$INIT_SYSTEM" in systemd) $STD systemctl restart "$service" ;; openrc) $STD rc-service "$service" restart ;; sysvinit) $STD /etc/init.d/"$service" restart ;; *) return 1 ;; esac } # ------------------------------------------------------------------------------ # svc_status(service) # # Gets service status (returns 0 if running) # ------------------------------------------------------------------------------ svc_status() { local service="$1" [[ -z "$service" ]] && return 1 case "$INIT_SYSTEM" in systemd) systemctl is-active --quiet "$service" ;; openrc) rc-service "$service" status &>/dev/null ;; sysvinit) /etc/init.d/"$service" status &>/dev/null ;; *) return 1 ;; esac } # ------------------------------------------------------------------------------ # svc_reload_daemon() # # Reloads init system daemon configuration (for systemd) # ------------------------------------------------------------------------------ svc_reload_daemon() { case "$INIT_SYSTEM" in systemd) $STD systemctl daemon-reload ;; *) # Other init systems don't need this return 0 ;; esac } # ============================================================================== # SECTION 4: NETWORK & CONNECTIVITY # ============================================================================== # ------------------------------------------------------------------------------ # get_ip() # # Gets the primary IPv4 address of the container # Returns: IP address string # ------------------------------------------------------------------------------ get_ip() { local ip="" # Try hostname -I first (most common) if command -v hostname &>/dev/null; then ip=$(hostname -I 2>/dev/null | awk '{print $1}' || true) fi # Fallback to ip command if [[ -z "$ip" ]] && command -v ip &>/dev/null; then ip=$(ip -4 addr show scope global | awk '/inet /{print $2}' | cut -d/ -f1 | head -1) fi # Fallback to ifconfig if [[ -z "$ip" ]] && command -v ifconfig &>/dev/null; then ip=$(ifconfig | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1' | head -1) fi echo "$ip" } # ------------------------------------------------------------------------------ # verb_ip6() # # Configures IPv6 based on IPV6_METHOD variable # If IPV6_METHOD=disable: disables IPv6 via sysctl # ------------------------------------------------------------------------------ verb_ip6() { set_std_mode # Set STD mode based on VERBOSE if [[ "${IPV6_METHOD:-}" == "disable" ]]; then msg_info "Disabling IPv6 (this may affect some services)" mkdir -p /etc/sysctl.d cat >/etc/sysctl.d/99-disable-ipv6.conf </dev/null || true fi msg_ok "Disabled IPv6" fi } # ------------------------------------------------------------------------------ # setting_up_container() # # Initial container setup: # - Verifies network connectivity # - Removes Python EXTERNALLY-MANAGED restrictions # - Disables network wait services # ------------------------------------------------------------------------------ setting_up_container() { msg_info "Setting up Container OS" # Wait for network local i for ((i = RETRY_NUM; i > 0; i--)); do if [[ -n "$(get_ip)" ]]; then break fi echo 1>&2 -en "${CROSS}${RD} No Network! " sleep "$RETRY_EVERY" done if [[ -z "$(get_ip)" ]]; then echo 1>&2 -e "\n${CROSS}${RD} No Network After $RETRY_NUM Tries${CL}" echo -e "${NETWORK}Check Network Settings" exit 1 fi # Remove Python EXTERNALLY-MANAGED restriction (Debian 12+, Ubuntu 23.04+) rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED 2>/dev/null || true # Disable network wait services for faster boot case "$INIT_SYSTEM" in systemd) systemctl disable -q --now systemd-networkd-wait-online.service 2>/dev/null || true ;; esac msg_ok "Set up Container OS" msg_ok "Network Connected: ${BL}$(get_ip)" } # ------------------------------------------------------------------------------ # network_check() # # Comprehensive network connectivity check for IPv4 and IPv6 # Tests connectivity to DNS servers and verifies DNS resolution # ------------------------------------------------------------------------------ network_check() { set +e trap - ERR local ipv4_connected=false local ipv6_connected=false sleep 1 # Check IPv4 connectivity if ping -c 1 -W 1 1.1.1.1 &>/dev/null || ping -c 1 -W 1 8.8.8.8 &>/dev/null || ping -c 1 -W 1 9.9.9.9 &>/dev/null; then msg_ok "IPv4 Internet Connected" ipv4_connected=true else msg_error "IPv4 Internet Not Connected" fi # Check IPv6 connectivity (if ping6 exists) if command -v ping6 &>/dev/null; then if ping6 -c 1 -W 1 2606:4700:4700::1111 &>/dev/null || ping6 -c 1 -W 1 2001:4860:4860::8888 &>/dev/null; then msg_ok "IPv6 Internet Connected" ipv6_connected=true else msg_error "IPv6 Internet Not Connected" fi fi # Prompt if both fail if [[ $ipv4_connected == false && $ipv6_connected == false ]]; then read -r -p "No Internet detected, would you like to continue anyway? " prompt if [[ "${prompt,,}" =~ ^(y|yes)$ ]]; then echo -e "${INFO}${RD}Expect Issues Without Internet${CL}" else echo -e "${NETWORK}Check Network Settings" exit 1 fi fi # DNS resolution checks local GIT_HOSTS=("github.com" "raw.githubusercontent.com" "git.community-scripts.org") local GIT_STATUS="Git DNS:" local DNS_FAILED=false for HOST in "${GIT_HOSTS[@]}"; do local RESOLVEDIP RESOLVEDIP=$(getent hosts "$HOST" 2>/dev/null | awk '{ print $1 }' | head -n1) if [[ -z "$RESOLVEDIP" ]]; then GIT_STATUS+=" $HOST:(${DNSFAIL:-FAIL})" DNS_FAILED=true else GIT_STATUS+=" $HOST:(${DNSOK:-OK})" fi done if [[ "$DNS_FAILED" == true ]]; then fatal "$GIT_STATUS" else msg_ok "$GIT_STATUS" fi # Verify APT repository DNS resolution (detect Tailscale MagicDNS / broken resolver) if command -v apt-get &>/dev/null; then local APT_DNS_OK=true for REPO_HOST in "deb.debian.org" "archive.ubuntu.com"; do if ! getent hosts "$REPO_HOST" &>/dev/null; then APT_DNS_OK=false break fi done if [[ "$APT_DNS_OK" == false ]]; then msg_warn "APT repository DNS resolution failed, injecting public DNS servers" echo -e "nameserver 8.8.8.8\nnameserver 1.1.1.1" >/etc/resolv.conf fi fi set -e trap 'error_handler $LINENO "$BASH_COMMAND"' ERR } # ============================================================================== # SECTION 5: OS UPDATE & PACKAGE MANAGEMENT # ============================================================================== # ------------------------------------------------------------------------------ # update_os() # # Updates container OS and sources appropriate tools.func # ------------------------------------------------------------------------------ update_os() { msg_info "Updating Container OS" # Configure APT cacher proxy if enabled (Debian/Ubuntu only) if [[ "$PKG_MANAGER" == "apt" && "${CACHER:-}" == "yes" ]]; then echo 'Acquire::http::Proxy-Auto-Detect "/usr/local/bin/apt-proxy-detect.sh";' >/etc/apt/apt.conf.d/00aptproxy cat </usr/local/bin/apt-proxy-detect.sh #!/bin/bash if nc -w1 -z "${CACHER_IP}" 3142; then echo -n "http://${CACHER_IP}:3142" else echo -n "DIRECT" fi EOF chmod +x /usr/local/bin/apt-proxy-detect.sh fi # Update and upgrade pkg_update pkg_upgrade # Remove Python EXTERNALLY-MANAGED restriction rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED 2>/dev/null || true msg_ok "Updated Container OS" # Source appropriate tools.func based on OS case "$OS_FAMILY" in alpine) source <(curl -fsSL "$COMMUNITY_SCRIPTS_URL/misc/alpine-tools.func") ;; *) source <(curl -fsSL "$COMMUNITY_SCRIPTS_URL/misc/tools.func") ;; esac } # ============================================================================== # SECTION 6: MOTD & SSH CONFIGURATION # ============================================================================== # ------------------------------------------------------------------------------ # motd_ssh() # # Configures Message of the Day and SSH settings # ------------------------------------------------------------------------------ motd_ssh() { # Set terminal to 256-color mode grep -qxF "export TERM='xterm-256color'" /root/.bashrc 2>/dev/null || echo "export TERM='xterm-256color'" >>/root/.bashrc # Get OS information local os_name="$OS_TYPE" local os_version="$OS_VERSION" if [[ -f /etc/os-release ]]; then os_name=$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '"') os_version=$(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '"') fi # Create MOTD profile script local PROFILE_FILE="/etc/profile.d/00_lxc-details.sh" cat >"$PROFILE_FILE" </dev/null | awk '{print \$1}' || ip -4 addr show scope global | awk '/inet /{print \$2}' | cut -d/ -f1 | head -1)${CL:-}" echo -e "${YW:-} Repository: ${GN:-}https://github.com/community-scripts/ProxmoxVED${CL:-}" echo "" EOF # Disable default MOTD scripts (Debian/Ubuntu) [[ -d /etc/update-motd.d ]] && chmod -x /etc/update-motd.d/* 2>/dev/null || true # Configure SSH root access if requested if [[ "${SSH_ROOT:-}" == "yes" ]]; then # Ensure SSH server is installed if [[ ! -f /etc/ssh/sshd_config ]]; then msg_info "Installing SSH server" case "$PKG_MANAGER" in apt) pkg_install openssh-server ;; apk) pkg_install openssh rc-update add sshd default 2>/dev/null || true ;; dnf | yum) pkg_install openssh-server ;; zypper) pkg_install openssh ;; emerge) pkg_install net-misc/openssh ;; esac msg_ok "Installed SSH server" fi local sshd_config="/etc/ssh/sshd_config" if [[ -f "$sshd_config" ]]; then sed -i "s/#PermitRootLogin prohibit-password/PermitRootLogin yes/g" "$sshd_config" sed -i "s/PermitRootLogin prohibit-password/PermitRootLogin yes/g" "$sshd_config" case "$INIT_SYSTEM" in systemd) svc_restart sshd 2>/dev/null || svc_restart ssh 2>/dev/null || true ;; openrc) svc_enable sshd 2>/dev/null || true svc_start sshd 2>/dev/null || true ;; *) svc_restart sshd 2>/dev/null || true ;; esac fi fi } # ============================================================================== # SECTION 7: CONTAINER CUSTOMIZATION # ============================================================================== # ------------------------------------------------------------------------------ # customize() # # Customizes container for passwordless login and creates update script # ------------------------------------------------------------------------------ customize() { if [[ "${PASSWORD:-}" == "" ]]; then msg_info "Customizing Container" # Remove root password for auto-login passwd -d root &>/dev/null || true case "$INIT_SYSTEM" in systemd) # Mask services that block boot in LXC containers # systemd-homed-firstboot.service hangs waiting for user input on Fedora systemctl mask systemd-homed-firstboot.service &>/dev/null || true systemctl mask systemd-homed.service &>/dev/null || true # Configure console-getty for auto-login in LXC containers # console-getty.service is THE service that handles /dev/console in LXC # It's present on all systemd distros but not enabled by default on Fedora/RHEL if [[ -f /usr/lib/systemd/system/console-getty.service ]]; then mkdir -p /etc/systemd/system/console-getty.service.d cat >/etc/systemd/system/console-getty.service.d/override.conf <<'EOF' [Service] ExecStart= ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud 115200,38400,9600 - $TERM EOF # Enable console-getty for LXC web console (required on Fedora/RHEL) systemctl enable console-getty.service &>/dev/null || true fi # Also configure container-getty@1 (Debian/Ubuntu default in LXC) if [[ -f /usr/lib/systemd/system/container-getty@.service ]]; then mkdir -p /etc/systemd/system/container-getty@1.service.d cat >/etc/systemd/system/container-getty@1.service.d/override.conf <<'EOF' [Service] ExecStart= ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud tty%I 115200,38400,9600 - $TERM EOF fi # Reload systemd and restart getty services to apply auto-login systemctl daemon-reload systemctl restart console-getty.service &>/dev/null || true systemctl restart container-getty@1.service &>/dev/null || true ;; openrc) # Alpine/Gentoo: modify inittab for auto-login if [[ -f /etc/inittab ]]; then sed -i 's|^tty1::respawn:.*|tty1::respawn:/sbin/agetty --autologin root --noclear tty1 38400 linux|' /etc/inittab fi touch /root/.hushlogin ;; sysvinit) # Devuan/older systems - modify inittab for auto-login # Devuan 5 (daedalus) uses SysVinit with various inittab formats # LXC can use /dev/console OR /dev/tty1 depending on how pct console connects if [[ -f /etc/inittab ]]; then # Backup original inittab cp /etc/inittab /etc/inittab.bak 2>/dev/null || true # Enable autologin on tty1 (for direct access) - handle various formats # Devuan uses format: 1:2345:respawn:/sbin/getty 38400 tty1 sed -i 's|^\(1:[0-9]*:respawn:\).*getty.*tty1.*|1:2345:respawn:/sbin/agetty --autologin root --noclear tty1 38400 linux|' /etc/inittab # CRITICAL: Add/replace console entry for LXC - this is what pct console uses! # Remove any existing console entries first (commented or not) sed -i '/^[^#]*:.*:respawn:.*getty.*console/d' /etc/inittab sed -i '/^# LXC console autologin/d' /etc/inittab # Add new console entry for LXC at the end echo "" >>/etc/inittab echo "# LXC console autologin (added by community-scripts)" >>/etc/inittab echo "co:2345:respawn:/sbin/agetty --autologin root --noclear console 115200 linux" >>/etc/inittab # Force a reload of inittab and respawn ALL getty processes # Kill ALL getty processes to force respawn with new autologin settings pkill -9 -f '[ag]etty' &>/dev/null || true # Small delay to let init notice the dead processes sleep 1 # Reload inittab - try multiple methods telinit q &>/dev/null || init q &>/dev/null || kill -HUP 1 &>/dev/null || true fi touch /root/.hushlogin ;; esac msg_ok "Customized Container" fi # Create update script # Use var_os for OS-based containers, otherwise use app name local update_script_name="${var_os:-$app}" echo "bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/ct/${update_script_name}.sh)\"" >/usr/bin/update chmod +x /usr/bin/update # Inject SSH authorized keys if provided if [[ -n "${SSH_AUTHORIZED_KEY:-}" ]]; then mkdir -p /root/.ssh echo "${SSH_AUTHORIZED_KEY}" >/root/.ssh/authorized_keys chmod 700 /root/.ssh chmod 600 /root/.ssh/authorized_keys fi } # ============================================================================== # SECTION 8: UTILITY FUNCTIONS # ============================================================================== # ------------------------------------------------------------------------------ # validate_tz(timezone) # # Validates if a timezone is valid # Returns: 0 if valid, 1 if invalid # ------------------------------------------------------------------------------ validate_tz() { local tz="$1" [[ -f "/usr/share/zoneinfo/$tz" ]] } # ------------------------------------------------------------------------------ # set_timezone(timezone) # # Sets container timezone # ------------------------------------------------------------------------------ set_timezone() { local tz="$1" if validate_tz "$tz"; then ln -sf "/usr/share/zoneinfo/$tz" /etc/localtime echo "$tz" >/etc/timezone 2>/dev/null || true # Update tzdata if available case "$PKG_MANAGER" in apt) dpkg-reconfigure -f noninteractive tzdata 2>/dev/null || true ;; esac msg_ok "Timezone set to $tz" else msg_warn "Invalid timezone: $tz" fi } # ------------------------------------------------------------------------------ # os_info() # # Prints detected OS information (for debugging) # ------------------------------------------------------------------------------ os_info() { echo "OS Type: $OS_TYPE" echo "OS Family: $OS_FAMILY" echo "OS Version: $OS_VERSION" echo "Pkg Manager: $PKG_MANAGER" echo "Init System: $INIT_SYSTEM" }