767 lines
40 KiB
Bash
767 lines
40 KiB
Bash
#!/usr/bin/env bash
|
||
# ==============================================================================
|
||
# Proxmox LXC – Multi-OS Installer
|
||
# Unterstützt: Debian / Ubuntu (immer aktuelle + vor/nachherige Version)
|
||
# Aufruf auf dem PVE-Host:
|
||
# bash <(curl -fsSL https://your-host/lxc-install.sh)
|
||
# ==============================================================================
|
||
|
||
set -euo pipefail
|
||
|
||
# ── Farben ─────────────────────────────────────────────────────────────────────
|
||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
|
||
CYAN='\033[0;36m'; BLUE='\033[0;34m'; 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 ────────────────────────────────────────────────────────────
|
||
[[ $EUID -ne 0 ]] && error "Muss als root auf dem PVE-Host ausgeführt werden."
|
||
command -v pct &>/dev/null || error "'pct' nicht gefunden – kein PVE-Host?"
|
||
command -v pvesh &>/dev/null || error "'pvesh' nicht gefunden – kein PVE-Host?"
|
||
|
||
# ══════════════════════════════════════════════════════════════════════════════
|
||
clear
|
||
echo -e "${BOLD}"
|
||
echo -e " ╔══════════════════════════════════════════════════════════╗"
|
||
echo -e " ║ Proxmox LXC – Multi-OS Installer ║"
|
||
echo -e " ║ Debian • Ubuntu | + Docker • Portainer ║"
|
||
echo -e " ╚══════════════════════════════════════════════════════════╝"
|
||
echo -e "${NC}"
|
||
|
||
# ── 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] ]]
|
||
}
|
||
|
||
# ══════════════════════════════════════════════════════════════════════════════
|
||
# SCHRITT 1 – DISTRIBUTION WÄHLEN
|
||
# ══════════════════════════════════════════════════════════════════════════════
|
||
echo -e "${CYAN}╔══ Schritt 1: Distribution ═══════════════════════════════════╗${NC}"
|
||
echo -e ""
|
||
echo -e " ${BOLD}[1]${NC} 🌀 ${BOLD}Debian${NC} – schlank, stabil, bewährt"
|
||
echo -e " ${BOLD}[2]${NC} 🟠 ${BOLD}Ubuntu${NC} – breite Paketbasis, LTS-Support"
|
||
echo -e ""
|
||
while true; do
|
||
echo -ne "${BOLD} Auswahl${NC} [${YELLOW}1${NC}]: "
|
||
read -r dist_sel
|
||
case "${dist_sel:-1}" in
|
||
1) DISTRO="debian"; DISTRO_LABEL="Debian"; DISTRO_MATCH="debian"; break ;;
|
||
2) DISTRO="ubuntu"; DISTRO_LABEL="Ubuntu"; DISTRO_MATCH="ubuntu"; break ;;
|
||
*) warn "Bitte 1 oder 2 eingeben." ;;
|
||
esac
|
||
done
|
||
echo ""
|
||
|
||
# ══════════════════════════════════════════════════════════════════════════════
|
||
# SCHRITT 2 – VERSION WÄHLEN (live aus PVE-Datenbank, immer aktuell)
|
||
# ══════════════════════════════════════════════════════════════════════════════
|
||
echo -e "${CYAN}╔══ Schritt 2: Version ════════════════════════════════════════╗${NC}"
|
||
echo ""
|
||
info "Lese verfügbare ${DISTRO_LABEL}-Templates aus der PVE-Datenbank …"
|
||
|
||
# Template-Liste aktualisieren (leise, falls nötig)
|
||
pveam update &>/dev/null || true
|
||
|
||
# Alle passenden Templates holen, nach Versionsnummer sortieren
|
||
mapfile -t ALL_TEMPLATES < <(
|
||
pveam available --section system 2>/dev/null \
|
||
| awk '{print $2}' \
|
||
| grep -i "^${DISTRO_MATCH}-" \
|
||
| sort -V
|
||
)
|
||
|
||
[[ ${#ALL_TEMPLATES[@]} -eq 0 ]] && error "Keine ${DISTRO_LABEL}-Templates gefunden. Bitte 'pveam update' ausführen."
|
||
|
||
# ── Versionsnummern extrahieren und deduplizieren ──────────────────────────────
|
||
# Debian-Muster: debian-12-standard_12.7-1_amd64.tar.zst → major=12
|
||
# Ubuntu-Muster: ubuntu-24.04-standard_24.04-1_amd64.tar.zst → major=24.04
|
||
declare -A VER_TO_TEMPLATE # major_version → neuestes Template
|
||
declare -a VER_ORDER=()
|
||
|
||
for t in "${ALL_TEMPLATES[@]}"; do
|
||
if [[ "$DISTRO" == "debian" ]]; then
|
||
ver=$(echo "$t" | grep -oP 'debian-\K[0-9]+' | head -1)
|
||
else
|
||
ver=$(echo "$t" | grep -oP 'ubuntu-\K[0-9]+\.[0-9]+' | head -1)
|
||
fi
|
||
[[ -z "$ver" ]] && continue
|
||
# Letztes (neustes) Template pro Version merken (sort -V sorgt dafür)
|
||
VER_TO_TEMPLATE["$ver"]="$t"
|
||
# Version in geordneter Liste nur einmal speichern
|
||
if [[ ! " ${VER_ORDER[*]} " =~ " ${ver} " ]]; then
|
||
VER_ORDER+=("$ver")
|
||
fi
|
||
done
|
||
|
||
[[ ${#VER_ORDER[@]} -eq 0 ]] && error "Konnte keine Versionsnummern aus den Templates extrahieren."
|
||
|
||
# ── Die 3 relevantesten Versionen bestimmen: vorherige / aktuelle / nächste ────
|
||
# "Aktuell" = höchste stabile Version (letzte in der sortierten Liste)
|
||
# "Vorherige" = eine davor
|
||
# "Nächste" = falls vorhanden (Alpha/Beta/RC Templates in PVE)
|
||
TOTAL=${#VER_ORDER[@]}
|
||
CURRENT_IDX=$(( TOTAL - 1 ))
|
||
|
||
# Prüfen ob es eine "nächste" Version gibt (höher als die letzte stabile)
|
||
# PVE-Datenbank enthält manchmal schon Beta-Templates → nehmen wir mit
|
||
NEXT_IDX=$(( CURRENT_IDX + 1 )) # existiert nicht → wird unten abgefangen
|
||
PREV_IDX=$(( CURRENT_IDX - 1 ))
|
||
|
||
# Verfügbare Slots bauen
|
||
declare -a SHOW_VERS=()
|
||
declare -a SHOW_LABELS=()
|
||
|
||
# Vorherige Version (falls vorhanden)
|
||
if (( PREV_IDX >= 0 )); then
|
||
SHOW_VERS+=("${VER_ORDER[$PREV_IDX]}")
|
||
SHOW_LABELS+=("Vorherige Version (${VER_ORDER[$PREV_IDX]})")
|
||
fi
|
||
|
||
# Aktuelle Version (Standard)
|
||
SHOW_VERS+=("${VER_ORDER[$CURRENT_IDX]}")
|
||
SHOW_LABELS+=("Aktuelle Version (${VER_ORDER[$CURRENT_IDX]}) ← empfohlen")
|
||
DEFAULT_VER_IDX=$(( ${#SHOW_VERS[@]} )) # 1-basierter Default für die Ausgabe
|
||
|
||
# Nächste Version (nur wenn tatsächlich ein neueres Template existiert)
|
||
# → In der Praxis liefert PVE hier manchmal schon den nächsten LTS
|
||
if (( NEXT_IDX < TOTAL )); then
|
||
SHOW_VERS+=("${VER_ORDER[$NEXT_IDX]}")
|
||
SHOW_LABELS+=("Nächste Version (${VER_ORDER[$NEXT_IDX]}) [experimentell]")
|
||
fi
|
||
|
||
echo -e " Gefundene ${DISTRO_LABEL}-Versionen:\n"
|
||
for i in "${!SHOW_VERS[@]}"; do
|
||
num=$(( i + 1 ))
|
||
if (( num == DEFAULT_VER_IDX )); then
|
||
echo -e " ${BOLD}[${num}]${NC} ${GREEN}${SHOW_LABELS[$i]}${NC}"
|
||
else
|
||
echo -e " ${BOLD}[${num}]${NC} ${SHOW_LABELS[$i]}"
|
||
fi
|
||
done
|
||
echo ""
|
||
|
||
while true; do
|
||
echo -ne "${BOLD} Auswahl${NC} [${YELLOW}${DEFAULT_VER_IDX}${NC}]: "
|
||
read -r ver_sel
|
||
ver_sel="${ver_sel:-$DEFAULT_VER_IDX}"
|
||
if [[ "$ver_sel" =~ ^[0-9]+$ ]] && (( ver_sel >= 1 && ver_sel <= ${#SHOW_VERS[@]} )); then
|
||
CHOSEN_VER="${SHOW_VERS[$((ver_sel-1))]}"
|
||
CHOSEN_TEMPLATE="${VER_TO_TEMPLATE[$CHOSEN_VER]}"
|
||
success "Gewählt: ${BOLD}${DISTRO_LABEL} ${CHOSEN_VER}${NC} → ${CHOSEN_TEMPLATE}"
|
||
break
|
||
else
|
||
warn "Bitte eine Zahl zwischen 1 und ${#SHOW_VERS[@]} eingeben."
|
||
fi
|
||
done
|
||
echo ""
|
||
|
||
# ── Distro-spezifische Defaults ────────────────────────────────────────────────
|
||
if [[ "$DISTRO" == "debian" ]]; then
|
||
DEFAULT_RAM=512; DEFAULT_DISK=4; DEFAULT_HOSTNAME="debian"
|
||
DISTRO_BADGE="https://img.shields.io/badge/Debian%20${CHOSEN_VER}-A81D33?style=for-the-badge&logo=debian&logoColor=white"
|
||
DISTRO_LINK="https://www.debian.org"
|
||
DOCS_LABEL="Debian Docs"; DOCS_URL="https://www.debian.org/releases/"
|
||
PKG_LABEL="Debian Packages"; PKG_URL="https://packages.debian.org"
|
||
SSH_CFG_MODE="sshd_config" # Debian: direkt in sshd_config
|
||
else
|
||
DEFAULT_RAM=1024; DEFAULT_DISK=8; DEFAULT_HOSTNAME="ubuntu-server"
|
||
VER_NICE="${CHOSEN_VER} LTS"
|
||
DISTRO_BADGE="https://img.shields.io/badge/Ubuntu%20${CHOSEN_VER// /%20}%20LTS-E95420?style=for-the-badge&logo=ubuntu&logoColor=white"
|
||
DISTRO_LINK="https://ubuntu.com/server"
|
||
DOCS_LABEL="Ubuntu Server Docs"; DOCS_URL="https://ubuntu.com/server/docs"
|
||
PKG_LABEL="Ubuntu Packages"; PKG_URL="https://packages.ubuntu.com"
|
||
SSH_CFG_MODE="sshd_config_d" # Ubuntu: Drop-in /etc/ssh/sshd_config.d/
|
||
fi
|
||
|
||
# ══════════════════════════════════════════════════════════════════════════════
|
||
# SCHRITT 3 – CONTAINER-KONFIGURATION
|
||
# ══════════════════════════════════════════════════════════════════════════════
|
||
echo -e "${CYAN}╔══ Schritt 3: Container-Konfiguration ════════════════════════╗${NC}"
|
||
echo ""
|
||
|
||
DEFAULT_CTID=$(pvesh get /cluster/nextid)
|
||
|
||
echo -e "${CYAN}── Grundkonfiguration ───────────────────────────────────────${NC}"
|
||
ask "Container-ID" "$DEFAULT_CTID" CTID
|
||
ask "Hostname" "$DEFAULT_HOSTNAME" HOSTNAME
|
||
ask "Root-Passwort" "ChangeMe123!" PASSWORD
|
||
echo ""
|
||
|
||
# ── Storage-Auswahl ────────────────────────────────────────────────────────────
|
||
echo -e "${CYAN}── Storage-Auswahl ──────────────────────────────────────────${NC}"
|
||
echo -e " Verfügbare Storages:\n"
|
||
mapfile -t STORAGE_LINES < <(
|
||
pvesm status --content rootdir 2>/dev/null \
|
||
| awk 'NR>1 && $3=="active" {print $1, $2, $5, $4}' | sort
|
||
)
|
||
[[ ${#STORAGE_LINES[@]} -eq 0 ]] && mapfile -t STORAGE_LINES < <(
|
||
pvesm status 2>/dev/null | awk 'NR>1 && $3=="active" {print $1, $2, $5, $4}' | sort
|
||
)
|
||
if [[ ${#STORAGE_LINES[@]} -eq 0 ]]; then
|
||
warn "Keine Storages gefunden – manuelle Eingabe."
|
||
ask "Storage-Name" "local-lvm" STORAGE
|
||
else
|
||
local_human_size() {
|
||
local kb=$1
|
||
(( kb >= 1073741824 )) && { printf "%.1f TiB" "$(echo "scale=1;$kb/1073741824"|bc)"; return; }
|
||
(( kb >= 1048576 )) && { printf "%.1f GiB" "$(echo "scale=1;$kb/1048576"|bc)"; return; }
|
||
(( kb >= 1024 )) && { printf "%.1f MiB" "$(echo "scale=1;$kb/1024"|bc)"; return; }
|
||
printf "%d KiB" "$kb"
|
||
}
|
||
declare -a ST_NAMES=()
|
||
printf " ${BOLD}%-4s %-20s %-14s %-12s %-12s${NC}\n" "Nr." "Name" "Typ" "Frei" "Gesamt"
|
||
echo -e " ──────────────────────────────────────────────────────────"
|
||
i=1
|
||
for line in "${STORAGE_LINES[@]}"; do
|
||
read -r sname stype sused stotal <<< "$line"
|
||
sfree=0
|
||
[[ "$stotal" =~ ^[0-9]+$ && "$sused" =~ ^[0-9]+$ ]] && sfree=$(( stotal - sused ))
|
||
if [[ "$stotal" =~ ^[0-9]+$ ]] && (( stotal > 0 )); then
|
||
fs=$(local_human_size "$sfree"); ts=$(local_human_size "$stotal")
|
||
pct=$(( sfree * 100 / stotal ))
|
||
col="$GREEN"; (( pct < 15 )) && col="$RED"; (( pct>=15 && pct<30 )) && col="$YELLOW"
|
||
else
|
||
fs="n/a"; ts="n/a"; col="$NC"
|
||
fi
|
||
printf " ${BOLD}%-4s${NC} ${col}%-20s${NC} %-14s %-12s %-12s\n" "[$i]" "$sname" "$stype" "$fs" "$ts"
|
||
ST_NAMES+=("$sname"); (( i++ ))
|
||
done
|
||
echo ""
|
||
while true; do
|
||
echo -ne "${BOLD} Auswahl${NC} [${YELLOW}1${NC}]: "
|
||
read -r sel; sel="${sel:-1}"
|
||
if [[ "$sel" =~ ^[0-9]+$ ]] && (( sel>=1 && sel<=${#ST_NAMES[@]} )); then
|
||
STORAGE="${ST_NAMES[$((sel-1))]}"; success "Storage: ${BOLD}${STORAGE}${NC}"; break
|
||
else warn "Ungültige Eingabe."; fi
|
||
done
|
||
fi
|
||
echo ""
|
||
|
||
echo -e "${CYAN}── Ressourcen ───────────────────────────────────────────────${NC}"
|
||
ask "Disk-Größe (GB)" "$DEFAULT_DISK" DISK
|
||
ask "RAM (MB)" "$DEFAULT_RAM" RAM
|
||
ask "Swap (MB)" "512" SWAP
|
||
ask "CPU-Kerne" "2" CORES
|
||
ask "Netzwerk-Bridge" "vmbr0" BRIDGE
|
||
echo ""
|
||
|
||
echo -e "${CYAN}── Netzwerk ─────────────────────────────────────────────────${NC}"
|
||
echo -e " DHCP: '${YELLOW}dhcp${NC}' | Statisch z.B.: '${YELLOW}192.168.1.100/24${NC}'"
|
||
ask "IP-Adresse" "dhcp" IP_ADDR
|
||
GATEWAY=""
|
||
[[ "$IP_ADDR" != "dhcp" ]] && ask "Gateway" "" GATEWAY
|
||
echo ""
|
||
|
||
echo -e "${CYAN}── Container-Typ ────────────────────────────────────────────${NC}"
|
||
ask_yn "Unprivilegiert? (empfohlen)" "j" && UNPRIVILEGED=1 || UNPRIVILEGED=0
|
||
echo ""
|
||
|
||
echo -e "${CYAN}── Zeitzone ─────────────────────────────────────────────────${NC}"
|
||
ask "Zeitzone" "Europe/Berlin" TIMEZONE
|
||
echo ""
|
||
|
||
# ══════════════════════════════════════════════════════════════════════════════
|
||
# SCHRITT 4 – SOFTWARE
|
||
# ══════════════════════════════════════════════════════════════════════════════
|
||
echo -e "${CYAN}╔══ Schritt 4: Software ════════════════════════════════════════╗${NC}"
|
||
echo ""
|
||
|
||
# ── Docker ────────────────────────────────────────────────────────────────────
|
||
echo -e "${CYAN}── Docker ───────────────────────────────────────────────────${NC}"
|
||
if ask_yn "🐳 Docker Engine installieren?" "j"; then
|
||
INSTALL_DOCKER=true
|
||
|
||
echo ""
|
||
echo -e "${CYAN}── Portainer ────────────────────────────────────────────────${NC}"
|
||
if ask_yn "🖥 Portainer CE (Docker Web-UI) installieren?" "j"; then
|
||
INSTALL_PORTAINER=true
|
||
ask "Portainer HTTPS-Port" "9443" PORTAINER_PORT
|
||
else
|
||
INSTALL_PORTAINER=false
|
||
PORTAINER_PORT="9443"
|
||
fi
|
||
else
|
||
INSTALL_DOCKER=false
|
||
INSTALL_PORTAINER=false
|
||
PORTAINER_PORT="9443"
|
||
fi
|
||
echo ""
|
||
|
||
# ── Ubuntu-spezifische Optionen ────────────────────────────────────────────────
|
||
ENABLE_UNATTENDED=false
|
||
DISABLE_SNAP=false
|
||
DISABLE_PRO_MOTD=false
|
||
|
||
if [[ "$DISTRO" == "ubuntu" ]]; then
|
||
echo -e "${CYAN}── Ubuntu-Optionen ──────────────────────────────────────────${NC}"
|
||
ask_yn "Automatische Sicherheitsupdates (unattended-upgrades)?" "j" \
|
||
&& ENABLE_UNATTENDED=true || true
|
||
ask_yn "Snap deaktivieren? (spart Ressourcen im LXC)" "j" \
|
||
&& DISABLE_SNAP=true || true
|
||
ask_yn "Ubuntu Pro / ESM Werbemeldungen im MOTD deaktivieren?" "j" \
|
||
&& DISABLE_PRO_MOTD=true || true
|
||
echo ""
|
||
fi
|
||
|
||
# ── Optionale Pakete ───────────────────────────────────────────────────────────
|
||
echo -e "${CYAN}── Optionale Pakete ─────────────────────────────────────────${NC}"
|
||
ask_yn "curl, wget, git (Basis-Tools)" "j" && INSTALL_BASETOOLS=true || INSTALL_BASETOOLS=false
|
||
ask_yn "htop, ncdu, net-tools (System-Monitoring)" "j" && INSTALL_MONITORING=true || INSTALL_MONITORING=false
|
||
ask_yn "vim + nano (Editoren)" "j" && INSTALL_EDITORS=true || INSTALL_EDITORS=false
|
||
ask_yn "cron + logrotate (Automatisierung)" "j" && INSTALL_CRON=true || INSTALL_CRON=false
|
||
ask_yn "fail2ban (Brute-Force-Schutz)" "n" && INSTALL_FAIL2BAN=true || INSTALL_FAIL2BAN=false
|
||
ask_yn "ufw (Firewall)" "n" && INSTALL_UFW=true || INSTALL_UFW=false
|
||
echo ""
|
||
|
||
# ══════════════════════════════════════════════════════════════════════════════
|
||
# SCHRITT 5 – SSH
|
||
# ══════════════════════════════════════════════════════════════════════════════
|
||
echo -e "${CYAN}╔══ Schritt 5: SSH-Zugriff ══════════════════════════════════════╗${NC}"
|
||
echo ""
|
||
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}Authentifizierung:${NC}"
|
||
echo -e " ${YELLOW}[1]${NC} Nur Passwort"
|
||
echo -e " ${YELLOW}[2]${NC} Nur SSH-Key"
|
||
echo -e " ${YELLOW}[3]${NC} Passwort + SSH-Key"
|
||
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 " Public Key eingeben (Inhalt von ${YELLOW}~/.ssh/id_rsa.pub${NC}):"
|
||
echo -ne " ${BOLD}Key:${NC} "
|
||
read -r SSH_PUBKEY
|
||
if [[ -z "$SSH_PUBKEY" ]]; then
|
||
warn "Kein Key eingegeben – falle auf Passwort zurück."
|
||
SSH_AUTH="password"
|
||
fi
|
||
fi
|
||
else
|
||
ENABLE_ROOT_SSH=false
|
||
SSH_PORT="22"
|
||
SSH_AUTH="password"
|
||
fi
|
||
echo ""
|
||
|
||
# ══════════════════════════════════════════════════════════════════════════════
|
||
# ZUSAMMENFASSUNG
|
||
# ══════════════════════════════════════════════════════════════════════════════
|
||
echo -e "${BOLD}╔════════════════════════ Zusammenfassung ════════════════════════╗${NC}"
|
||
printf "${BOLD}║${NC} %-28s %-30s ${BOLD}║${NC}\n" "Distribution:" "${DISTRO_LABEL} ${CHOSEN_VER}"
|
||
printf "${BOLD}║${NC} %-28s %-30s ${BOLD}║${NC}\n" "Template:" "$CHOSEN_TEMPLATE"
|
||
printf "${BOLD}║${NC} %-28s %-30s ${BOLD}║${NC}\n" "Container-ID:" "$CTID"
|
||
printf "${BOLD}║${NC} %-28s %-30s ${BOLD}║${NC}\n" "Hostname:" "$HOSTNAME"
|
||
printf "${BOLD}║${NC} %-28s %-30s ${BOLD}║${NC}\n" "Storage:" "$STORAGE"
|
||
printf "${BOLD}║${NC} %-28s %-30s ${BOLD}║${NC}\n" "Disk / RAM / Swap:" "${DISK}GB / ${RAM}MB / ${SWAP}MB"
|
||
printf "${BOLD}║${NC} %-28s %-30s ${BOLD}║${NC}\n" "CPU / Netzwerk:" "${CORES} Kerne / ${IP_ADDR}"
|
||
printf "${BOLD}║${NC} %-28s %-30s ${BOLD}║${NC}\n" "Zeitzone:" "$TIMEZONE"
|
||
printf "${BOLD}║${NC} %-28s %-30s ${BOLD}║${NC}\n" "Unprivilegiert:" "$([[ $UNPRIVILEGED -eq 1 ]] && echo Ja || echo Nein)"
|
||
echo -e "${BOLD}║${NC} ───────────────────────────────────────────────────────── ${BOLD}║${NC}"
|
||
printf "${BOLD}║${NC} %-28s %-30s ${BOLD}║${NC}\n" "Docker:" "$($INSTALL_DOCKER && echo Ja || echo Nein)"
|
||
printf "${BOLD}║${NC} %-28s %-30s ${BOLD}║${NC}\n" "Portainer CE:" "$($INSTALL_PORTAINER && echo "Ja (:${PORTAINER_PORT})" || echo Nein)"
|
||
if [[ "$DISTRO" == "ubuntu" ]]; then
|
||
printf "${BOLD}║${NC} %-28s %-30s ${BOLD}║${NC}\n" "Unattended-Upgrades:" "$($ENABLE_UNATTENDED && echo Ja || echo Nein)"
|
||
printf "${BOLD}║${NC} %-28s %-30s ${BOLD}║${NC}\n" "Snap deaktiviert:" "$($DISABLE_SNAP && echo Ja || echo Nein)"
|
||
fi
|
||
printf "${BOLD}║${NC} %-28s %-30s ${BOLD}║${NC}\n" "Basis-Tools:" "$($INSTALL_BASETOOLS && echo Ja || echo Nein)"
|
||
printf "${BOLD}║${NC} %-28s %-30s ${BOLD}║${NC}\n" "Monitoring-Tools:" "$($INSTALL_MONITORING && echo Ja || echo Nein)"
|
||
printf "${BOLD}║${NC} %-28s %-30s ${BOLD}║${NC}\n" "Editoren:" "$($INSTALL_EDITORS && echo Ja || echo Nein)"
|
||
printf "${BOLD}║${NC} %-28s %-30s ${BOLD}║${NC}\n" "Cron / Logrotate:" "$($INSTALL_CRON && echo Ja || echo Nein)"
|
||
printf "${BOLD}║${NC} %-28s %-30s ${BOLD}║${NC}\n" "fail2ban:" "$($INSTALL_FAIL2BAN && echo Ja || echo Nein)"
|
||
printf "${BOLD}║${NC} %-28s %-30s ${BOLD}║${NC}\n" "UFW Firewall:" "$($INSTALL_UFW && echo Ja || echo Nein)"
|
||
printf "${BOLD}║${NC} %-28s %-30s ${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 ─────────────────────────────────────────────────────
|
||
info "Prüfe Template …"
|
||
DOWNLOADED=$(pveam list local 2>/dev/null | awk '{print $1}' | grep -F "$CHOSEN_TEMPLATE" || true)
|
||
if [[ -z "$DOWNLOADED" ]]; then
|
||
info "Lade Template herunter: ${CHOSEN_TEMPLATE} …"
|
||
pveam download local "$CHOSEN_TEMPLATE"
|
||
success "Template heruntergeladen."
|
||
else
|
||
success "Template bereits lokal vorhanden."
|
||
fi
|
||
TEMPLATE_PATH="local:vztmpl/${CHOSEN_TEMPLATE}"
|
||
|
||
# ── LXC erstellen ─────────────────────────────────────────────────────────────
|
||
info "Erstelle LXC-Container ${CTID} …"
|
||
NET_CONFIG="name=eth0,bridge=${BRIDGE}"
|
||
[[ "$IP_ADDR" == "dhcp" ]] && NET_CONFIG+=",ip=dhcp" || {
|
||
NET_CONFIG+=",ip=${IP_ADDR}"
|
||
[[ -n "$GATEWAY" ]] && NET_CONFIG+=",gw=${GATEWAY}"
|
||
}
|
||
|
||
FEATURES="keyctl=1,nesting=1"
|
||
# Bei Docker + unprivilegiert: nesting zwingend, bei privilegiert nicht nötig aber schadet nicht
|
||
$INSTALL_DOCKER && [[ $UNPRIVILEGED -eq 1 ]] && FEATURES="keyctl=1,nesting=1"
|
||
|
||
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 "$FEATURES" \
|
||
--onboot 1 --start 0
|
||
|
||
success "Container ${CTID} erstellt."
|
||
|
||
info "Starte Container …"
|
||
pct start "$CTID"
|
||
sleep 6
|
||
|
||
lxc_exec() { pct exec "$CTID" -- bash -c "$*"; }
|
||
|
||
# ── Noninteraktiv setzen (Ubuntu) ──────────────────────────────────────────────
|
||
[[ "$DISTRO" == "ubuntu" ]] && \
|
||
lxc_exec "echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections 2>/dev/null || true"
|
||
|
||
# ── System aktualisieren ───────────────────────────────────────────────────────
|
||
info "System aktualisieren …"
|
||
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 & Locale ──────────────────────────────────────────────────────────
|
||
info "Zeitzone & Locale konfigurieren …"
|
||
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
|
||
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
|
||
"
|
||
success "Zeitzone: ${TIMEZONE} | Locale: de_DE.UTF-8"
|
||
|
||
# ── Ubuntu-spezifisch: Snap / Pro-MOTD / Unattended ───────────────────────────
|
||
if [[ "$DISTRO" == "ubuntu" ]]; then
|
||
if $DISABLE_SNAP; then
|
||
info "Snap deaktivieren …"
|
||
lxc_exec "
|
||
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 2>/dev/null || true
|
||
cat > /etc/apt/preferences.d/no-snapd << 'PIN'
|
||
Package: snapd
|
||
Pin: release a=*
|
||
Pin-Priority: -10
|
||
PIN
|
||
" 2>/dev/null
|
||
success "Snap deaktiviert."
|
||
fi
|
||
|
||
if $DISABLE_PRO_MOTD; then
|
||
info "Ubuntu Pro MOTD deaktivieren …"
|
||
lxc_exec "
|
||
for f in 10-help-text 50-motd-news 88-esm-announce 91-contract-ua-esm-status 95-hwe-eol; do
|
||
[ -f /etc/update-motd.d/\$f ] && chmod -x /etc/update-motd.d/\$f || true
|
||
done
|
||
command -v pro &>/dev/null && pro config set motd=false 2>/dev/null || true
|
||
" 2>/dev/null
|
||
success "Ubuntu Pro MOTD deaktiviert."
|
||
fi
|
||
|
||
if $ENABLE_UNATTENDED; then
|
||
info "Unattended-Upgrades 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
|
||
systemctl enable unattended-upgrades --now 2>/dev/null || true
|
||
"
|
||
success "Unattended-Upgrades aktiv."
|
||
fi
|
||
fi
|
||
|
||
# ── Optionale Pakete ───────────────────────────────────────────────────────────
|
||
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 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 (5 Versuche / 10 Min → 1h Ban)."
|
||
fi
|
||
|
||
# ── UFW konfigurieren ──────────────────────────────────────────────────────────
|
||
if $INSTALL_UFW; then
|
||
info "UFW vorkonfigurieren …"
|
||
lxc_exec "
|
||
ufw default deny incoming
|
||
ufw default allow outgoing
|
||
ufw allow ${SSH_PORT}/tcp comment 'SSH'
|
||
"
|
||
success "UFW konfiguriert (deaktiviert – mit 'ufw enable' aktivieren)."
|
||
fi
|
||
|
||
# ── Docker installieren ────────────────────────────────────────────────────────
|
||
if $INSTALL_DOCKER; then
|
||
info "Docker Engine installieren (offizieller Kanal) …"
|
||
lxc_exec "
|
||
export DEBIAN_FRONTEND=noninteractive
|
||
apt-get install -y -qq ca-certificates curl gnupg lsb-release
|
||
|
||
install -m 0755 -d /etc/apt/keyrings
|
||
curl -fsSL https://download.docker.com/linux/${DISTRO}/gpg \
|
||
| gpg --dearmor -o /etc/apt/keyrings/docker.gpg --yes
|
||
chmod a+r /etc/apt/keyrings/docker.gpg
|
||
|
||
echo \"deb [arch=\$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
|
||
https://download.docker.com/linux/${DISTRO} \
|
||
\$(. /etc/os-release && echo \"\$VERSION_CODENAME\") stable\" \
|
||
> /etc/apt/sources.list.d/docker.list
|
||
|
||
apt-get update -qq
|
||
apt-get install -y -qq \
|
||
docker-ce docker-ce-cli containerd.io \
|
||
docker-buildx-plugin docker-compose-plugin
|
||
|
||
systemctl enable docker --now
|
||
"
|
||
success "Docker installiert."
|
||
|
||
info "Docker-Funktionstest …"
|
||
lxc_exec "docker run --rm hello-world" &>/dev/null \
|
||
&& success "Docker läuft korrekt." \
|
||
|| warn "Docker-Test fehlgeschlagen – bitte manuell prüfen."
|
||
fi
|
||
|
||
# ── Portainer installieren ─────────────────────────────────────────────────────
|
||
if $INSTALL_PORTAINER; then
|
||
info "Portainer CE installieren (Port ${PORTAINER_PORT}) …"
|
||
lxc_exec "
|
||
docker volume create portainer_data
|
||
docker run -d \
|
||
--name portainer \
|
||
--restart always \
|
||
-p 8000:8000 \
|
||
-p ${PORTAINER_PORT}:9443 \
|
||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||
-v portainer_data:/data \
|
||
portainer/portainer-ce:latest
|
||
"
|
||
success "Portainer CE gestartet."
|
||
fi
|
||
|
||
# ── SSH konfigurieren ──────────────────────────────────────────────────────────
|
||
if $ENABLE_ROOT_SSH; then
|
||
info "SSH-Server konfigurieren …"
|
||
lxc_exec "export DEBIAN_FRONTEND=noninteractive && apt-get install -y -qq openssh-server"
|
||
|
||
if [[ "$SSH_CFG_MODE" == "sshd_config_d" ]]; then
|
||
# Ubuntu: Drop-in Datei
|
||
lxc_exec "
|
||
mkdir -p /etc/ssh/sshd_config.d
|
||
cat > /etc/ssh/sshd_config.d/99-lxc.conf << SSHEOF
|
||
PermitRootLogin yes
|
||
Port ${SSH_PORT}
|
||
SSHEOF
|
||
"
|
||
else
|
||
# Debian: direkt in sshd_config
|
||
lxc_exec "
|
||
sed -i 's/^#*PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config
|
||
grep -q '^PermitRootLogin' /etc/ssh/sshd_config || echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config
|
||
sed -i 's/^#*Port .*/Port ${SSH_PORT}/' /etc/ssh/sshd_config
|
||
grep -q '^Port ' /etc/ssh/sshd_config || echo 'Port ${SSH_PORT}' >> /etc/ssh/sshd_config
|
||
"
|
||
fi
|
||
|
||
# Auth-Methode
|
||
case "$SSH_AUTH" in
|
||
password)
|
||
lxc_exec "
|
||
CFG=${SSH_CFG_MODE/sshd_config_d//etc/ssh/sshd_config.d/99-lxc.conf}
|
||
[[ '$SSH_CFG_MODE' == 'sshd_config_d' ]] && FILE=/etc/ssh/sshd_config.d/99-lxc.conf || FILE=/etc/ssh/sshd_config
|
||
echo 'PasswordAuthentication yes' >> \$FILE
|
||
echo 'PubkeyAuthentication no' >> \$FILE
|
||
" ;;
|
||
key|both)
|
||
lxc_exec "
|
||
[[ '$SSH_CFG_MODE' == 'sshd_config_d' ]] && FILE=/etc/ssh/sshd_config.d/99-lxc.conf || FILE=/etc/ssh/sshd_config
|
||
[[ '$SSH_AUTH' == 'both' ]] && echo 'PasswordAuthentication yes' >> \$FILE || echo 'PasswordAuthentication no' >> \$FILE
|
||
echo 'PubkeyAuthentication yes' >> \$FILE
|
||
mkdir -p /root/.ssh && chmod 700 /root/.ssh
|
||
echo '${SSH_PUBKEY}' >> /root/.ssh/authorized_keys
|
||
chmod 600 /root/.ssh/authorized_keys
|
||
" ;;
|
||
esac
|
||
|
||
lxc_exec "systemctl enable ssh --now && systemctl restart ssh"
|
||
|
||
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}$( [[ "$SSH_PORT" != "22" ]] && echo " -p ${SSH_PORT}" )"
|
||
else
|
||
warn "SSH-Port noch nicht erreichbar – kurz warten und erneut testen."
|
||
fi
|
||
fi
|
||
|
||
# ── Portainer-Timer zurücksetzen ───────────────────────────────────────────────
|
||
if $INSTALL_PORTAINER; then
|
||
info "Portainer-Timer zurücksetzen (Neustart nach Installation) …"
|
||
pct exec "$CTID" -- docker restart portainer &>/dev/null \
|
||
&& success "Portainer neu gestartet – 5-Minuten-Fenster beginnt jetzt." \
|
||
|| warn "Portainer-Neustart fehlgeschlagen – manuell: docker restart portainer"
|
||
fi
|
||
|
||
# ── Proxmox-Notes setzen ───────────────────────────────────────────────────────
|
||
info "Setze Proxmox-Hinweise …"
|
||
sleep 2
|
||
NOTES_IP=$(pct exec "$CTID" -- hostname -I 2>/dev/null | awk '{print $1}' || echo "")
|
||
[[ -z "$NOTES_IP" ]] && NOTES_IP="n/a (DHCP)"
|
||
|
||
SSH_LINE=""
|
||
$ENABLE_ROOT_SSH && {
|
||
[[ "$SSH_PORT" == "22" ]] \
|
||
&& SSH_LINE="ssh root@${NOTES_IP}" \
|
||
|| SSH_LINE="ssh root@${NOTES_IP} -p ${SSH_PORT}"
|
||
}
|
||
|
||
NOTE="[](${DISTRO_LINK})"
|
||
$INSTALL_DOCKER && \
|
||
NOTE+=" [](https://www.docker.com)"
|
||
NOTE+=$'\n\n---\n\n'
|
||
NOTE+="**🌐 IP-Adresse:** \`${NOTES_IP}\`"$'\n\n'
|
||
$INSTALL_PORTAINER && \
|
||
NOTE+="**🐳 Portainer:** [https://${NOTES_IP}:${PORTAINER_PORT}](https://${NOTES_IP}:${PORTAINER_PORT})"$'\n\n'
|
||
$ENABLE_ROOT_SSH && \
|
||
NOTE+="**🔑 SSH:** \`${SSH_LINE}\`"$'\n\n'
|
||
|
||
INST=""
|
||
$INSTALL_BASETOOLS && INST+="curl wget git "
|
||
$INSTALL_MONITORING && INST+="htop ncdu net-tools "
|
||
$INSTALL_EDITORS && INST+="vim nano "
|
||
$INSTALL_CRON && INST+="cron logrotate "
|
||
$INSTALL_FAIL2BAN && INST+="fail2ban "
|
||
$INSTALL_UFW && INST+="ufw "
|
||
$ENABLE_UNATTENDED && INST+="unattended-upgrades "
|
||
[[ -n "$INST" ]] && NOTE+="**📦 Pakete:** \`${INST% }\`"$'\n\n'
|
||
|
||
NOTE+="**🕐 Zeitzone:** ${TIMEZONE}"$'\n\n'
|
||
NOTE+="---"$'\n\n'
|
||
NOTE+="Installiert mit [gitea.vourx.com](https://gitea.vourx.com)"
|
||
NOTE+=" | [${DOCS_LABEL}](${DOCS_URL})"
|
||
NOTE+=" | [${PKG_LABEL}](${PKG_URL})"
|
||
|
||
pvesh set /nodes/$(hostname)/lxc/${CTID}/config --description "${NOTE}" 2>/dev/null \
|
||
&& success "Proxmox-Hinweise gesetzt." \
|
||
|| warn "Hinweise konnten nicht gesetzt werden."
|
||
|
||
# ── Aufräumen ──────────────────────────────────────────────────────────────────
|
||
info "System aufräumen …"
|
||
lxc_exec "export DEBIAN_FRONTEND=noninteractive && apt-get autoremove -y -qq && apt-get autoclean -qq"
|
||
success "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 %s${NC}\n" "Distribution:" "$DISTRO_LABEL" "$CHOSEN_VER"
|
||
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" "Zeitzone:" "$TIMEZONE"
|
||
echo -e "${GREEN}║${NC}"
|
||
$INSTALL_DOCKER && \
|
||
printf "${GREEN}║${NC} %-22s ${BOLD}%s${NC}\n" "Docker:" "installiert & aktiv"
|
||
if $INSTALL_PORTAINER; then
|
||
printf "${GREEN}║${NC} %-22s ${BOLD}https://%s:%s${NC}\n" "Portainer UI:" "$CONTAINER_IP" "$PORTAINER_PORT"
|
||
echo -e "${GREEN}║${NC} ${YELLOW} ⏱ Bitte innerhalb von 5 Min. Admin-Account anlegen!${NC}"
|
||
fi
|
||
if $ENABLE_ROOT_SSH; then
|
||
echo -e "${GREEN}║${NC}"
|
||
echo -e "${GREEN}║${NC} ${CYAN}── SSH ─────────────────────────────────────────────────${NC}"
|
||
[[ "$SSH_PORT" == "22" ]] \
|
||
&& printf "${GREEN}║${NC} ${BOLD}ssh root@%s${NC}\n" "$CONTAINER_IP" \
|
||
|| printf "${GREEN}║${NC} ${BOLD}ssh root@%s -p %s${NC}\n" "$CONTAINER_IP" "$SSH_PORT"
|
||
printf "${GREEN}║${NC} %-22s ${BOLD}%s${NC}\n" "Auth:" "$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} Mit 'ufw enable' im Container 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}"
|
||
if $INSTALL_PORTAINER; then
|
||
echo -e "\n ${YELLOW}Portainer Timeout? →${NC} ${BOLD}pct exec ${CTID} -- docker restart portainer${NC}"
|
||
fi
|
||
echo "" |