Skip to content

Rename pretix-discord-middleware references to dispatch - #5

Open
MasonEgger wants to merge 4 commits into
mainfrom
rename-dispatch
Open

Rename pretix-discord-middleware references to dispatch#5
MasonEgger wants to merge 4 commits into
mainfrom
rename-dispatch

Conversation

@MasonEgger

Copy link
Copy Markdown
Member

Summary

Follows the service repo's rename to pytexas/dispatch (which now also hosts the MonthlyMeetupDispatch workflow, see pytexas/dispatch#3).

  • Compose include, justfile pull recipes, ansible service list, and group_vars mappings all point at dispatch.
  • secrets/pretix-discord-middleware.sops.env renamed to secrets/dispatch.sops.env (contents unchanged; the creation rule matches by path pattern, no re-encryption needed).

Before/During Deploy

  1. Add the meetup dispatch values to secrets/dispatch.sops.env: PYTEXAS_MARKETING_WEBHOOK, PYTEXAS_MEETUP_WEBHOOK, and optionally PYTEXAS_DISCORD_BOT_TOKEN (needs Manage Events).
  2. After the next ansible run creates the new dispatch/ compose project on the droplet, bring the old pretix-discord-middleware/ project down manually, or its stale worker keeps polling the same task queue with old code.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant