#!/usr/bin/env bash set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" STATIC_ROOT="$REPO_ROOT/dist/client" PUBLIC_ROOT="/var/www/azerothai" NGINX_SITE="/etc/nginx/sites-available/azerothai-tailnet" NGINX_LINK="/etc/nginx/sites-enabled/azerothai-tailnet" LOCAL_PROXY_PORT="${AZEROTHAI_TAILNET_PROXY_PORT:-8788}" APP_PORT="${AZEROTHAI_APP_PORT:-8787}" need(){ command -v "$1" >/dev/null 2>&1 || { echo "Missing required command: $1" >&2; exit 1; }; } if [[ ! -d "$STATIC_ROOT" ]]; then echo "Missing built client assets at $STATIC_ROOT" >&2 echo "Run: cd $REPO_ROOT && pnpm build" >&2 exit 1 fi need tailscale need curl TAILSCALE_IP="$(tailscale ip -4 2>/dev/null | head -n1 || true)" if [[ -z "$TAILSCALE_IP" ]]; then echo "No active Tailscale IPv4 address found. Run: sudo tailscale up --hostname=azerothai-lab" >&2 exit 2 fi echo "==> Checking AzerothAI backend" curl -fsS "http://127.0.0.1:$APP_PORT/api/health" >/dev/null || { echo "AzerothAI is not responding on 127.0.0.1:$APP_PORT" >&2 exit 1 } echo "==> Installing nginx if needed" if ! command -v nginx >/dev/null 2>&1; then sudo apt-get update sudo DEBIAN_FRONTEND=noninteractive apt-get install -y nginx fi echo "==> Publishing built client assets to an nginx-readable directory" sudo rm -rf "$PUBLIC_ROOT" sudo mkdir -p "$PUBLIC_ROOT" sudo cp -a "$STATIC_ROOT"/. "$PUBLIC_ROOT"/ sudo find "$PUBLIC_ROOT" -type d -exec chmod 755 {} + sudo find "$PUBLIC_ROOT" -type f -exec chmod 644 {} + echo "==> Configuring a static-asset-safe reverse proxy" sudo tee "$NGINX_SITE" >/dev/null < Verifying proxied page and repairing asset paths if needed" PAGE="$(curl -fsS -H "Host: $TAILSCALE_IP:$LOCAL_PROXY_PORT" "http://127.0.0.1:$LOCAL_PROXY_PORT/")" map_asset_path(){ local raw="$1" clean target base match srcdir dstdir clean="${raw%%\?*}" [[ "$clean" == /* ]] || return 0 target="$PUBLIC_ROOT$clean" [[ -f "$target" ]] && return 0 base="$(basename "$clean")" match="$(find "$PUBLIC_ROOT" "$REPO_ROOT/dist" "$REPO_ROOT/.next" -type f -name "$base" -print -quit 2>/dev/null || true)" [[ -n "$match" ]] || return 0 dstdir="$(dirname "$target")" sudo mkdir -p "$dstdir" sudo cp -a "$match" "$target" } while IFS= read -r asset; do [[ -n "$asset" ]] || continue map_asset_path "$asset" done < <(printf '%s' "$PAGE" | grep -oE '(href|src)="[^"]+\.(css|js|mjs|svg|png|ico|woff|woff2)(\?[^"]*)?"' | cut -d'"' -f2 | sort -u || true) CSS_PATH="$(printf '%s' "$PAGE" | grep -oE 'href="[^"]+\.css[^"]*"' | head -n1 | cut -d'"' -f2 || true)" if [[ -n "$CSS_PATH" ]]; then case "$CSS_PATH" in http://*|https://*) CSS_URL="$CSS_PATH" ;; /*) CSS_URL="http://127.0.0.1:$LOCAL_PROXY_PORT$CSS_PATH" ;; *) CSS_URL="http://127.0.0.1:$LOCAL_PROXY_PORT/$CSS_PATH" ;; esac if curl -fsS -H "Host: $TAILSCALE_IP:$LOCAL_PROXY_PORT" "$CSS_URL" >/dev/null; then echo "Stylesheet reachable through proxy: $CSS_PATH" else echo "Stylesheet still returned 404: $CSS_PATH" >&2 echo "Built CSS files found:" >&2 find "$PUBLIC_ROOT" -type f -name '*.css' -print | head -20 >&2 || true exit 44 fi else echo "Warning: no stylesheet link found in HTML." >&2 fi echo "==> Verifying all JavaScript bundles through proxy" JS_FAILURES=0 while IFS= read -r script; do [[ -n "$script" ]] || continue case "$script" in http://*|https://*) JS_URL="$script" ;; /*) JS_URL="http://127.0.0.1:$LOCAL_PROXY_PORT$script" ;; *) JS_URL="http://127.0.0.1:$LOCAL_PROXY_PORT/$script" ;; esac CODE="$(curl -sS -o /dev/null -w '%{http_code}' -H "Host: $TAILSCALE_IP:$LOCAL_PROXY_PORT" "$JS_URL" || true)" echo "$CODE $script" if [[ "$CODE" != "200" ]]; then JS_FAILURES=$((JS_FAILURES+1)); fi done < <(printf '%s' "$PAGE" | grep -oE 'src="[^"]+\\.(js|mjs)(\\?[^"]*)?"' | cut -d'"' -f2 | sort -u || true) STATE_CODE="$(curl -sS -o /dev/null -w '%{http_code}' -H "Host: $TAILSCALE_IP:$LOCAL_PROXY_PORT" "http://127.0.0.1:$LOCAL_PROXY_PORT/api/state" || true)" echo "$STATE_CODE /api/state" if [[ "$STATE_CODE" != "200" ]]; then echo "Dashboard API state is not reachable through the proxy." >&2 exit 45 fi if [[ "$JS_FAILURES" -ne 0 ]]; then echo "$JS_FAILURES JavaScript bundle(s) are still unavailable." >&2 exit 46 fi echo "==> Publishing privately with Tailscale Serve" sudo tailscale serve --bg "$LOCAL_PROXY_PORT" DNS_NAME="$(tailscale status --json 2>/dev/null | python3 -c 'import json,sys; d=json.load(sys.stdin); print((d.get("Self",{}).get("DNSName") or "").rstrip("."))' 2>/dev/null || true)" echo echo "============================================================" echo " AzerothAI private dashboard proxy is ready" echo "============================================================" if [[ -n "$DNS_NAME" ]]; then echo "Open on any device in your tailnet:" echo " https://$DNS_NAME/" else echo "Run: tailscale serve status" echo "Then open the HTTPS URL it prints." fi echo echo "Direct raw-IP access to :8787 can still be used for diagnostics," echo "but the Tailscale Serve HTTPS URL is the recommended remote dashboard."