Skip to content

Deploy config: Caddy site block, systemd timer and publish script - #73

Merged
JacobStephens2 merged 2 commits into
mainfrom
issue-63
Sep 26, 2026
Merged

JacobStephens2 merged 2 commits into
mainfrom
issue-63

Conversation

@JacobStephens2

Copy link
Copy Markdown
Owner

Summary

Deploy config for thirdshift.app, in site/deploy/ (never served). Installing it on the server stays a human step (#70).

site/deploy/
├── Caddyfile                         # thirdshift.app: file_server + auto TLS, /install.sh 302, /deploy/* 404
│                                     # www.thirdshift.app: 301 to https://thirdshift.app{uri}
├── publish.sh                        # pull-only publish; git + GNU coreutils
├── thirdshift-site-publish.service   # oneshot, User=thirdshift-site, sandboxed
├── thirdshift-site-publish.timer     # every 5 min
└── README.md                         # manual install steps
publish.sh <checkout> <web-root>
  fetch main; check the checkout out at its tip        # always, so the installed units/Caddy block track main
  if current/commit.txt's commit has the same site/ (minus deploy/) as main
    return                                             # re-runs change nothing
  copy site/ to copies/<commit>.<random>/, drop deploy/, write commit.txt
  ln -s + mv -T current.new current                    # one atomic rename
  prune copies except the live one and the one it replaced

The deployed commit is served at /commit.txt, for the Prompts and skills page's label (#68).

Evidence

tests/site_deploy.rs runs against a local bare origin and a temp web root (Linux only, because of mv -T):

  • Before: cargo test --test site_deploy → 7 failed (no site/deploy/)
    After: 7 passed; full cargo test, cargo fmt --check, cargo clippy -- -D warnings green
publish                     → serves index.html + prompts/, commit.txt == main, no deploy/, no README.md
publish; publish            → web root snapshot (paths + mtimes) identical
commit README + site/deploy → web root unchanged; checkout at the new commit
commit site/index.html      → current is a symlink to a new copy; old copy intact
4 publishes                 → 2 copies kept
caddy adapt                 → thirdshift.app route has file_server + 302 to the installer; www has 301
systemd-analyze verify      → clean

The Caddy and systemd checks skip with a message when the tool is missing, so on CI only systemd-analyze runs. Local runs:

$ caddy validate --adapter caddyfile --config site/deploy/Caddyfile
Valid configuration

I also ran the block live on local ports:

GET /             → 200
GET /install.sh   → 302 Location: https://github.com/JacobStephens2/thirdshift/releases/latest/download/thirdshift-installer.sh
GET /deploy/a     → 404
www GET /x?y=1    → 301 Location: https://thirdshift.app/x?y=1

Merge Danger

Door: two-way

Nothing runs until a human installs it (#70). The binary and the crate are untouched: site/ stays out of the package, and the packaging test now asserts it.

Blast Radius: none

Once installed, the timer runs whatever publish.sh is on main as thirdshift-site. It can write only to /var/lib/thirdshift-site and /srv/thirdshift.app.

Unaddressed findings

Standards

  • Shotgun Surgery: the server paths /var/lib/thirdshift-site/checkout and /srv/thirdshift.app repeat across the service, Caddyfile and README. Static config files can't share a variable. Parameterising them would add indirection to files a human copies by hand.

Spec

  • The Caddy/systemd tests skip when the tool is absent, so CI never runs caddy validate. Adding Caddy to CI is out of scope for this issue. I verified locally (see Evidence), and systemd-analyze does run on the Ubuntu runner.
  • Scope creep: respond /deploy/* 404, pruning old copies, SELinux notes in the README, and service hardening. Each is small and serves a stated goal: the deploy directory is never served, the disk doesn't fill, and the service runs unprivileged.
  • No lock against a manual run racing the timer. flock is util-linux, and the spec limits the script to git and coreutils. systemd already stops the timer overlapping itself.
  • mv -T fails if current is a real directory. current is only ever created as a symlink by this script. A fresh install never has a directory there.
  • Prompts and skills page, generated from the source #68 must know to read /commit.txt. The path is documented in the script and the deploy README, which is where Prompts and skills page, generated from the source #68 will look.

Closes #63

🤖 Generated with Claude Code

JacobStephens2 and others added 2 commits September 26, 2026 13:25
… timer

site/deploy/ holds the Caddy site block (automatic TLS, www redirect,
/install.sh 302 to the latest installer), a pull-only publish script that
swaps a copy of site/ into the web root atomically and records the commit,
systemd units that run it every five minutes, and the install README.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A commit that only touched site/deploy/ left the deploy checkout on the
old commit, so reinstalling the units or Caddy block from it would have
installed stale files. The checkout now always follows main; only the copy
and swap is skipped. The web root's copies live in copies/ rather than
releases/, which already means GitHub releases here, and the tests keep
the machine's git config out and check the adapted Caddy routes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

Deploy config: Caddy site block, systemd timer and publish script

1 participant