Rename pretix-discord-middleware references to dispatch - #5
Open
MasonEgger wants to merge 4 commits into
Open
Conversation
Build out a complete infra-as-code stack: one DigitalOcean droplet in sfo3, hardened by ansible, hosting a unified docker compose project that includes the pretix-discord-middleware and pytexas-discord-bot service repos. Replaces the empty repo with everything needed to bootstrap and reproduce the platform from scratch. Layout splits operational concerns across two justfiles: - Repo-root justfile: maintenance (up / down / restart / logs / pull), meant to run on the droplet itself after SSH. Direct docker compose calls, no SSH out. - bootstrap/justfile: laptop-side recipes (apply, plan, destroy, sops, rekey, setup, lint) that drive terraform and ansible against the remote droplet. The split mirrors how often each set runs: bootstrap rarely, maintenance daily. Terraform owns the droplet, cloud firewall, DO project, infra.pytx.org DNS records, and a Spaces bucket that holds its own state via the self-referential bootstrap pattern -- backend.tf.disabled is renamed into place after the first apply creates the bucket. State migrates into Spaces with `terraform init -migrate-state`. Ansible installs Docker + Tailscale, clones the infra and service repos into /srv/pytexas, decrypts sops secrets on the controller and pushes plaintext .env files over SSH at mode 0600, then brings up the unified compose stack. Cloud-init wait + apt lock_timeout cover the first-boot race conditions; community.docker.docker_compose_v2 handles the up. Sops + age handles every secret across five encrypted files: terraform credentials, ansible-time vars (Tailscale host auth key), the master substrate .env (Tailscale container key, hostname, Caddy domain), and per-service .env files for middleware and bot. Mason's age public key is the sole recipient; multi-operator onboarding is documented in secrets/README.md alongside key rotation and lost-laptop recovery. The master compose at the repo root pulls in pretix-discord-middleware and pytexas-discord-bot via `include:`. Substrate services are named pytexas-temporal / pytexas-caddy to avoid colliding with the middleware's profile-gated standalone services. Master Temporal passes --tailscale-state-dir=/var/lib/tailscale so the tsnet identity persists across container restarts (without it the tailnet node name drifts). The temporal-ts-net image and the master Caddyfile reverse-proxy to the middleware's web service at `web:8000` via the project's default network.
The scaffold commit's own late-session restructure (file-by-file copy → git-clone deploy, single justfile → bootstrap/maintenance split, three compose projects → one unified project) left the subsystem READMEs pointing at recipes and paths that no longer exist. Bring them back in sync so the next operator isn't sent down dead ends. - terraform/README.md: drop the dead `tf-*` recipe references, remove duplicated firewall / not-managed sections, delete the scoped-key story (that resource was removed), and correct "doesn't manage DNS" -- terraform now owns the infra.pytx.org records while the zone itself stays out of terraform. - ansible/README.md: services role now clones the infra repo and runs docker compose up; master compose lives at the repo root, not compose/. - secrets/README.md: sops-edit/sops-check/sops-rekey are now `just sops <file>` / `just rekey` (run from bootstrap/), and the terraform.sops.env reference documents the real SPACES_*/AWS_* dual naming instead of the removed capture-creds recipe. Add gitleaks via .pre-commit-config.yaml + .gitleaks.toml as push-time protection against accidental plaintext-secret commits -- the one real risk if the repo goes public. The sops-encrypted files are allowlisted so their ENC[...] / age blocks aren't flagged. Add CLAUDE.md so future Claude Code sessions start with the non-obvious context: the bootstrap-vs-maintenance justfile split, the self-referential state bucket, the sops recipient model, the unified compose project's naming constraints, and the Tailscale state-dir gotcha.
Two latent foot-guns that the running deployment papered over but a clean re-test would have tripped on. The services role's restructure earlier this session dropped the "ensure services_root exists and is owned by the deploy user" task, relying on /srv/pytexas already existing from the pre-restructure runs. On a truly fresh droplet, /srv/pytexas doesn't exist, and the git clone that follows runs as the deploy user -- which can't mkdir inside root-owned /srv. Restore the explicit create-and-chown step (running as root via play-level become) before the clone, so fresh bootstraps work. Add prevent_destroy = true on the Spaces bucket holding terraform state. The previous protection (force_destroy = false) only fires *after* terraform has already torn down the droplet and other resources, leaving a half-wrecked state when the bucket-delete then fails on non-empty. prevent_destroy fails at plan time instead, so a routine teardown can't accidentally take the state bucket with it. To keep a "start the droplet over" workflow available without the full destroy-and-rebootstrap dance, add `just rebuild` (terraform apply -replace=digitalocean_droplet.main -auto-approve). Terraform replaces only the droplet; firewall, DNS, project, and bucket are untouched, and the DNS records auto-update to the new IP. Follow with `just apply` to re-run ansible against the fresh droplet. terraform/README.md "Destroy ordering caveat" becomes "Rebuilding vs. destroying", documenting both paths (rebuild as the daily lever, full teardown as the rare conscious-disarm operation). lessons.md adds the generalized takeaway: when refactoring a role, trace the fresh-start path explicitly -- a step that's redundant on an existing system can be load-bearing on a clean bootstrap.
The service repo is now pytexas/dispatch and hosts the meetup dispatch workflow alongside the pretix pipeline. Updates the compose include, justfile pull recipes, ansible service list, and group_vars service and secrets mappings, and renames the sops env file. The next deploy creates a new dispatch compose project; the old pretix-discord-middleware project on the droplet must be brought down manually so its stale worker stops polling the task queue.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follows the service repo's rename to pytexas/dispatch (which now also hosts the MonthlyMeetupDispatch workflow, see pytexas/dispatch#3).
dispatch.secrets/pretix-discord-middleware.sops.envrenamed tosecrets/dispatch.sops.env(contents unchanged; the creation rule matches by path pattern, no re-encryption needed).Before/During Deploy
secrets/dispatch.sops.env:PYTEXAS_MARKETING_WEBHOOK,PYTEXAS_MEETUP_WEBHOOK, and optionallyPYTEXAS_DISCORD_BOT_TOKEN(needs Manage Events).dispatch/compose project on the droplet, bring the oldpretix-discord-middleware/project down manually, or its stale worker keeps polling the same task queue with old code.