AtAuth
7

Configure Feed

Select the types of activity you want to include in your feed.

docs: add DO k8s handoff doc for new team

Namespace-scoped kubeconfig and RBAC already provisioned on
the cluster. Doc covers connection setup, deployment architecture,
env vars, common operations, CI/CD, and gotchas.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Bryan Brooks (Mar 1, 2026, 11:32 AM -0600) dcfe22db 43d24712

+242
+242
docs/DO-HANDOFF.md
··· 1 + # ATAuth - DigitalOcean Kubernetes Handoff 2 + 3 + This document provides everything you need to connect to and manage the ATAuth deployment on DigitalOcean managed Kubernetes. 4 + 5 + ## Cluster Overview 6 + 7 + | Item | Value | 8 + |------|-------| 9 + | Provider | DigitalOcean Managed Kubernetes | 10 + | Cluster | `do-nyc1-storm-dr-cluster` (nyc1 region) | 11 + | Kubernetes | v1.34.1 | 12 + | Namespace | `atauth` | 13 + | Nodes | 3x worker (4 vCPU, 8GB RAM each) | 14 + | Container registry | `registry.digitalocean.com/ghostmesh-registry` | 15 + 16 + ## Connecting with kubectl 17 + 18 + You will receive a kubeconfig file (`atauth-team-kubeconfig.yaml`) separately. This kubeconfig is scoped to the `atauth` namespace only -- you cannot access other namespaces on the cluster. 19 + 20 + ### Setup 21 + 22 + ```bash 23 + # Option 1: Set KUBECONFIG env var 24 + export KUBECONFIG=/path/to/atauth-team-kubeconfig.yaml 25 + 26 + # Option 2: Merge into your default config 27 + cp ~/.kube/config ~/.kube/config.backup 28 + KUBECONFIG=~/.kube/config:/path/to/atauth-team-kubeconfig.yaml kubectl config view --merge --flatten > /tmp/merged && mv /tmp/merged ~/.kube/config 29 + kubectl config use-context atauth-team@do-nyc1-storm-dr-cluster 30 + ``` 31 + 32 + ### Verify 33 + 34 + ```bash 35 + kubectl get pods -n atauth 36 + # Should show the atauth pod running 37 + 38 + kubectl get deployments -n atauth 39 + # NAME READY UP-TO-DATE AVAILABLE 40 + # atauth 1/1 1 1 41 + ``` 42 + 43 + ## What You Have Access To 44 + 45 + Your ServiceAccount (`atauth-team`) has full CRUD access within the `atauth` namespace: 46 + 47 + - Pods (including logs, exec, port-forward) 48 + - Deployments and ReplicaSets 49 + - Services 50 + - Ingresses 51 + - ConfigMaps 52 + - Secrets 53 + - PersistentVolumeClaims 54 + - Events 55 + 56 + You do **not** have access to cluster-scoped resources or other namespaces. 57 + 58 + ## Current Deployment Architecture 59 + 60 + ``` 61 + ┌──────────────────────┐ 62 + Internet ──> nginx │ Ingress (nginx) │ 63 + ingress ──┤ apricot.workingtitle │ 64 + │ .zip │ 65 + └──────────┬───────────┘ 66 + 67 + ┌──────────▼───────────┐ 68 + │ Service (ClusterIP) │ 69 + │ atauth:3100 │ 70 + └──────────┬───────────┘ 71 + 72 + ┌──────────▼───────────┐ 73 + │ Deployment │ 74 + │ 1 replica, Recreate │ 75 + │ strategy │ 76 + └──────────┬───────────┘ 77 + 78 + ┌──────────▼───────────┐ 79 + │ PVC (1Gi, RWO) │ 80 + │ do-block-storage │ 81 + │ SQLite DB at │ 82 + │ /app/data/gateway.db │ 83 + └──────────────────────┘ 84 + ``` 85 + 86 + ### Key Resources 87 + 88 + | Resource | Name | Notes | 89 + |----------|------|-------| 90 + | Deployment | `atauth` | 1 replica, `Recreate` strategy (required -- RWO PVC) | 91 + | Service | `atauth` | ClusterIP, port 3100 | 92 + | Ingress | `atauth` | Host: `apricot.workingtitle.zip`, TLS via `atauth-tls` secret | 93 + | PVC | `atauth-data` | 1Gi, `do-block-storage`, holds SQLite DB | 94 + | ConfigMap | `atauth-config` | Non-sensitive env vars (CORS, OIDC issuer, WebAuthn, etc.) | 95 + | Secret | `atauth-secrets` | `ATAUTH_ADMIN_TOKEN`, `ATAUTH_OIDC_KEY_SECRET`, `ATAUTH_FORWARD_AUTH_SESSION_SECRET` | 96 + | Secret | `atauth-tls` | TLS cert/key for the ingress | 97 + | Secret | `registry-ghostmesh-registry` | Docker registry pull credentials | 98 + 99 + ### Container Image 100 + 101 + - Registry: `registry.digitalocean.com/ghostmesh-registry/atauth` 102 + - Tags: short git SHA (e.g., `43d2471`) + `latest` 103 + - Platform: `linux/amd64` (required -- DO nodes are amd64) 104 + - Dockerfile: `gateway/Dockerfile` 105 + 106 + ### Environment Variables 107 + 108 + Sourced from `atauth-config` ConfigMap: 109 + 110 + | Env Var | ConfigMap Key | Description | 111 + |---------|---------------|-------------| 112 + | `OAUTH_CLIENT_ID` | `ATAUTH_OAUTH_CLIENT_ID` | AT Protocol OAuth client metadata URL | 113 + | `OAUTH_REDIRECT_URI` | `ATAUTH_OAUTH_REDIRECT_URI` | OAuth callback URL | 114 + | `CORS_ORIGINS` | `ATAUTH_CORS_ORIGINS` | Comma-separated allowed origins | 115 + | `OIDC_ENABLED` | `ATAUTH_OIDC_ENABLED` | Enable OIDC provider | 116 + | `OIDC_ISSUER` | `ATAUTH_OIDC_ISSUER` | OIDC issuer URL | 117 + | `FORWARD_AUTH_ENABLED` | `ATAUTH_FORWARD_AUTH_ENABLED` | Enable nginx forward-auth proxy | 118 + | `FORWARD_AUTH_SESSION_TTL` | `ATAUTH_FORWARD_AUTH_SESSION_TTL` | Session TTL in seconds | 119 + | `FORWARD_AUTH_PROXY_COOKIE_TTL` | `ATAUTH_FORWARD_AUTH_PROXY_COOKIE_TTL` | Proxy cookie TTL in seconds | 120 + | `MFA_ENABLED` | `ATAUTH_MFA_ENABLED` | Enable passkey/WebAuthn MFA | 121 + | `WEBAUTHN_RP_ID` | `ATAUTH_WEBAUTHN_RP_ID` | WebAuthn relying party ID | 122 + | `WEBAUTHN_ORIGIN` | `ATAUTH_WEBAUTHN_ORIGIN` | WebAuthn origin URL | 123 + | `WEBAUTHN_RP_NAME` | `ATAUTH_WEBAUTHN_RP_NAME` | WebAuthn relying party display name | 124 + 125 + Sourced from `atauth-secrets` Secret: 126 + 127 + | Env Var | Secret Key | Description | 128 + |---------|------------|-------------| 129 + | `ADMIN_TOKEN` | `ATAUTH_ADMIN_TOKEN` | Admin API bearer token | 130 + | `OIDC_KEY_SECRET` | `ATAUTH_OIDC_KEY_SECRET` | OIDC signing key secret | 131 + | `FORWARD_AUTH_SESSION_SECRET` | `ATAUTH_FORWARD_AUTH_SESSION_SECRET` | Session encryption secret | 132 + 133 + Hardcoded in deployment spec: 134 + 135 + | Env Var | Value | 136 + |---------|-------| 137 + | `PORT` | `3100` | 138 + | `HOST` | `0.0.0.0` | 139 + | `NODE_ENV` | `production` | 140 + | `DB_PATH` | `/app/data/gateway.db` | 141 + 142 + ## Common Operations 143 + 144 + ### View logs 145 + 146 + ```bash 147 + kubectl logs -n atauth deployment/atauth -f 148 + ``` 149 + 150 + ### Restart the deployment 151 + 152 + ```bash 153 + kubectl rollout restart deployment/atauth -n atauth 154 + kubectl rollout status deployment/atauth -n atauth 155 + ``` 156 + 157 + ### Deploy a new image 158 + 159 + ```bash 160 + kubectl set image deployment/atauth atauth=registry.digitalocean.com/ghostmesh-registry/atauth:<tag> -n atauth 161 + kubectl rollout status deployment/atauth -n atauth --timeout=120s 162 + ``` 163 + 164 + ### Update config 165 + 166 + ```bash 167 + # Edit a ConfigMap value 168 + kubectl patch configmap atauth-config -n atauth --type merge \ 169 + -p '{"data":{"ATAUTH_CORS_ORIGINS":"https://apricot.workingtitle.zip,https://new-app.example.com"}}' 170 + 171 + # Restart to pick up changes (env vars are read at startup) 172 + kubectl rollout restart deployment/atauth -n atauth 173 + ``` 174 + 175 + ### Update secrets 176 + 177 + ```bash 178 + # Update a secret value (must be base64 encoded) 179 + kubectl patch secret atauth-secrets -n atauth --type merge \ 180 + -p '{"data":{"ATAUTH_ADMIN_TOKEN":"'$(echo -n "new-token-value" | base64)'"}}' 181 + 182 + kubectl rollout restart deployment/atauth -n atauth 183 + ``` 184 + 185 + ### Port-forward for local access 186 + 187 + ```bash 188 + kubectl port-forward -n atauth svc/atauth 3100:3100 189 + # ATAuth is now accessible at http://localhost:3100 190 + ``` 191 + 192 + ### Shell into the container 193 + 194 + ```bash 195 + kubectl exec -it -n atauth deployment/atauth -- /bin/sh 196 + ``` 197 + 198 + ### Check health 199 + 200 + ```bash 201 + # Via port-forward 202 + curl http://localhost:3100/health 203 + 204 + # Or check readiness probe status 205 + kubectl describe pod -n atauth -l app=atauth | grep -A5 "Conditions:" 206 + ``` 207 + 208 + ## CI/CD Pipeline 209 + 210 + The existing Gitea Actions workflow (`.gitea/workflows/deploy.yml`) runs on push to `main`: 211 + 212 + 1. **Test job** (`ubuntu-latest` runner): `npm ci`, typecheck, lint, vitest 213 + 2. **Build and deploy job** (`host` runner): Docker build, push to DO registry, `kubectl set image`, rollout status 214 + 215 + The pipeline uses these Gitea org-level secrets: 216 + - `DO_REGISTRY_TOKEN` -- DO container registry API token 217 + - `DO_KUBECONFIG` -- base64-encoded kubeconfig for the `ci-deployer` ServiceAccount 218 + 219 + ## Important Gotchas 220 + 221 + 1. **Recreate strategy is required.** The PVC is RWO (ReadWriteOnce). RollingUpdate will deadlock if the new pod lands on a different node than the old one. 222 + 223 + 2. **Domain must be `workingtitle.zip`, not `arcnode.xyz`.** The `arcnode.xyz` domain hosts a PDS (AT Protocol Personal Data Server). Using it for ATAuth causes same-site cookie/header conflicts. 224 + 225 + 3. **Docker images must target `linux/amd64`.** The DO worker nodes run amd64. ARM images will crash with exec format errors. 226 + 227 + 4. **HMAC token encoding.** ATAuth signs HMAC tokens with `createHmac('sha256', secretString)` using the UTF-8 encoding of the hex secret. Downstream services verifying tokens must match this encoding. 228 + 229 + 5. **`req.accepts('json')` matches `*/*`.** Use `req.is('json')` for Content-Type checks in Express routes. 230 + 231 + 6. **Registry pull secret must exist in namespace.** The `registry-ghostmesh-registry` secret is already present. If you recreate the namespace, you must re-add it or image pulls will fail. 232 + 233 + 7. **SQLite WAL mode.** The DB runs in WAL mode on DO block storage. Single-writer only -- do not scale beyond 1 replica. 234 + 235 + ## Live URLs 236 + 237 + | URL | Description | 238 + |-----|-------------| 239 + | `https://apricot.workingtitle.zip` | ATAuth gateway (public) | 240 + | `https://apricot.workingtitle.zip/.well-known/openid-configuration` | OIDC discovery | 241 + | `https://apricot.workingtitle.zip/admin/login` | Admin dashboard | 242 + | `https://apricot.workingtitle.zip/health` | Health check endpoint |