- PROJECT_PLAN: register D-044, re-revise D-025 to "tunnel OR setup code",
update A13, O-8, §5.1, §12 and the change log.
- SPEC-003 §4/§7/§9/§12: the gate now serves the wizard to the tunnel OR an
unlocked request; AC-7/AC-10 reworded ("refused the wizard" — the boilerplate
may still answer `/` and `/setup`), AC-14 added.
- SPEC-005 §3/§4/§6/§9: the boilerplate, the unlock action, the code rules and
the ACs; AC-7 added for single-use/rate-limit/redaction.
- deploy-zoraxy §1/§3/§5/§7 + README: the LXC/Proxmox topology, how to read the
code (journalctl · pct exec 11020), the code gate replacing the tunnel-only
bootstrap (the tunnel stays as gate a); firewall and Zoraxy sections kept.
150 lines
6.3 KiB
Markdown
150 lines
6.3 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 — the setup code
|
|
|
|
The **first user becomes the top admin** (the root of the chain). MyDay bootstraps through a
|
|
**`/setup` wizard** that is **gated — never first-visitor-wins** (D-025/D-044). On first start it
|
|
prints a **one-time 128-bit SETUP CODE** in its startup log:
|
|
|
|
```
|
|
======================================================================
|
|
MyDay is NOT set up yet — no admin exists.
|
|
======================================================================
|
|
Open the setup wizard at:
|
|
https://myday.sechpoint.app/setup
|
|
SETUP CODE: 9f3a-1b2c-…
|
|
```
|
|
|
|
Open the domain (or `http://localhost:8080`) and enter that code at `/setup`. **Until then every
|
|
page is a friendly first-run page — never a bare 404** — and every other route answers 404. Read
|
|
the code again with:
|
|
|
|
```bash
|
|
journalctl -u myday # inside the container
|
|
pct exec 11020 -- journalctl -u myday # from the Proxmox host
|
|
```
|
|
|
|
> **Why a code, not a tunnel?** MyDay's reference deployment is an **LXC container behind
|
|
> Proxmox + Zoraxy**. An SSH tunnel is impractical there: `pct enter`/SSH lands on the container
|
|
> **gateway**, not loopback, so a tunnel to `127.0.0.1:8080` cannot be formed. The setup code
|
|
> closes first-visitor-wins instead. A **direct SSH tunnel** to the app's loopback port still works
|
|
> as a second gate and needs **no code** (D-025 gate a).
|
|
|
|
> **Note:** the code is **single-use** and the entry form is **rate-limited**. Complete the wizard,
|
|
> then **restart** so the app adopts the `config.yaml` the wizard wrote.
|
|
|
|
> **Bonus:** to use the tunnel gate, reach the app over **`localhost` / `127.0.0.1`** — only a
|
|
> loopback origin counts as a secure context for the browser's **WebAuthn** dialogs. Over plain http
|
|
> the cookies are sent without `Secure` (required by the tunnel bootstrap); behind the proxy (https)
|
|
> they are hardened — see *Deployment*.
|
|
|
|
Only **after** the top admin exists should you put MyDay behind a reverse proxy and expose it.
|
|
|
|
---
|
|
|
|
## 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 first-run setup code (no tunnel needed) · 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, D-025 revised).
|
|
- **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 (the tunnel) it keeps the bare, non-`Secure` cookie
|
|
so the bootstrap works. 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
|
|
# read the one-time setup code, then open the domain and enter it at /setup
|
|
journalctl -u myday # inside the container
|
|
pct exec 11020 -- journalctl -u myday # from the Proxmox host
|
|
# (or, for the no-code tunnel gate: ssh -L 8080:127.0.0.1:8080 user@server)
|
|
```
|
|
|
|
---
|
|
|
|
## 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.
|