<feed xmlns='http://www.w3.org/2005/Atom'>
<title>doot.git/scripts/deploy, branch master</title>
<subtitle>doot — personal productivity web app
</subtitle>
<id>https://git.terst.org/doot.git/atom?h=master</id>
<link rel='self' href='https://git.terst.org/doot.git/atom?h=master'/>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/'/>
<updated>2026-09-20T00:34:23+00:00</updated>
<entry>
<title>Verify deploys, and add a self-recovering health check</title>
<updated>2026-09-20T00:34:23+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-09-20T00:34:23+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=c58f7519936dc547ee2aec5bfbb1e477356656d9'/>
<id>urn:sha1:c58f7519936dc547ee2aec5bfbb1e477356656d9</id>
<content type='text'>
Deploy used to end with `systemctl status`, which a human skims and a script
ignores: it exits zero whether or not anything works, and says nothing about
the sub-services behind the gateway. A deploy could therefore report success
while /scout/ served 502s — which is exactly what happened, undetected, for
three and a half weeks.

scripts/deploy now:
  * syncs scripts/ to ${SITE_DIR}/app-scripts/ so the timer runs the deployed
    version from a stable path
  * restarts sub-services that are not active. They are separate units and a
    doot deploy does not touch them, so one sitting in `failed` after
    exhausting its restart limit stays down through any number of green
    deploys.
  * calls scripts/verify-deploy, which asserts and exits non-zero

scripts/verify-deploy asserts all five binaries are executable, all four units
are active, all four gateway routes answer without a 5xx, and the running build
stamp matches the commit just built.

scripts/health-check runs the same assertions on a 5-minute timer and recovers
what it can: chmod +x a binary that lost its execute bit, reset-failed and
start a unit that gave up. There is no mail/ntfy/apprise on this host, so
alerting is a greppable HEALTH ALERT line in the journal, a status JSON at
data/health-status.json for the dashboard to surface later, and a non-zero exit
so the unit shows in `systemctl --failed`. OnBootSec=2min because a reboot is
when this failure mode first appeared.

Shared assertions live in scripts/lib/site-checks.sh so verify-deploy and
health-check cannot drift apart.

Two bugs were found and fixed in these checks by testing their failure paths,
both of which would have made them report a broken site as healthy:
  * `$(curl ... || echo 000)` concatenates curl's own "000" transport-error
    output with the echoed one, giving "000000", which matched no case arm and
    was reported ok.
  * check_services sets FAILED_SERVICES, but health-check captured it with
    `$(...)`, so the array was populated in a subshell and discarded — silently
    disabling service recovery entirely. Both the calling convention and the
    subshell semantics are now pinned by tests.

scripts/test-health-checks.sh: 9 cases, mostly negative, against a temp tree.
Recovery additionally verified live on this host by stopping hawaii.service and
by clearing scout's execute bit; both were detected, recovered and re-verified.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
</content>
</entry>
<entry>
<title>Stop deploy stripping the execute bit from site binaries</title>
<updated>2026-09-20T00:25:17+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-09-20T00:23:59+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=de3a2ba7280ab66cc4ec05a80925291b9648706d'/>
<id>urn:sha1:de3a2ba7280ab66cc4ec05a80925291b9648706d</id>
<content type='text'>
The permission step ran

    find ${SITE_DIR} -type f -exec chmod 644 {} +
    chmod +x ${SITE_DIR}/app

over the whole site directory, so it cleared +x on every other executable there
and restored it only for doot's own app. Four binaries were caught:

    services/scout/app      scout.service
    services/hawaii/app     hawaii sub-service
    bin/claudomator         claudomator@doot.terst.org.service
    bin/claude              exec'd by claudomator via --claude-bin

Nothing appears to break at deploy time: a running service keeps executing from
its already-open file. The damage surfaces at the next reboot, when systemd
cannot exec the binary and the unit dies with status=203/EXEC. scout.service
went down that way at the 2026-08-25 reboot and stayed down three and a half
weeks, serving 502s behind /scout/; claudomator has been crash-looping on the
same error, 426k restarts and counting. Neither is alerted on, because doot
itself is healthy and only those paths fail.

Permission fixing is now scoped to the directories this script actually manages
(public, templates, migrations) plus doot's own binary and .env. Every
executable under services/*/app and bin/* is then re-asserted +x, since that is
what an outage looks like when it is wrong and asserting it is free.

Verified against a synthetic site tree: all five binaries keep 755, assets 644.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
</content>
</entry>
<entry>
<title>Guard deploy script to only run on tungsten</title>
<updated>2026-07-17T22:24:08+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-07-17T22:24:08+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=0739ea660e6507d3cfbbe9bc4a0903d75b7db9ef'/>
<id>urn:sha1:0739ea660e6507d3cfbbe9bc4a0903d75b7db9ef</id>
<content type='text'>
Refuses to run if hostname doesn't match the documented deploy host,
preventing an accidental deploy from a sandbox/dev environment.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
</content>
</entry>
<entry>
<title>fix: cd to repo root in deploy script</title>
<updated>2026-03-22T09:41:33+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-03-22T09:41:33+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=7bdc204083c066a89848823bd2dc7fabef4b7f76'/>
<id>urn:sha1:7bdc204083c066a89848823bd2dc7fabef4b7f76</id>
<content type='text'>
Script uses relative paths (npm, go build, rsync) so it must run from
the repo root regardless of the caller's working directory.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat: Phase 1 — remove bug feature and dead code</title>
<updated>2026-03-21T21:24:02+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-03-21T21:24:02+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=764d4d2d07449aec72c87afe941b7c63ea05e08c'/>
<id>urn:sha1:764d4d2d07449aec72c87afe941b7c63ea05e08c</id>
<content type='text'>
- Delete Bug struct, BugToAtom, SourceBug, TypeBug, TypeNote
- Remove bug store methods (SaveBug, GetBugs, ResolveBug, etc.)
- Remove HandleGetBugs, HandleReportBug, bug branches in handlers
- Remove bug routes, bugs.html template, bug UI from index.html
- Remove AddMealToPlanner stub + interface method
- Migration 018: DROP TABLE IF EXISTS bugs

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>chore: migrate deploy + ops scripts from SSH to local execution</title>
<updated>2026-03-16T08:53:44+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-03-16T08:53:44+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=7d627d16cd4bb88e625291dff4cc886c68dbf785'/>
<id>urn:sha1:7d627d16cd4bb88e625291dff4cc886c68dbf785</id>
<content type='text'>
Replace SSH-based deploy.sh with scripts/deploy that runs locally on the
server. Update scripts/bugs, scripts/logs, and scripts/resolve-bug to drop
SSH wrappers. Update CLAUDE.md and DESIGN.md to reflect new local workflow.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
</feed>
