Files
Gitea/infra/pkg/config/config.go
JMR-devandClaude Opus 5.5 973c1bbcd3 Keep the ACME contact email in Secret Manager
gitea:acmeEmail sat in Pulumi.prod.yaml, which this public repository
publishes, and was then copied into instance metadata. It now lives in a
gitea-acme-email secret instead, read by the VM when it renders the
Caddyfile.

- scripts/bootstrap.sh creates the secret empty and prints how to set
  it, the same as github-pat: the address is chosen, not generated.
- Pulumi grants the VM secretAccessor on it and nothing more. It is kept
  out of secrets.Names, whose members also get secretVersionAdder and are
  mapped to `gitea generate secret` by vm/bootstrap.sh.
- The gitea:acmeEmail config key and the acme-email metadata entry are
  gone.
- vm/bootstrap.sh renders the whole `email` directive. If the secret is
  unreadable it renders a comment instead and warns: Caddy still issues
  certificates under an account with no contact address, whereas an
  empty `email` would fail to parse and leave nothing serving TLS. Same
  directive-or-comment pattern as CADDY_PUBLISH_PORTS and CADDY_SYSCTL.

README setup gains the secret step, plus two that were missing: ADC
login (Pulumi's GCS backend and provider do not use the gcloud login),
and exporting PULUMI_CONFIG_PASSPHRASE from Secret Manager before
`stack init`. Without the latter, init prompts for a new passphrase and
the stack is encrypted with a key Cloud Build's infra trigger never sees.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 04:04:18 -05:00

142 lines
3.8 KiB
Go

// Package config turns loose Pulumi stack config into one typed struct, so the
// rest of the program never reaches back into the config bag and every key has
// exactly one spelling.
package config
import (
"fmt"
"strings"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi/config"
)
type Config struct {
Project string
Region string
Zone string
Domain string
DNSZone string
AppName string
PodmanCIDR string
MachineType string
BootDiskGB int
DataDiskGB int
RequireSigninView bool
// WAFMode is the Coraza SecRuleEngine setting. It also decides whether the
// fail2ban jail that acts on WAF verdicts is enabled at all.
WAFMode string
GitHubOwner string
GitHubRepo string
GitHubInstallationID int
GitHubPATSecret string
InfraBuildServiceAccount string
}
func Load(ctx *pulumi.Context) (*Config, error) {
gcp := config.New(ctx, "gcp")
c := config.New(ctx, "gitea")
cfg := &Config{
Project: gcp.Require("project"),
Region: gcp.Get("region"),
Zone: c.Get("zone"),
Domain: c.Require("domain"),
DNSZone: c.Require("dnsZone"),
AppName: c.Get("appName"),
PodmanCIDR: c.Get("podmanSubnet"),
MachineType: c.Get("machineType"),
BootDiskGB: c.GetInt("bootDiskGb"),
DataDiskGB: c.GetInt("dataDiskGb"),
RequireSigninView: c.GetBool("requireSigninView"),
WAFMode: c.Get("wafMode"),
GitHubOwner: c.Get("githubOwner"),
GitHubRepo: c.Get("githubRepo"),
GitHubInstallationID: c.GetInt("githubAppInstallationId"),
GitHubPATSecret: c.Get("githubPatSecret"),
InfraBuildServiceAccount: c.Get("infraBuildServiceAccount"),
}
applyDefaults(cfg)
return cfg, validate(cfg)
}
func applyDefaults(c *Config) {
if c.Region == "" {
c.Region = "us-east1"
}
if c.Zone == "" {
// "-b", not "-a": us-east1 has zones b, c and d and NO -a zone, so the
// obvious default would generate an invalid zone in the very region this
// stack targets. "-b" exists in every US region we would plausibly use.
// Set gitea:zone explicitly if you care which one.
c.Zone = c.Region + "-b"
}
if c.AppName == "" {
c.AppName = "Gitea"
}
if c.PodmanCIDR == "" {
c.PodmanCIDR = "10.89.10.0/24"
}
if c.MachineType == "" {
c.MachineType = "e2-small"
}
if c.BootDiskGB == 0 {
c.BootDiskGB = 20
}
if c.DataDiskGB == 0 {
c.DataDiskGB = 30
}
if c.GitHubPATSecret == "" {
c.GitHubPATSecret = "github-pat"
}
if c.WAFMode == "" {
// Detect first, tune, then block. Starting a WAF in blocking mode in
// front of an app that legitimately carries code and markdown in POST
// bodies is how you get paged on day one.
c.WAFMode = "DetectionOnly"
}
}
func validate(c *Config) error {
if !strings.Contains(c.Domain, ".") {
return fmt.Errorf("gitea:domain %q does not look like a hostname", c.Domain)
}
if !strings.HasPrefix(c.Zone, c.Region) {
return fmt.Errorf("gitea:zone %q is not in gcp:region %q", c.Zone, c.Region)
}
switch c.WAFMode {
case "On", "DetectionOnly", "Off":
default:
return fmt.Errorf("gitea:wafMode %q must be On, DetectionOnly, or Off", c.WAFMode)
}
return nil
}
// GitHubConfigured reports whether enough GitHub settings are present to wire up
// push triggers. Everything else still deploys without them, so a fresh stack can
// come up before the GitHub App install is done.
func (c *Config) GitHubConfigured() bool {
return c.GitHubOwner != "" &&
c.GitHubOwner != "CHANGEME" &&
c.GitHubRepo != "" &&
c.GitHubInstallationID > 0
}
// ARHost is the Artifact Registry endpoint for the configured region.
func (c *Config) ARHost() string { return c.Region + "-docker.pkg.dev" }
// FQDN returns the domain with a trailing dot, as Cloud DNS requires.
func (c *Config) FQDN() string { return c.Domain + "." }