THIS APPLICATION IS A PERSONAL PROJECT TO MANAGE THINGS AROUND THE HOUSE. THIS APPLICATION IS MEANT TO BE RUN LOCALLY ON A PERSONAL NETWORK, UTILIZING A DOCKER CONTAINER AND/OR A RASPBERRY PI WITH NO ACCESS ALLOWED FROM OUTSIDE YOUR PERSONAL NETWORK. PLEASE DO NOT SPIN THIS UP AND ALLOW ANYONE ON THE INTERNET TO ACCESS IT.
Chores, calendar, and (eventually) finances. Runs as a Docker container on a Raspberry Pi.
mgmt/
├── server.js Node/Express backend
├── package.json
├── Dockerfile
├── data/ ← mount this as a Docker volume
│ ├── config.json PIN hash, Google credentials, calendar selection
│ └── chores/
│ ├── daily.txt one chore per line
│ ├── weekly.txt
│ └── monthly.txt
└── static/ served as-is by Express
├── index.html main dashboard
├── index.js
├── admin.html admin settings page (PIN-protected)
├── admin.js
├── setup.html first-time setup wizard
├── setup.js
└── index.css
When you open the app for the first time (or after a full reset), you are
automatically redirected to the setup wizard at /setup.html. It walks you
through two steps:
- Create a 4-digit admin PIN — protects the admin settings page
- Google Calendar credentials (optional) — paste your OAuth Client ID and Client Secret so the app can connect to a private Google Calendar. You can skip this and add them later from the admin page.
After setup completes you land on the main dashboard.
The app uses the OAuth 2.0 Device Authorization Flow, which works on any hostname or IP address — no redirect URI or domain name required.
- Go to console.cloud.google.com and create a new project (or select an existing one).
- In the sidebar: APIs & Services → Library → search for Google Calendar API → Enable it.
- APIs & Services → Credentials → Create Credentials → OAuth 2.0 Client ID
- Application type: TV and Limited Input devices (this is the type that supports the device flow — no redirect URI needed)
- Give it a name (e.g. "MGMT Dashboard") and click Create.
- Copy the Client ID and Client Secret from the confirmation dialog.
If Google asks you to configure the OAuth consent screen first, set it to Internal (if using a Google Workspace account) or External with your own email as a test user.
During first-time setup: paste the Client ID and Secret into the fields on setup step 2.
After setup / to update credentials: Admin page → Google Calendar section → enter Client ID and Secret → Save credentials.
Credentials are stored in data/config.json. If you prefer to inject them as
environment variables instead, set GOOGLE_CLIENT_ID and
GOOGLE_CLIENT_SECRET — env vars always take priority over the saved values.
- Go to the Admin page → Google Calendar section.
- Click Connect Google Calendar.
- The admin page shows a short code and a link to google.com/device.
- Open that link in any browser, sign in to your Google Account, and enter the code. You have a few minutes before it expires.
- The admin page detects approval automatically and prompts you to choose which calendar to display.
- Select a calendar and click Use this calendar. The main dashboard will now show upcoming events from that calendar.
To switch accounts or calendars, click Disconnect and repeat the flow.
cd src
npm install
node server.js
# Open http://localhost:3000To use Google Calendar locally, set the env vars before starting:
GOOGLE_CLIENT_ID=your-id GOOGLE_CLIENT_SECRET=your-secret node server.jsOr skip the env vars and enter the credentials via the admin page after setup.
docker build -t mgmt .Mount your local data/ directory so chores and config persist across
container restarts and image rebuilds:
docker run -d \
-p 3000:3000 \
-v /home/pi/mgmt/data:/app/data \
--restart unless-stopped \
--name mgmt \
mgmtAccess from any device on your network: http://<pi-ip>:3000
--restart unless-stopped keeps the container running after a Pi reboot
with no extra configuration.
If you prefer to pass Google credentials as env vars rather than through the UI:
docker run -d \
-p 3000:3000 \
-v /home/pi/mgmt/data:/app/data \
-e GOOGLE_CLIENT_ID=your-id \
-e GOOGLE_CLIENT_SECRET=your-secret \
--restart unless-stopped \
--name mgmt \
mgmtOr use docker-compose.yml — add them under the environment: key.
Navigate to the admin page by clicking Login on the dashboard and entering your 4-digit PIN. From there you can:
- Google Calendar — save/update OAuth credentials, connect or disconnect your Google Account, and choose which calendar to display
- Admin PIN — change the 4-digit PIN
- Add/Remove Chores — manage individual tasks or bulk-import from a
.txtfile - Danger Zone — reset everything (clears PIN, credentials, tokens, and all chores)
| File | Reset schedule |
|---|---|
data/chores/daily.txt |
Every day at 00:00 |
data/chores/weekly.txt |
Every Sunday at 00:00 |
data/chores/monthly.txt |
1st of each month at 00:00 |
- One chore per line; blank lines are ignored
- The main page fetches
GET /api/choreson load and builds the checkbox list - Checked items are hidden and persisted in
sessionStorageuntil the next reset - Adding/removing from the admin page writes directly to the
.txtfiles - Bulk import reads a
.txtfile line-by-line and adds each line as a chore
Admin page → Danger Zone → Reset Everything
This clears the admin PIN, Google credentials, OAuth tokens, calendar selection, calendar embed URL, and all chores, then redirects to the setup wizard.
Alternatively, to reset manually: delete or empty data/config.json and the
files in data/chores/, then restart the server.
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/chores |
— | All chores grouped by reset type |
| GET | /api/chores/:type |
— | Chores for one type (daily/weekly/monthly) |
| POST | /api/chores/:type |
PIN | Add a chore — { "text": "…" } |
| DELETE | /api/chores/:type/by-text |
PIN | Remove by text — { "text": "…" } |
| DELETE | /api/chores/:type/:index |
PIN | Remove by line index (0-based) |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/calendar-url |
— | Get embed URL from config |
| POST | /api/calendar-url |
PIN | Save embed URL — { "url": "…" } |
| GET | /api/calendar/events |
— | Unified: returns OAuth events, iframe URL, or none |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/auth/google/status |
— | Connection state and calendar info |
| GET | /api/auth/google/credentials |
PIN | Whether credentials are set and their source |
| POST | /api/auth/google/credentials |
PIN | Save Client ID + Secret — { "clientId", "clientSecret" } |
| POST | /api/auth/google/device/start |
PIN | Begin device auth flow — returns { userCode, verificationUrl } |
| GET | /api/auth/google/device/poll |
PIN | Poll for user approval — returns { status } |
| DELETE | /api/auth/google |
PIN | Disconnect (clears tokens) |
| GET | /api/auth/google/calendars |
PIN | List the user's calendars |
| POST | /api/auth/google/calendar |
PIN | Select which calendar to show — { "calendarId", "calendarSummary" } |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/pin/exists |
— | Returns { "exists": bool } |
| POST | /api/pin/verify |
— | Verify PIN hash — { "hash": "<sha256 hex>" } → { "valid": bool } |
| POST | /api/pin |
PIN | Set new PIN — { "hash": "<sha256 hex>" } |
| POST | /api/reset |
PIN | Wipe all data and return to setup state |
The PIN is never transmitted as plaintext — the browser hashes it with SHA-256 and only the hash is sent to and stored by the server.
Auth (PIN) means the request must include an X-Admin-Pin-Hash header
containing the SHA-256 hex hash of the current 4-digit PIN.