#!/usr/bin/env bash # --- Installer Information --- cat <&2 -------------------------------------------------- Traefik Manager Installer -------------------------------------------------- Documentation: https://traefik-manager.xyzlab.dev/traefik-stack.html Source Code: https://github.com/chr0nzz/traefik-manager Running this script will configure the service on your host. Ensure you have root/sudo access. Usage: curl -fsSL https://get-traefik.xyzlab.dev | bash -------------------------------------------------- EOF set -euo pipefail SCRIPT_VERSION="1.0.0" BOLD="\033[1m" DIM="\033[2m" GREEN="\033[32m" CYAN="\033[36m" YELLOW="\033[33m" RED="\033[31m" RESET="\033[0m" INSTALL_DIR="${HOME}/traefik-stack" COMPOSE_CMD="" INSTALL_MODE="" DEPLOY_METHOD="" RESTART_METHOD="" TRAEFIK_CONTAINER="traefik" TRAEFIK_SYSTEMD="false" TRAEFIK_SERVICE_NAME="traefik" MOUNT_STATIC_CONFIG="false" TRAEFIK_YML_HOST_PATH="" SIGNAL_FILE_PATH="/signals/restart.sig" # ─── Helpers ────────────────────────────────────────────────────────────────── print_banner() { echo "" echo -e "${CYAN}${BOLD}" echo " ████████╗██████╗ █████╗ ███████╗███████╗██╗██╗ ██╗" echo " ██╔══╝██╔══██╗██╔══██╗██╔════╝██╔════╝██║██║ ██╔╝" echo " ██║ ██████╔╝███████║█████╗ █████╗ ██║█████╔╝ " echo " ██║ ██╔══██╗██╔══██║██╔══╝ ██╔══╝ ██║██╔═██╗ " echo " ██║ ██║ ██║██║ ██║███████╗██║ ██║██║ ██╗" echo " ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝" echo "" echo " ◉" echo " │" echo " ╔═════╗" echo " ◉ ─── ╠ ╣ ─── ◉" echo " ╚═════╝" echo " │" echo " ◉" echo -e "${RESET}" echo -e " ${DIM}+ Traefik Manager - Interactive Setup${RESET}" echo "" } step() { echo -e "\n${CYAN}${BOLD}▸ $1${RESET}"; } ok() { echo -e " ${GREEN}✔${RESET} $1"; } warn() { echo -e " ${YELLOW}⚠${RESET} $1"; } info() { echo -e " ${DIM}ℹ $1${RESET}"; } die() { echo -e "\n ${RED}✖ Error: $1${RESET}\n"; exit 1; } sep() { echo -e "\n ${DIM}────────────────────────────────────────${RESET}"; } ask() { local prompt="$1" default="${2:-}" var_name="$3" if [[ -n "$default" ]]; then echo -ne " ${BOLD}${prompt}${RESET} ${DIM}[${default}]${RESET}: " else echo -ne " ${BOLD}${prompt}${RESET}: " fi read -r input /dev/null sudo apt-get update -qq sudo apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin sudo systemctl enable --now docker sudo usermod -aG docker "${USER}" || true ok "Docker installed" elif [[ "$OS_ID" == "fedora" || "$OS_ID" == "rhel" || "$OS_ID" == "centos" || \ "$OS_ID" == "rocky" || "$OS_ID" == "almalinux" || \ "$OS_LIKE" == *"rhel"* || "$OS_LIKE" == *"fedora"* ]]; then info "Detected RHEL/Fedora" sudo dnf -y install dnf-plugins-core sudo dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo 2>/dev/null \ || sudo dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin sudo systemctl enable --now docker sudo usermod -aG docker "${USER}" || true ok "Docker installed" elif [[ "$OS_ID" == "arch" || "$OS_LIKE" == *"arch"* ]]; then info "Detected Arch" sudo pacman -Sy --noconfirm docker docker-compose sudo systemctl enable --now docker sudo usermod -aG docker "${USER}" || true ok "Docker installed" else warn "Using Docker convenience script" curl -fsSL https://get.docker.com | sudo sh sudo usermod -aG docker "${USER}" || true ok "Docker installed" fi if ! docker info &>/dev/null 2>&1; then echo "" warn "Docker was installed but the current shell does not have the docker group yet." warn "Please log out and back in, then re-run:" echo "" echo -e " ${CYAN}curl -fsSL https://get-traefik.xyzlab.dev | bash${RESET}" echo "" exit 0 fi } check_docker() { step "Checking Docker" command -v curl &>/dev/null && ok "curl found" || die "curl is required." if command -v docker &>/dev/null && docker info &>/dev/null 2>&1; then ok "Docker found and running" else warn "Docker is not installed or the daemon is not running." ask_yn "Install Docker now?" "y" INSTALL_DOCKER_NOW if [[ "$INSTALL_DOCKER_NOW" == "true" ]]; then install_docker else die "Docker is required. Aborting." fi fi if docker compose version &>/dev/null 2>&1; then ok "docker compose (v2) found" COMPOSE_CMD="docker compose" elif command -v docker-compose &>/dev/null; then ok "docker-compose (v1) found" COMPOSE_CMD="docker-compose" else die "Docker Compose is required. Install the Docker Compose plugin and re-run." fi } # ─── Native deps ────────────────────────────────────────────────────────────── check_native_deps() { step "Checking dependencies" command -v curl &>/dev/null && ok "curl found" || die "curl is required." command -v git &>/dev/null && ok "git found" || die "git is required. Install it and re-run." if ! command -v python3 &>/dev/null; then die "Python 3.11 or newer is required. Install it and re-run." fi local py_ok py_ok=$(python3 -c "import sys; print('ok' if sys.version_info >= (3, 11) else 'old')") if [[ "$py_ok" != "ok" ]]; then die "Python 3.11 or newer is required. Found: $(python3 --version)" fi ok "Python $(python3 --version | cut -d' ' -f2) found" command -v systemctl &>/dev/null && ok "systemd found" || die "systemd is required for the Linux service install." } # ─── Mode selection ─────────────────────────────────────────────────────────── gather_mode() { step "What would you like to install?" ask_choice "Choose an option" INSTALL_MODE \ "Traefik + Traefik Manager (full stack)" \ "Traefik Manager only" if [[ "$INSTALL_MODE" == "Traefik Manager only" ]]; then sep echo "" ask_choice "Deployment method" DEPLOY_METHOD \ "Docker" \ "Linux service (systemd)" else DEPLOY_METHOD="Docker" fi } # ─── Restart method gathering (Docker) ──────────────────────────────────────── gather_restart_method_docker() { local ask_container="${1:-false}" sep echo "" echo -e " ${BOLD}-- Static Config Editor --${RESET}" info "TM can restart Traefik automatically when you save static config changes." local choice ask_choice "How should TM restart Traefik?" choice \ "Docker socket proxy (recommended - one extra container, minimal socket exposure)" \ "Poison pill (no extra container - adds a healthcheck to Traefik compose)" \ "Direct Docker socket (simplest - full Docker access, higher risk)" case "$choice" in "Docker socket proxy"*) RESTART_METHOD="proxy" ;; "Poison pill"*) RESTART_METHOD="poison-pill" ;; "Direct Docker socket"*) RESTART_METHOD="socket" ;; esac if [[ "$ask_container" == "true" ]]; then ask "Traefik container name" "traefik" TRAEFIK_CONTAINER else TRAEFIK_CONTAINER="traefik" fi } # ─── Full stack config ──────────────────────────────────────────────────────── gather_full_stack() { step "General" echo -e " ${DIM}Press Enter to accept defaults shown in brackets.${RESET}\n" ask "Install directory" "$INSTALL_DIR" INSTALL_DIR sep echo "" echo -e " ${BOLD}Deployment type${RESET}" info "Internal = LAN / VPN / Tailscale only. External = reachable from the internet." ask_choice "Where will this be accessed from?" DEPLOYMENT_TYPE \ "External (internet-facing)" \ "Internal only (LAN / VPN / Tailscale)" if [[ "$DEPLOYMENT_TYPE" == "External"* ]]; then EXTERNAL=true else EXTERNAL=false; fi sep echo "" echo -e " ${BOLD}-- Domain --${RESET}" ask "Your domain (e.g. example.com)" "" DOMAIN [[ -z "$DOMAIN" ]] && die "A domain is required." ask "Traefik dashboard subdomain" "traefik.$DOMAIN" TRAEFIK_DASHBOARD_HOST ask "Traefik Manager subdomain" "manager.$DOMAIN" TM_HOST ask_yn "Enable Traefik API dashboard UI?" "y" ENABLE_DASHBOARD sep echo "" echo -e " ${BOLD}-- TLS / Certificates --${RESET}" gather_tls_method sep echo "" echo -e " ${BOLD}-- Dynamic Config --${RESET}" info "Single file is simpler. Directory (one .yml per service) is easier at scale." ask_choice "Dynamic config layout" CONFIG_LAYOUT \ "Single file (dynamic.yml)" \ "Directory - one .yml file per service" sep echo "" echo -e " ${BOLD}-- Optional Mounts --${RESET}" info "Expose extra Traefik data to Traefik Manager for richer visibility." ask_yn "Mount access logs?" "y" MOUNT_ACCESS_LOGS ask_yn "Mount SSL certs (acme.json)?" "y" MOUNT_CERTS ask_yn "Mount Traefik static config (traefik.yml)?" "n" MOUNT_STATIC_CONFIG if [[ "$MOUNT_STATIC_CONFIG" == "true" ]]; then gather_restart_method_docker "false" fi sep echo "" echo -e " ${BOLD}-- Docker Network --${RESET}" ask "Docker network name" "traefik-net" DOCKER_NETWORK ask "Traefik internal API port" "8080" TRAEFIK_API_PORT if [[ "$EXTERNAL" == "true" ]]; then sep echo "" echo -e " ${YELLOW}${BOLD}Firewall / Port Requirements${RESET}" echo -e " ${DIM}The following ports must be open on this server's firewall:${RESET}\n" if [[ "$TLS_TYPE" != "none" ]]; then echo -e " ${CYAN}80/tcp${RESET} HTTP (redirects to HTTPS + ACME HTTP-01 challenge)" echo -e " ${CYAN}443/tcp${RESET} HTTPS" else echo -e " ${CYAN}80/tcp${RESET} HTTP" fi echo "" echo -e " ${DIM} sudo ufw allow 80/tcp${RESET}" if [[ "$TLS_TYPE" != "none" ]]; then echo -e " ${DIM} sudo ufw allow 443/tcp${RESET}" fi echo -e " ${DIM} sudo ufw reload${RESET}" echo "" echo -ne " ${BOLD}Press Enter when ports are open to continue...${RESET}" read -r "${INSTALL_DIR}/traefik/traefik.yml" < "${INSTALL_DIR}/traefik/config/dynamic.yml" <<'EOF' http: routers: {} services: {} middlewares: {} EOF ok "traefik/config/dynamic.yml created" else mkdir -p "${INSTALL_DIR}/traefik/config" cat > "${INSTALL_DIR}/traefik/config/example-app.yml.disabled" <<'EOF' http: routers: my-app: rule: "Host(`app.example.com`)" entryPoints: - websecure service: my-app tls: certResolver: letsencrypt services: my-app: loadBalancer: servers: - url: "http://my-app-container:3000" EOF ok "traefik/config/ directory created" fi } build_compose_full() { local tls_label_traefik="" tls_label_tm="" if [[ "$TLS_TYPE" != "none" ]]; then tls_label_traefik=' - "traefik.http.routers.dashboard.tls.certresolver='"${CERT_RESOLVER}"'"' tls_label_tm=' - "traefik.http.routers.traefik-manager.tls.certresolver='"${CERT_RESOLVER}"'"' fi local traefik_env="" if [[ -n "$DNS_ENV_BLOCK" ]]; then traefik_env=" environment: ${DNS_ENV_BLOCK}" fi local traefik_vols=" - /var/run/docker.sock:/var/run/docker.sock:ro - ./traefik/traefik.yml:/traefik.yml:ro - ./traefik/acme.json:/acme.json - ./traefik/logs:/logs" if [[ "$CONFIG_LAYOUT" == "Single file"* ]]; then traefik_vols+=" - ./traefik/config/dynamic.yml:/etc/traefik/config/dynamic.yml:ro" else traefik_vols+=" - ./traefik/config:/etc/traefik/config:ro" fi local traefik_healthcheck="" local traefik_static_labels="" if [[ "$MOUNT_STATIC_CONFIG" == "true" ]]; then traefik_static_labels=' - "traefik-manager.role=traefik" - "traefik-manager.static-config=/app/traefik.yml" - "traefik-manager.restart-method='"${RESTART_METHOD}"'"' if [[ "$RESTART_METHOD" == "poison-pill" ]]; then traefik_vols+=" - tm-signals:/signals" traefik_healthcheck=' healthcheck: test: ["CMD-SHELL", "[ ! -f /signals/restart.sig ] || (rm /signals/restart.sig && kill -TERM 1)"] interval: 5s timeout: 3s retries: 1' fi fi local tm_vols=" - ./traefik-manager/config:/app/config - ./traefik-manager/backups:/app/backups" if [[ "$MOUNT_STATIC_CONFIG" != "true" || "$RESTART_METHOD" != "proxy" ]]; then tm_vols=" - /var/run/docker.sock:/var/run/docker.sock:ro ${tm_vols}" fi if [[ "$MOUNT_ACCESS_LOGS" == "true" ]]; then tm_vols+=" - ./traefik/logs:/app/logs:ro" fi if [[ "$MOUNT_CERTS" == "true" ]]; then tm_vols+=" - ./traefik/acme.json:/app/acme.json:ro" fi if [[ "$MOUNT_STATIC_CONFIG" == "true" ]]; then tm_vols+=" - ./traefik/traefik.yml:/app/traefik.yml" if [[ "$RESTART_METHOD" == "poison-pill" ]]; then tm_vols+=" - tm-signals:/signals" fi fi if [[ "$CONFIG_LAYOUT" == "Single file"* ]]; then tm_vols+=" - ./traefik/config/dynamic.yml:/app/config/dynamic.yml" else tm_vols+=" - ./traefik/config:/app/config/dynamic" fi local tm_networks=" - ${DOCKER_NETWORK}" if [[ "$MOUNT_STATIC_CONFIG" == "true" && "$RESTART_METHOD" == "proxy" ]]; then tm_networks+=" - socket-proxy-net" fi local static_env="" if [[ "$MOUNT_STATIC_CONFIG" == "true" ]]; then static_env=" - STATIC_CONFIG_PATH=/app/traefik.yml - RESTART_METHOD=${RESTART_METHOD} - TRAEFIK_CONTAINER=${TRAEFIK_CONTAINER}" if [[ "$RESTART_METHOD" == "proxy" ]]; then static_env+=" - DOCKER_HOST=tcp://socket-proxy:2375" elif [[ "$RESTART_METHOD" == "poison-pill" ]]; then static_env+=" - SIGNAL_FILE_PATH=/signals/restart.sig" fi fi local cookie_secure="false" [[ "$TLS_TYPE" != "none" ]] && cookie_secure="true" local port_443="" [[ "$TLS_TYPE" != "none" ]] && port_443=' - "443:443"' local socket_proxy_service="" if [[ "$MOUNT_STATIC_CONFIG" == "true" && "$RESTART_METHOD" == "proxy" ]]; then socket_proxy_service=" socket-proxy: image: tecnativa/docker-socket-proxy container_name: socket-proxy restart: unless-stopped networks: - socket-proxy-net volumes: - /var/run/docker.sock:/var/run/docker.sock:ro environment: CONTAINERS: 1 POST: 1" fi local extra_networks="" if [[ "$MOUNT_STATIC_CONFIG" == "true" && "$RESTART_METHOD" == "proxy" ]]; then extra_networks=" socket-proxy-net: internal: true" fi local volumes_section="" if [[ "$MOUNT_STATIC_CONFIG" == "true" && "$RESTART_METHOD" == "poison-pill" ]]; then volumes_section=" volumes: tm-signals:" fi cat > "${INSTALL_DIR}/docker-compose.yml" < "${INSTALL_DIR}/docker-compose.yml" </dev/null; then sudo useradd --system --no-create-home --shell /usr/sbin/nologin traefik-manager ok "System user traefik-manager created" else ok "System user traefik-manager already exists" fi sudo chown -R traefik-manager: "${NATIVE_INSTALL_DIR}" sudo chown -R traefik-manager: "${NATIVE_DATA_DIR}" if [[ "$MOUNT_STATIC_CONFIG" == "true" && "$RESTART_METHOD" == "poison-pill" ]]; then sudo chown -R traefik-manager: "${SIGNAL_FILE_PATH%/*}" fi if [[ "$MOUNT_STATIC_CONFIG" == "true" && "$RESTART_METHOD" == "socket" ]]; then sudo usermod -aG docker traefik-manager || true fi SVC_USER="traefik-manager" else SVC_USER="${USER}" fi local config_env="" if [[ "$CONFIG_LAYOUT" == "Single file"* ]]; then config_env="Environment=CONFIG_PATH=${NATIVE_CONFIG_PATH}" else config_env="Environment=CONFIG_DIR=${NATIVE_CONFIG_DIR}" fi local optional_env="" if [[ "${MOUNT_CERTS:-false}" == "true" ]]; then optional_env+="Environment=ACME_JSON_PATH=${ACME_JSON_HOST_PATH} " fi if [[ "${MOUNT_ACCESS_LOGS:-false}" == "true" ]]; then optional_env+="Environment=ACCESS_LOG_PATH=${ACCESS_LOG_PATH} " fi if [[ "$MOUNT_STATIC_CONFIG" == "true" ]]; then optional_env+="Environment=STATIC_CONFIG_PATH=${TRAEFIK_YML_HOST_PATH} Environment=RESTART_METHOD=${RESTART_METHOD} " if [[ "$RESTART_METHOD" == "socket" ]]; then optional_env+="Environment=TRAEFIK_CONTAINER=${TRAEFIK_CONTAINER} " fi if [[ "$RESTART_METHOD" == "poison-pill" ]]; then optional_env+="Environment=SIGNAL_FILE_PATH=${SIGNAL_FILE_PATH} " fi fi if [[ "$MOUNT_STATIC_CONFIG" == "true" && "$TRAEFIK_SYSTEMD" == "true" ]]; then sudo tee /etc/systemd/system/traefik-restart.path > /dev/null < /dev/null < /dev/null <&1 | grep -A3 "AUTO-GENERATED" | grep "Password:" | grep -oP '(?<=Password: )\S+' || true) if [[ -n "$log_line" ]]; then TEMP_PASSWORD="$log_line" ok "Temporary password retrieved" break fi sleep 1.5 (( attempts++ )) || true done if [[ -z "$TEMP_PASSWORD" ]]; then warn "Could not retrieve temporary password. Check: docker logs traefik-manager" fi } fetch_password_native() { step "Waiting for Traefik Manager to generate temporary password" TEMP_PASSWORD="" local attempts=0 while [[ $attempts -lt 20 ]]; do local log_line log_line=$(sudo journalctl -u traefik-manager --no-pager -n 50 2>/dev/null | grep -A3 "AUTO-GENERATED" | grep "Password:" | grep -oP '(?<=Password: )\S+' || true) if [[ -n "$log_line" ]]; then TEMP_PASSWORD="$log_line" ok "Temporary password retrieved" break fi sleep 1.5 (( attempts++ )) || true done if [[ -z "$TEMP_PASSWORD" ]]; then warn "Could not retrieve temporary password. Check: sudo journalctl -u traefik-manager" fi } # ─── Summaries ──────────────────────────────────────────────────────────────── print_static_config_summary() { if [[ "$MOUNT_STATIC_CONFIG" != "true" ]]; then return; fi echo "" echo -e " ${CYAN}${BOLD}Static Config Editor${RESET}" case "$RESTART_METHOD" in proxy) echo -e " ${DIM}Restart method socket proxy (tecnativa/docker-socket-proxy)${RESET}" echo -e " ${DIM}The socket-proxy service is running alongside TM with minimal permissions.${RESET}" ;; poison-pill) echo -e " ${DIM}Restart method poison pill (signal file)${RESET}" if [[ "$TRAEFIK_SYSTEMD" == "true" ]]; then echo -e " ${DIM}Traefik running as systemd service: ${TRAEFIK_SERVICE_NAME}${RESET}" echo -e " ${DIM}traefik-restart.path watcher is active - restarts ${TRAEFIK_SERVICE_NAME} when TM writes the signal file.${RESET}" else echo -e " ${YELLOW}⚠${RESET} ${DIM}Add this healthcheck to your Traefik service if not already set:${RESET}" echo "" echo -e " ${DIM}healthcheck:${RESET}" echo -e " ${DIM} test: [\"CMD-SHELL\", \"[ ! -f /signals/restart.sig ] || (rm /signals/restart.sig && kill -TERM 1)\"]${RESET}" echo -e " ${DIM} interval: 5s${RESET}" echo -e " ${DIM} timeout: 3s${RESET}" echo -e " ${DIM} retries: 1${RESET}" echo "" fi ;; socket) echo -e " ${DIM}Restart method direct Docker socket${RESET}" warn "Full Docker socket is mounted in TM. Keep TM behind authentication." ;; esac } print_summary_full() { local scheme="http" [[ "$TLS_TYPE" != "none" ]] && scheme="https" echo "" echo -e "${GREEN}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" echo -e "${GREEN}${BOLD} Setup complete!${RESET}" echo -e "${GREEN}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" echo "" echo -e " Traefik dashboard ${CYAN}${scheme}://${TRAEFIK_DASHBOARD_HOST}${RESET}" echo -e " Traefik Manager ${CYAN}${scheme}://${TM_HOST}${RESET}" echo "" if [[ -n "$TEMP_PASSWORD" ]]; then echo -e " ${YELLOW}${BOLD}Temporary password ${TEMP_PASSWORD}${RESET}" else echo -e " ${YELLOW}Temporary password run: docker logs traefik-manager${RESET}" fi echo -e " Install dir ${DIM}${INSTALL_DIR}${RESET}" echo "" if [[ "$CONFIG_LAYOUT" == "Single file"* ]]; then echo -e " ${DIM}Dynamic config ${INSTALL_DIR}/traefik/config/dynamic.yml${RESET}" else echo -e " ${DIM}Dynamic config ${INSTALL_DIR}/traefik/config/*.yml${RESET}" fi print_static_config_summary echo "" echo -e " ${DIM}cd ${INSTALL_DIR}${RESET}" echo -e " ${DIM}${COMPOSE_CMD} logs -f traefik-manager${RESET}" echo "" echo -e " ${CYAN}${BOLD}Updating${RESET}" echo -e " ${DIM} cd ${INSTALL_DIR} && ${COMPOSE_CMD} pull && ${COMPOSE_CMD} up -d${RESET}" echo "" if [[ "$EXTERNAL" == "true" ]]; then warn "DNS A records for ${TRAEFIK_DASHBOARD_HOST} and ${TM_HOST} must point to this server's IP." fi if [[ "$TLS_TYPE" == "none" ]]; then warn "TLS is disabled. Consider enabling it before exposing this publicly." fi echo "" } print_summary_tm_docker() { local scheme="http" local access_url="" if [[ "${USE_TRAEFIK_LABELS:-false}" == "true" ]]; then [[ "${TLS_TYPE:-none}" != "none" ]] && scheme="https" access_url="${scheme}://${TM_HOST}" else access_url="http://$(hostname -I | awk '{print $1}'):${TM_PORT}" fi echo "" echo -e "${GREEN}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" echo -e "${GREEN}${BOLD} Setup complete!${RESET}" echo -e "${GREEN}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" echo "" echo -e " Traefik Manager ${CYAN}${access_url}${RESET}" echo "" if [[ -n "$TEMP_PASSWORD" ]]; then echo -e " ${YELLOW}${BOLD}Temporary password ${TEMP_PASSWORD}${RESET}" else echo -e " ${YELLOW}Temporary password run: docker logs traefik-manager${RESET}" fi echo -e " Install dir ${DIM}${INSTALL_DIR}${RESET}" print_static_config_summary echo "" echo -e " ${DIM}cd ${INSTALL_DIR}${RESET}" echo -e " ${DIM}${COMPOSE_CMD} logs -f traefik-manager${RESET}" echo "" echo -e " ${CYAN}${BOLD}Updating${RESET}" echo -e " ${DIM} cd ${INSTALL_DIR} && ${COMPOSE_CMD} pull && ${COMPOSE_CMD} up -d${RESET}" echo "" } print_summary_native() { echo "" echo -e "${GREEN}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" echo -e "${GREEN}${BOLD} Setup complete!${RESET}" echo -e "${GREEN}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" echo "" echo -e " Traefik Manager ${CYAN}http://$(hostname -I | awk '{print $1}'):${TM_PORT}${RESET}" echo "" if [[ -n "$TEMP_PASSWORD" ]]; then echo -e " ${YELLOW}${BOLD}Temporary password ${TEMP_PASSWORD}${RESET}" else echo -e " ${YELLOW}Temporary password run: sudo journalctl -u traefik-manager${RESET}" fi echo -e " Install dir ${DIM}${NATIVE_INSTALL_DIR}${RESET}" echo -e " Data dir ${DIM}${NATIVE_DATA_DIR}${RESET}" print_static_config_summary echo "" echo -e " ${DIM}sudo systemctl status traefik-manager${RESET}" echo -e " ${DIM}sudo journalctl -u traefik-manager -f${RESET}" echo "" echo -e " ${CYAN}${BOLD}Updating${RESET}" echo -e " ${DIM} cd ${NATIVE_INSTALL_DIR} && git pull${RESET}" echo -e " ${DIM} venv/bin/pip install -q -r requirements.txt gunicorn${RESET}" echo -e " ${DIM} sudo systemctl restart traefik-manager${RESET}" echo "" } # ─── Main ───────────────────────────────────────────────────────────────────── main() { print_banner gather_mode if [[ "$INSTALL_MODE" == "Traefik + Traefik Manager"* ]]; then check_docker gather_full_stack scaffold_full build_traefik_static build_dynamic_config build_compose_full start_docker fetch_password_docker print_summary_full elif [[ "$DEPLOY_METHOD" == "Docker" ]]; then check_docker gather_tm_docker scaffold_tm_docker build_compose_tm start_docker fetch_password_docker print_summary_tm_docker else check_native_deps gather_tm_native install_tm_native fetch_password_native print_summary_native fi } main "$@"