#!/usr/bin/env bash
# ═══════════════════════════════════════════════════════════════════════
# ByteLyst Single-VM Kubernetes Setup (k3s)
# ═══════════════════════════════════════════════════════════════════════
# Deploys the ByteLyst ecosystem on Kubernetes using k3s.
#
# PREREQUISITE: Run ../docker/setup.sh phases 1-5 first to install
#   system deps, Gitea, clone repos, build + publish @bytelyst/* packages.
#   This script handles k8s-specific deployment only.
#
# What this script does:
#   Phase 1: Pre-flight checks (verify docker phases ran)
#   Phase 2: Install k3s (lightweight K8s)
#   Phase 3: Build Docker images (reused by k3s via --docker runtime)
#   Phase 4: Generate K8s secrets (JWT, etc.)
#   Phase 5: Apply K8s manifests (namespaces → config → infra → platform → products)
#   Phase 6: Health check
#
# Usage:
#   sudo ./setup-k8s.sh                  # Full install
#   sudo ./setup-k8s.sh --resume         # Auto-resume from last completed phase
#   sudo ./setup-k8s.sh --phase=N        # Run single phase (1-6)
#   sudo ./setup-k8s.sh --status         # Show phase status
#   sudo ./setup-k8s.sh --reset          # Clear markers, start fresh
#   sudo ./setup-k8s.sh --teardown       # Remove all K8s resources
# ═══════════════════════════════════════════════════════════════════════
set -euo pipefail

# ── Configuration ────────────────────────────────────────────────────
INSTALL_DIR="/opt/bytelyst"
K8S_DIR="$(cd "$(dirname "$0")" && pwd)"
STATE_DIR="${INSTALL_DIR}/.setup-state-k8s"
COMPOSE_FILE="docker-compose.ecosystem.yml"

# Well-known emulator keys (public, safe to embed)
COSMOS_EMULATOR_KEY="C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="

# Image prefix for k3s imports
IMAGE_PREFIX="bytelyst"

# Services that need Docker image builds (not pre-built infra)
BUILD_SERVICES=(
  platform-service extraction-service mcp-server
  admin-web tracker-web
  peakpulse-backend chronomind-backend jarvisjr-backend nomgap-backend
  mindlyst-backend lysnrai-backend notelett-backend flowmonk-backend
  actiontrail-backend localmemgpt-backend
  lysnrai-dashboard chronomind-web jarvisjr-web flowmonk-web notelett-web
  mindlyst-web nomgap-web actiontrail-web localmemgpt-web
)

# Namespaces that need the shared ConfigMap + Secrets
CONFIG_NAMESPACES=(bytelyst-platform bytelyst-dashboards bytelyst-products)

# ── Helpers ──────────────────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
log()  { echo -e "${BLUE}[k8s]${NC} $*"; }
ok()   { echo -e "${GREEN}  ✓ $*${NC}"; }
warn() { echo -e "${YELLOW}  ⚠ $*${NC}"; }
fail() { echo -e "${RED}  ✗ $*${NC}" >&2; exit 1; }

mark_phase_done()   { mkdir -p "$STATE_DIR"; date -Iseconds > "${STATE_DIR}/phase${1}.done"; }
is_phase_done()     { [ -f "${STATE_DIR}/phase${1}.done" ]; }
reset_markers()     { rm -f "${STATE_DIR}"/phase*.done 2>/dev/null; log "Phase markers cleared."; }

last_completed_phase() {
  local last=0
  for i in 1 2 3 4 5 6; do
    if is_phase_done "$i"; then last=$i; else break; fi
  done
  echo "$last"
}

