#!/usr/bin/env bash # setup-pve201.sh — one-shot Phase B host setup. Run on pve-201 from the repo root. # # Usage (run on pve-201, in the repo root, on branch chore/tim-tunnel-routing): # BASIC_AUTH_USER=front BASIC_AUTH_PASS= sudo -E bash deployment/setup-pve201.sh # # What it does (idempotent — safe to re-run): # 1. Installs flights-tim-tunnel.service systemd unit and brings it up. # 2. Smoke-tests the tunnel (curl to flights.test.aeroflot.ru via 127.0.0.1:8443). # 3. Installs the new ui-dashboard.gnerim.ru nginx vhost + htpasswd dir. # 4. Renders /etc/nginx/htpasswd/ui-dashboard from BASIC_AUTH_USER/PASS. # 5. Reloads nginx after `nginx -t` passes. # # Each step prints a heading and exits non-zero on failure. Re-running after a # fix continues where it failed (everything is overwrite-safe). set -euo pipefail if [ "$(id -u)" -ne 0 ]; then echo "fatal: run as root (sudo -E bash $0)" >&2 exit 2 fi REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" cd "$REPO_ROOT" step() { printf '\n=== %s ===\n' "$*"; } ok() { printf ' ok: %s\n' "$*"; } fail() { printf ' FAIL: %s\n' "$*" >&2; exit 1; } # ---------- 1. systemd unit ---------- step "1. flights-tim-tunnel.service" UNIT_SRC="$REPO_ROOT/deployment/systemd/flights-tim-tunnel.service" UNIT_DST="/etc/systemd/system/flights-tim-tunnel.service" [ -f "$UNIT_SRC" ] || fail "missing $UNIT_SRC — wrong branch?" if [ -f "$UNIT_DST" ] && cmp -s "$UNIT_SRC" "$UNIT_DST"; then ok "$UNIT_DST already up-to-date" else cp "$UNIT_SRC" "$UNIT_DST" ok "installed $UNIT_DST" fi systemctl daemon-reload systemctl enable --now flights-tim-tunnel.service sleep 2 systemctl is-active flights-tim-tunnel.service >/dev/null \ || { systemctl status flights-tim-tunnel.service --no-pager; fail "tunnel unit not active"; } ok "unit active" # ---------- 2. tunnel smoke test ---------- step "2. tunnel smoke test" ss -ltn | grep -qE '127\.0\.0\.1:8443\s' || fail "no listener on 127.0.0.1:8443" ok "listener present" SWAGGER_RC=$(curl -sS -k --max-time 10 -o /dev/null -w "%{http_code}" \ --resolve flights.test.aeroflot.ru:8443:127.0.0.1 \ https://flights.test.aeroflot.ru:8443/swagger/index.html) case "$SWAGGER_RC" in 401) ok "swagger HTTP 401 (real backend, WAF passed)" ;; 403) ok "swagger HTTP 403 (WAF rate-limit — egress IP is correct, just throttled)" ;; 200) fail "swagger HTTP 200 — likely WAF interstitial (tunnel bypassed)" ;; *) fail "swagger unexpected HTTP $SWAGGER_RC" ;; esac API_RC=$(curl -sS -k --max-time 10 -o /dev/null -w "%{http_code}" \ --resolve flights.test.aeroflot.ru:8443:127.0.0.1 \ https://flights.test.aeroflot.ru:8443/api/health) case "$API_RC" in 200) ok "api/health HTTP 200" ;; 403) ok "api/health HTTP 403 (WAF rate-limit — transient, egress IP confirmed correct)" ;; *) fail "api/health HTTP $API_RC" ;; esac # ---------- 3. nginx vhost ---------- step "3. nginx vhost" VHOST_SRC="$REPO_ROOT/deployment/nginx/ui-dashboard.gnerim.ru.conf" VHOST_DST="/etc/nginx/sites-available/ui-dashboard.gnerim.ru" [ -f "$VHOST_SRC" ] || fail "missing $VHOST_SRC" if [ -f "$VHOST_DST" ] && cmp -s "$VHOST_SRC" "$VHOST_DST"; then ok "$VHOST_DST already up-to-date" else if [ -f "$VHOST_DST" ]; then BAK="${VHOST_DST}.bak.$(date +%Y%m%d-%H%M%S)" cp "$VHOST_DST" "$BAK" ok "backed up old vhost to $BAK" fi cp "$VHOST_SRC" "$VHOST_DST" ok "installed $VHOST_DST" fi ENABLED="/etc/nginx/sites-enabled/ui-dashboard.gnerim.ru" if [ ! -L "$ENABLED" ]; then ln -sf "$VHOST_DST" "$ENABLED" ok "created sites-enabled symlink" else ok "sites-enabled symlink already present" fi mkdir -p /etc/nginx/htpasswd ok "/etc/nginx/htpasswd ensured" # Install proxy_cache zone declaration (must live in http {} context) CACHE_CONF_SRC="$REPO_ROOT/deployment/nginx/conf.d/flights-api-cache.conf" CACHE_CONF_DST="/etc/nginx/conf.d/flights-api-cache.conf" if [ -f "$CACHE_CONF_DST" ] && cmp -s "$CACHE_CONF_SRC" "$CACHE_CONF_DST"; then ok "$CACHE_CONF_DST already up-to-date" else cp "$CACHE_CONF_SRC" "$CACHE_CONF_DST" ok "installed $CACHE_CONF_DST" fi # Cache directory — nginx auto-creates with proper perms on first start, but # we pre-create with the right ownership so reload picks it up cleanly. CACHE_DIR="/var/cache/nginx/flights-api" NGINX_USER="$(awk '/^user / {gsub(";",""); print $2}' /etc/nginx/nginx.conf 2>/dev/null | head -1)" NGINX_USER="${NGINX_USER:-www-data}" mkdir -p "$CACHE_DIR" chown -R "$NGINX_USER":"$NGINX_USER" "$CACHE_DIR" ok "$CACHE_DIR ensured (owner: $NGINX_USER)" # ---------- 4. htpasswd ---------- step "4. htpasswd" : "${BASIC_AUTH_USER:?BASIC_AUTH_USER required (export it before sudo -E)}" : "${BASIC_AUTH_PASS:?BASIC_AUTH_PASS required (export it before sudo -E)}" HASH=$(openssl passwd -apr1 "$BASIC_AUTH_PASS") HTPASSWD_PATH="/etc/nginx/htpasswd/ui-dashboard" echo "${BASIC_AUTH_USER}:${HASH}" > "$HTPASSWD_PATH" chmod 644 "$HTPASSWD_PATH" ok "wrote $HTPASSWD_PATH" # ---------- 5. nginx reload ---------- step "5. nginx -t + reload" nginx -t systemctl reload nginx ok "nginx reloaded" # ---------- summary ---------- step "done" echo "Tunnel: $(systemctl is-active flights-tim-tunnel.service)" echo "Nginx: $(systemctl is-active nginx)" echo echo "Try:" echo " curl -u ${BASIC_AUTH_USER}: -I https://ui-dashboard.gnerim.ru/ # expect 502 until container is deployed (Workflow A)" echo " curl -u ${BASIC_AUTH_USER}: -I https://ui-dashboard.gnerim.ru/api/health # expect 200 from real upstream"