diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e65f294..fadea85 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -116,6 +116,8 @@ jobs: filters: &deploy-filter | deploy: - 'deploy/**' + - '.env.example' # make deploy-vars reads it + - 'Makefile' - '.golangci.yml' - 'tools/**' # the golangci-lint pin - '.github/workflows/ci.yml' diff --git a/AGENTS.md b/AGENTS.md index 94723b6..bb2c4a4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -85,7 +85,7 @@ The most common defect is a change that works on the path you tested and is miss ## Verifying -- **`make check-fast` before every push (mandatory).** It runs gofmt, `go vet`, golangci-lint, the test-location check and plain `go test` (no `-race`) on only the packages your changes can affect, plus the generators whose inputs changed, the `deploy/` module when it changed, and the migration order when a migration changed (see "Migrations"). Changes mean everything that differs from the merge base with origin/dev (origin/main when there is no dev): commits, staged, unstaged and untracked files, mapped to packages by `scripts/affected.sh` exactly as CI does, so a shared input (go.mod, tooling, lint config, migrations) checks everything. It takes seconds (a docs-only change: under a second; a full run: under 10 s warm), so it fits machines where a full `make ci` is not allowed. Database tests skip unless `TEST_DATABASE_URL` is set; CI still runs `-race`, Postgres and the probes. `make hooks` installs a pre-push hook that runs it from every worktree of the clone (`make hooks-remove` undoes it; `git push --no-verify` skips it once, for emergencies only). Anything that runs git or go from a hook drops git's repository env first (`git rev-parse --local-env-vars`), as the hook and `scripts/check-fast.sh` do. +- **`make check-fast` before every push (mandatory).** It runs gofmt, `go vet`, golangci-lint, the test-location check and plain `go test` (no `-race`) on only the packages your changes can affect, plus the generators whose inputs changed, the `deploy/` module (vet, lint, names and `make deploy-vars`) when it, `.env.example` or the Makefile changed, and the migration order when a migration changed (see "Migrations"). Changes mean everything that differs from the merge base with origin/dev (origin/main when there is no dev): commits, staged, unstaged and untracked files, mapped to packages by `scripts/affected.sh` exactly as CI does, so a shared input (go.mod, tooling, lint config, migrations) checks everything. It takes seconds (a docs-only change: under a second; a full run: under 10 s warm), so it fits machines where a full `make ci` is not allowed. Database tests skip unless `TEST_DATABASE_URL` is set; CI still runs `-race`, Postgres and the probes. `make hooks` installs a pre-push hook that runs it from every worktree of the clone (`make hooks-remove` undoes it; `git push --no-verify` skips it once, for emergencies only). Anything that runs git or go from a hook drops git's repository env first (`git rev-parse --local-env-vars`), as the hook and `scripts/check-fast.sh` do. - **Release smoke** (`make release-smoke`; CI runs it in the `ci` job whenever a build input changes: a Dockerfile, or code a Dockerfile's binary is built from): builds every binary that has a Dockerfile with that Dockerfile's own build-stage `ENV` and `RUN` line (`scripts/dockerfile-builds.sh` reads them, so never copy build flags elsewhere) and fails on a dynamically linked one, runs `server migrate` and `admin migrate` on fresh databases, and boots web and admin with `APP_ENV=production` and only the config production requires until `/healthz` answers 200. It catches what tests cannot: an image that will not start on distroless (a dependency that links dynamically even with `CGO_ENABLED=0`), and a production config that refuses to boot without an optional integration. A new required production setting goes in its env list. - `make lint test` before every commit when you can run the full suite. `make ci` runs exactly what CI runs: build, generated-code diff (sqlc, mocks, templ), gofmt + golangci-lint, the test-location check, `go test -race` against a throwaway Postgres with per-boundary coverage floors, and the probes that prove the layering and test rules still bite (`make lint-layering lint-tests`). - CI on a PR checks only what the change can affect: `scripts/affected.sh` maps changed files to Go packages and adds every package whose code or tests import them (`go list -test -deps`), then builds, lints, tests and coverage-checks just those, and reruns only the generators whose inputs or outputs changed. Changes to go.mod/go.sum, tools/, scripts/, workflows, `.golangci.yml`, the Makefile, migrations or sqlc config run everything, as do pushes to dev and main and the merge queue. `make affected` shows what your branch would run. A new shared input (a file that can change many packages' results without being in their package graph) belongs in the script's full-run list. diff --git a/deploy/platform.yaml b/deploy/platform.yaml index 2a82946..db92c54 100644 --- a/deploy/platform.yaml +++ b/deploy/platform.yaml @@ -127,4 +127,11 @@ unmanaged_variables: - ADMIN_AUDIT_RECONCILE_AFTER - SECRETS_REFRESH_INTERVAL - SECRETS_MAX_STALENESS + # Sign in with Google / Apple and object storage: the non-secret halves + # (their secrets are Doppler's, or setup-owned on env). + - GOOGLE_CLIENT_ID + - APPLE_CLIENT_ID + - APPLE_TEAM_ID + - STORAGE_S3_REGION + - STORAGE_S3_PATH_STYLE - backup/BACKUP_S3_PATH_STYLE diff --git a/deploy/platform/doppler.go b/deploy/platform/doppler.go index 584e14f..f21153a 100644 --- a/deploy/platform/doppler.go +++ b/deploy/platform/doppler.go @@ -69,6 +69,14 @@ var ( {"AUTH_SECRET_PREVIOUS", IfOnRailway}, {"AUTH_SECRET_REVOKED", IfOnRailway}, {"RESEND_API_KEY", RailwayOrResend}, + // Optional features: seeded only when set on Railway. + {"GOOGLE_CLIENT_SECRET", IfOnRailway}, + {"APPLE_PRIVATE_KEY", IfOnRailway}, + {"APPLE_KEY_ID", IfOnRailway}, // not secret, but rotates with the key + {"STORAGE_S3_ENDPOINT", IfOnRailway}, + {"STORAGE_S3_BUCKET", IfOnRailway}, // the scoped keys work for one bucket, so it rotates with them + {"STORAGE_S3_ACCESS_KEY_ID", IfOnRailway}, + {"STORAGE_S3_SECRET_ACCESS_KEY", IfOnRailway}, } // adminDoppler lives in the admin branch config, which the web token // cannot read. diff --git a/deploy/platform/graph.go b/deploy/platform/graph.go index 38093ab..711d5a4 100644 --- a/deploy/platform/graph.go +++ b/deploy/platform/graph.go @@ -205,6 +205,16 @@ func (config Config) Build(env string) (Project, error) { for _, secretVar := range config.SecretVars() { ignore = append(ignore, secretVar.Service+"/"+secretVar.Name) } + // Doppler-owned secrets are setup's, never IaC's: kept while a service + // still reads Railway, removed by setup once it reads Doppler (never by + // an apply that could run before the switch). + for _, service := range config.PublicServices() { + for _, name := range RailwayCopies(service) { + if pattern := service + "/" + name; !slices.Contains(ignore, pattern) { + ignore = append(ignore, pattern) + } + } + } // A pattern scoped to a service this environment does not have is an // error to Railway, not a no-op (docs/railway-go-iac-issues.md #18): // keep only the ones whose service is in the graph. diff --git a/scripts/check-fast.sh b/scripts/check-fast.sh index d7f00ae..34394ed 100755 --- a/scripts/check-fast.sh +++ b/scripts/check-fast.sh @@ -65,7 +65,7 @@ while IFS='=' read -r k v; do done <<<"$aff" deploy=false for f in "${changed[@]}"; do - case $f in deploy/* | .golangci.yml | tools/*) deploy=true ;; esac + case $f in deploy/* | .golangci.yml | tools/* | .env.example | Makefile) deploy=true ;; esac # deploy-vars reads .env.example and the Makefile done [ "$full" = true ] && deploy=true done_ @@ -149,9 +149,9 @@ if [ "${#pkgs[@]}" -gt 0 ]; then fi if [ "$deploy" = true ] && [ -d deploy ]; then - step "deploy/ vet, lint, names" + step "deploy/ vet, lint, names, vars" if ! out=$(mkdir -p .cache && "$GO" build -o .cache/namelint ./scripts/namelint 2>&1 && - cd deploy && { "$GO" vet ./... && "$GO" tool -modfile=../tools/go.mod golangci-lint run -c ../.golangci.yml ./... && ../.cache/namelint ./...; } 2>&1); then + cd deploy && { "$GO" vet ./... && "$GO" tool -modfile=../tools/go.mod golangci-lint run -c ../.golangci.yml ./... && ../.cache/namelint ./... && cd .. && make --no-print-directory deploy-vars >/dev/null; } 2>&1); then done_ echo "$out" | grep -vE '^(ok|\?) ' | sed 's/^/ /' fail deploy diff --git a/scripts/ci-check.sh b/scripts/ci-check.sh index 0a69bb1..3a7ea2a 100755 --- a/scripts/ci-check.sh +++ b/scripts/ci-check.sh @@ -183,6 +183,7 @@ deploy_mod() { (cd .railway && go vet railway.go) golangci-lint run --allow-parallel-runners -c ../.golangci.yml ./... "$tmp/namelint" ./... + cd .. && make --no-print-directory deploy-vars } # node_modules comes from the cache keyed by the lockfile (see the action);