From 582eb5c7180f7431799b3352b79404bb8ef5995d Mon Sep 17 00:00:00 2001 From: "CanbiZ (MickLesk)" <47820557+MickLesk@users.noreply.github.com> Date: Mon, 27 Apr 2026 13:57:46 +0200 Subject: [PATCH] add compare --- misc/main/alpine-install.func | 240 ++++++++++++++++ misc/main/install.func | 513 ++++++++++++++++++++++++++++++++++ 2 files changed, 753 insertions(+) create mode 100644 misc/main/alpine-install.func create mode 100644 misc/main/install.func diff --git a/misc/main/alpine-install.func b/misc/main/alpine-install.func new file mode 100644 index 00000000..c54a7d59 --- /dev/null +++ b/misc/main/alpine-install.func @@ -0,0 +1,240 @@ +# Copyright (c) 2021-2026 community-scripts ORG +# Author: tteck (tteckster) +# Co-Author: MickLesk +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE + +if ! command -v curl >/dev/null 2>&1; then + apk update && apk add curl >/dev/null 2>&1 +fi +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func) +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func) +load_functions +catch_errors + +# Persist diagnostics setting inside container (exported from build.func) +# so addon scripts running later can find the user's choice +if [[ ! -f /usr/local/community-scripts/diagnostics ]]; then + mkdir -p /usr/local/community-scripts + echo "DIAGNOSTICS=${DIAGNOSTICS:-no}" >/usr/local/community-scripts/diagnostics +fi + +# Get LXC IP address (must be called INSIDE container, after network is up) +get_lxc_ip + +# ------------------------------------------------------------------------------ +# post_progress_to_api() +# +# - Lightweight progress ping from inside the container +# - Updates the existing telemetry record status +# - Arguments: +# * $1: status (optional, default: "configuring") +# - Signals that the installation is actively progressing (not stuck) +# - Fire-and-forget: never blocks or fails the script +# - Only executes if DIAGNOSTICS=yes and RANDOM_UUID is set +# ------------------------------------------------------------------------------ +post_progress_to_api() { + command -v curl &>/dev/null || return 0 + [[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0 + [[ -z "${RANDOM_UUID:-}" ]] && return 0 + + local progress_status="${1:-configuring}" + + curl -fsS -m 5 -X POST "https://telemetry.community-scripts.org/telemetry" \ + -H "Content-Type: application/json" \ + -d "{\"random_id\":\"${RANDOM_UUID}\",\"execution_id\":\"${EXECUTION_ID:-${RANDOM_UUID}}\",\"type\":\"lxc\",\"nsapp\":\"${app:-unknown}\",\"status\":\"${progress_status}\"}" &>/dev/null || true +} + +# This function enables IPv6 if it's not disabled and sets verbose mode +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)" + $STD sysctl -w net.ipv6.conf.all.disable_ipv6=1 + $STD sysctl -w net.ipv6.conf.default.disable_ipv6=1 + $STD sysctl -w net.ipv6.conf.lo.disable_ipv6=1 + mkdir -p /etc/sysctl.d + $STD tee /etc/sysctl.d/99-disable-ipv6.conf >/dev/null <&2 -en "${CROSS}${RD} No Network! " + sleep $RETRY_EVERY + i=$((i - 1)) + done + + if [ "$(ip addr show | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}' | cut -d'/' -f1)" = "" ]; then + echo 1>&2 -e "\n${CROSS}${RD} No Network After $RETRY_NUM Tries${CL}" + echo -e "${NETWORK}Check Network Settings" + exit 121 + fi + msg_ok "Set up Container OS" + msg_ok "Network Connected: ${BL}$(ip addr show | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1 | tail -n1)${CL}" + post_progress_to_api +} + +# This function checks the network connection by pinging a known IP address and prompts the user to continue if the internet is not connected +network_check() { + set +e + trap - ERR + ipv4_connected=false + + # Check IPv4 connectivity to Cloudflare, Google & Quad9 DNS servers + 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 + + if [[ $ipv4_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 122 + fi + fi + + # DNS resolution checks for GitHub-related domains + GIT_HOSTS=("github.com" "raw.githubusercontent.com" "api.github.com" "git.community-scripts.org") + GIT_STATUS="Git DNS:" + DNS_FAILED=false + + for HOST in "${GIT_HOSTS[@]}"; do + RESOLVEDIP=$(getent hosts "$HOST" | awk '{ print $1 }' | grep -E '(^([0-9]{1,3}\.){3}[0-9]{1,3}$)|(^[a-fA-F0-9:]+$)' | head -n1) + if [[ -z "$RESOLVEDIP" ]]; then + GIT_STATUS+="$HOST:($DNSFAIL)" + DNS_FAILED=true + else + GIT_STATUS+=" $HOST:($DNSOK)" + fi + done + + if [[ "$DNS_FAILED" == true ]]; then + fatal "$GIT_STATUS" + else + msg_ok "$GIT_STATUS" + fi + + set -e + trap 'error_handler $LINENO "$BASH_COMMAND"' ERR +} + +# This function updates the Container OS by running apk upgrade with mirror fallback +update_os() { + msg_info "Updating Container OS" + if ! $STD apk -U upgrade; then + msg_warn "apk update failed (dl-cdn.alpinelinux.org), trying alternate mirrors..." + local alpine_mirrors="mirror.init7.net ftp.halifax.rwth-aachen.de mirrors.edge.kernel.org alpine.mirror.wearetriple.com mirror.leaseweb.com uk.alpinelinux.org dl-2.alpinelinux.org dl-4.alpinelinux.org" + local apk_ok=false + for m in $(printf '%s\n' $alpine_mirrors | shuf); do + if timeout 2 bash -c "echo >/dev/tcp/$m/80" 2>/dev/null; then + msg_custom "${INFO}" "${YW}" "Attempting mirror: ${m}" + cat </etc/apk/repositories +http://$m/alpine/latest-stable/main +http://$m/alpine/latest-stable/community +EOF + if $STD apk -U upgrade; then + msg_ok "CDN set to ${m}: tests passed" + apk_ok=true + break + else + msg_warn "Mirror ${m} failed" + fi + fi + done + if [[ "$apk_ok" != true ]]; then + msg_error "All Alpine mirrors failed. Check network or try again later." + exit 1 + fi + fi + local tools_content + tools_content=$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func) || { + msg_error "Failed to download tools.func" + exit 115 + } + source /dev/stdin <<<"$tools_content" + if ! declare -f fetch_and_deploy_gh_release >/dev/null 2>&1; then + msg_error "tools.func loaded but incomplete — missing expected functions" + exit 115 + fi + msg_ok "Updated Container OS" + post_progress_to_api +} + +# This function modifies the message of the day (motd) and SSH settings +motd_ssh() { + echo "export TERM='xterm-256color'" >>/root/.bashrc + + PROFILE_FILE="/etc/profile.d/00_lxc-details.sh" + echo "echo -e \"\"" >"$PROFILE_FILE" + echo -e "echo -e \"${BOLD}${APPLICATION} LXC Container${CL}"\" >>"$PROFILE_FILE" + echo -e "echo -e \"${TAB}${GATEWAY}${YW} Provided by: ${GN}community-scripts ORG ${YW}| GitHub: ${GN}https://github.com/community-scripts/ProxmoxVE${CL}\"" >>"$PROFILE_FILE" + echo "echo \"\"" >>"$PROFILE_FILE" + echo -e "echo -e \"${TAB}${OS}${YW} OS: ${GN}\$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '\"') - Version: \$(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '\"')${CL}\"" >>"$PROFILE_FILE" + echo -e "echo -e \"${TAB}${HOSTNAME}${YW} Hostname: ${GN}\$(hostname)${CL}\"" >>"$PROFILE_FILE" + echo -e "echo -e \"${TAB}${INFO}${YW} IP Address: ${GN}\$(ip -4 addr show eth0 | awk '/inet / {print \$2}' | cut -d/ -f1 | head -n 1)${CL}\"" >>"$PROFILE_FILE" + + # Configure SSH if enabled + if [[ "${SSH_ROOT}" == "yes" ]]; then + # Enable sshd service + $STD rc-update add sshd + # Allow root login via SSH + sed -i "s/#PermitRootLogin prohibit-password/PermitRootLogin yes/g" /etc/ssh/sshd_config + # Start the sshd service + $STD /etc/init.d/sshd start + fi + post_progress_to_api +} + +# Validate Timezone for some LXC's +validate_tz() { + [[ -f "/usr/share/zoneinfo/$1" ]] +} + +# This function customizes the container and enables passwordless login for the root user +customize() { + if [[ "$PASSWORD" == "" ]]; then + msg_info "Customizing Container" + passwd -d root >/dev/null 2>&1 + + # Ensure agetty is available + apk add --no-cache --force-broken-world util-linux >/dev/null 2>&1 + + # Create persistent autologin boot script + mkdir -p /etc/local.d + cat <<'EOF' >/etc/local.d/autologin.start +#!/bin/sh +sed -i 's|^tty1::respawn:.*|tty1::respawn:/sbin/agetty --autologin root --noclear tty1 38400 linux|' /etc/inittab +kill -HUP 1 +EOF + touch /root/.hushlogin + + chmod +x /etc/local.d/autologin.start + rc-update add local >/dev/null 2>&1 + + # Apply autologin immediately for current session + /etc/local.d/autologin.start + + msg_ok "Customized Container" + fi + + echo "bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)\"" >/usr/bin/update + chmod +x /usr/bin/update + post_progress_to_api +} diff --git a/misc/main/install.func b/misc/main/install.func new file mode 100644 index 00000000..a1a1e0ff --- /dev/null +++ b/misc/main/install.func @@ -0,0 +1,513 @@ +# Copyright (c) 2021-2026 community-scripts ORG +# Author: tteck (tteckster) +# Co-Author: MickLesk +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE + +# ============================================================================== +# INSTALL.FUNC - CONTAINER INSTALLATION & SETUP +# ============================================================================== +# +# This file provides installation functions executed inside LXC containers +# after creation. Handles: +# +# - Network connectivity verification (IPv4/IPv6) +# - OS updates and package installation +# - DNS resolution checks +# - MOTD and SSH configuration +# - Container customization and auto-login +# +# Usage: +# - Sourced by -install.sh scripts +# - Executes via pct exec inside container +# - Requires internet connectivity +# +# ============================================================================== + +# ============================================================================== +# SECTION 1: INITIALIZATION +# ============================================================================== + +if ! command -v curl >/dev/null 2>&1; then + printf "\r\e[2K%b" '\033[93m Setup Source \033[m' >&2 + apt update >/dev/null 2>&1 + apt install -y curl >/dev/null 2>&1 +fi +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func) +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func) +load_functions +catch_errors + +# Persist diagnostics setting inside container (exported from build.func) +# so addon scripts running later can find the user's choice +if [[ ! -f /usr/local/community-scripts/diagnostics ]]; then + mkdir -p /usr/local/community-scripts + echo "DIAGNOSTICS=${DIAGNOSTICS:-no}" >/usr/local/community-scripts/diagnostics +fi + +# Get LXC IP address (must be called INSIDE container, after network is up) +get_lxc_ip + +# ------------------------------------------------------------------------------ +# post_progress_to_api() +# +# - Lightweight progress ping from inside the container +# - Updates the existing telemetry record status +# - Arguments: +# * $1: status (optional, default: "configuring") +# - Signals that the installation is actively progressing (not stuck) +# - Fire-and-forget: never blocks or fails the script +# - Only executes if DIAGNOSTICS=yes and RANDOM_UUID is set +# ------------------------------------------------------------------------------ +post_progress_to_api() { + command -v curl &>/dev/null || return 0 + [[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0 + [[ -z "${RANDOM_UUID:-}" ]] && return 0 + + local progress_status="${1:-configuring}" + + curl -fsS -m 5 -X POST "https://telemetry.community-scripts.org/telemetry" \ + -H "Content-Type: application/json" \ + -d "{\"random_id\":\"${RANDOM_UUID}\",\"execution_id\":\"${EXECUTION_ID:-${RANDOM_UUID}}\",\"type\":\"lxc\",\"nsapp\":\"${app:-unknown}\",\"status\":\"${progress_status}\"}" &>/dev/null || true +} + +# ============================================================================== +# SECTION 2: NETWORK & CONNECTIVITY +# ============================================================================== + +# ------------------------------------------------------------------------------ +# verb_ip6() +# +# - Configures IPv6 based on DISABLEIPV6 variable +# - If DISABLEIPV6=yes: disables IPv6 via sysctl +# - Sets verbose mode via set_std_mode() +# ------------------------------------------------------------------------------ +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 + $STD tee /etc/sysctl.d/99-disable-ipv6.conf >/dev/null </dev/null) || true + fi + + for ((i = RETRY_NUM; i > 0; i--)); do + if [ "$(hostname -I)" != "" ]; then + break + fi + echo 1>&2 -en "${CROSS}${RD} No Network! " + sleep $RETRY_EVERY + done + if [ "$(hostname -I)" = "" ]; then + echo 1>&2 -e "\n${CROSS}${RD} No Network After $RETRY_NUM Tries${CL}" + echo -e "${NETWORK}Check Network Settings" + exit 121 + fi + rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED + systemctl disable -q --now systemd-networkd-wait-online.service + msg_ok "Set up Container OS" + #msg_custom "${CM}" "${GN}" "Network Connected: ${BL}$(hostname -I)" + msg_ok "Network Connected: ${BL}$(hostname -I)" + post_progress_to_api +} + +# ------------------------------------------------------------------------------ +# network_check() +# +# - Comprehensive network connectivity check for IPv4 and IPv6 +# - Tests connectivity to multiple DNS servers: +# * IPv4: 1.1.1.1 (Cloudflare), 8.8.8.8 (Google), 9.9.9.9 (Quad9) +# * IPv6: 2606:4700:4700::1111, 2001:4860:4860::8888, 2620:fe::fe +# - Verifies DNS resolution for GitHub and Community-Scripts domains +# - Prompts user to continue if no internet detected +# - Uses fatal() on DNS resolution failure for critical hosts +# ------------------------------------------------------------------------------ +network_check() { + set +e + trap - ERR + ipv4_connected=false + ipv6_connected=false + sleep 1 + + # Check IPv4 connectivity to Google, Cloudflare & Quad9 DNS servers. + 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 to Google, Cloudflare & Quad9 DNS servers. + if ping6 -c 1 -W 1 2606:4700:4700::1111 &>/dev/null || ping6 -c 1 -W 1 2001:4860:4860::8888 &>/dev/null || ping6 -c 1 -W 1 2620:fe::fe &>/dev/null; then + msg_ok "IPv6 Internet Connected" + ipv6_connected=true + else + msg_error "IPv6 Internet Not Connected" + fi + + # If both IPv4 and IPv6 checks fail, prompt the user + 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 122 + fi + fi + + # DNS resolution checks for GitHub-related domains (IPv4 and/or IPv6) + GIT_HOSTS=("github.com" "raw.githubusercontent.com" "api.github.com" "git.community-scripts.org") + GIT_STATUS="Git DNS:" + DNS_FAILED=false + + for HOST in "${GIT_HOSTS[@]}"; do + RESOLVEDIP=$(getent hosts "$HOST" | awk '{ print $1 }' | grep -E '(^([0-9]{1,3}\.){3}[0-9]{1,3}$)|(^[a-fA-F0-9:]+$)' | head -n1) + if [[ -z "$RESOLVEDIP" ]]; then + GIT_STATUS+="$HOST:($DNSFAIL)" + DNS_FAILED=true + else + GIT_STATUS+=" $HOST:($DNSOK)" + fi + done + + if [[ "$DNS_FAILED" == true ]]; then + fatal "$GIT_STATUS" + else + msg_ok "$GIT_STATUS" + fi + + set -e + trap 'error_handler' ERR +} + +# ============================================================================== +# SECTION 3: OS UPDATE & PACKAGE MANAGEMENT +# ============================================================================== + +# ------------------------------------------------------------------------------ +# apt_update_safe() +# +# - Runs apt-get update with CDN mirror fallback +# - On failure, detects distro (Debian/Ubuntu) and tries alternate mirrors +# - Three-phase approach: global mirrors → primary mirror → regional mirrors +# - Falls back to manual user prompt if all auto mirrors fail +# - Detects hash mismatch, SSL errors, and generic apt failures +# ------------------------------------------------------------------------------ +apt_update_safe() { + if $STD apt-get update; then + return 0 + fi + + 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 distro + distro=$(. /etc/os-release 2>/dev/null && echo "$ID" || echo "debian") + + local eu_mirrors us_mirrors ap_mirrors + if [[ "$distro" == "ubuntu" ]]; then + eu_mirrors="de.archive.ubuntu.com fr.archive.ubuntu.com se.archive.ubuntu.com nl.archive.ubuntu.com it.archive.ubuntu.com ch.archive.ubuntu.com mirrors.xtom.de" + us_mirrors="us.archive.ubuntu.com archive.ubuntu.com mirrors.edge.kernel.org mirror.csclub.uwaterloo.ca mirrors.ocf.berkeley.edu mirror.math.princeton.edu" + ap_mirrors="au.archive.ubuntu.com jp.archive.ubuntu.com kr.archive.ubuntu.com tw.archive.ubuntu.com mirror.aarnet.edu.au" + else + 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" + 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" + 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" + fi + + 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_custom "${INFO}" "${YW}" "Attempting mirror: ${mirror}" + if _try_apt_mirror "$mirror"; then + apt_ok=true + break + fi + done + + # Phase 2: Try primary mirror + if [[ "$apt_ok" != true ]]; then + local primary + if [[ "$distro" == "ubuntu" ]]; then + primary="archive.ubuntu.com" + else + primary="ftp.debian.org" + fi + if timeout 2 bash -c "echo >/dev/tcp/$primary/80" 2>/dev/null; then + msg_custom "${INFO}" "${YW}" "Attempting mirror: ${primary}" + if _try_apt_mirror "$primary"; 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_custom "${INFO}" "${YW}" "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)." + if [[ "$distro" == "ubuntu" ]]; then + msg_warn "Find Ubuntu mirrors at: https://launchpad.net/ubuntu/+archivemirrors" + else + msg_warn "Find Debian mirrors at: https://www.debian.org/mirror/list" + fi + local custom_mirror + while true; do + read -rp " Enter a mirror hostname (or 'skip' to abort): " custom_mirror /etc/apt/apt.conf.d/00aptproxy + local _proxy_raw="${CACHER_IP}" + local _proxy_host _proxy_port _proxy_url + # Parse host and port from URL or plain IP/hostname + _proxy_host=$(echo "$_proxy_raw" | sed -e 's|https\?://||' -e 's|/.*||' | cut -d: -f1) + _proxy_port=$(echo "$_proxy_raw" | sed -e 's|https\?://||' -e 's|/.*||' | cut -s -d: -f2) + if [[ "$_proxy_raw" =~ ^https?:// ]]; then + # Full URL provided — use as-is for proxy output, extract port for nc check + _proxy_url="$_proxy_raw" + _proxy_port="${_proxy_port:-80}" + else + # Legacy: plain IP or hostname — default to http + port 3142 + _proxy_port="${_proxy_port:-3142}" + _proxy_url="http://${_proxy_raw}:${_proxy_port}" + fi + cat </usr/local/bin/apt-proxy-detect.sh +#!/bin/bash +if nc -w1 -z "${_proxy_host}" ${_proxy_port}; then + echo -n "${_proxy_url}" +else + echo -n "DIRECT" +fi +EOF + chmod +x /usr/local/bin/apt-proxy-detect.sh + fi + apt_update_safe + $STD apt-get -o Dpkg::Options::="--force-confold" -y dist-upgrade + rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED + msg_ok "Updated Container OS" + post_progress_to_api + + local tools_content + tools_content=$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func) || { + msg_error "Failed to download tools.func" + exit 115 + } + source /dev/stdin <<<"$tools_content" + if ! declare -f fetch_and_deploy_gh_release >/dev/null 2>&1; then + msg_error "tools.func loaded but incomplete — missing expected functions" + exit 115 + fi +} + +# ============================================================================== +# SECTION 4: MOTD & SSH CONFIGURATION +# ============================================================================== + +# ------------------------------------------------------------------------------ +# motd_ssh() +# +# - Configures Message of the Day (MOTD) with container information +# - Creates /etc/profile.d/00_lxc-details.sh with: +# * Application name +# * Warning banner (DEV repository) +# * OS name and version +# * Hostname and IP address +# * GitHub repository link +# - Disables executable flag on /etc/update-motd.d/* scripts +# - Enables root SSH access if SSH_ROOT=yes +# - Configures TERM environment variable for better terminal support +# ------------------------------------------------------------------------------ +motd_ssh() { + # Set terminal to 256-color mode + grep -qxF "export TERM='xterm-256color'" /root/.bashrc || echo "export TERM='xterm-256color'" >>/root/.bashrc + + PROFILE_FILE="/etc/profile.d/00_lxc-details.sh" + echo "echo -e \"\"" >"$PROFILE_FILE" + echo -e "echo -e \"${BOLD}${APPLICATION} LXC Container${CL}"\" >>"$PROFILE_FILE" + echo -e "echo -e \"${TAB}${GATEWAY}${YW} Provided by: ${GN}community-scripts ORG ${YW}| GitHub: ${GN}https://github.com/community-scripts/ProxmoxVE${CL}\"" >>"$PROFILE_FILE" + echo "echo \"\"" >>"$PROFILE_FILE" + echo -e "echo -e \"${TAB}${OS}${YW} OS: ${GN}\$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '\"') - Version: \$(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '\"')${CL}\"" >>"$PROFILE_FILE" + echo -e "echo -e \"${TAB}${HOSTNAME}${YW} Hostname: ${GN}\$(hostname)${CL}\"" >>"$PROFILE_FILE" + echo -e "echo -e \"${TAB}${INFO}${YW} IP Address: ${GN}\$(hostname -I | awk '{print \$1}')${CL}\"" >>"$PROFILE_FILE" + + # Disable default MOTD scripts + chmod -x /etc/update-motd.d/* + + if [[ "${SSH_ROOT}" == "yes" ]]; then + sed -i "s/#PermitRootLogin prohibit-password/PermitRootLogin yes/g" /etc/ssh/sshd_config + systemctl restart sshd + fi + post_progress_to_api +} + +# ============================================================================== +# SECTION 5: CONTAINER CUSTOMIZATION +# ============================================================================== + +# ------------------------------------------------------------------------------ +# customize() +# +# - Customizes container for passwordless root login if PASSWORD is empty +# - Configures getty for auto-login via /etc/systemd/system/container-getty@1.service.d/override.conf +# - Creates /usr/bin/update script for easy application updates +# - Injects SSH authorized keys if SSH_AUTHORIZED_KEY variable is set +# - Sets proper permissions on SSH directories and key files +# ------------------------------------------------------------------------------ +customize() { + if [[ "$PASSWORD" == "" ]]; then + msg_info "Customizing Container" + GETTY_OVERRIDE="/etc/systemd/system/container-getty@1.service.d/override.conf" + mkdir -p "$(dirname "$GETTY_OVERRIDE")" + cat <"$GETTY_OVERRIDE" + [Service] + ExecStart= + ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud tty%I 115200,38400,9600 \$TERM +EOF + systemctl daemon-reload + systemctl restart "$(basename "$(dirname "$GETTY_OVERRIDE")" | sed 's/\.d//')" + msg_ok "Customized Container" + fi + echo "bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)\"" >/usr/bin/update + chmod +x /usr/bin/update + + 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 + post_progress_to_api +}