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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 7 additions & 0 deletions deploy/platform.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 8 additions & 0 deletions deploy/platform/doppler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
10 changes: 10 additions & 0 deletions deploy/platform/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 3 additions & 3 deletions scripts/check-fast.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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_
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions scripts/ci-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading