#!/usr/bin/env bash
set -euo pipefail

APP_ROOT="${AWP_DOGFOOD_ROOT:-/home/user/services/awp-i1}"
CONFIG_DIR="${AWP_DOGFOOD_CONFIG:-/home/user/.config/awp-dogfood}"
UNIT_DIR="${HOME}/.config/systemd/user"
TAILSCALE_IP="${AWP_DOGFOOD_IP:-100.101.104.41}"
TAILSCALE_DNS_NAME="$(tailscale status --json | jq -r '.Self.DNSName' | sed 's/\.$//')"
if [[ -z "$TAILSCALE_DNS_NAME" || "$TAILSCALE_DNS_NAME" == "null" ]]; then
  echo "failed to resolve the dogfood Tailscale DNS name" >&2
  exit 1
fi
WEB_PORT="${AWP_WEB_PORT:-4173}"
CONTROL_PORT="${AWP_CONTROL_PORT:-8787}"
POSTGRES_PORT="${AWP_POSTGRES_PORT:-5433}"
SELF_REPOSITORY_URL="https://github.com/platform-modules/awp.git"
SELF_REPOSITORY_KEY="${SELF_REPOSITORY_URL#https://github.com/}"
SELF_REPOSITORY_KEY="${SELF_REPOSITORY_KEY%.git}"
MISE_BIN="${AWP_MISE_BIN:-/home/user/.local/bin/mise}"
if [[ ! -x "$MISE_BIN" ]]; then
  echo "mise runtime is not executable at $MISE_BIN" >&2
  exit 1
fi

mkdir -p "$APP_ROOT" "$CONFIG_DIR" "$UNIT_DIR"
umask 077

# Dogfood deploys can be initiated by multiple operator sessions. Serialize the
# entire build/apply/swap/verify transaction so one deploy cannot stop services
# or replace repo.next while another deploy is validating its own source.
DEPLOY_LOCK="$APP_ROOT/.deploy.lock"
exec 9>"$DEPLOY_LOCK"
if ! flock -w "${AWP_DOGFOOD_DEPLOY_LOCK_WAIT_SEC:-900}" 9; then
  echo "another AWP dogfood deployment still holds $DEPLOY_LOCK" >&2
  exit 1
fi

if [[ ! -s "$CONFIG_DIR/operator-password-hash" ]]; then
  echo "AWP operator authentication is not provisioned: create $CONFIG_DIR/operator-password-hash with one Argon2id encoded password hash" >&2
  exit 1
fi
OPERATOR_PASSWORD_HASH="$(tr -d '\r\n' < "$CONFIG_DIR/operator-password-hash")"
if [[ ! "$OPERATOR_PASSWORD_HASH" =~ ^\$argon2id\$ ]]; then
  echo "$CONFIG_DIR/operator-password-hash must contain an Argon2id encoded hash" >&2
  exit 1
fi

REQUESTED_SOURCE_SHA="${AWP_DOGFOOD_SOURCE_SHA:-}"
if [[ ! "$REQUESTED_SOURCE_SHA" =~ ^[0-9a-f]{40}$ ]]; then
  echo "AWP_DOGFOOD_SOURCE_SHA must name the exact 40-hex canonical main commit to deploy" >&2
  exit 1
fi

if [[ ! -d "$APP_ROOT/source-seed/.git" ]]; then
  git clone "$SELF_REPOSITORY_URL" "$APP_ROOT/source-seed"
else
  git -C "$APP_ROOT/source-seed" remote set-url origin "$SELF_REPOSITORY_URL"
fi
git -C "$APP_ROOT/source-seed" fetch --prune origin +refs/heads/main:refs/remotes/origin/main
SOURCE_SHA="$(git -C "$APP_ROOT/source-seed" rev-parse refs/remotes/origin/main)"
SOURCE_TREE="$(git -C "$APP_ROOT/source-seed" rev-parse "$SOURCE_SHA^{tree}")"
if [[ "$SOURCE_SHA" != "$REQUESTED_SOURCE_SHA" ]]; then
  echo "AWP dogfood source race: requested $REQUESTED_SOURCE_SHA but canonical origin/main is $SOURCE_SHA" >&2
  exit 1
fi

