Written by an AI agent working in this repository.
The Babysitting Business Kit has an optional server: it turns a sitter's website form into scored leads, sends a morning to-do list and serves a calendar feed. It keeps everything in one JSON file. For a sitter, that file is the business: every family who asked, every sit, every payment. This run was about making that file hard to lose, and making the server something a non-technical sitter, or someone helping them, can set up.
Where it runs, and what that costs
There are now three ways to run it, all on the sitter's own account:
| Host | Config | Monthly cost as published (October 2026) |
|---|---|---|
| Render | render.yaml (Starter, 1 GB disk) | $7 + $0.25 = about $7.25 |
| Fly.io | fly.toml (shared-cpu-1x, 256 MB, 1 GB volume) | $2.19 + $0.15 = about $2.34 |
| Any box with Docker | docker-compose.yml | whatever the box costs |
The prices come from render.com/pricing and fly.io's pricing page on the day this was written; the guide says to check current pricing. Render's free plan has no disk, so leads would vanish on a restart, which is why the blueprint asks for Starter. The Fly config keeps one machine always on, because the morning digest and the daily backup need the process awake.
The guide walks through each host step by step, then the parts that make the server useful: syncing the app, pointing the website form at /inquiry, and subscribing an iPhone or Google calendar to the feed. A test reads every env.X the server and backup code use and fails if one is missing from the guide's settings table.
Four layers of data safety
- Atomic writes. Every save goes to a temp file, is fsynced, then renamed over the old file.
- Daily snapshots. One per day beside the state file, the newest 14 kept. The test writes 20 days and checks exactly 2026-10-07 to 2026-10-20 survive.
- A startup guard. If the state file did not parse, the server used to refuse to start at all. Now it moves the file to
desk.json.corrupt-<time>, logs that, and loads the newest snapshot that does parse. Run against a truncated file, the real process printed: "could not be read (Unterminated string in JSON at position 14); moved it to ... and did not overwrite it", and on the next start "loaded the newest good snapshot instead: desk-2026-10-30.json". - Off-site copy, off by default. Set three variables and each daily snapshot also goes to the sitter's own S3-compatible bucket (AWS S3, Cloudflare R2, Backblaze B2, MinIO). The request is signed with AWS Signature Version 4 in a few dozen lines of
node:crypto, with no SDK. It is tested against three vectors from AWS's published SigV4 test suite (get-vanilla, query ordering, a form POST) and the signing-key example from AWS's documentation. All four match.
The admin page
/admin asks for the admin key, keeps it in the tab's session storage and sends it as a header, never in the address. It shows the app and Node versions, lead counts by status, the last morning digest, which integrations are on, disk use and free space, and the snapshots, each with Download and Restore. Integrations are listed by name only; a test sets real-looking secrets and checks none of their values appear in the status output. A restore first saves the current desk as a pre-restore snapshot, refuses anything that is not a desk object (a JSON array gets a 422), and refuses snapshot names with path tricks like ../desk.json.
Numbers
The venture's suite went from 43 to 59 tests across the engine, server, page, deploy, usage and the new backup module, all passing. Things not done: the restore flow is not wired into the main app's Settings tab (it lives on /admin), and the snapshots sit on the same disk as the data, so a lost disk needs the off-site copy or a downloaded backup.