# ═══════════════════════════════════════════════════════════════════════
# PHASE 1: Pre-flight Checks
# ═══════════════════════════════════════════════════════════════════════
phase1_preflight() {
  log "Phase 1: Pre-flight checks..."

  # Verify docker setup phases 1-5 ran
  local docker_state="${INSTALL_DIR}/.setup-state"
  for p in 1 2 3 4 5; do
    if [ ! -f "${docker_state}/phase${p}.done" ]; then
      fail "Docker phase ${p} not completed. Run first: sudo ../docker/setup.sh --resume"
    fi
  done
  ok "Docker phases 1-5 completed"

  # Verify repos exist
  local plat_dir="${INSTALL_DIR}/learning_ai_common_plat"
  [ -d "$plat_dir" ] || fail "Missing ${plat_dir}. Run docker/setup.sh phases 1-5 first."
  ok "Repos cloned"

  # Verify Docker is available (needed for image builds)
  command -v docker &>/dev/null || fail "Docker not found. Run docker/setup.sh phase 1."
  ok "Docker available"

  # Pre-flight: disk + memory
  local disk_gb mem_gb
  disk_gb=$(df -BG / | awk 'NR==2 {gsub(/G/,"",$4); print $4}')
  mem_gb=$(free -g | awk '/^Mem:/ {print $2}')
  log "  Disk: ${disk_gb} GB free, RAM: ${mem_gb} GB total"
  [ "${disk_gb:-0}" -ge 20 ] || warn "Low disk (${disk_gb} GB). Recommend 40+ GB free."
  [ "${mem_gb:-0}" -ge 16 ] || warn "Low RAM (${mem_gb} GB). Recommend 32 GB."

  ok "Phase 1 complete. Pre-flight passed."
}

# ═══════════════════════════════════════════════════════════════════════
# PHASE 2: Install k3s
# ═══════════════════════════════════════════════════════════════════════
phase2_k3s() {
  log "Phase 2: Installing k3s..."

  if command -v kubectl &>/dev/null && kubectl cluster-info &>/dev/null 2>&1; then
    ok "k3s already installed and running"
  else
    # Install k3s with:
    #   --docker: use Docker as container runtime (reuse existing images)
    #   --disable=traefik: we manage our own ingress
    #   --write-kubeconfig-mode=644: allow non-root kubectl
    #   --kube-apiserver-arg: extend NodePort range to use our service ports
    log "  Installing k3s (Docker runtime, extended NodePort range)..."
    curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="\
      --docker \
      --disable=traefik \
      --write-kubeconfig-mode=644 \
      --kube-apiserver-arg=service-node-port-range=1024-32767" \
      sh -

    # Wait for k3s to be ready
    log "  Waiting for k3s node to be Ready..."
    local retries=30
    while [ $retries -gt 0 ]; do
      if kubectl get nodes 2>/dev/null | grep -q " Ready"; then
        break
      fi
      sleep 5
      retries=$((retries - 1))
    done

    if [ $retries -eq 0 ]; then
      fail "k3s node did not become Ready within 150 seconds."
    fi
  fi

  # Verify
  kubectl get nodes
  ok "Phase 2 complete. k3s is running."
}

