Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ APP_PORT=8080
APP_ENV=development
JWT_SECRET=change-me-in-production

# Database
# Local dev: DB_HOST=localhost
# Docker Compose: DB_HOST is overridden to "db" in docker-compose.yml
# Re-attach interrupted sessions/upgrades at startup. Production keeps the
# default (true). False locally — every dev's API shares one database and would
# resume the same sessions. See docs/shared-dev-database.md.
ORCHESTRATOR_RESUME=false

# Database — the shared PostgreSQL in the dev cluster, via `make forward-db`.
# DB_* and JWT_SECRET must be identical across the team.
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ tmp/
# Go
vendor/

# Local PostgreSQL data (bind mount from docker-compose)
# Leftover data from the removed docker-compose PostgreSQL. Kept ignored only so
# nobody commits it by accident — delete both `data/` and this rule once the
# shared dev database (docs/shared-dev-database.md) has proved itself.
data/

# Air logs
Expand Down
20 changes: 15 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,21 @@ Go REST API backend for managing VTA setup sessions with per-user namespace isol
| Database | PostgreSQL 18 |
| K8s client | `k8s.io/client-go` v0.36 |
| Hot reload | Air (`github.com/air-verse/air`) |
| Container | Docker Compose (dev) + multi-stage Dockerfile (prod) |
| Container | Multi-stage Dockerfile + Helm (prod) |

## Quick Start

The database is **shared**: one PostgreSQL in the dev cluster that every
developer tunnels to. There is no local database. Read
`docs/shared-dev-database.md` once — it changes how migrations, accounts and
`ORCHESTRATOR_RESUME` behave.

```bash
cp .env.example .env
make dev # start DB (Docker) + API with Air hot-reload; migrations run automatically
make enroll # create first admin + print 24h enrollment token (run in a separate terminal)
make forward-db # tunnel the shared dev database (own terminal, keep open)
make forward-vault # tunnel Vault — required for setup work (own terminal)
make dev # API with Air hot-reload; migrations run automatically
make enroll # first admin + 24h enrollment token — ONCE for the whole team
```

API: `http://localhost:8080`
Expand All @@ -33,9 +40,10 @@ See `.env.example` for all options. Key ones:
| --- | --- | --- |
| `APP_PORT` | `8080` | HTTP listen port |
| `APP_ENV` | `development` | Set to `production` to disable `/docs` |
| `DB_HOST` | `localhost` | Overridden to `db` in docker-compose |
| `DB_HOST` | `localhost` | The `make forward-db` tunnel to the shared dev database |
| `DB_NAME` | `vtafarm` | |
| `JWT_SECRET` | `change-me-in-production` | HS256 signing secret |
| `JWT_SECRET` | `change-me-in-production` | HS256 signing secret — identical across the team, since they share one set of accounts |
| `ORCHESTRATOR_RESUME` | `true` | Re-attach interrupted sessions/upgrades at startup. Crash recovery, so production keeps the default; local `.env` sets `false` so that N developers' APIs don't all resume the same rows |
| `KUBECONFIG` | `~/.kube/config` | Leave empty; auto-detected |
| `K8S_NAMESPACE_PREFIX` | `vtafarm-user` | Per-user namespace prefix |
| `DID_HOSTING_DID` / `DID_HOSTING_PRIVATE_KEY` | — | vtafarm-api's **own** keypair (`make gen-keypair`) for the DID-hosting control API, enrolled in a daemon's ACL with `role=admin`. Not anything a daemon issued, so one keypair serves every daemon it is enrolled in. There are deliberately no DID-hosting **URLs** here — see "Shared infrastructure comes from the platform stack" below |
Expand All @@ -55,10 +63,12 @@ See `.env.example` for all options. Key ones:
├── migrations/
│ ├── 000001_init.up.sql
│ └── 000001_init.down.sql
├── k8s/dev-postgres/ # The shared dev database (Secret / PVC / Deployment / Service)
├── docs/
│ ├── vta-setup-design.md # API design for VTA setup automation (Mode A + shared shape)
│ ├── full-stack-setup-design.md # Authoritative design for the full_stack mode (all 4 components)
│ ├── custom-domain-design.md # Custom + platform domains, the dev- prefix (§17 = what has shipped)
│ ├── shared-dev-database.md # One PostgreSQL for the team + what it changes
│ └── vault-transit-upgrade.md # Vault / transit upgrade + restore runbook
└── internal/
├── apidocs/
Expand Down
75 changes: 60 additions & 15 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,16 @@ NAMESPACE ?= default
DEPLOY_ENV ?= production
INGRESS_HOST ?=

