Skip to content

feat: selvage on twill, a new app vm - #95

Merged
jaspermayone merged 2 commits into
mainfrom
feature-selvage-twill
Nov 13, 2026
Merged

jaspermayone merged 2 commits into
mainfrom
feature-selvage-twill

Conversation

@jaspermayone

@jaspermayone jaspermayone commented Nov 12, 2026 •

Copy link
Copy Markdown
Member

Adds Selvage, the Patchwork Slack bot, on a new app VM twill (VMID 363, 10.100.90.63, 3 GB, 20 GB disk). Production only: there is no Selvage staging instance yet.

What changes

  • modules/selvage (exported as nixosModules.selvage): two containers from ghcr.io/patchworklabsorg/selvage:main.
    • selvage-web runs ./bin/rails db:migrate (both databases) in a one-off container, then ./bin/rails server on 127.0.0.1:3009.
    • selvage-worker runs bundle exec good_job start on the image of the running web container, after /up answers.
    • The units do not start while CENTRAL_DATABASE_URL, RESTRICTED_DATABASE_URL, SECRET_KEY_BASE, SLACK_SIGNING_SECRET or SLACK_BOT_TOKEN is empty. An empty signing secret would let anyone sign a Slack request.
    • Optional ghcr login, because the selvage repo is private.
  • hosts/twill: the host, nginx on tailscale0:80 that passes only POST /slack/* and GET /up (every other path is 404), and skein and seam pinned in networking.hosts.
  • hosts/central-db-schema.nix: selvage in restricted.apps, with no shared schemas. hem:5433 also gets the role, with no password.
  • hosts/alastor/edge.nix: route selvage-twill for selvage.patchworklabs.org.
  • bin/new-selvage-secrets: writes restricted-prod-selvage.age, selvage-env.age and selvage-ghcr-token.age. It decrypts the existing central-prod-selvage.age (and quilt-ghcr-token.age) with your age identity, into variables only. It prints no value and writes no empty key.
  • secrets/secrets.nix, ad/org.json (twill, twill-admins), Terraform vms, README.

The secrets are not in this PR. twill builds without them: each secret is declared only when its .age file exists.

flowchart LR
  slack((Slack)) --> traefik["alastor: Traefik"]
  traefik -- "tailnet :80" --> nginx["twill: nginx, /slack/ and /up only"]
  nginx -- "127.0.0.1:3009" --> web[selvage-web]
  worker[selvage-worker] -- "waits for /up" --> web
  web -- "tailnet :5432" --> skein["skein: patchwork_central"]
  web -- "tailnet :5432" --> seam["seam: patchwork_central_restricted"]
  worker --> skein
  worker --> seam
Loading

Order of operations

README.md, "Selvage on twill", "Go live" has every step. In short: tailnet policy (jaspermayone/infra) and DNS (patchworklabsorg/dns) first, then bin/install-host twill, the host key into secrets.nix, bin/new-selvage-secrets, re-encrypt ad-join.age alone (not agenix -r, which restarts Stash and Krater), Slack values, then merge. The merge deploys seam and twill. alastor needs nix flake update patchwork-infra and a deploy in jaspermayone/infra.

Checks

  • nix flake check --no-build --all-systems: passed.
  • checks.aarch64-darwin.ad-org, terraform-fmt, ansible-filters: built and passed.
  • nix eval of twill and seam toplevel drvPath: passed, also with stand-in secret files present.
  • bin/new-selvage-secrets: run against throwaway keys and files. Checked the output, a failed decrypt (no file left behind) and the stop on an existing file. shellcheck: clean.
  • Not run: the full host builds and the Linux VM checks. The local x86_64-linux builder was not reachable. CI builds them.

Related

@jaspermayone
jaspermayone requested a review from a team November 12, 2026 04:08
@jaspermayone
jaspermayone merged commit 2aeaaeb into main Nov 13, 2026
5 checks passed
@jaspermayone
jaspermayone deleted the feature-selvage-twill branch November 13, 2026 01:34
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