# ═══════════════════════════════════════════════════════════════════════
# PHASE 3: Build Docker Images + Tag for k3s
# ═══════════════════════════════════════════════════════════════════════
phase3_images() {
  log "Phase 3: Building Docker images for k3s..."

  local plat_dir="${INSTALL_DIR}/learning_ai_common_plat"

  # Restore Gitea token for Docker builds
  if [ -z "${GITEA_NPM_TOKEN:-}" ] && [ -f "${INSTALL_DIR}/.gitea_token" ]; then
    GITEA_NPM_TOKEN=$(cat "${INSTALL_DIR}/.gitea_token")
    export GITEA_NPM_TOKEN
  fi
  [ -n "${GITEA_NPM_TOKEN:-}" ] || fail "GITEA_NPM_TOKEN not set. Run docker/setup.sh phase 2."

  # Detect Docker host IP for Gitea access during builds
  local docker_host_ip
  docker_host_ip=$(ip -4 addr show docker0 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}' || echo "172.17.0.1")
  export GITEA_NPM_HOST="${docker_host_ip}"
  export DOCKER_BUILDKIT=1
  export COMPOSE_DOCKER_CLI_BUILD=1

  # Stop Ollama to free RAM during builds
  if systemctl is-active --quiet ollama 2>/dev/null; then
    log "  Stopping Ollama to free RAM for builds..."
    systemctl stop ollama 2>/dev/null || true
  fi

  # Build images using docker compose (same Dockerfiles as docker approach)
  local env_file="${plat_dir}/.env.ecosystem"
  if [ ! -f "$env_file" ]; then
    # Generate minimal env for compose config parsing
    log "  Generating temporary .env.ecosystem for compose builds..."
    cat > "$env_file" <<-ENV
		COSMOS_KEY=${COSMOS_EMULATOR_KEY}
		JWT_SECRET=build-time-placeholder
		COSMOS_ENDPOINT=http://localhost:8081
		COSMOS_DATABASE=bytelyst
		DB_PROVIDER=cosmos
		ENV
  fi

  local build_ok=0 build_fail=0
  local total=${#BUILD_SERVICES[@]}
  local idx=0
  mkdir -p "${STATE_DIR}/builds"

  for svc in "${BUILD_SERVICES[@]}"; do
    idx=$((idx + 1))
    log "  [${idx}/${total}] Building ${svc}..."

    local log_file="${STATE_DIR}/builds/${svc}.log"
    if docker compose -f "${plat_dir}/${COMPOSE_FILE}" --env-file "$env_file" \
        build "$svc" > "$log_file" 2>&1; then

      # Tag for k3s: compose builds as <project>-<svc>, we tag as bytelyst/<svc>
      local compose_image
      compose_image=$(docker compose -f "${plat_dir}/${COMPOSE_FILE}" --env-file "$env_file" \
        images --format json 2>/dev/null | jq -r ".[] | select(.Service==\"${svc}\") | .Repository + \":\" + .Tag" 2>/dev/null || true)

      if [ -n "$compose_image" ] && [ "$compose_image" != ":" ]; then
        docker tag "$compose_image" "${IMAGE_PREFIX}/${svc}:latest" 2>/dev/null || true
      fi

      build_ok=$((build_ok + 1))
      ok "  [${idx}/${total}] ${svc} — built"
    else
      build_fail=$((build_fail + 1))
      warn "  [${idx}/${total}] ${svc} — FAILED (see ${log_file})"
    fi
  done

  # Restart Ollama
  if command -v ollama &>/dev/null; then
    log "  Restarting Ollama..."
    systemctl start ollama 2>/dev/null || nohup ollama serve > /var/log/ollama.log 2>&1 &
  fi

  # Prune build cache
  docker builder prune -f --filter "until=1h" > /dev/null 2>&1 || true

  log "  Built: ${build_ok}, Failed: ${build_fail}"
  [ "$build_fail" -eq 0 ] || warn "  ${build_fail} images failed to build. Fix and re-run: sudo ./setup-k8s.sh --phase=3"

  if [ "$build_fail" -gt 0 ]; then
    fail "Phase 3 incomplete: ${build_fail}/${total} images failed. Fix and re-run: sudo ./setup-k8s.sh --phase=3"
  fi

  ok "Phase 3 complete. ${build_ok}/${total} images ready."
}

# ═══════════════════════════════════════════════════════════════════════
# PHASE 4: Generate K8s Secrets + Patch ConfigMap
# ═══════════════════════════════════════════════════════════════════════
phase4_config() {
  log "Phase 4: Generating K8s configuration..."

  # Apply namespaces first
  log "  Creating namespaces..."
  kubectl apply -f "${K8S_DIR}/namespaces.yaml"

  # Generate random JWT secret
  local jwt_secret
  jwt_secret=$(openssl rand -base64 32)

  # Patch the secrets template with real JWT secret
  local secrets_file="${K8S_DIR}/config/secrets.yaml"
  log "  Generating secrets (JWT_SECRET)..."

  # Well-known Azurite emulator key (public, safe)
  local azurite_key="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="
  local azurite_conn="DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=${azurite_key};BlobEndpoint=http://azurite.bytelyst-infra.svc:10000/devstoreaccount1;"

  # Apply secrets to all namespaces that need them
  for ns in "${CONFIG_NAMESPACES[@]}"; do
    kubectl create secret generic bytelyst-secrets \
      --namespace="$ns" \
      --from-literal=JWT_SECRET="$jwt_secret" \
      --from-literal=AZURE_BLOB_ACCOUNT_KEY="$azurite_key" \
      --from-literal=AZURE_BLOB_CONNECTION_STRING="$azurite_conn" \
      --dry-run=client -o yaml | kubectl apply -f -
  done
  ok "Secrets applied to ${#CONFIG_NAMESPACES[@]} namespaces"

  # Detect node IP for Ollama external service
  local node_ip
  node_ip=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}' 2>/dev/null || echo "172.17.0.1")
  log "  Node IP for Ollama: ${node_ip}"

  # Patch Ollama endpoint with actual node IP
  sed "s/NODE_IP_PLACEHOLDER/${node_ip}/" "${K8S_DIR}/infra/ollama-external.yaml" \
    | kubectl apply -f -
  ok "Ollama external service configured (${node_ip}:11434)"

  # Apply ConfigMap to all namespaces that need it
  for ns in "${CONFIG_NAMESPACES[@]}"; do
    sed "s/namespace: bytelyst-platform/namespace: ${ns}/" "${K8S_DIR}/config/configmap.yaml" \
      | kubectl apply -f -
  done
  ok "ConfigMap applied to ${#CONFIG_NAMESPACES[@]} namespaces"

  ok "Phase 4 complete."
}