.PHONY: build test gen-keypair tidy dev \
# ─── Dev cluster ──────────────────────────────────────────────────────────────
# The database is shared and lives here — see docs/shared-dev-database.md.
DEV_CONTEXT ?= k8s-fpp-dev
DEV_DB ?= vtafarm-dev-postgres
DB_PORT ?= 5432
VAULT_PORT ?= 8200

.PHONY: build test check-pg-image gen-keypair tidy dev \
migrate migrate-down migrate-new enroll enroll-prod \
up down reset \
deploy-db forward-db forward-vault \
image-build image-push \
deploy

Expand All @@ -18,19 +25,42 @@ build:
go build -o bin/api ./main.go

# Same checks CI runs (.github/workflows)
test:
test: check-pg-image
go vet ./...
go test ./...

# Dev and production must run the identical PostgreSQL image — a version that
# only differs locally turns "works on dev" into a guess. Enforced here rather
# than by convention, because the two files are edited months apart.
check-pg-image:
@dev=$$(grep -o 'postgres:[0-9a-z.-]*' k8s/dev-postgres/deployment.yaml); \
prod=$$(grep -o 'postgres:[0-9a-z.-]*' helm/vtafarm-api/values.yaml); \
if [ "$$dev" != "$$prod" ]; then \
echo "PostgreSQL image mismatch:"; \
echo " k8s/dev-postgres/deployment.yaml : $$dev"; \
echo " helm/vtafarm-api/values.yaml : $$prod"; \
exit 1; \
fi; \
echo "PostgreSQL image matches in dev and production: $$dev"

gen-keypair:
go run ./cmd/gen-keypair

tidy:
go mod tidy

# Start DB + API with Air hot-reload
# Start the API with Air hot-reload. The database is the shared one in the dev
# cluster, so `make forward-db` must already be running in another terminal —
# checked here because otherwise the failure is a bare "connection refused".
dev:
$(MAKE) up
@nc -z localhost $(DB_PORT) 2>/dev/null || { \
echo "Nothing listening on localhost:$(DB_PORT)."; \
echo "Start the tunnel to the shared dev database first, in another terminal:"; \
echo ""; \
echo " make forward-db"; \
echo ""; \
exit 1; \
}
air

# ─── Migrations (run locally against DB_HOST=localhost) ───────────────────────
Expand All @@ -54,16 +84,31 @@ enroll:
enroll-prod:
kubectl exec -n $(NAMESPACE) deploy/$(NAME) -- ./enroll

# ─── Docker Compose (DB only) ─────────────────────────────────────────────────
up:
docker compose up -d

down:
docker compose down

reset:
docker compose down -v
docker compose up -d
# ─── Dev cluster ──────────────────────────────────────────────────────────────
# Deploy / update the shared database. Applies only — the PVC is never deleted
# here, so team data survives every redeploy. The context is explicit so this
# can't land in docker-desktop by accident.
deploy-db:
kubectl --context $(DEV_CONTEXT) apply -f k8s/dev-postgres/

# Tunnels. Keep each running in its own terminal while developing. The loops are
# not cosmetic: kubectl port-forward dies on a dropped connection or a pod
# restart and never comes back on its own.
forward-db:
@echo "Forwarding $(DEV_CONTEXT) svc/$(DEV_DB) → localhost:$(DB_PORT) (Ctrl-C to stop)"
@trap 'exit 0' INT; while true; do \
kubectl --context $(DEV_CONTEXT) port-forward svc/$(DEV_DB) $(DB_PORT):5432 || true; \
echo "port-forward dropped — reconnecting in 2s"; \
sleep 2; \
done

