Takes the stack from "bootstrap ran" to a live deployment at https://gitea.jasonmross.dev, fixing everything that broke along the way. Each of these paths was running for the first time, so most of the fixes are bugs that only show up on a first deploy.
This branch is what is currently deployed: pulumi preview against it reports 73 unchanged.
Fixes found by deploying
Pulumi never ran.Pulumi.yaml points the Go runtime at a prebuilt ./gitea-infra that nothing built. make check and the infra Cloud Build step now build it.
make build failed three ways.
$SHORT_SHA is empty for gcloud builds submit, which made the image tag invalid.
cb-image@ could not read the uploaded source in <project>_cloudbuild. There is now a Pulumi-managed <project>-gitea-build-source bucket, readable by cb-image@ only.
The Dockerfiles use COPY --chmod, so BuildKit is required.
Containers resolved no hostnames. aardvark-dns sits on the bridge gateway, so container lookups take the host's input hook. gitea_filter's drop policy overrode netavark's accept. The ruleset is now rendered with the podman subnet and gateway and accepts DNS from the bridge. setup_nftables now validates before installing, where it used to install first.
DNS-01 challenge returned 403. The googleclouddns plugin lists the project's managed zones, which the zone-scoped dns.admin cannot grant. The VM account gets project-level roles/dns.reader; writes stay zone-scoped.
Other changes
ACME email moved to Secret Manager (gitea-acme-email). It is out of this public repo and out of instance metadata. If the secret is unreadable, Caddy still issues certificates, just without a contact address.
Dependabot: grpc v1.83.2, otel v1.45.0 / log v0.21.0 (with the otelslog bridge v0.20.1 that otel line needs), and toolchain go1.26.9 for ten newly disclosed stdlib vulnerabilities. govulncheck reports nothing reachable. Supersedes #1.
Stack config for gitea-496920. The stack is encrypted with the passphrase in Secret Manager, so the infra trigger can open it.
Docs: README setup now covers ADC, exporting the passphrase before stack init, and the ACME secret. The runbook notes that Caddy's certificates live on the boot disk.
Makefile: the ZONE fallback is now -b; us-east1 has no -a zone.
Verified on the live instance
Let's Encrypt certificate via dns-01; /api/healthz passes.
Port 80 redirects with a 308; git SSH answers on 2222; port 22 is closed from outside.
fail2ban jails use nft-prerouting; firewalld is masked.
The data disk was reused across an instance replacement.
Not covered
The infra trigger (cloudbuild/infra.yaml) has never run, because the GitHub App isn't installed yet. githubAppInstallationId is still "0", so there are no triggers and no weekly rebuild job.
Takes the stack from "bootstrap ran" to a live deployment at https://gitea.jasonmross.dev, fixing everything that broke along the way. Each of these paths was running for the first time, so most of the fixes are bugs that only show up on a first deploy.
This branch is what is currently deployed: `pulumi preview` against it reports 73 unchanged.
## Fixes found by deploying
- **Pulumi never ran.** `Pulumi.yaml` points the Go runtime at a prebuilt `./gitea-infra` that nothing built. `make check` and the infra Cloud Build step now build it.
- **`make build` failed three ways.**
- `$SHORT_SHA` is empty for `gcloud builds submit`, which made the image tag invalid.
- `cb-image@` could not read the uploaded source in `<project>_cloudbuild`. There is now a Pulumi-managed `<project>-gitea-build-source` bucket, readable by `cb-image@` only.
- The Dockerfiles use `COPY --chmod`, so BuildKit is required.
- **Containers resolved no hostnames.** aardvark-dns sits on the bridge gateway, so container lookups take the host's input hook. `gitea_filter`'s drop policy overrode netavark's accept. The ruleset is now rendered with the podman subnet and gateway and accepts DNS from the bridge. `setup_nftables` now validates before installing, where it used to install first.
- **DNS-01 challenge returned 403.** The googleclouddns plugin lists the project's managed zones, which the zone-scoped `dns.admin` cannot grant. The VM account gets project-level `roles/dns.reader`; writes stay zone-scoped.
## Other changes
- **ACME email moved to Secret Manager** (`gitea-acme-email`). It is out of this public repo and out of instance metadata. If the secret is unreadable, Caddy still issues certificates, just without a contact address.
- **Dependabot:** grpc v1.83.2, otel v1.45.0 / log v0.21.0 (with the otelslog bridge v0.20.1 that otel line needs), and toolchain go1.26.9 for ten newly disclosed stdlib vulnerabilities. govulncheck reports nothing reachable. Supersedes #1.
- **Stack config** for `gitea-496920`. The stack is encrypted with the passphrase in Secret Manager, so the infra trigger can open it.
- **Docs:** README setup now covers ADC, exporting the passphrase before `stack init`, and the ACME secret. The runbook notes that Caddy's certificates live on the boot disk.
- **Makefile:** the `ZONE` fallback is now `-b`; us-east1 has no `-a` zone.
## Verified on the live instance
- Let's Encrypt certificate via dns-01; `/api/healthz` passes.
- Port 80 redirects with a 308; git SSH answers on 2222; port 22 is closed from outside.
- fail2ban jails use `nft-prerouting`; firewalld is masked.
- The data disk was reused across an instance replacement.
## Not covered
- The infra trigger (`cloudbuild/infra.yaml`) has never run, because the GitHub App isn't installed yet. `githubAppInstallationId` is still `"0"`, so there are no triggers and no weekly rebuild job.
- The WAF is still in `DetectionOnly`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Takes the stack from "bootstrap ran" to a live deployment at https://gitea.jasonmross.dev, fixing everything that broke along the way. Each of these paths was running for the first time, so most of the fixes are bugs that only show up on a first deploy.
This branch is what is currently deployed:
pulumi previewagainst it reports 73 unchanged.Fixes found by deploying
Pulumi.yamlpoints the Go runtime at a prebuilt./gitea-infrathat nothing built.make checkand the infra Cloud Build step now build it.make buildfailed three ways.$SHORT_SHAis empty forgcloud builds submit, which made the image tag invalid.cb-image@could not read the uploaded source in<project>_cloudbuild. There is now a Pulumi-managed<project>-gitea-build-sourcebucket, readable bycb-image@only.COPY --chmod, so BuildKit is required.gitea_filter's drop policy overrode netavark's accept. The ruleset is now rendered with the podman subnet and gateway and accepts DNS from the bridge.setup_nftablesnow validates before installing, where it used to install first.dns.admincannot grant. The VM account gets project-levelroles/dns.reader; writes stay zone-scoped.Other changes
gitea-acme-email). It is out of this public repo and out of instance metadata. If the secret is unreadable, Caddy still issues certificates, just without a contact address.gitea-496920. The stack is encrypted with the passphrase in Secret Manager, so the infra trigger can open it.stack init, and the ACME secret. The runbook notes that Caddy's certificates live on the boot disk.ZONEfallback is now-b; us-east1 has no-azone.Verified on the live instance
/api/healthzpasses.nft-prerouting; firewalld is masked.Not covered
cloudbuild/infra.yaml) has never run, because the GitHub App isn't installed yet.githubAppInstallationIdis still"0", so there are no triggers and no weekly rebuild job.DetectionOnly.🤖 Generated with Claude Code