# ═══════════════════════════════════════════════════════════════════════
# PHASE 5: Apply K8s Manifests (ordered deployment)
# ═══════════════════════════════════════════════════════════════════════
phase5_deploy() {
  log "Phase 5: Deploying services to k3s..."

  # ── 5a: Infrastructure ──────────────────────────────────────────────
  log "  Deploying infrastructure..."
  for f in "${K8S_DIR}"/infra/*.yaml; do
    local name
    name=$(basename "$f" .yaml)
    [ "$name" = "ollama-external" ] && continue  # Already applied in phase 4
    log "    Applying ${name}..."
    kubectl apply -f "$f"
  done
  ok "Infrastructure deployed"

  # Wait for Cosmos emulator (everything depends on it)
  log "  Waiting for Cosmos emulator to be Ready (this can take 2-3 minutes)..."
  kubectl wait --for=condition=Ready pod -l app=cosmos-emulator \
    -n bytelyst-infra --timeout=300s 2>/dev/null || warn "Cosmos emulator not ready yet"

  # ── 5b: Platform services ───────────────────────────────────────────
  log "  Deploying platform services..."
  kubectl apply -f "${K8S_DIR}/platform/"
  ok "Platform services deployed"

  # Wait for platform-service
  log "  Waiting for platform-service..."
  kubectl wait --for=condition=Ready pod -l app=platform-service \
    -n bytelyst-platform --timeout=120s 2>/dev/null || warn "platform-service not ready yet"

  # ── 5c: Dashboards ─────────────────────────────────────────────────
  log "  Deploying dashboards..."
  kubectl apply -f "${K8S_DIR}/dashboards/"
  ok "Dashboards deployed"

  # ── 5d: Product services ────────────────────────────────────────────
  log "  Deploying product backends + web apps..."
  kubectl apply -f "${K8S_DIR}/products/"
  ok "Product services deployed"

  # ── Summary ─────────────────────────────────────────────────────────
  log "  Waiting 30s for services to stabilize..."
  sleep 30

  echo ""
  log "  Pod status across all namespaces:"
  kubectl get pods -A -l app.kubernetes.io/part-of=bytelyst 2>/dev/null || \
    kubectl get pods -A 2>/dev/null | grep -E "bytelyst-"
  echo ""

  ok "Phase 5 complete. All manifests applied."
}

# ═══════════════════════════════════════════════════════════════════════
# PHASE 6: Health Check
# ═══════════════════════════════════════════════════════════════════════
phase6_verify() {
  log "Phase 6: Verifying service health..."

  # Create reusable health check script
  cat > "${INSTALL_DIR}/check-health-k8s.sh" <<'HEALTH'
#!/usr/bin/env bash
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'

check() {
  local name="$1" url="$2"
  if curl -sf --connect-timeout 3 "$url" > /dev/null 2>&1; then
    echo -e "${GREEN}  ✓ ${name}${NC}  ${url}"
  else
    echo -e "${RED}  ✗ ${name}${NC}  ${url}"
  fi
}

echo ""
echo "═══ K8s Cluster ═══"
echo -n "  Nodes: "; kubectl get nodes --no-headers 2>/dev/null | wc -l | tr -d ' '
echo -n "  Pods:  "; kubectl get pods -A --no-headers 2>/dev/null | wc -l | tr -d ' '
echo -n "  Ready: "; kubectl get pods -A --no-headers 2>/dev/null | grep -c "Running" || echo 0

echo ""
echo "═══ Infrastructure ═══"
check "Gitea (npm)"          "http://localhost:3300/api/v1/version"
check "Ollama (LLM)"         "http://localhost:11434/api/version"
check "Cosmos Explorer"      "http://localhost:1234"
check "Azurite (Blob)"       "http://localhost:10000/devstoreaccount1?comp=list"
check "Mailpit"              "http://localhost:8025"
check "Loki"                 "http://localhost:3100/ready"
check "Grafana"              "http://localhost:3000/api/health"

echo ""
echo "═══ Platform Services ═══"
check "platform-service"     "http://localhost:4003/health"
check "extraction-service"   "http://localhost:4005/health"
check "mcp-server"           "http://localhost:4007/health"

echo ""
echo "═══ Dashboards ═══"
check "admin-web"            "http://localhost:3001"
check "tracker-web"          "http://localhost:3003"

echo ""
echo "═══ Product Backends ═══"
check "peakpulse"            "http://localhost:4010/health"
check "chronomind"           "http://localhost:4011/health"
check "jarvisjr"             "http://localhost:4012/health"
check "nomgap"               "http://localhost:4013/health"
check "mindlyst"             "http://localhost:4014/health"
check "lysnrai"              "http://localhost:4015/health"
check "notelett"             "http://localhost:4016/health"
check "flowmonk"             "http://localhost:4017/health"
check "actiontrail"          "http://localhost:4018/health"
check "localmemgpt"          "http://localhost:4019/health"

echo ""
echo "═══ Product Web Apps ═══"
check "lysnrai-dashboard"    "http://localhost:3002"
check "chronomind-web"       "http://localhost:3030"
check "jarvisjr-web"         "http://localhost:3035"
check "flowmonk-web"         "http://localhost:3040"
check "notelett-web"         "http://localhost:3045"
check "mindlyst-web"         "http://localhost:3050"
check "nomgap-web"           "http://localhost:3055"
check "actiontrail-web"      "http://localhost:3060"
check "localmemgpt-web"      "http://localhost:3070"

echo ""
echo "═══ K8s Quick Commands ═══"
echo "  kubectl get pods -A                    # All pods"
echo "  kubectl top pods -A                    # Resource usage"
echo "  kubectl logs deploy/<name> -n <ns> -f  # Stream logs"
echo "  kubectl rollout restart deploy/<name>  # Rolling restart"
echo ""
HEALTH
  chmod +x "${INSTALL_DIR}/check-health-k8s.sh"

  # Run it
  bash "${INSTALL_DIR}/check-health-k8s.sh"

  ok "Phase 6 complete."
}

# ═══════════════════════════════════════════════════════════════════════
# TEARDOWN: Remove all K8s resources
# ═══════════════════════════════════════════════════════════════════════
teardown() {
  log "Tearing down all ByteLyst K8s resources..."
  for ns in bytelyst-products bytelyst-dashboards bytelyst-platform bytelyst-infra; do
    log "  Deleting namespace: ${ns}"
    kubectl delete namespace "$ns" --ignore-not-found=true 2>/dev/null || true
  done
  reset_markers
  ok "Teardown complete. k3s itself is still running (uninstall with: /usr/local/bin/k3s-uninstall.sh)"
}

# ═══════════════════════════════════════════════════════════════════════
# MAIN
# ═══════════════════════════════════════════════════════════════════════
run_phase() {
  local n="$1"
  case "$n" in
    1) phase1_preflight ;;
    2) phase2_k3s ;;
    3) phase3_images ;;
    4) phase4_config ;;
    5) phase5_deploy ;;
    6) phase6_verify ;;
    *) fail "Unknown phase: $n" ;;
  esac
  mark_phase_done "$n"
}

usage() {
  echo "Usage: sudo ./setup-k8s.sh [OPTIONS]"
  echo ""
  echo "Options:"
  echo "  --resume            Auto-resume from last completed phase"
  echo "  --phase=N           Run ONLY phase N (1-6)"
  echo "  --reset             Clear phase markers"
  echo "  --status            Show completed phases"
  echo "  --teardown          Remove all K8s resources"
  echo "  -h, --help          Show this help"
  echo ""
  echo "Phases:"
  echo "  1  Pre-flight checks (verify docker phases ran)"
  echo "  2  Install k3s"
  echo "  3  Build Docker images + tag for k3s"
  echo "  4  Generate K8s config (Secrets + ConfigMap)"
  echo "  5  Apply manifests (infra → platform → dashboards → products)"
  echo "  6  Health check"
  echo ""
  echo "PREREQUISITE: Run ../docker/setup.sh (phases 1-5) first."
}

main() {
  local mode="full" start_phase=1 only_phase=0

  for arg in "$@"; do
    case "$arg" in
      --resume)
        mode="resume" ;;
      --phase=*)
        mode="single"
        only_phase="${arg#*=}" ;;
      --reset)
        reset_markers; exit 0 ;;
      --status)
        echo "K8s phase completion:"
        for i in 1 2 3 4 5 6; do
          if is_phase_done "$i"; then
            echo "  Phase $i: DONE ($(cat "${STATE_DIR}/phase${i}.done"))"
          else
            echo "  Phase $i: pending"
          fi
        done
        exit 0 ;;
      --teardown)
        teardown; exit 0 ;;
      -h|--help)
        usage; exit 0 ;;
      *)
        warn "Unknown option: $arg"; usage; exit 1 ;;
    esac
  done

  mkdir -p "$INSTALL_DIR"
  exec > >(tee -a "${INSTALL_DIR}/setup-k8s.log") 2>&1

  echo ""
  echo "╔═══════════════════════════════════════════════════════════════╗"
  echo "║       ByteLyst K8s Deployment (k3s on single VM)             ║"
  echo "║       30 services · 4 namespaces · production-grade          ║"
  echo "╚═══════════════════════════════════════════════════════════════╝"
  echo ""
  log "Log file: ${INSTALL_DIR}/setup-k8s.log"

  [ "$(id -u)" -eq 0 ] || fail "This script must be run as root (sudo)."

  local start_time
  start_time=$(date +%s)

  if [ "$mode" = "single" ]; then
    log "Running ONLY phase ${only_phase}..."
    run_phase "$only_phase"
    ok "Phase ${only_phase} complete."
    exit 0
  fi

  if [ "$mode" = "resume" ]; then
    local last
    last=$(last_completed_phase)
    if [ "$last" -ge 6 ]; then
      ok "All phases completed. Use --reset to start over."
      exit 0
    fi
    start_phase=$((last + 1))
    log "Resuming from phase ${start_phase}."
  fi

  for n in 1 2 3 4 5 6; do
    [ "$n" -ge "$start_phase" ] || continue
    run_phase "$n"
  done

  local elapsed=$(( $(date +%s) - start_time ))
  local minutes=$(( elapsed / 60 ))
  local seconds=$(( elapsed % 60 ))

  echo ""
  echo "╔═══════════════════════════════════════════════════════════════╗"
  echo "║  K8s deployment complete in ${minutes}m ${seconds}s                          ║"
  echo "║                                                               ║"
  echo "║  Health check:  /opt/bytelyst/check-health-k8s.sh            ║"
  echo "║  All pods:      kubectl get pods -A                          ║"
  echo "║  Pod resources:  kubectl top pods -A                         ║"
  echo "║  Stream logs:   kubectl logs deploy/<name> -n <ns> -f        ║"
  echo "║  Scale up:      kubectl scale deploy/<name> --replicas=2     ║"
  echo "║  Teardown:      sudo ./setup-k8s.sh --teardown               ║"
  echo "║                                                               ║"
  echo "║  Grafana:       http://localhost:3000  (admin / bytelyst)    ║"
  echo "║  Mailpit:       http://localhost:8025                        ║"
  echo "║  Gitea:         http://localhost:3300                        ║"
  echo "║  Ollama:        http://localhost:11434                       ║"
  echo "╚═══════════════════════════════════════════════════════════════╝"
  echo ""
}

main "$@"
