NextMyDay/README.md
Claus Lohmar 9fdc9eded6 docs: update the plan, specs and deploy guide for the setup toggle (D-045)
- PROJECT_PLAN.md: rewrite §5.1 for the toggle gate, mark D-025 superseded,
  refresh D-026 / O-8 / A13 / §12 and add the D-045 change-log entry.
- SPEC-005: §1/§3/§5/§6 (AC-1/AC-2/AC-7 + AC-8) reflect the three states and
  the optional code; §9 marks the D-044 tunnel bits superseded; new §10.
- SPEC-003: §4/§7 pipeline + AC-7/AC-10/AC-14; new §13 (D-045); D-025 ref
  removed from the governing decisions.
- docs/deploy-zoraxy.md: §5 rewritten around the toggle + optional code;
  §1/§2/§3/§7 de-tunnelled; see-also updated.
- README: first-run + deployment sections rewritten (enable setup: true,
  open /setup, complete, the app flips it off).
- config.example.yaml: document the toggle and the optional code.
2026-10-15 06:47:36 +00:00

158 lines
6.5 KiB
Markdown

# MyDay
> A self-hosted employee **time, absence & reporting** app by **Sechpoint**.
> Delivered as a **PWA** for mobile and desktop. **Single-tenant** — one company per instance.
**Status:** 🚧 **M0 + M0.1 implemented** — the auth & routing foundation and the web/PWA layer
(the SPEC-002…009 slices) are built: routing · identity/sessions · the door · `/setup` ·
enroll/login (+ the passkey nudge) · roles/chain/recovery · the module registry · the web shell.
Next milestone: **M1 — org structure**. The design of record is
[`PROJECT_PLAN.md`](PROJECT_PLAN.md).
---
## What it will do
| Area | |
|---|---|
| **Org structure** | users, the manager hierarchy, departments, positions |
| **Time** | presence and timesheets (a per-user mode) |
| **Absence** | leave requests, approvals, balances |
| **Reporting** | rollups and aggregations |
Built as **one Go binary**: a small core (`MyDay.auth` — identity, sessions, the door) plus
drop-in **modules**.
---
## Running it
```bash
# 1. configure
cp config.example.yaml config.yaml # then edit; config.yaml is gitignored
# 2. build
go build -o myday ./cmd/myday
# 3. run
./myday
```
---
## ⚠️ First run — enable setup, then complete the wizard
The **first user becomes the top admin** (the root of the chain). Setup is gated by the **`setup`
config toggle** (D-045): enable it, start MyDay, complete the wizard — the app then writes
**`setup: false`** and the instance becomes productive.
```yaml
# config.yaml
setup: true # enable the /setup wizard
setup_code: "" # OPTIONAL second factor — set it if the domain is reachable by others
```
1. **Start MyDay.** The startup banner states the mode and prints the setup URL.
2. **Open the wizard at `/setup`** (e.g. `https://myday.sechpoint.app/setup`).
- `setup_code` **empty** → the wizard is served directly;
- `setup_code` **set** → the code form comes first; enter the code printed in the startup banner.
3. **Complete the wizard** — domain → top admin (TOTP) → mail/locale. It creates the admin and
writes **`setup: false`** to `config.yaml`.
4. **Restart** so the app adopts the persisted secrets: `systemctl restart myday`.
While `setup: true` and setup is unfinished:
- `GET /` points at the wizard, and `GET /setup` serves it (or the code form);
- **every other route answers 404** — there is no app yet.
While `setup: false` and **no admin exists**, the instance is **locked**: `GET /` and `GET /setup`
show a page telling you to set `setup: true` and restart, and every other route 404. This state is
**not publicly claimable** — a fresh instance never exposes its bootstrap by accident.
> **Residual risk:** while `setup: true`, whoever reaches `/setup` first can claim the admin.
> **Set `setup_code`** (or complete setup immediately) if the domain is open to others. The code is
> **single-use** and the entry form is **rate-limited**.
Read the code again (when one is set):
```bash
journalctl -u myday # inside the container
pct exec 11020 -- journalctl -u myday # from the Proxmox host
```
Only **after** the top admin exists should you put MyDay behind a reverse proxy and expose it.
> **Note:** the setup wizard enrolls **TOTP**; the admin adds a **passkey** at first sign-in on the
> real (https) origin, via the passkey nudge.
---
## Deployment — behind Zoraxy (TLS at the proxy)
> **Full walkthrough:** [`docs/deploy-zoraxy.md`](docs/deploy-zoraxy.md) — topology · config ·
> the Zoraxy ProxyHost · the firewall rule · the **`setup: true` bootstrap** · systemd ·
> the misconfig warnings. This section is the summary.
The reference deployment terminates **TLS at [Zoraxy](https://github.com/tobychui/zoraxy)** at
**`10.1.0.10`**; the app itself speaks **plain HTTP**. Config (`config.example.yaml` →
`config.yaml`, D-043):
- **App** — `server.listen: "0.0.0.0:8080"`; the app binds any address (there is **no** setup-mode
bind refusal).
- **Proxy** — Zoraxy terminates TLS and forwards to `http://<app-host>:8080`, setting
`X-Forwarded-For` and `X-Forwarded-Proto`.
- **`trusted_proxies`** — `["10.1.0.10"]` (Zoraxy). The app **honours `X-Forwarded-Proto`** from a
trusted proxy to decide a request is `https`, and then sets the session cookie **`Secure` +
`__Host-`** and sends **HSTS**. Over plain http it keeps the bare, non-`Secure` cookie.
A **wrong value fails silently** — no `Secure` cookie, no HSTS.
- **`base_url`** — `https://myday.sechpoint.app`: the public origin, used in invite/recovery links.
- **Firewall** — **`:8080` MUST be reachable only from `10.1.0.10`.** Anything else that can reach
the port bypasses the proxy **and its TLS**; the app would then serve plain HTTP to that client.
```bash
# on a fresh instance: enable setup, start MyDay, then open the domain at /setup
# config.yaml: setup: true
# (optional) setup_code: "…" # a second factor; the printed code comes first
sudo systemctl restart myday
# read the mode / setup code in the log:
journalctl -u myday # inside the container
pct exec 11020 -- journalctl -u myday # from the Proxmox host
```
---
## What `/setup` writes — and the restart
The wizard **persists `config.yaml`** (`setup: false`, the base URL, mail/locale, and the secrets)
next to the loaded file — never over the committed `config.example.yaml`. If the file is not
writable it **prints** the values for you to save by hand, and setup still completes.
The **secrets are generated at startup** when a value is still `CHANGE_ME…`, so the wizard's
ceremony is never signed with a known key. The persisted file then holds **those same values** —
so **a restart adopts them** and the admin's TOTP secret (sealed with the generated key) keeps
working. Boot a productive instance from the **written `config.yaml`**, not from the example:
`config.example.yaml`'s placeholders fail closed in productive mode by design.
---
## Documentation
| | |
|---|---|
| [`PROJECT_PLAN.md`](PROJECT_PLAN.md) | the plan & decision log of record |
| [`AGENT.md`](AGENT.md) | instructions for AI agents working here |
| [`docs/specs/`](docs/specs/) | implementation specs |
| [`docs/deploy-zoraxy.md`](docs/deploy-zoraxy.md) | the Zoraxy deployment guide (D-043) |
| [`config.example.yaml`](config.example.yaml) | configuration template |
---
## Security
- Secrets live in **`config.yaml`** (gitignored) — **never commit them**.
- Auth is **Passkey (primary) + TOTP (fallback)**.
- See `PROJECT_PLAN.md` §10 for the deferred-security register and its **beta gate**.
---
## License
TBD.