No description
  • JavaScript 66.4%
  • CSS 23.3%
  • HTML 7.6%
  • Shell 1.5%
  • Dockerfile 1.2%
Find a file
Kevin Sivic dde44cfa5e
All checks were successful
Build and push image / test (push) Successful in 10s
Build and push image / build (push) Successful in 15s
chore(tooling): stop hardcoding a stale model in the commit trailer
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 18:40:15 -04:00
.forgejo/workflows feat(ci): add Forgejo build workflow 2026-08-03 10:51:36 -04:00
config chore: initial repo setup 2026-08-03 10:26:38 -04:00
lib feat(store): report roster members who have not submitted a week 2026-08-23 18:34:00 -04:00
public feat(ui): show which roster members have not answered yet 2026-08-23 18:37:44 -04:00
scripts chore(tooling): stop hardcoding a stale model in the commit trailer 2026-08-23 18:40:15 -04:00
.dockerignore chore: initial repo setup 2026-08-03 10:26:38 -04:00
.gitignore chore: initial repo setup 2026-08-03 10:26:38 -04:00
CLAUDE.md chore(docs): correct stale push guidance in CLAUDE.md 2026-08-23 18:39:08 -04:00
docker-compose.yml chore: initial repo setup 2026-08-03 10:26:38 -04:00
Dockerfile chore: initial repo setup 2026-08-03 10:26:38 -04:00
package-lock.json chore: initial repo setup 2026-08-03 10:26:38 -04:00
package.json chore: initial repo setup 2026-08-03 10:26:38 -04:00
README.md chore: initial repo setup 2026-08-03 10:26:38 -04:00
server.js refactor(store): extract Store class with Nullable factory and tests 2026-08-03 11:37:37 -04:00
vitest.config.mjs refactor(store): extract Store class with Nullable factory and tests 2026-08-03 11:37:37 -04:00

Garage Days

A tiny self-hosted board for the Premature Apexers to post weekly availability for race car work sessions, and see which day(s) get the most hands.

No accounts, no passwords — pick your name from a fixed roster and mark your days. The app never auto-picks a day for you; it ranks all seven so a human still makes the call (someone might have the most votes on a day the one guy with the welder can't make it).

How it works

  • A week runs MondaySunday.
  • Each person marks themselves available/not per day, with an optional short note (e.g. "evenings only", "can bring the trailer").
  • "Copy from last week" pulls your prior week's marks into the editor so you can tweak instead of starting blank — it doesn't save until you hit Save.
  • The left panel ranks all seven days by headcount, highlighting the top day(s), and lists who's in (with their notes) for each.

Running it

Local test, no Docker

npm install
npm start

Then open http://localhost:3000

On your homelab (Docker + Traefik)

  1. Copy this whole folder onto your server (e.g. alongside your other compose stacks).
  2. Edit docker-compose.yml:
    • Set the networks: name to whatever network your Traefik is attached to (same pattern as your other services).
    • Set the Host() rule to the subdomain you want, and match your existing certresolver name.
  3. docker compose up -d --build

Data persists in ./data/availability.json on the host via the bind mount, so it survives container rebuants/restarts.

Managing the roster

Edit config/roster.json — it's just a JSON array of names:

["Kevin", "Josh", "Melanie", "Kyra", "Tom", "Aaron P"]

Restart the container after changing it (docker compose restart garage-days) — no rebuild needed since it's bind-mounted.

Notes / things you may want to change

  • No auth at all beyond picking a name from a dropdown — anyone with the URL can mark availability as anyone else. Fine for a small trusted team on your own network; if you ever expose this outside your LAN/VPN, put it behind Authentik like your other services rather than relying on obscurity.
  • Storage is a single JSON file, not a database. That's intentional for a team this size — dead simple to back up (it's one file) and to inspect by hand. If the group ever grows past a dozen or so people or you want history/analytics, swap lib/store.js for SQLite; the API layer doesn't need to change.
  • Notes are capped at 280 characters server-side.