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
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ GITHUB_VTC_PACKAGE_NAME=vtc
# Passkey / WebAuthn
# Dev: RPID=localhost, ORIGINS=http://localhost:5173
# Prod: RPID=vtafarm.firstperson.dev, ORIGINS=https://vtafarm.firstperson.dev
# Extra browser origins allowed to call the API. The Vite dev server's ports
# are always allowed, so this stays empty in development.
CORS_ALLOWED_ORIGINS=

WEBAUTHN_RP_ID=localhost
WEBAUTHN_RP_ORIGINS=http://localhost:5173
WEBAUTHN_RP_DISPLAY_NAME="VTA Farm"
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ jobs:
KUBECONFIG_PATH: ${{ secrets.KUBECONFIG_PATH }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
INGRESS_HOST: vtafarm-api.firstperson.dev
FRONTEND_HOST: vtafarm.firstperson.dev
CLUSTER_DOMAIN: firstperson.dev
steps:
- uses: actions/checkout@v6

Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ TAG ?= $(shell git rev-parse --short HEAD)
NAMESPACE ?= default
DEPLOY_ENV ?= production
INGRESS_HOST ?=
FRONTEND_HOST ?=
CLUSTER_DOMAIN ?=

# ─── Dev cluster ──────────────────────────────────────────────────────────────
# The database is shared and lives here — see docs/shared-dev-database.md.
Expand Down Expand Up @@ -132,5 +134,7 @@ deploy:
--set image.tag=$(TAG) \
--set app.env=$(DEPLOY_ENV) \
--set ingress.host=$(INGRESS_HOST) \
--set frontendHost=$(FRONTEND_HOST) \
--set cluster.domain=$(CLUSTER_DOMAIN) \
--install --atomic --timeout=10m \
--namespace=$(NAMESPACE)
8 changes: 8 additions & 0 deletions helm/vtafarm-api/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
{{- define "app.labels" -}}
app: {{ .Values.name }}
{{- end }}

{{- define "app.frontendHost" -}}
{{ required "frontendHost is required - WebAuthn and CORS both derive from it" .Values.frontendHost }}
{{- end }}

{{- define "app.frontendOrigin" -}}
https://{{ include "app.frontendHost" . }}
{{- end }}
7 changes: 4 additions & 3 deletions helm/vtafarm-api/templates/vtafarm-api/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ data:
VAULT_K8S_AUTH_MOUNT: {{ .Values.vault.k8sAuthMount | quote }}
VAULT_APPROLE_MOUNT: {{ .Values.vault.appRoleMount | quote }}
VAULT_SKIP_VERIFY: {{ .Values.vault.skipVerify | quote }}
CLUSTER_DOMAIN: {{ .Values.cluster.domain | quote }}
CLUSTER_DOMAIN: {{ required "cluster.domain is required - it is the zone tenant hostnames are created under" .Values.cluster.domain | quote }}
MONITOR_CPU_PCT: {{ .Values.monitor.cpuPct | quote }}
MONITOR_MEM_PCT: {{ .Values.monitor.memPct | quote }}
MONITOR_STORAGE_PCT: {{ .Values.monitor.storagePct | quote }}
Expand All @@ -32,6 +32,7 @@ data:
GITHUB_PACKAGE_NAME: {{ .Values.github.packageName | quote }}
GITHUB_MEDIATOR_PACKAGE_NAME: {{ .Values.github.mediatorPackageName | quote }}
GITHUB_DID_HOSTING_DAEMON_PACKAGE_NAME: {{ .Values.github.didHostingDaemonPackageName | quote }}
WEBAUTHN_RP_ID: {{ .Values.webauthn.rpID | quote }}
WEBAUTHN_RP_ORIGINS: {{ .Values.webauthn.rpOrigins | quote }}
WEBAUTHN_RP_ID: {{ .Values.webauthn.rpID | default (include "app.frontendHost" .) | quote }}
WEBAUTHN_RP_ORIGINS: {{ .Values.webauthn.rpOrigins | default (include "app.frontendOrigin" .) | quote }}
WEBAUTHN_RP_DISPLAY_NAME: {{ .Values.webauthn.rpDisplayName | quote }}
CORS_ALLOWED_ORIGINS: {{ .Values.cors.allowedOrigins | default (include "app.frontendOrigin" .) | quote }}
19 changes: 16 additions & 3 deletions helm/vtafarm-api/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,13 @@ vault:
skipVerify: "true"
existingSecret: "vtafarm-api-vault"

# The frontend's public hostname. WebAuthn and CORS both derive from it, since
# the browser only ever sees the app on one origin.
frontendHost: ""

cluster:
domain: "firstperson.dev"
# Zone the tenant hostnames are created under - vta-alice.<domain>.
domain: ""

# Monitor endpoints (/api/v1/monitor/*) polled by UptimeRobot.
# MONITOR_TOKEN lives in the vtafarm-api-secrets Secret — see k8s/secret.yaml.example.
Expand All @@ -77,9 +82,17 @@ github:
didHostingDaemonPackageName: did-hosting-daemon

webauthn:
rpID: "vtafarm.firstperson.dev"
rpOrigins: "https://vtafarm.firstperson.dev"
rpDisplayName: "VTA Farm"
# Both default to frontendHost. Override rpID only to scope credentials to a
# parent domain: a valid RP ID is the origin's domain or a registrable suffix
# of it, never anything else.
rpID: ""
rpOrigins: ""

# Browser origins allowed to call the API with credentials. Defaults to the
# frontend's; the localhost dev servers are always allowed on top of it.
cors:
allowedOrigins: ""

# No CPU limit: the request gives fair-share under contention; a CFS quota
# would throttle bursts on an idle node. Memory keeps a hard limit.
Expand Down
5 changes: 5 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ type Config struct {
// database, where every API would resume the same rows.
// See docs/shared-dev-database.md.
OrchestratorResume bool
// CORSAllowedOrigins are the deployment's own browser origins - normally
// just the frontend's. The localhost dev servers are allowed unconditionally
// alongside these, see internal/router.
CORSAllowedOrigins []string
// MaxStackConnections caps how many vta_only sessions may connect to one
// shared full_stack. 0 disables the cap.
//
Expand Down Expand Up @@ -175,6 +179,7 @@ func Load() *Config {

ACMEClusterIssuer: getEnv("ACME_CLUSTER_ISSUER", DefaultACMEIssuer),
OrchestratorResume: getEnvBool("ORCHESTRATOR_RESUME", true),
CORSAllowedOrigins: splitComma(getEnv("CORS_ALLOWED_ORIGINS", "")),
MaxStackConnections: getEnvInt("MAX_STACK_CONNECTIONS", 10),
DB: DBConfig{
Host: getEnv("DB_HOST", "localhost"),
Expand Down
11 changes: 10 additions & 1 deletion internal/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,17 @@ func Setup(
) *gin.Engine {
r := gin.Default()

// The Vite dev server's ports stay hardcoded - they are a local debugging
// convenience that costs nothing to carry, and no deployment would think to
// configure them. Everything else is the deployment's own frontend origin,
// which differs per install.
allowOrigins := append(
[]string{"http://localhost:5173", "http://localhost:5174", "http://localhost:5175"},
cfg.CORSAllowedOrigins...,
)

r.Use(cors.New(cors.Config{
AllowOrigins: []string{"https://vtafarm.firstperson.dev", "http://localhost:5173", "http://localhost:5174", "http://localhost:5175"},
AllowOrigins: allowOrigins,
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Authorization", "Content-Type"},
ExposeHeaders: []string{"Content-Length"},
Expand Down
7 changes: 5 additions & 2 deletions scripts/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
set -e

# Required env vars (set by GitHub Actions):
# SSH_PRIVATE_KEY, SERVER_IP, KUBECONFIG_PATH, DOCKER_USERNAME
# SSH_PRIVATE_KEY, SERVER_IP, KUBECONFIG_PATH, DOCKER_USERNAME,
# INGRESS_HOST, FRONTEND_HOST, CLUSTER_DOMAIN
# TAG defaults to the short git SHA via the Makefile (git rev-parse --short HEAD).
# Pre-requisite: Secret "vtafarm-api-postgresql" must exist in the cluster namespace.
# See k8s/postgresql-secret.yaml — apply once manually before first deploy.
Expand Down Expand Up @@ -38,7 +39,9 @@ curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
echo "Deploying vtafarm..."
make deploy \
DOCKER_USERNAME="$DOCKER_USERNAME" \
INGRESS_HOST="$INGRESS_HOST"
INGRESS_HOST="$INGRESS_HOST" \
FRONTEND_HOST="$FRONTEND_HOST" \
CLUSTER_DOMAIN="$CLUSTER_DOMAIN"

# ── Cleanup ───────────────────────────────────────────────────────────────────
eval $(ssh-agent -k)
Expand Down