forward-vault:
@echo "Forwarding $(DEV_CONTEXT) vault/svc/vault → localhost:$(VAULT_PORT) (Ctrl-C to stop)"
@trap 'exit 0' INT; while true; do \
kubectl --context $(DEV_CONTEXT) port-forward -n vault svc/vault $(VAULT_PORT):8200 || true; \
echo "port-forward dropped — reconnecting in 2s"; \
sleep 2; \
done

# ─── Docker Hub ───────────────────────────────────────────────────────────────
image-build:
Expand Down
43 changes: 27 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,25 @@ Go REST API backend for managing VTA setup sessions with per-user namespace isol
| Database | PostgreSQL 18 |
| K8s client | client-go v0.36 |
| Hot reload | Air |
| Container | Docker Compose (dev) / Helm (prod) |
| Container | Helm (prod) |

---

## Local Development

The API runs directly on your machine while only the database runs in Docker.
This gives the API direct access to your local `~/.kube/config` without any
networking workarounds.
The API runs directly on your machine, against the **shared PostgreSQL in the dev
cluster** — there is no local database. Running the API locally gives it direct
access to your `~/.kube/config` without any networking workarounds.

The database being shared has consequences worth reading once:
[`docs/shared-dev-database.md`](docs/shared-dev-database.md). It already exists —
`make deploy-db` (re)deploys it and is not something you need for daily work.

### Prerequisites

- Go 1.26+
- Docker & Docker Compose
- [Air](https://github.com/air-verse/air) — `go install github.com/air-verse/air@latest`
- `kubectl` configured with access to a cluster (for K8s features)
- `kubectl` with access to the dev cluster (context `k8s-fpp-dev`)

### Setup

Expand All @@ -37,23 +40,30 @@ networking workarounds.
cp .env.example .env
```

2. Start the DB + API (migrations run automatically on startup):
`DB_*` and `JWT_SECRET` must match the rest of the team — one database means
one set of accounts, and a token signed with a different secret is rejected.

2. Open the two tunnels into the dev cluster. Each needs its own terminal and
stays open while you develop — both reconnect on their own, since
`kubectl port-forward` drops on pod restarts.

```bash
make dev
make forward-db # localhost:5432 → svc/vtafarm-dev-postgres
make forward-vault # localhost:8200 → vault/svc/vault
```

The API is now available at `http://localhost:8080`.
API docs: `http://localhost:8080/docs`
Vault is required for VTA setup — the API provisions per-user Vault
policies/roles.

3. Port-forward Vault so the locally-running API can reach it — required for
VTA setup (the API provisions per-user Vault policies/roles). Run in a
separate terminal and keep it open:
3. Start the API (migrations run automatically on startup):

```bash
kubectl port-forward -n vault svc/vault 8200:8200
make dev
```

The API is now available at `http://localhost:8080`.
API docs: `http://localhost:8080/docs`

4. (Optional) Generate a DID hosting keypair (required only if DID hosting is enabled):

```bash
Expand Down Expand Up @@ -90,9 +100,10 @@ Copy `.env.example` and adjust as needed:
| --- | --- | --- |
| `APP_PORT` | `8080` | HTTP listen port |
| `APP_ENV` | `development` | Set to `production` to disable `/docs` |
| `DB_HOST` | `localhost` | Points to the Docker-managed PostgreSQL |
| `DB_HOST` | `localhost` | The `make forward-db` tunnel to the shared dev database |
| `DB_NAME` | `vtafarm` | |
| `JWT_SECRET` | _(required)_ | HS256 signing secret — see below |
| `JWT_SECRET` | _(required)_ | HS256 signing secret — must match the team, see below |
| `ORCHESTRATOR_RESUME` | `true` | Re-attach interrupted sessions at startup. Set `false` locally — see [`docs/shared-dev-database.md`](docs/shared-dev-database.md) |
| `CLUSTER_INGRESS_IP` | _(required)_ | External IP of the cluster's Ingress-NGINX LoadBalancer |
| `CLOUDFLARE_API_TOKEN` | _(optional)_ | Required for VTA setup wizard |
| `CLOUDFLARE_ZONE_ID` | _(optional)_ | Required for VTA setup wizard |
Expand Down
17 changes: 0 additions & 17 deletions docker-compose.yml

This file was deleted.

Loading