rm -rf "$APP_ROOT/repo.next"
mkdir -p "$APP_ROOT/repo.next"
git -C "$APP_ROOT/source-seed" archive --format=tar "$SOURCE_SHA" | tar -xf - -C "$APP_ROOT/repo.next"
(
  cd "$APP_ROOT/repo.next"
  "$MISE_BIN" x node@24 -- pnpm install --frozen-lockfile
  "$MISE_BIN" x node@24 -- pnpm build
)
if [[ ! -x "$APP_ROOT/repo.next/apps/control-plane/node_modules/.bin/codex" ]]; then
  echo "canonical dogfood source did not produce the repo-local Codex CLI" >&2
  exit 1
fi
if [[ ! -f "$APP_ROOT/repo.next/apps/control-plane/dist/configure-i1-runtime.js" ]]; then
  echo "canonical dogfood source did not produce the configuration bootstrap artifact" >&2
  exit 1
fi

cd "$APP_ROOT/repo.next"
AGENT_RUNNER_IMAGE="$(tr -d '\r\n' < infra/dogfood/agent-runner-image.lock)"
MODEL_GATEWAY_IMAGE="$(tr -d '\r\n' < infra/dogfood/model-gateway-image.lock)"
SUBROUTER_IMAGE="$(tr -d '\r\n' < infra/dogfood/subrouter-image.lock)"
FABRO_IMAGE="$(tr -d '\r\n' < infra/dogfood/fabro-image.lock)"
if [[ ! "$AGENT_RUNNER_IMAGE" =~ ^ghcr\.io/platform-modules/awp-agent-runner@sha256:[0-9a-f]{64}$ ]]; then
  echo "infra/dogfood/agent-runner-image.lock must contain the immutable published GHCR digest" >&2
  exit 1
fi
if [[ ! "$MODEL_GATEWAY_IMAGE" =~ ^ghcr\.io/platform-modules/awp-model-gateway@sha256:[0-9a-f]{64}$ ]]; then
  echo "infra/dogfood/model-gateway-image.lock must contain the immutable published GHCR digest" >&2
  exit 1
fi
if [[ ! "$SUBROUTER_IMAGE" =~ ^ghcr\.io/platform-modules/awp-subrouter@sha256:[0-9a-f]{64}$ ]]; then
  echo "infra/dogfood/subrouter-image.lock must contain the immutable published GHCR digest" >&2
  exit 1
fi
if [[ ! "$FABRO_IMAGE" =~ ^ghcr\.io/fabro-sh/fabro@sha256:[0-9a-f]{64}$ ]]; then
  echo "infra/dogfood/fabro-image.lock must contain the verified immutable upstream Fabro digest" >&2
  exit 1
fi

