#!/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 ""