Files
2026-05-26 16:05:17 +02:00

708 lines
32 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# ==============================================================================
# Proxmox LXC: Ubuntu Server 26.04 LTS
# Aufruf auf dem PVE-Host:
# bash <(curl -fsSL https://your-host/ubuntu-lxc.sh)
# ==============================================================================
set -euo pipefail
# ── Farben ─────────────────────────────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
success() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; }
# ── Voraussetzungen prüfen ─────────────────────────────────────────────────────
[[ $EUID -ne 0 ]] && error "Dieses Skript muss als root auf dem Proxmox-Host ausgeführt werden."
command -v pct &>/dev/null || error "'pct' nicht gefunden läuft das Skript auf einem PVE-Host?"
command -v pvesh &>/dev/null || error "'pvesh' nicht gefunden läuft das Skript auf einem PVE-Host?"
# ══════════════════════════════════════════════════════════════════════════════
echo -e "\n${BOLD}╔══════════════════════════════════════════════════════════╗${NC}"
echo -e "${BOLD}║ Proxmox LXC Ubuntu Server 26.04 LTS Installer ║${NC}"
echo -e "${BOLD}╚══════════════════════════════════════════════════════════╝${NC}\n"
# ── Standardwerte ──────────────────────────────────────────────────────────────
DEFAULT_CTID=$(pvesh get /cluster/nextid)
DEFAULT_HOSTNAME="ubuntu-server"
DEFAULT_DISK=8 # GB (Ubuntu braucht mehr als Debian)
DEFAULT_RAM=1024 # MB
DEFAULT_SWAP=512 # MB
DEFAULT_CORES=2
DEFAULT_BRIDGE="vmbr0"
DEFAULT_IP="dhcp"
DEFAULT_GW=""
# ── Hilfsfunktionen ────────────────────────────────────────────────────────────
ask() {
local prompt="$1" default="$2" var_name="$3"
echo -ne "${BOLD}${prompt}${NC} [${YELLOW}${default}${NC}]: "
read -r input
printf -v "$var_name" '%s' "${input:-$default}"
}
ask_yn() {
local prompt="$1" default="${2:-j}"
echo -ne "${BOLD}${prompt}${NC} [${YELLOW}${default}${NC}]: "
read -r yn
[[ "${yn:-$default}" =~ ^[Jj1Yy] ]]
}
ask_password() {
local prompt="$1" var_name="$2"
local pw1 pw2
while true; do
echo -ne "${BOLD}${prompt}${NC}: "
read -rs pw1; echo ""
echo -ne "${BOLD}Passwort bestätigen${NC}: "
read -rs pw2; echo ""
if [[ "$pw1" != "$pw2" ]]; then
warn "Passwörter stimmen nicht überein bitte erneut eingeben."
else
printf -v "$var_name" '%s' "$pw1"
break
fi
done
}
# ── Storage-Auswahl (dynamisch) ────────────────────────────────────────────────
select_storage() {
echo -e "${CYAN}── Storage-Auswahl ──────────────────────────────────────────${NC}"
echo -e " Verfügbare Storages auf diesem Host:\n"
mapfile -t STORAGE_LINES < <(
pvesm status --content rootdir 2>/dev/null \
| awk 'NR>1 && $3=="active" {print $1, $2, $5, $4}' | sort
)
if [[ ${#STORAGE_LINES[@]} -eq 0 ]]; then
mapfile -t STORAGE_LINES < <(
pvesm status 2>/dev/null \
| awk 'NR>1 && $3=="active" {print $1, $2, $5, $4}' | sort
)
fi
if [[ ${#STORAGE_LINES[@]} -eq 0 ]]; then
warn "Keine aktiven Storages gefunden bitte manuell eingeben."
ask "Storage-Name" "local-lvm" STORAGE
return
fi
local i=1
local -a STORAGE_NAMES=()
printf " ${BOLD}%-4s %-20s %-14s %-12s %-12s${NC}\n" "Nr." "Name" "Typ" "Frei" "Gesamt"
echo -e " ──────────────────────────────────────────────────────────"
for line in "${STORAGE_LINES[@]}"; do
read -r sname stype sused stotal <<< "$line"
local sfree=0
if [[ "$stotal" =~ ^[0-9]+$ ]] && [[ "$sused" =~ ^[0-9]+$ ]]; then
sfree=$(( stotal - sused ))
fi
human_size() {
local kb=$1
if (( kb >= 1073741824 )); then printf "%.1f TiB" "$(echo "scale=1; $kb/1073741824" | bc)"
elif (( kb >= 1048576 )); then printf "%.1f GiB" "$(echo "scale=1; $kb/1048576" | bc)"
elif (( kb >= 1024 )); then printf "%.1f MiB" "$(echo "scale=1; $kb/1024" | bc)"
else printf "%d KiB" "$kb"; fi
}
local free_str total_str
if [[ "$stotal" =~ ^[0-9]+$ ]] && (( stotal > 0 )); then
free_str=$(human_size "$sfree")
total_str=$(human_size "$stotal")
else
free_str="n/a"; total_str="n/a"
fi
local color="$GREEN"
if [[ "$stotal" =~ ^[0-9]+$ ]] && (( stotal > 0 )); then
local pct=$(( sfree * 100 / stotal ))
(( pct < 15 )) && color="$RED"
(( pct >= 15 && pct < 30 )) && color="$YELLOW"
fi
printf " ${BOLD}%-4s${NC} ${color}%-20s${NC} %-14s %-12s %-12s\n" \
"[$i]" "$sname" "$stype" "$free_str" "$total_str"
STORAGE_NAMES+=("$sname")
(( i++ ))
done
echo ""
local DEFAULT_NUM=1
while true; do
echo -ne "${BOLD}Auswahl (Nummer eingeben)${NC} [${YELLOW}${DEFAULT_NUM}${NC}]: "
read -r sel
sel="${sel:-$DEFAULT_NUM}"
if [[ "$sel" =~ ^[0-9]+$ ]] && (( sel >= 1 && sel <= ${#STORAGE_NAMES[@]} )); then
STORAGE="${STORAGE_NAMES[$((sel-1))]}"
success "Storage gewählt: ${BOLD}${STORAGE}${NC}"
break
else
warn "Ungültige Eingabe. Bitte eine Zahl zwischen 1 und ${#STORAGE_NAMES[@]} eingeben."
fi
done
echo ""
}
# ── Ubuntu-Template auswählen ──────────────────────────────────────────────────
select_ubuntu_template() {
echo -e "${CYAN}── Ubuntu-Template ──────────────────────────────────────────${NC}"
echo -e " Verfügbare Ubuntu-Templates:\n"
# Alle Ubuntu-Templates aus PVE-Datenbank holen (lokal heruntergeladen + verfügbar)
mapfile -t AVAIL < <(
pveam available --section system 2>/dev/null \
| awk '/ubuntu/ {print $2}' | sort -V
)
mapfile -t LOCAL < <(
pveam list local 2>/dev/null \
| awk '/ubuntu/ {print $1}' | sort -V
)
if [[ ${#AVAIL[@]} -eq 0 ]]; then
warn "Keine Ubuntu-Templates in der PVE-Datenbank. Versuche 'pveam update'."
UBUNTU_TEMPLATE=""
return
fi
local i=1
local -a TNAMES=()
local default_idx=1
printf " ${BOLD}%-4s %-55s %-12s${NC}\n" "Nr." "Template" "Status"
echo -e " ──────────────────────────────────────────────────────────────────"
for t in "${AVAIL[@]}"; do
local status="${YELLOW}verfügbar${NC}"
local bold_start="" bold_end=""
# Prüfen ob lokal vorhanden
for l in "${LOCAL[@]}"; do
if [[ "$l" == *"$t"* ]]; then
status="${GREEN}lokal vorhanden${NC}"
break
fi
done
# 26.04 als Default vormerken
if [[ "$t" == *"26.04"* ]]; then
default_idx=$i
bold_start="${BOLD}"
bold_end="${NC}"
fi
printf " ${BOLD}%-4s${NC} ${bold_start}%-55s${bold_end} " "[$i]" "$t"
echo -e "$status"
TNAMES+=("$t")
(( i++ ))
done
echo ""
while true; do
echo -ne "${BOLD}Auswahl (Nummer eingeben)${NC} [${YELLOW}${default_idx}${NC}]: "
read -r sel
sel="${sel:-$default_idx}"
if [[ "$sel" =~ ^[0-9]+$ ]] && (( sel >= 1 && sel <= ${#TNAMES[@]} )); then
UBUNTU_TEMPLATE="${TNAMES[$((sel-1))]}"
success "Template gewählt: ${BOLD}${UBUNTU_TEMPLATE}${NC}"
break
else
warn "Ungültige Eingabe."
fi
done
echo ""
}
# ══════════════════════════════════════════════════════════════════════════════
# KONFIGURATION
# ══════════════════════════════════════════════════════════════════════════════
echo -e "${CYAN}── Container-Grundkonfiguration ─────────────────────────────${NC}"
ask "Container-ID" "$DEFAULT_CTID" CTID
ask "Hostname" "$DEFAULT_HOSTNAME" HOSTNAME
ask_password "Root-Passwort" PASSWORD
echo ""
select_storage
ask "Disk-Größe (GB)" "$DEFAULT_DISK" DISK
ask "RAM (MB)" "$DEFAULT_RAM" RAM
ask "Swap (MB)" "$DEFAULT_SWAP" SWAP
ask "CPU-Kerne" "$DEFAULT_CORES" CORES
ask "Netzwerk-Bridge" "$DEFAULT_BRIDGE" BRIDGE
echo ""
echo -e "${CYAN}── Netzwerk ──────────────────────────────────────────────────${NC}"
echo -e " Für DHCP einfach '${YELLOW}dhcp${NC}' eingeben."
echo -e " Für statische IP z.B. '${YELLOW}192.168.1.50/24${NC}'"
ask "IP-Adresse" "$DEFAULT_IP" IP_ADDR
if [[ "$IP_ADDR" != "dhcp" ]]; then
ask "Gateway" "$DEFAULT_GW" GATEWAY
fi
echo ""
echo -e "${CYAN}── Container-Typ ─────────────────────────────────────────────${NC}"
if ask_yn "Unprivilegierten Container erstellen? (empfohlen)" "j"; then
UNPRIVILEGED=1
else
UNPRIVILEGED=0
fi
echo ""
echo -e "${CYAN}── Zeitzone ──────────────────────────────────────────────────${NC}"
ask "Zeitzone" "Europe/Berlin" TIMEZONE
echo ""
echo -e "${CYAN}── Ubuntu-spezifische Einstellungen ──────────────────────────${NC}"
if ask_yn "Unattended-Upgrades aktivieren? (automatische Sicherheitsupdates)" "j"; then
ENABLE_UNATTENDED=true
else
ENABLE_UNATTENDED=false
fi
if ask_yn "Snap-Dienst deaktivieren? (spart Ressourcen im LXC)" "j"; then
DISABLE_SNAP=true
else
DISABLE_SNAP=false
fi
if ask_yn "Ubuntu Pro / ESM Werbemeldungen deaktivieren?" "j"; then
DISABLE_PRO_MOTD=true
else
DISABLE_PRO_MOTD=false
fi
echo ""
echo -e "${CYAN}── Optionale Pakete ──────────────────────────────────────────${NC}"
echo -e " Wähle zusätzliche Pakete die installiert werden sollen:\n"
if ask_yn "curl, wget, git (Basis-Tools)" "j"; then
INSTALL_BASETOOLS=true
else
INSTALL_BASETOOLS=false
fi
if ask_yn "htop, ncdu, tree, net-tools (System-Monitoring)" "j"; then
INSTALL_MONITORING=true
else
INSTALL_MONITORING=false
fi
if ask_yn "vim + nano (Editoren)" "j"; then
INSTALL_EDITORS=true
else
INSTALL_EDITORS=false
fi
if ask_yn "ufw (Firewall, vorkonfiguriert aber deaktiviert)" "n"; then
INSTALL_UFW=true
else
INSTALL_UFW=false
fi
if ask_yn "cron + logrotate (Automatisierung)" "j"; then
INSTALL_CRON=true
else
INSTALL_CRON=false
fi
if ask_yn "fail2ban (Schutz gegen Brute-Force)" "n"; then
INSTALL_FAIL2BAN=true
else
INSTALL_FAIL2BAN=false
fi
echo ""
echo -e "${CYAN}── SSH-Zugriff ───────────────────────────────────────────────${NC}"
if ask_yn "SSH-Root-Login von externen Terminals aktivieren?" "j"; then
ENABLE_ROOT_SSH=true
ask "SSH-Port" "22" SSH_PORT
echo ""
echo -e " ${BOLD}Authentifizierungsmethode:${NC}"
echo -e " ${YELLOW}[1]${NC} Nur Passwort"
echo -e " ${YELLOW}[2]${NC} Nur SSH-Key (du gibst deinen Public Key ein)"
echo -e " ${YELLOW}[3]${NC} Passwort ${BOLD}und${NC} SSH-Key (beide erlaubt)"
echo -ne "${BOLD}Auswahl${NC} [${YELLOW}1${NC}]: "
read -r ssh_auth_sel
case "${ssh_auth_sel:-1}" in
2) SSH_AUTH="key" ;;
3) SSH_AUTH="both" ;;
*) SSH_AUTH="password" ;;
esac
SSH_PUBKEY=""
if [[ "$SSH_AUTH" == "key" || "$SSH_AUTH" == "both" ]]; then
echo ""
echo -e " Gib deinen SSH-Public-Key ein (Inhalt von ${YELLOW}~/.ssh/id_rsa.pub${NC} o.ä.):"
echo -ne " ${BOLD}Public Key:${NC} "
read -r SSH_PUBKEY
if [[ -z "$SSH_PUBKEY" ]]; then
warn "Kein Public Key eingegeben falle auf Passwort-Auth zurück."
SSH_AUTH="password"
fi
fi
else
ENABLE_ROOT_SSH=false
SSH_PORT="22"
SSH_AUTH="password"
fi
echo ""
echo -e "${CYAN}── Template-Auswahl ──────────────────────────────────────────${NC}"
select_ubuntu_template
# ── Zusammenfassung ────────────────────────────────────────────────────────────
echo ""
echo -e "${BOLD}╔══════════════════════ Zusammenfassung ════════════════════════╗${NC}"
printf "${BOLD}${NC} %-26s %-28s ${BOLD}${NC}\n" "Container-ID:" "$CTID"
printf "${BOLD}${NC} %-26s %-28s ${BOLD}${NC}\n" "Hostname:" "$HOSTNAME"
printf "${BOLD}${NC} %-26s %-28s ${BOLD}${NC}\n" "Template:" "$UBUNTU_TEMPLATE"
printf "${BOLD}${NC} %-26s %-28s ${BOLD}${NC}\n" "Storage:" "$STORAGE"
printf "${BOLD}${NC} %-26s %-28s ${BOLD}${NC}\n" "Disk / RAM / Swap:" "${DISK} GB / ${RAM} MB / ${SWAP} MB"
printf "${BOLD}${NC} %-26s %-28s ${BOLD}${NC}\n" "CPU-Kerne:" "$CORES"
printf "${BOLD}${NC} %-26s %-28s ${BOLD}${NC}\n" "Netzwerk:" "$IP_ADDR"
printf "${BOLD}${NC} %-26s %-28s ${BOLD}${NC}\n" "Zeitzone:" "$TIMEZONE"
printf "${BOLD}${NC} %-26s %-28s ${BOLD}${NC}\n" "Unprivilegiert:" "$( [[ $UNPRIVILEGED -eq 1 ]] && echo "Ja" || echo "Nein" )"
printf "${BOLD}${NC} %-26s %-28s ${BOLD}${NC}\n" "Unattended-Upgrades:" "$( $ENABLE_UNATTENDED && echo "Ja" || echo "Nein" )"
printf "${BOLD}${NC} %-26s %-28s ${BOLD}${NC}\n" "Snap deaktiviert:" "$( $DISABLE_SNAP && echo "Ja" || echo "Nein" )"
printf "${BOLD}${NC} %-26s %-28s ${BOLD}${NC}\n" "Ubuntu-Pro-MOTD aus:" "$( $DISABLE_PRO_MOTD && echo "Ja" || echo "Nein" )"
printf "${BOLD}${NC} %-26s %-28s ${BOLD}${NC}\n" "Basis-Tools:" "$( $INSTALL_BASETOOLS && echo "Ja" || echo "Nein" )"
printf "${BOLD}${NC} %-26s %-28s ${BOLD}${NC}\n" "Monitoring-Tools:" "$( $INSTALL_MONITORING && echo "Ja" || echo "Nein" )"
printf "${BOLD}${NC} %-26s %-28s ${BOLD}${NC}\n" "Editoren:" "$( $INSTALL_EDITORS && echo "Ja" || echo "Nein" )"
printf "${BOLD}${NC} %-26s %-28s ${BOLD}${NC}\n" "UFW Firewall:" "$( $INSTALL_UFW && echo "Ja" || echo "Nein" )"
printf "${BOLD}${NC} %-26s %-28s ${BOLD}${NC}\n" "Cron/Logrotate:" "$( $INSTALL_CRON && echo "Ja" || echo "Nein" )"
printf "${BOLD}${NC} %-26s %-28s ${BOLD}${NC}\n" "fail2ban:" "$( $INSTALL_FAIL2BAN && echo "Ja" || echo "Nein" )"
printf "${BOLD}${NC} %-26s %-28s ${BOLD}${NC}\n" "Root-SSH:" "$( $ENABLE_ROOT_SSH && echo "Ja (Port ${SSH_PORT}, ${SSH_AUTH})" || echo "Nein" )"
echo -e "${BOLD}╚═══════════════════════════════════════════════════════════════╝${NC}"
echo ""
ask_yn "Jetzt installieren?" "j" || { echo "Abgebrochen."; exit 0; }
# ══════════════════════════════════════════════════════════════════════════════
# INSTALLATION
# ══════════════════════════════════════════════════════════════════════════════
# ── Template herunterladen falls nötig ────────────────────────────────────────
info "Prüfe Template: ${UBUNTU_TEMPLATE}"
TEMPLATE_STORAGE="local"
DOWNLOADED=$(pveam list "$TEMPLATE_STORAGE" 2>/dev/null | awk '{print $1}' | grep -F "$UBUNTU_TEMPLATE" || true)
if [[ -z "$DOWNLOADED" ]]; then
info "Lade Template herunter …"
pveam download "$TEMPLATE_STORAGE" "$UBUNTU_TEMPLATE"
success "Template heruntergeladen."
else
success "Template bereits vorhanden."
fi
TEMPLATE_PATH="${TEMPLATE_STORAGE}:vztmpl/${UBUNTU_TEMPLATE}"
# ── LXC-Container erstellen ────────────────────────────────────────────────────
info "Erstelle LXC-Container $CTID"
NET_CONFIG="name=eth0,bridge=${BRIDGE}"
if [[ "$IP_ADDR" == "dhcp" ]]; then
NET_CONFIG+=",ip=dhcp"
else
NET_CONFIG+=",ip=${IP_ADDR}"
[[ -n "${GATEWAY:-}" ]] && NET_CONFIG+=",gw=${GATEWAY}"
fi
pct create "$CTID" "$TEMPLATE_PATH" \
--hostname "$HOSTNAME" \
--password "$PASSWORD" \
--storage "$STORAGE" \
--rootfs "${STORAGE}:${DISK}" \
--memory "$RAM" \
--swap "$SWAP" \
--cores "$CORES" \
--net0 "$NET_CONFIG" \
--unprivileged "$UNPRIVILEGED" \
--features "keyctl=1,nesting=1" \
--onboot 1 \
--start 0
success "Container $CTID erstellt."
# ── Container starten ──────────────────────────────────────────────────────────
info "Starte Container …"
pct start "$CTID"
sleep 6
# ── Hilfsfunktion ──────────────────────────────────────────────────────────────
lxc_exec() { pct exec "$CTID" -- bash -c "$*"; }
# ── Ubuntu-spezifisch: DEBIAN_FRONTEND nichtinteraktiv setzen ─────────────────
lxc_exec "echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections 2>/dev/null || true"
# ── System aktualisieren ───────────────────────────────────────────────────────
info "System aktualisieren (apt update & upgrade) …"
lxc_exec "
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get upgrade -y -qq -o Dpkg::Options::='--force-confold'
apt-get install -y -qq ca-certificates locales
"
success "System aktuell."
# ── Zeitzone setzen ────────────────────────────────────────────────────────────
info "Zeitzone setzen: ${TIMEZONE}"
lxc_exec "
export DEBIAN_FRONTEND=noninteractive
ln -sf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime
echo '${TIMEZONE}' > /etc/timezone
dpkg-reconfigure -f noninteractive tzdata 2>/dev/null || true
"
success "Zeitzone gesetzt."
# ── Locale konfigurieren ───────────────────────────────────────────────────────
info "Locale konfigurieren …"
lxc_exec "
export DEBIAN_FRONTEND=noninteractive
sed -i 's/^# *de_DE.UTF-8/de_DE.UTF-8/' /etc/locale.gen 2>/dev/null || true
sed -i 's/^# *en_US.UTF-8/en_US.UTF-8/' /etc/locale.gen 2>/dev/null || true
locale-gen 2>/dev/null || true
update-locale LANG=de_DE.UTF-8 LC_MESSAGES=en_US.UTF-8 2>/dev/null || true
" 2>/dev/null
success "Locale konfiguriert."
# ── Snap deaktivieren (Ubuntu-spezifisch) ─────────────────────────────────────
if $DISABLE_SNAP; then
info "Snap deaktivieren …"
lxc_exec "
# Snap läuft in LXC oft nicht korrekt und kostet Ressourcen
systemctl stop snapd 2>/dev/null || true
systemctl disable snapd 2>/dev/null || true
systemctl mask snapd 2>/dev/null || true
apt-get purge -y -qq snapd 2>/dev/null || true
rm -rf /snap /var/snap /var/lib/snapd /var/cache/snapd ~/snap 2>/dev/null || true
# Snap-APT-Pin setzen damit er nicht neu installiert wird
cat > /etc/apt/preferences.d/no-snapd << 'SNAP_PIN'
Package: snapd
Pin: release a=*
Pin-Priority: -10
SNAP_PIN
" 2>/dev/null
success "Snap deaktiviert und gesperrt."
fi
# ── Ubuntu Pro / ESM MOTD-Werbung deaktivieren ────────────────────────────────
if $DISABLE_PRO_MOTD; then
info "Ubuntu Pro MOTD-Meldungen deaktivieren …"
lxc_exec "
# MOTD-Skripte die Ubuntu Pro bewerben deaktivieren
chmod -x /etc/update-motd.d/10-help-text 2>/dev/null || true
chmod -x /etc/update-motd.d/50-motd-news 2>/dev/null || true
chmod -x /etc/update-motd.d/88-esm-announce 2>/dev/null || true
chmod -x /etc/update-motd.d/91-contract-ua-esm-status 2>/dev/null || true
chmod -x /etc/update-motd.d/95-hwe-eol 2>/dev/null || true
# pro config falls vorhanden
command -v pro &>/dev/null && pro config set motd=false 2>/dev/null || true
" 2>/dev/null
success "Ubuntu Pro MOTD deaktiviert."
fi
# ── Unattended-Upgrades konfigurieren ─────────────────────────────────────────
if $ENABLE_UNATTENDED; then
info "Unattended-Upgrades (automatische Sicherheitsupdates) konfigurieren …"
lxc_exec "
export DEBIAN_FRONTEND=noninteractive
apt-get install -y -qq unattended-upgrades update-notifier-common
cat > /etc/apt/apt.conf.d/20auto-upgrades << 'AUTO'
APT::Periodic::Update-Package-Lists \"1\";
APT::Periodic::Unattended-Upgrade \"1\";
APT::Periodic::AutocleanInterval \"7\";
APT::Periodic::Download-Upgradeable-Packages \"1\";
AUTO
# Nur Security-Updates automatisch einspielen
sed -i 's|//\s*\"\${distro_id}:\${distro_codename}-security\";|\"\${distro_id}:\${distro_codename}-security\";|' \
/etc/apt/apt.conf.d/50unattended-upgrades 2>/dev/null || true
systemctl enable unattended-upgrades --now 2>/dev/null || true
"
success "Unattended-Upgrades aktiv (nur Security-Updates)."
fi
# ── Optionale Pakete installieren ──────────────────────────────────────────────
PKGS=()
$INSTALL_BASETOOLS && PKGS+=(curl wget git)
$INSTALL_MONITORING && PKGS+=(htop ncdu tree net-tools iputils-ping)
$INSTALL_EDITORS && PKGS+=(vim nano)
$INSTALL_CRON && PKGS+=(cron logrotate)
$INSTALL_FAIL2BAN && PKGS+=(fail2ban)
$INSTALL_UFW && PKGS+=(ufw)
if [[ ${#PKGS[@]} -gt 0 ]]; then
info "Installiere optionale Pakete: ${PKGS[*]}"
lxc_exec "export DEBIAN_FRONTEND=noninteractive && apt-get install -y -qq ${PKGS[*]}"
success "Pakete installiert."
fi
# ── fail2ban konfigurieren ─────────────────────────────────────────────────────
if $INSTALL_FAIL2BAN; then
info "fail2ban konfigurieren …"
lxc_exec "
cat > /etc/fail2ban/jail.local << 'F2B'
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
backend = systemd
[sshd]
enabled = true
F2B
systemctl enable fail2ban --now 2>/dev/null || true
"
success "fail2ban aktiv (SSH-Schutz: 5 Versuche / 10 Min → 1h Ban)."
fi
# ── UFW konfigurieren ──────────────────────────────────────────────────────────
if $INSTALL_UFW; then
info "UFW vorkonfigurieren (deaktiviert, SSH freigegeben) …"
lxc_exec "
ufw default deny incoming
ufw default allow outgoing
ufw allow ${SSH_PORT}/tcp comment 'SSH'
# Bewusst nicht aktiviert bitte manuell mit 'ufw enable' aktivieren
"
success "UFW konfiguriert (noch deaktiviert mit 'ufw enable' aktivieren)."
fi
# ── SSH vollständig konfigurieren ─────────────────────────────────────────────
if $ENABLE_ROOT_SSH; then
info "Installiere und konfiguriere SSH-Server …"
lxc_exec "export DEBIAN_FRONTEND=noninteractive && apt-get install -y -qq openssh-server"
# Ubuntu 22.04+ nutzt /etc/ssh/sshd_config.d/ wir schreiben eine Override-Datei
lxc_exec "
mkdir -p /etc/ssh/sshd_config.d
cat > /etc/ssh/sshd_config.d/99-proxmox-lxc.conf << SSHCFG
PermitRootLogin yes
Port ${SSH_PORT}
SSHCFG
"
if [[ "$SSH_AUTH" == "password" ]]; then
lxc_exec "
echo 'PasswordAuthentication yes' >> /etc/ssh/sshd_config.d/99-proxmox-lxc.conf
echo 'PubkeyAuthentication no' >> /etc/ssh/sshd_config.d/99-proxmox-lxc.conf
"
elif [[ "$SSH_AUTH" == "key" ]]; then
lxc_exec "
echo 'PasswordAuthentication no' >> /etc/ssh/sshd_config.d/99-proxmox-lxc.conf
echo 'PubkeyAuthentication yes' >> /etc/ssh/sshd_config.d/99-proxmox-lxc.conf
mkdir -p /root/.ssh && chmod 700 /root/.ssh
echo '${SSH_PUBKEY}' >> /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys
"
else
lxc_exec "
echo 'PasswordAuthentication yes' >> /etc/ssh/sshd_config.d/99-proxmox-lxc.conf
echo 'PubkeyAuthentication yes' >> /etc/ssh/sshd_config.d/99-proxmox-lxc.conf
mkdir -p /root/.ssh && chmod 700 /root/.ssh
echo '${SSH_PUBKEY}' >> /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys
"
fi
lxc_exec "systemctl enable ssh --now && systemctl restart ssh"
info "Prüfe SSH-Erreichbarkeit auf Port ${SSH_PORT}"
sleep 3
CT_TEST_IP=$(pct exec "$CTID" -- hostname -I 2>/dev/null | awk '{print $1}' || true)
if [[ -n "$CT_TEST_IP" ]] && nc -z -w5 "$CT_TEST_IP" "$SSH_PORT" 2>/dev/null; then
success "SSH erreichbar → ssh root@${CT_TEST_IP} -p ${SSH_PORT}"
else
warn "SSH-Port noch nicht erreichbar evtl. kurz warten und erneut testen."
fi
fi
# ── Proxmox Hinweise-Panel setzen (Markdown) ──────────────────────────────────
info "Setze Proxmox-Hinweise (Notes) …"
sleep 2
NOTES_IP=$(pct exec "$CTID" -- hostname -I 2>/dev/null | awk '{print $1}' || echo "")
[[ -z "$NOTES_IP" ]] && NOTES_IP="n/a (DHCP bitte in Konsole prüfen)"
UBUNTU_VER=$(pct exec "$CTID" -- bash -c ". /etc/os-release && echo \$VERSION" 2>/dev/null || echo "Ubuntu 26.04 LTS")
SSH_LINE=""
if $ENABLE_ROOT_SSH; then
[[ "$SSH_PORT" == "22" ]] && SSH_LINE="ssh root@${NOTES_IP}" \
|| SSH_LINE="ssh root@${NOTES_IP} -p ${SSH_PORT}"
fi
INSTALLED_LIST=""
$INSTALL_BASETOOLS && INSTALLED_LIST+="curl wget git "
$INSTALL_MONITORING && INSTALLED_LIST+="htop ncdu net-tools "
$INSTALL_EDITORS && INSTALLED_LIST+="vim nano "
$INSTALL_CRON && INSTALLED_LIST+="cron logrotate "
$INSTALL_FAIL2BAN && INSTALLED_LIST+="fail2ban "
$INSTALL_UFW && INSTALLED_LIST+="ufw "
$ENABLE_UNATTENDED && INSTALLED_LIST+="unattended-upgrades "
NOTE="[![Ubuntu](https://img.shields.io/badge/Ubuntu%2026.04%20LTS-E95420?style=for-the-badge&logo=ubuntu&logoColor=white)](https://ubuntu.com/server)"
NOTE+=$'\n\n'
NOTE+="---"$'\n\n'
NOTE+="**🌐 IP-Adresse:** \`${NOTES_IP}\`"$'\n\n'
if $ENABLE_ROOT_SSH; then
NOTE+="**🔑 SSH:** \`${SSH_LINE}\`"$'\n\n'
fi
if [[ -n "$INSTALLED_LIST" ]]; then
NOTE+="**📦 Pakete:** \`${INSTALLED_LIST% }\`"$'\n\n'
fi
NOTE+="**🕐 Zeitzone:** ${TIMEZONE}"$'\n\n'
$DISABLE_SNAP && NOTE+="**🚫 Snap:** deaktiviert"$'\n\n'
$ENABLE_UNATTENDED && NOTE+="**🔒 Auto-Updates:** aktiv (Security)"$'\n\n'
NOTE+="---"$'\n\n'
NOTE+="Installiert mit [gitea.vourx.com](https://gitea.vourx.com)"
NOTE+=" | [Ubuntu Server Docs](https://ubuntu.com/server/docs)"
NOTE+=" | [Ubuntu Packages](https://packages.ubuntu.com)"
pvesh set /nodes/$(hostname)/lxc/${CTID}/config --description "${NOTE}" 2>/dev/null \
&& success "Proxmox-Hinweise gesetzt." \
|| warn "Hinweise konnten nicht gesetzt werden bitte manuell eintragen."
# ── System aufräumen ───────────────────────────────────────────────────────────
info "System aufräumen …"
lxc_exec "
export DEBIAN_FRONTEND=noninteractive
apt-get autoremove -y -qq
apt-get autoclean -qq
"
success "Fertig aufgeräumt."
# ── Abschluss ──────────────────────────────────────────────────────────────────
CONTAINER_IP=$(pct exec "$CTID" -- hostname -I 2>/dev/null | awk '{print $1}' || echo "unbekannt")
echo ""
echo -e "${BOLD}${GREEN}╔══════════════════════════════════════════════════════════╗${NC}"
echo -e "${GREEN}${NC} ${BOLD}Installation abgeschlossen!${NC}"
echo -e "${BOLD}${GREEN}╠══════════════════════════════════════════════════════════╣${NC}"
echo -e "${GREEN}${NC}"
printf "${GREEN}${NC} %-22s ${BOLD}%s${NC}\n" "Container-ID:" "$CTID"
printf "${GREEN}${NC} %-22s ${BOLD}%s${NC}\n" "Hostname:" "$HOSTNAME"
printf "${GREEN}${NC} %-22s ${BOLD}%s${NC}\n" "IP-Adresse:" "$CONTAINER_IP"
printf "${GREEN}${NC} %-22s ${BOLD}%s${NC}\n" "Template:" "$UBUNTU_TEMPLATE"
printf "${GREEN}${NC} %-22s ${BOLD}%s${NC}\n" "Zeitzone:" "$TIMEZONE"
echo -e "${GREEN}${NC}"
if $ENABLE_UNATTENDED; then
printf "${GREEN}${NC} %-22s ${BOLD}%s${NC}\n" "Auto-Updates:" "aktiv (nur Security)"
fi
if $DISABLE_SNAP; then
printf "${GREEN}${NC} %-22s ${BOLD}%s${NC}\n" "Snap:" "deaktiviert & gesperrt"
fi
if $ENABLE_ROOT_SSH; then
echo -e "${GREEN}${NC}"
echo -e "${GREEN}${NC} ${CYAN}── SSH-Verbindung ──────────────────────────────────────${NC}"
if [[ "$SSH_PORT" == "22" ]]; then
printf "${GREEN}${NC} ${BOLD}ssh root@%s${NC}\n" "$CONTAINER_IP"
else
printf "${GREEN}${NC} ${BOLD}ssh root@%s -p %s${NC}\n" "$CONTAINER_IP" "$SSH_PORT"
fi
printf "${GREEN}${NC} %-22s ${BOLD}%s${NC}\n" "Auth-Methode:" "$SSH_AUTH"
fi
if $INSTALL_UFW; then
echo -e "${GREEN}${NC}"
echo -e "${GREEN}${NC} ${YELLOW}🔒 UFW installiert aber noch nicht aktiv.${NC}"
echo -e "${GREEN}${NC} ${YELLOW} Im Container mit 'ufw enable' aktivieren.${NC}"
fi
echo -e "${GREEN}${NC}"
echo -e "${GREEN}${NC} ${YELLOW}⚠ Root-Passwort nach erstem Login bitte ändern!${NC}"
echo -e "${GREEN}${NC}"
echo -e "${BOLD}${GREEN}╚══════════════════════════════════════════════════════════╝${NC}"
echo ""