Improve package mirror resiliency in misc/build.func and misc/install.func by adding fallback and detection logic for Debian/Ubuntu and Alpine CDNs. Changes include: - Detect distro (debian vs ubuntu) and use appropriate mirror lists and primary mirror (ftp.debian.org vs archive.ubuntu.com). - Add retry loops that try a shuffled list of regional mirrors, test connectivity, update repo files, and attempt package installs/updates again. - For Alpine (apk) installs and updates, try alternate mirrors on failure, write /etc/apk/repositories, and surface clearer warnings/errors if all mirrors fail. - Update interactive prompts and informational messages to reference the correct mirror list (Ubuntu vs Debian) and generalize prompt wording. - Improve exit handling so failures are reported and abort the process when necessary. These changes make automated installs more robust against CDN sync issues and unreachable primary mirrors.
1198 lines
36 KiB
Bash
1198 lines
36 KiB
Bash
# 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 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_info "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_info "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_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)."
|
|
if [[ "$distro" == "ubuntu" ]]; then
|
|
msg_info "Find Ubuntu mirrors at: https://launchpad.net/ubuntu/+archivemirrors"
|
|
else
|
|
msg_info "Find Debian mirrors at: https://www.debian.org/mirror/list"
|
|
fi
|
|
while true; do
|
|
read -rp " Enter a mirror hostname (or 'skip' to abort): " custom_mirror </dev/tty
|
|
[[ -z "$custom_mirror" ]] && continue
|
|
[[ "$custom_mirror" == "skip" ]] && break
|
|
[[ ! "$custom_mirror" =~ ^[a-zA-Z0-9._-]+$ ]] && {
|
|
msg_warn "Invalid hostname format."
|
|
continue
|
|
}
|
|
if _try_apt_mirror "$custom_mirror"; then
|
|
apt_ok=true
|
|
break
|
|
fi
|
|
msg_warn "Mirror '${custom_mirror}' also failed. Try another or type 'skip'."
|
|
done
|
|
fi
|
|
|
|
if [[ "$apt_ok" != true ]]; then
|
|
msg_error "All mirrors failed. Check network or try again later."
|
|
return 1
|
|
fi
|
|
fi
|
|
;;
|
|
apk)
|
|
if ! $STD apk update; 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_info "Attempting mirror: ${m}"
|
|
cat <<EOF >/etc/apk/repositories
|
|
http://$m/alpine/latest-stable/main
|
|
http://$m/alpine/latest-stable/community
|
|
EOF
|
|
if $STD apk update; 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."
|
|
return 1
|
|
fi
|
|
fi
|
|
;;
|
|
dnf)
|
|
$STD dnf makecache
|
|
;;
|
|
yum)
|
|
$STD yum makecache
|
|
;;
|
|
zypper)
|
|
$STD zypper refresh
|
|
;;
|
|
emerge)
|
|
$STD emerge --sync
|
|
;;
|
|
*)
|
|
msg_error "Unknown package manager: $PKG_MANAGER"
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# pkg_upgrade()
|
|
#
|
|
# Upgrades all installed packages
|
|
# ------------------------------------------------------------------------------
|
|
pkg_upgrade() {
|
|
case "$PKG_MANAGER" in
|
|
apt)
|
|
$STD apt-get -o Dpkg::Options::="--force-confold" -y dist-upgrade
|
|
;;
|
|
apk)
|
|
$STD apk -U upgrade
|
|
;;
|
|
dnf)
|
|
$STD dnf -y upgrade
|
|
;;
|
|
yum)
|
|
$STD yum -y update
|
|
;;
|
|
zypper)
|
|
$STD zypper -n update
|
|
;;
|
|
emerge)
|
|
$STD emerge --quiet --update --deep @world
|
|
;;
|
|
*)
|
|
msg_error "Unknown package manager: $PKG_MANAGER"
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# pkg_install(packages...)
|
|
#
|
|
# Installs one or more packages
|
|
# Arguments:
|
|
# packages - List of packages to install
|
|
# ------------------------------------------------------------------------------
|
|
pkg_install() {
|
|
local packages=("$@")
|
|
[[ ${#packages[@]} -eq 0 ]] && return 0
|
|
|
|
case "$PKG_MANAGER" in
|
|
apt)
|
|
$STD apt-get install -y "${packages[@]}"
|
|
;;
|
|
apk)
|
|
$STD apk add --no-cache "${packages[@]}"
|
|
;;
|
|
dnf)
|
|
$STD dnf install -y "${packages[@]}"
|
|
;;
|
|
yum)
|
|
$STD yum install -y "${packages[@]}"
|
|
;;
|
|
zypper)
|
|
$STD zypper install -y "${packages[@]}"
|
|
;;
|
|
emerge)
|
|
$STD emerge --quiet "${packages[@]}"
|
|
;;
|
|
*)
|
|
msg_error "Unknown package manager: $PKG_MANAGER"
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# pkg_remove(packages...)
|
|
#
|
|
# Removes one or more packages
|
|
# ------------------------------------------------------------------------------
|
|
pkg_remove() {
|
|
local packages=("$@")
|
|
[[ ${#packages[@]} -eq 0 ]] && return 0
|
|
|
|
case "$PKG_MANAGER" in
|
|
apt)
|
|
$STD apt-get remove -y "${packages[@]}"
|
|
;;
|
|
apk)
|
|
$STD apk del "${packages[@]}"
|
|
;;
|
|
dnf)
|
|
$STD dnf remove -y "${packages[@]}"
|
|
;;
|
|
yum)
|
|
$STD yum remove -y "${packages[@]}"
|
|
;;
|
|
zypper)
|
|
$STD zypper remove -y "${packages[@]}"
|
|
;;
|
|
emerge)
|
|
$STD emerge --quiet --unmerge "${packages[@]}"
|
|
;;
|
|
*)
|
|
msg_error "Unknown package manager: $PKG_MANAGER"
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# pkg_clean()
|
|
#
|
|
# Cleans package manager cache to free space
|
|
# ------------------------------------------------------------------------------
|
|
pkg_clean() {
|
|
case "$PKG_MANAGER" in
|
|
apt)
|
|
$STD apt-get autoremove -y
|
|
$STD apt-get autoclean
|
|
;;
|
|
apk)
|
|
$STD apk cache clean
|
|
;;
|
|
dnf)
|
|
$STD dnf clean all
|
|
$STD dnf autoremove -y
|
|
;;
|
|
yum)
|
|
$STD yum clean all
|
|
;;
|
|
zypper)
|
|
$STD zypper clean
|
|
;;
|
|
emerge)
|
|
$STD emerge --quiet --depclean
|
|
;;
|
|
*)
|
|
return 0
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# ==============================================================================
|
|
# SECTION 3: SERVICE/INIT SYSTEM ABSTRACTION
|
|
# ==============================================================================
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# svc_enable(service)
|
|
#
|
|
# Enables a service to start at boot
|
|
# ------------------------------------------------------------------------------
|
|
svc_enable() {
|
|
local service="$1"
|
|
[[ -z "$service" ]] && return 1
|
|
|
|
case "$INIT_SYSTEM" in
|
|
systemd)
|
|
$STD systemctl enable "$service"
|
|
;;
|
|
openrc)
|
|
$STD rc-update add "$service" default
|
|
;;
|
|
sysvinit)
|
|
if command -v update-rc.d &>/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 <<EOF
|
|
# Disable IPv6 (set by community-scripts)
|
|
net.ipv6.conf.all.disable_ipv6 = 1
|
|
net.ipv6.conf.default.disable_ipv6 = 1
|
|
net.ipv6.conf.lo.disable_ipv6 = 1
|
|
EOF
|
|
$STD sysctl -p /etc/sysctl.d/99-disable-ipv6.conf
|
|
|
|
# For OpenRC, ensure sysctl runs at boot
|
|
if [[ "$INIT_SYSTEM" == "openrc" ]]; then
|
|
$STD rc-update add sysctl default 2>/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? <y/N> " 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 <<EOF >/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" <<EOF
|
|
echo ""
|
|
echo -e "${BOLD:-}${YW:-}${APPLICATION:-Container} LXC Container - DEV Repository${CL:-}"
|
|
echo -e "${RD:-}WARNING: This is a DEVELOPMENT version (ProxmoxVED). Do NOT use in production!${CL:-}"
|
|
echo -e "${YW:-} OS: ${GN:-}${os_name} - Version: ${os_version}${CL:-}"
|
|
echo -e "${YW:-} Hostname: ${GN:-}\$(hostname)${CL:-}"
|
|
echo -e "${YW:-} IP Address: ${GN:-}\$(hostname -I 2>/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"
|
|
}
|