Files
Gitea/infra/pkg/registry/registry.go
JMR-devandClaude Opus 5 c0382d5d31 Gitea on GCE: podman quadlets, Pulumi, Cloud Build
Self-hosted Gitea on a single e2-small AlmaLinux 10 VM in us-east1,
serving gitea.jasonmross.dev.

Runtime is podman quadlets (systemd .container/.network/.volume units).
Both images are built on Debian 13: Gitea from a GPG-verified release
binary, and Caddy from an xcaddy build carrying the Google Cloud DNS
provider (ACME DNS-01) and the Coraza WAF with the OWASP CRS embedded.

Infrastructure is a Pulumi program in Go against a GCS state backend.
Cloud Build handles CI: a push trigger for images, one for infra, and a
weekly scheduled rebuild. Everything Cloud Build touches is 2nd gen.

Notable design decisions, each documented where it lives:

- Quadlets track a floating :prod tag. AutoUpdate=registry compares
  digests for a tag, so a digest-pinned image silently disables
  auto-updates.
- Git transport and LFS bypass the WAF. With the bypass removed, a plain
  git push returns 403 -- packfiles trip CRS reliably.
- gitea:wafMode drives both SecRuleEngine and whether the fail2ban jail
  acting on WAF verdicts exists. Banning on detections that were never
  blocks would turn a tuning false positive into an nftables ban.
- fail2ban bans at the nftables prerouting hook. Published container
  ports are DNAT'd and never traverse INPUT, where the stock actions
  install their rules.
- The DNS zone, backup bucket, and Gitea signing secrets are not
  Pulumi-owned, so pulumi destroy cannot take them with it.
- The podman subnet is pinned because it is what Gitea's
  REVERSE_PROXY_TRUSTED_PROXIES names.

Three update layers: dnf5-automatic for the OS, podman-auto-update with
health-gated rollback for containers, and a weekly image rebuild that
gives the second layer something to pull.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 21:45:33 -05:00

40 lines
1.4 KiB
Go

// Package registry holds the Artifact Registry repository the VM pulls from.
package registry
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/artifactregistry"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"gitea-infra/pkg/config"
)
const RepoID = "gitea"
func New(ctx *pulumi.Context, cfg *config.Config, deps []pulumi.Resource) (*artifactregistry.Repository, error) {
return artifactregistry.NewRepository(ctx, "gitea-repo", &artifactregistry.RepositoryArgs{
RepositoryId: pulumi.String(RepoID),
Location: pulumi.String(cfg.Region),
Format: pulumi.String("DOCKER"),
Description: pulumi.String("Gitea and Caddy images, Debian 13 base"),
CleanupPolicies: artifactregistry.RepositoryCleanupPolicyArray{
// Untagged layers accumulate fast with a weekly rebuild.
&artifactregistry.RepositoryCleanupPolicyArgs{
Id: pulumi.String("delete-untagged"),
Action: pulumi.String("DELETE"),
Condition: &artifactregistry.RepositoryCleanupPolicyConditionArgs{
TagState: pulumi.String("UNTAGGED"),
OlderThan: pulumi.String("604800s"), // 7 days
},
},
// Keep enough tagged versions to roll back a few deploys.
&artifactregistry.RepositoryCleanupPolicyArgs{
Id: pulumi.String("keep-recent"),
Action: pulumi.String("KEEP"),
MostRecentVersions: &artifactregistry.RepositoryCleanupPolicyMostRecentVersionsArgs{
KeepCount: pulumi.Int(20),
},
},
},
}, pulumi.DependsOn(deps))
}