Files
recurring-checklist/server
Waters ChurchandClaude Opus 5 469db5bc60 Bring the checklist app under version control
The project has lived only as a folder on the Mac Studio and, briefly, as a
transfer tarball. This is the first commit: the Express server and its web
front end, the Capacitor iOS and Android wrappers, and the release and backup
scripts.

The repo is the source, not a backup of the machine. Left out on purpose, by
the .gitignore files that were already in the tree: node_modules, build output,
mobile/www (regenerated from server/public on every build), and server/data,
which holds the live checklists and belongs on the NAS rather than in git.

Every credential is excluded too — server/.env, the Play upload keystore and
its properties file, the App Store Connect key and ios-signing.config. The one
secret that was not in an ignored file was the NAS SSH password, hard-coded in
build-deploy.sh and grepped back out of it by backup-to-drive.sh. Both now read
it from the login keychain (service checklist-nas, account Christian), falling
back to NAS_PASS in the environment, so rotating it is one keychain entry
rather than a history rewrite.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-24 13:00:57 -04:00
..

Recurring Checklist — Self-hosted Web Version

A small Node.js web app that anyone on your network can hit from a browser. Designed to run on a Synology NAS via Docker (Container Manager). Every Sunday at 3:00 PM it emails you a digest of who checked off what — and what got missed — then resets the lists for the next week.

Users → http://nas-ip:3000 → tick boxes → server stores state
                                     ↓
                              Sun 3:00 PM cron
                                     ↓
                       Gmail SMTP → digest email → you

What's where

File Purpose
server.js Express server, REST API, scheduler, email
public/ Static frontend (HTML/CSS/JS) served by Express
data/ Persisted state (checklists.json) — must be a volume mount
.env.example Copy to .env and fill in
Dockerfile Builds the container image
docker-compose.yml One-command deploy

1. Set up a Gmail App Password (for the digest email)

  1. Sign in to the Google account you want to send from (your own, or a dedicated one — both work).
  2. Turn on 2-Step Verification at https://myaccount.google.com/security.
  3. Once 2-Step is on, go to https://myaccount.google.com/apppasswords.
  4. Create an app password called "Recurring Checklist". Google gives you a 16-character string like abcd efgh ijkl mnop. Save it.
  5. You'll paste that into SMTP_PASS in .env (with or without spaces, both work).

Don't want to use Gmail? Any SMTP works. Office 365, Fastmail, your own Postfix on the Synology — just swap the SMTP_* vars in .env.


2. Deploy on Synology with Container Manager

These steps assume DSM 7.2+ with Container Manager installed.

A. Copy the server/ folder to the NAS

Drop this whole folder into something like /volume1/docker/recurring-checklist/ using File Station, SMB, rsync — whatever's easiest. The folder must end up with Dockerfile, docker-compose.yml, server.js, public/, etc.

B. Create the .env file on the NAS

In that folder, copy .env.example to .env and edit it:

cp .env.example .env
nano .env

Fill in:

  • ADMIN_PASSWORD — a password you'll use to manage lists in the browser.
  • REPORT_EMAIL — your inbox.
  • SMTP_USER — the Gmail address you set up in step 1.
  • SMTP_PASS — the 16-character app password.
  • SMTP_FROM — Recurring Checklist <yourgmail@gmail.com> is fine.
  • REPORT_TZ — your local timezone (default America/New_York).
  • REPORT_CRON — 0 15 * * 0 = Sunday at 3:00 PM. Leave alone unless you want to change the time.

C. Open Container Manager → Project → Create

  1. Project name: recurring-checklist
  2. Path: browse to /volume1/docker/recurring-checklist/
  3. Source: "Use existing docker-compose.yml"
  4. Click Next through the rest and Done. Container Manager will build the image (takes 30–90 seconds the first time) and start it.

D. Open it in a browser

From any device on your network:

http://<your-nas-ip>:3000

You'll be asked for a name (your name shows up in the weekly email). That's it — start checking things off.

E. Log in as admin to create lists

  • Top-right of the page → 🔒 Admin → enter the ADMIN_PASSWORD from .env.
  • A new bar appears under the list tabs. Pull it down (click or swipe) to reveal + Add List, Rename, Delete, Export/Import JSON, and Send Digest Now (use this to test email).
  • In Add Task, build out your Startup and Shutdown lists.
  • Click 🔓 Admin again to leave admin mode so the controls hide again.

3. Test the email before Sunday

While logged in as admin, hit Send Digest Now in the menu drawer. You should get an email within a few seconds. If not, check the container logs in Container Manager (it'll print the SMTP error).


4. Optional: HTTPS via Synology reverse proxy

In Control Panel → Login Portal → Advanced → Reverse Proxy:

Source Destination
https://checklist.your-domain.com (443) http://localhost:3000

Synology handles the Let's Encrypt cert. The app itself only listens on plain HTTP inside the container — TLS terminates at DSM.


5. Running outside Docker (e.g. for local development)

cd server
npm install
cp .env.example .env   # then edit
npm start

Open http://localhost:3000.


Backups

Everything that matters lives in data/checklists.json. Add that path (/volume1/docker/recurring-checklist/data) to your Hyper Backup task and you're set.


Troubleshooting

  • Email never arrives. Inside Container Manager, click the container → Log. Look for [digest] Failed: .... Common causes:
    • Wrong Gmail app password.
    • 2-Step Verification not enabled on the Gmail account.
    • Synology firewall blocking outbound port 587.
  • "Wrong password" when trying admin login. Check ADMIN_PASSWORD in .env and that you restarted the container after editing.
  • Port 3000 is taken. Edit docker-compose.yml and change the left side of the port mapping (e.g. "8088:3000"), then redeploy.
  • State got messed up. Stop the container, edit data/checklists.json directly, restart.