<feed xmlns='http://www.w3.org/2005/Atom'>
<title>doot.git/scripts, 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:36:17+00:00</updated>
<entry>
<title>Fold the DB-ping health check in; delete the watchdog that never ran</title>
<updated>2026-09-20T00:36:17+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-09-20T00:36:17+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=36a82d934366cc2ddbd92ee3885b1c547eeb8244'/>
<id>urn:sha1:36a82d934366cc2ddbd92ee3885b1c547eeb8244</id>
<content type='text'>
scripts/health-watchdog.sh was written after the 2026-08-04 incident, where a
liveness stub stayed green for three days while every database-touching request
path was wedged. Its header says "Meant to run from cron every few minutes". It
was never scheduled — not in any unit, not in any crontab — so it has been dead
code since August, offering coverage it did not provide.

Its one real idea is worth keeping: /health pings the database and returns 503
when that fails, which the gateway route checks cannot see, because `/` answers
303 to the login page without going near the database.

check_app_health now does that poll (two attempts, five seconds apart, so a
single blip is not an outage), verify-deploy asserts it, and health-check
recovers from it by restarting the main service — the action the old watchdog
would have taken. The dead script is removed rather than left to imply cover.

Tests now 11, including a stub server returning 503 to prove a listening-but-
wedged app is caught.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
</content>
</entry>
<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>Fix production wedge: propagate context to Google Calendar API calls</title>
<updated>2026-08-04T20:10:25+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-08-04T20:10:25+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=0a410243dea33f204764000be81814e541dcae48'/>
<id>urn:sha1:0a410243dea33f204764000be81814e541dcae48</id>
<content type='text'>
Three .Do() calls in google_calendar.go accepted a ctx parameter but
never chained .Context(ctx) into the actual SDK call, so the existing
global 60s request timeout never reached the blocking network call.
One hung Google Calendar request wedged every DB/session-touching
request path in production for three days (2026-08-01 through
2026-08-04), undetected because /health unconditionally returned 200
throughout.

- Wire .Context(ctx) into GetUpcomingEvents, GetEventsByDateRange, and
  GetCalendarList.
- Bound aggregateData's four external fetches with a per-fetch
  sub-context as defense-in-depth (only effective if the callee
  actually honors ctx -- documented as such, not oversold).
- Make GetUpcomingEvents/GetEventsByDateRange fetch calendars
  concurrently instead of sequentially: a review of this fix caught
  that a shared per-fetch deadline over a sequential loop would starve
  calendars past the first under any real latency, silently caching
  partial results as complete. Concurrent fetches give every calendar
  an equal shot at the same deadline instead.
- /health now does a real PingContext DB check instead of a static
  "ok" (Handler.PingDB, tested for both healthy and closed-DB cases).
- Add internal/api/context_audit_test.go: an AST-based structural
  guard that fails any future .Do() call in google_*.go missing
  .Context(...) anywhere in its chain, so this class of bug can't
  silently recur. Verified by deliberately reintroducing the original
  bug against a backup and confirming the guard catches it.
- Add scripts/health-watchdog.sh: cron job restarts the service if
  /health fails twice in a row, five minutes apart.

go test ./... -race is green. Deployed and live-verified.
</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>chore: unify and centralize agent configuration in .agent/</title>
<updated>2026-03-22T23:45:19+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-03-22T23:45:19+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=8abc63efdbc0bb96cd6c9aa99d6e9166e0bcabae'/>
<id>urn:sha1:8abc63efdbc0bb96cd6c9aa99d6e9166e0bcabae</id>
<content type='text'>
</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>
<entry>
<title>Add build version footer, deploy ldflags, template test helper, and logs script</title>
<updated>2026-02-08T01:42:07+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-02-08T01:42:07+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=d793e16f336189d38a9d43310713dd13e3b7c438'/>
<id>urn:sha1:d793e16f336189d38a9d43310713dd13e3b7c438</id>
<content type='text'>
- Display build commit hash in unobtrusive footer overlay
- Inject buildCommit/buildTime via ldflags in deploy.sh
- Add assertTemplateContains test helper, refactor existing template tests
- Add scripts/logs for fetching production journalctl via SSH

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