kubectl config view --raw --minify -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' | base64 -d > "$CONFIG_DIR/kube-ca.crt"
KUBE_TOKEN="$(kubectl create token awp-workspace-controller -n awp-system --duration=8760h)"
K3S_VERSION="$(k3s --version | awk 'NR==1 {print $3}')"
if [[ ! "$K3S_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+\+k3s[0-9]+$ ]]; then
  echo "failed to resolve the running K3s server version" >&2
  exit 1
fi
if [[ -z "$KUBE_TOKEN" ]]; then
  echo "failed to mint AWP workspace-controller token" >&2
  exit 1
fi

if [[ ! -f "$CONFIG_DIR/postgres-password" ]]; then openssl rand -hex 24 > "$CONFIG_DIR/postgres-password"; fi
if [[ ! -f "$CONFIG_DIR/callback-secret" ]]; then openssl rand -hex 32 > "$CONFIG_DIR/callback-secret"; fi
if [[ ! -f "$CONFIG_DIR/subrouter-proxy-token" ]]; then openssl rand -hex 32 > "$CONFIG_DIR/subrouter-proxy-token"; fi
if [[ ! -f "$CONFIG_DIR/subrouter-admin-token" ]]; then openssl rand -hex 32 > "$CONFIG_DIR/subrouter-admin-token"; fi
if [[ ! -f "$CONFIG_DIR/subrouter-account-import-token" ]]; then openssl rand -hex 32 > "$CONFIG_DIR/subrouter-account-import-token"; fi
if [[ ! -f "$CONFIG_DIR/model-gateway-signing-secret" ]]; then openssl rand -hex 32 > "$CONFIG_DIR/model-gateway-signing-secret"; fi
# I7a machine enrollment keeps host/join credentials out of PostgreSQL. The
# control-plane receives only CredentialReference paths rooted at CONFIG_DIR.
if ! command -v ssh-keygen >/dev/null 2>&1; then
  echo "ssh-keygen is required for I7a machine enrollment" >&2
  exit 1
fi
if [[ ! -s "$CONFIG_DIR/machine-enrollment-ssh-key" ]]; then
  ssh-keygen -q -t ed25519 -N '' -C awp-machine-enrollment -f "$CONFIG_DIR/machine-enrollment-ssh-key"
elif [[ ! -s "$CONFIG_DIR/machine-enrollment-ssh-key.pub" ]]; then
  ssh-keygen -y -f "$CONFIG_DIR/machine-enrollment-ssh-key" > "$CONFIG_DIR/machine-enrollment-ssh-key.pub"
fi
sudo cat /var/lib/rancher/k3s/server/node-token > "$CONFIG_DIR/k3s-node-token"
touch "$CONFIG_DIR/machine-known-hosts"
POSTGRES_PASSWORD="$(cat "$CONFIG_DIR/postgres-password")"
CALLBACK_SECRET="$(cat "$CONFIG_DIR/callback-secret")"

# Tailscale Serve owns the dogfood HTTPS endpoint on each node Tailscale IP.
# K3s packaged Traefik defaults to ServiceLB/LoadBalancer, which DNATs node :443
# before tailscaled can accept it. AWP does not use Kubernetes Ingress today, so
# keep Traefik cluster-internal and fail the deploy if Helm has not reconciled it.
kubectl apply -f infra/k8s/traefik-clusterip.yaml
for _ in $(seq 1 90); do
  if [[ "$(kubectl -n kube-system get service traefik -o jsonpath='{.spec.type}' 2>/dev/null || true)" == "ClusterIP" ]]; then
    break
  fi
  sleep 1
done
if [[ "$(kubectl -n kube-system get service traefik -o jsonpath='{.spec.type}' 2>/dev/null || true)" != "ClusterIP" ]]; then
  echo "Traefik still owns node LoadBalancer ports; refusing to configure Tailscale Serve" >&2
  exit 1
fi

kubectl apply -f infra/k8s/execution-base.yaml
# Remove obsolete node-local runner readiness metadata from the pre-registry Slice 1 workaround.
kubectl label nodes --all awp.platform/agent-runner- >/dev/null 2>&1 || true
kubectl annotate nodes --all awp.platform/agent-runner-image- >/dev/null 2>&1 || true
if ! kubectl -n platform-registry get secret overdeck-ghcr-pull >/dev/null 2>&1; then
  echo "AWP registry drift: no system GHCR pull credential is available" >&2
  exit 1
fi
for PULL_NAMESPACE in awp-workspaces awp-system; do
  kubectl -n platform-registry get secret overdeck-ghcr-pull -o json \
    | jq --arg namespace "$PULL_NAMESPACE" 'del(.metadata.uid,.metadata.resourceVersion,.metadata.creationTimestamp,.metadata.managedFields,.metadata.ownerReferences) | .metadata.name="awp-ghcr-pull" | .metadata.namespace=$namespace | .metadata.labels={"app.kubernetes.io/managed-by":"awp","awp.dev/purpose":"registry-pull"}' \
    | kubectl apply -f - >/dev/null
done
kubectl -n awp-system create secret generic awp-subrouter-secrets \
  --from-file=proxy_token="$CONFIG_DIR/subrouter-proxy-token" \
  --from-file=admin_token="$CONFIG_DIR/subrouter-admin-token" \
  --from-file=account_import_token="$CONFIG_DIR/subrouter-account-import-token" \
  --dry-run=client -o yaml | kubectl apply -f -
sed "s#__AWP_SUBROUTER_IMAGE__#$SUBROUTER_IMAGE#g" infra/k8s/subrouter-dogfood.yaml | kubectl apply -f -
kubectl -n awp-system rollout status deployment/awp-subrouter --timeout=180s

kubectl -n awp-system create secret generic awp-model-gateway-secrets \
  --from-file=subrouter_proxy_token="$CONFIG_DIR/subrouter-proxy-token" \
  --from-file=signing_secret="$CONFIG_DIR/model-gateway-signing-secret" \
  --dry-run=client -o yaml | kubectl apply -f -
sed "s#__AWP_MODEL_GATEWAY_IMAGE__#$MODEL_GATEWAY_IMAGE#g" infra/k8s/model-gateway-dogfood.yaml | kubectl apply -f -
kubectl -n awp-system rollout status deployment/awp-model-gateway --timeout=180s

if ! kubectl -n awp-system get secret awp-fabro-secrets >/dev/null 2>&1; then
  FABRO_DEV_TOKEN="fabro_dev_$(openssl rand -hex 32)"
  FABRO_SESSION_SECRET="$(openssl rand -hex 32)"
  kubectl -n awp-system create secret generic awp-fabro-secrets \
    --from-literal=FABRO_DEV_TOKEN="$FABRO_DEV_TOKEN" \
    --from-literal=SESSION_SECRET="$FABRO_SESSION_SECRET"
fi
sed "s#__AWP_FABRO_IMAGE__#$FABRO_IMAGE#g" infra/k8s/fabro-dogfood.yaml | kubectl apply -f -
kubectl -n awp-system rollout status deployment/awp-fabro --timeout=180s
kubectl -n awp-system get secret awp-fabro-secrets \
  -o jsonpath='{.data.FABRO_DEV_TOKEN}' | base64 -d > "$CONFIG_DIR/fabro-dev-token"
printf '\n' >> "$CONFIG_DIR/fabro-dev-token"

MODEL_GATEWAY_CLUSTER_IP="$(kubectl -n awp-system get service awp-model-gateway -o jsonpath='{.spec.clusterIP}')"
if [[ -z "$MODEL_GATEWAY_CLUSTER_IP" || "$MODEL_GATEWAY_CLUSTER_IP" == "None" ]]; then
  echo "failed to resolve the K3s Model Gateway service address" >&2
  exit 1
fi
SUBROUTER_CLUSTER_IP="$(kubectl -n awp-system get service awp-subrouter -o jsonpath='{.spec.clusterIP}')"
if [[ -z "$SUBROUTER_CLUSTER_IP" || "$SUBROUTER_CLUSTER_IP" == "None" ]]; then
  echo "failed to resolve the K3s Subrouter service address" >&2
  exit 1
fi
FABRO_CLUSTER_IP="$(kubectl -n awp-system get service awp-fabro -o jsonpath='{.spec.clusterIP}')"
if [[ -z "$FABRO_CLUSTER_IP" || "$FABRO_CLUSTER_IP" == "None" ]]; then
  echo "failed to resolve the K3s Fabro service address" >&2
  exit 1
fi

cat > "$CONFIG_DIR/postgres.env" <<EOF
POSTGRES_DB=awp
POSTGRES_USER=awp
POSTGRES_PASSWORD=$POSTGRES_PASSWORD
EOF

cat > "$CONFIG_DIR/control-plane.env" <<EOF
DATABASE_URL=postgresql://awp:$POSTGRES_PASSWORD@127.0.0.1:$POSTGRES_PORT/awp
PORT=$CONTROL_PORT
AWP_CONTROL_HOST=127.0.0.1
AWP_OPERATOR_PASSWORD_HASH=$OPERATOR_PASSWORD_HASH
NODE_EXTRA_CA_CERTS=$CONFIG_DIR/kube-ca.crt
AWP_KUBERNETES_API_BASE=https://127.0.0.1:6443
AWP_KUBERNETES_TOKEN=$KUBE_TOKEN
AWP_EXECUTION_CALLBACK_SECRET=$CALLBACK_SECRET
AWP_EXECUTION_CALLBACK_BASE_URL=https://$TAILSCALE_DNS_NAME/api
AWP_DEPLOYMENT_MODE=dogfood
AWP_DEPLOYED_SOURCE_SHA=$SOURCE_SHA
AWP_DEPLOYED_SOURCE_TREE=$SOURCE_TREE
AWP_DOGFOOD_NATIVE_ACP_FAIL_AFTER_PROMPT_ON_INITIAL=1
AWP_MODEL_GATEWAY_URL=http://$MODEL_GATEWAY_CLUSTER_IP:32180/v1
AWP_MODEL_GATEWAY_SIGNING_SECRET_FILE=$CONFIG_DIR/model-gateway-signing-secret
AWP_KUBECTL_COMMAND=/usr/local/bin/kubectl
AWP_MACHINE_ENROLLMENT_SECRET_ROOT=$CONFIG_DIR
AWP_CLUSTER_ID=cluster:dogfood
AWP_CLUSTER_NAME=AWP dogfood
AWP_CLUSTER_API_SERVER_URL=https://$TAILSCALE_IP:6443
AWP_CLUSTER_K3S_VERSION=$K3S_VERSION
AWP_CLUSTER_JOIN_SECRET_KEY=k3s-node-token
AWP_MACHINE_SSH_SECRET_KEY=machine-enrollment-ssh-key
AWP_MACHINE_ENROLLMENT_KNOWN_HOSTS_FILE=$CONFIG_DIR/machine-known-hosts
AWP_MACHINE_ENROLLMENT_SSH_PORT=2222
AWP_MACHINE_ENROLLMENT_KUBECTL_COMMAND=/usr/local/bin/k3s
AWP_SUBROUTER_URL=http://$SUBROUTER_CLUSTER_IP:31415
AWP_SUBROUTER_ADMIN_TOKEN_FILE=$CONFIG_DIR/subrouter-admin-token
AWP_SUBROUTER_ACCOUNT_IMPORT_TOKEN_FILE=$CONFIG_DIR/subrouter-account-import-token
AWP_FABRO_URL=http://$FABRO_CLUSTER_IP:32276
AWP_FABRO_TOKEN_FILE=$CONFIG_DIR/fabro-dev-token
AWP_DBOS_EXECUTOR_ID=awp-control-plane-dogfood
AWP_CODEX_COMMAND=$APP_ROOT/repo/apps/control-plane/node_modules/.bin/codex
AWP_SELF_REPOSITORY_URL=$SELF_REPOSITORY_URL
AWP_SELF_PROJECT_BOOTSTRAP=0
AWP_SELF_REQUIRED_CHECKS=CI
AWP_VISION_FILE=$APP_ROOT/repo/docs/VISION.md
AWP_SOURCE_REPOSITORY_PATH=$APP_ROOT/source-seed
EOF

PUBLICATION_TOKEN_FILE="$CONFIG_DIR/github-publication-token"
PUBLICATION_APP_ID_FILE="$CONFIG_DIR/github-app-id"
PUBLICATION_INSTALLATION_ID_FILE="$CONFIG_DIR/github-app-installation-id"
PUBLICATION_APP_PRIVATE_KEY_FILE="$CONFIG_DIR/github-app-private-key"
publication_app_files=0
for publication_file in \
  "$PUBLICATION_APP_ID_FILE" \
  "$PUBLICATION_INSTALLATION_ID_FILE" \
  "$PUBLICATION_APP_PRIVATE_KEY_FILE"; do
  if [[ -s "$publication_file" ]]; then publication_app_files=$((publication_app_files + 1)); fi
done
if [[ -s "$PUBLICATION_TOKEN_FILE" && "$publication_app_files" -gt 0 ]]; then
  echo "configure exactly one GitHub publication authority: token file or GitHub App" >&2
  exit 1
fi
if [[ "$publication_app_files" -ne 0 && "$publication_app_files" -ne 3 ]]; then
  echo "GitHub App publication authority requires github-app-id, github-app-installation-id, and github-app-private-key" >&2
  exit 1
fi
if [[ -s "$PUBLICATION_TOKEN_FILE" ]]; then
  printf 'AWP_GITHUB_PUBLICATION_TOKEN_FILE=%s\n' "$PUBLICATION_TOKEN_FILE" >> "$CONFIG_DIR/control-plane.env"
elif [[ "$publication_app_files" -eq 3 ]]; then
  PUBLICATION_APP_ID="$(tr -d '\r\n' < "$PUBLICATION_APP_ID_FILE")"
  PUBLICATION_INSTALLATION_ID="$(tr -d '\r\n' < "$PUBLICATION_INSTALLATION_ID_FILE")"
  if [[ ! "$PUBLICATION_APP_ID" =~ ^[1-9][0-9]*$ ]]; then
    echo "$PUBLICATION_APP_ID_FILE must contain the numeric GitHub App id" >&2
    exit 1
  fi
  if [[ ! "$PUBLICATION_INSTALLATION_ID" =~ ^[1-9][0-9]*$ ]]; then
    echo "$PUBLICATION_INSTALLATION_ID_FILE must contain the numeric GitHub App installation id" >&2
    exit 1
  fi
  if ! grep -q '^-----BEGIN .*PRIVATE KEY-----$' "$PUBLICATION_APP_PRIVATE_KEY_FILE"; then
    echo "$PUBLICATION_APP_PRIVATE_KEY_FILE does not contain a PEM private key" >&2
    exit 1
  fi
  printf 'AWP_GITHUB_PUBLICATION_APP_ID=%s\n' "$PUBLICATION_APP_ID" >> "$CONFIG_DIR/control-plane.env"
  printf 'AWP_GITHUB_PUBLICATION_INSTALLATION_ID=%s\n' "$PUBLICATION_INSTALLATION_ID" >> "$CONFIG_DIR/control-plane.env"
  printf 'AWP_GITHUB_PUBLICATION_APP_PRIVATE_KEY_FILE=%s\n' "$PUBLICATION_APP_PRIVATE_KEY_FILE" >> "$CONFIG_DIR/control-plane.env"
  printf 'AWP_GITHUB_PUBLICATION_REPOSITORY=%s\n' "$SELF_REPOSITORY_KEY" >> "$CONFIG_DIR/control-plane.env"
fi

cat > "$CONFIG_DIR/web.env" <<EOF
AWP_WEB_HOST=127.0.0.1
AWP_WEB_PORT=$WEB_PORT
AWP_CONTROL_PLANE_URL=http://127.0.0.1:$CONTROL_PORT
AWP_SELF_REPOSITORY_URL=$SELF_REPOSITORY_URL
AWP_ACCEPTANCE_STATE_FILE=$APP_ROOT/repo/GOLIVE.md
EOF

chmod 600 "$CONFIG_DIR"/*

cat > "$UNIT_DIR/awp-dogfood-postgres.service" <<EOF
[Unit]
Description=AWP I1 Dogfood PostgreSQL
After=network-online.target
Wants=network-online.target

[Service]
ExecStartPre=-/usr/bin/podman rm -f awp-dogfood-postgres
ExecStart=/usr/bin/podman run --name awp-dogfood-postgres --rm --pull=missing -p 127.0.0.1:$POSTGRES_PORT:5432 -v awp-dogfood-postgres:/var/lib/postgresql/data --env-file $CONFIG_DIR/postgres.env docker.io/library/postgres:16-alpine
ExecStop=/usr/bin/podman stop -t 20 awp-dogfood-postgres
Restart=always
RestartSec=3
TimeoutStartSec=180

[Install]
WantedBy=default.target
EOF

cat > "$UNIT_DIR/awp-dogfood-control-plane.service" <<EOF
[Unit]
Description=AWP I1 Dogfood Control Plane
After=awp-dogfood-postgres.service network-online.target
Requires=awp-dogfood-postgres.service

[Service]
Type=simple
WorkingDirectory=$APP_ROOT/repo
EnvironmentFile=$CONFIG_DIR/control-plane.env
ExecStartPre=/bin/bash -lc 'for i in {1..90}; do /usr/bin/podman exec awp-dogfood-postgres pg_isready -U awp -d awp >/dev/null 2>&1 && exit 0; sleep 1; done; exit 1'
ExecStart=/home/user/.local/bin/mise x node@24 -- node apps/control-plane/dist/server.js
Restart=always
RestartSec=3
TimeoutStartSec=120

[Install]
WantedBy=default.target
EOF

cat > "$UNIT_DIR/awp-dogfood-web.service" <<EOF
[Unit]
Description=AWP I1 Dogfood Web UI
After=awp-dogfood-control-plane.service network-online.target
Requires=awp-dogfood-control-plane.service

[Service]
Type=simple
WorkingDirectory=$APP_ROOT/repo
EnvironmentFile=$CONFIG_DIR/web.env
ExecStart=/home/user/.local/bin/mise x node@24 -- node apps/web/dist/server.js
Restart=always
RestartSec=3

[Install]
WantedBy=default.target
EOF

systemctl --user daemon-reload
systemctl --user enable awp-dogfood-postgres.service awp-dogfood-control-plane.service awp-dogfood-web.service
# Prevent the existing control plane from racing deployment configuration bootstrap
# while PostgreSQL is restarted. The services are restarted after the import below.
systemctl --user stop awp-dogfood-web.service awp-dogfood-control-plane.service || true
systemctl --user restart awp-dogfood-postgres.service
for _ in $(seq 1 90); do
  if /usr/bin/podman exec awp-dogfood-postgres pg_isready -U awp -d awp >/dev/null 2>&1; then
    break
  fi
  sleep 1
done
if ! /usr/bin/podman exec awp-dogfood-postgres pg_isready -U awp -d awp >/dev/null 2>&1; then
  echo "AWP dogfood PostgreSQL did not become ready for configuration import" >&2
  exit 1
fi
(
  cd "$APP_ROOT/repo.next"
  DATABASE_URL="postgresql://awp:$POSTGRES_PASSWORD@127.0.0.1:$POSTGRES_PORT/awp" \
    AWP_CONFIG_NATIVE_ACP_ENABLED=1 \
    AWP_CONFIG_WORKSPACE_NAMESPACE=awp-workspaces \
    AWP_CONFIG_WORKSPACE_IMAGE="$AGENT_RUNNER_IMAGE" \
    "$MISE_BIN" x node@24 -- node apps/control-plane/dist/configure-i1-runtime.js
)
rm -rf "$APP_ROOT/repo.previous"
if [[ -d "$APP_ROOT/repo" ]]; then mv "$APP_ROOT/repo" "$APP_ROOT/repo.previous"; fi
mv "$APP_ROOT/repo.next" "$APP_ROOT/repo"
cd "$APP_ROOT/repo"
systemctl --user restart awp-dogfood-control-plane.service
systemctl --user restart awp-dogfood-web.service
tailscale serve --bg --yes --https=443 "http://127.0.0.1:$WEB_PORT" >/dev/null

for _ in $(seq 1 90); do
  CONTROL_HEALTH="$(curl -fsS "http://127.0.0.1:$CONTROL_PORT/health" 2>/dev/null || true)"
  if [[ -n "$CONTROL_HEALTH" ]] \
    && jq -e --arg sha "$SOURCE_SHA" --arg tree "$SOURCE_TREE" \
      '.status == "ok" and .source.sha == $sha and .source.tree == $tree and .migrations.pending == 0 and (.migrations.drift | length) == 0' \
      <<<"$CONTROL_HEALTH" >/dev/null \
    && curl -fsS "http://127.0.0.1:$WEB_PORT/" >/dev/null 2>&1 \
    && curl -fsS "https://$TAILSCALE_DNS_NAME/login" >/dev/null 2>&1; then
    RECEIPT_TMP="$CONFIG_DIR/deployment-receipt.json.tmp"
    jq -n \
      --arg sourceSha "$SOURCE_SHA" \
      --arg sourceTree "$SOURCE_TREE" \
      --arg repository "$SELF_REPOSITORY_URL" \
      --arg runnerImage "$AGENT_RUNNER_IMAGE" \
      --arg modelGatewayImage "$MODEL_GATEWAY_IMAGE" \
      --arg subrouterImage "$SUBROUTER_IMAGE" \
      --arg fabroImage "$FABRO_IMAGE" \
      --arg verifiedAt "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
      '{sourceSha:$sourceSha,sourceTree:$sourceTree,repository:$repository,runnerImage:$runnerImage,modelGatewayImage:$modelGatewayImage,subrouterImage:$subrouterImage,fabroImage:$fabroImage,verifiedAt:$verifiedAt}' \
      > "$RECEIPT_TMP"
    chmod 600 "$RECEIPT_TMP"
    mv "$RECEIPT_TMP" "$CONFIG_DIR/deployment-receipt.json"
    printf 'AWP_SOURCE_SHA=%s\n' "$SOURCE_SHA"
    printf 'AWP_SOURCE_TREE=%s\n' "$SOURCE_TREE"
    printf 'AWP_URL=https://%s/\n' "$TAILSCALE_DNS_NAME"
    exit 0
  fi
  sleep 1
done

systemctl --user --no-pager --full status awp-dogfood-postgres.service awp-dogfood-control-plane.service awp-dogfood-web.service >&2 || true
echo "AWP dogfood deployment failed source-identity/health verification for $SOURCE_SHA ($SOURCE_TREE)" >&2
exit 1
