Files
smb-online/TODO.md
T
mivanchenko 6b7d47fa4c Fix deploy pipeline checkout: no Node on host runner, repo is private
actions/checkout@v4 failed with "Cannot find: node in PATH" (the
runner intentionally has no Node toolchain). Replaced with a plain
git clone authenticated via a new DEPLOY_TOKEN Gitea Actions secret,
since the repo needs auth even for a read-only clone.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 18:18:44 +02:00

125 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TODO / Backlog
## Rethink the tier model — decouple tier from business type
**Goal:** tiers should NOT be tied to a kind of business. Which "tier" (and whether a
client's site is self-hosted or runs on Google's free services) is a decision made
**between me and the client**, case by case — not something baked into a demo.
Implications to work through:
- [x] **Demos are stack-agnostic starters.** ~~Drop tier/stack labels and jargon (self-hosted,
Cal.com, Google-Stack, "≈ 0 €") from the public-facing demos & chooser.~~ DONE 2026-06-25:
chooser reworded; all three demo pages neutralised — demo bars now read "Beispielseite · So
könnte Ihr Online-Auftritt aussehen", booking widgets relabelled to generic
"Online-Terminbuchung"/"Sichere Buchung", Google-Kalender/Cal.com/self-hosted/Tier labels
removed from rendered copy *and* HTML comments, demo booking dates now render on the current
week via JS so nothing looks stale.
- [x] After a client signs, **I decide hosting**: self-host the page on the homelab, or stand it
up on the client's free Google account. Already how it works in practice — the CRM has
`tier` + `stack_notes` columns recording that per-client decision, and every deploy this
session (happynails' credential, the client stacks in `deploy/clients/`) went through that
path rather than a fixed per-tier template. Nothing left to build here.
- [ ] **Needs your call:** Revisit the `Clients.tier` field semantics — keep A/B as an internal effort/price band, or
replace with something hosting-agnostic (e.g. `hosting = google | selfhosted`, `plan = …`).
- [ ] **Blocked on the above:** Onboarding form + workflow currently ask for Tier A/B — realign
once the field semantics are settled.
## Onboard dashboard → manage Leads & Clients (CRUD) — DONE 2026-06-25
Built as a DB-first back-office (architecture pivot: **Postgres is the source of truth**, Sheets
is a one-way mirror — see `backoffice/` and the `smb-db-source-of-truth` memory). Live at
**onboard.mivanchenko.de/crm** behind the existing basic-auth.
- [x] **Read:** Leads & Clients tables, served by `smb-crm` straight from Postgres.
- [x] **Add:** "+ Neu" modal per entity; auto-IDs (C-#### / L-epoch), renewal auto-compute.
- [x] **Edit:** per-row ✎ modal, token-protected PATCH.
- [x] **Delete:** per-row 🗑 (token-protected), audit-logged — replaces the scratchpad cleanup.
- [x] Behind Caddy basic-auth; mutations gated by `CRM_API_TOKEN`; all changes audit-logged.
- [x] One-way **DB→Sheets mirror** keeps the Sheet overview current after every change.
- [x] **n8n ingest swap:** `lead-intake` & `onboarding` now POST into the DB API (not Sheets);
Telegram notify unchanged. `renewal-reminder` still reads Sheets (fine — Sheets mirrors DB).
Follow-ups (not yet done):
- [ ] Extend CRUD UI beyond Leads/Clients (projects, bookings, invoices, activity) if useful.
Dashboard (`backoffice/app/static/index.html`) still only has Leads/Clients tabs.
- [x] Daily backup of the `smb-db` Postgres volume — DONE: `deploy/backup/smb-db-backup.sh`
(`pg_dump` + gzip, keeps 14 days, cron on the homelab).
- [ ] Make the DB→Sheets overwrite atomic (currently clear-then-write; brief mid-write window a
reader could catch the cleared sheet — harmless for a human overview).
## Credentials in the CRM — copy-to-clipboard — DONE 2026-07-15
**Goal:** be able to grab a client's login (e.g. their Easy!Appointments provider password) from
the CRM dashboard itself instead of digging through Vaultwarden or a live n8n execution log —
prompted by not being able to find the happynails booking password anywhere. New `credentials`
entity (`backoffice/db/init.sql`, `backoffice/app/db.py`), **deliberately never mirrored to
Sheets** (`mirror: False`, enforced in `mirror_async`/`mirror_entity`/`/api/sync` and skipped by
`import_from_sheets.py`) and gated by `X-CRM-Token` even to read. Dashboard has a new
**Credentials** tab with a masked value, "👁 zeigen" reveal toggle, "📋 Kopieren" copy button.
`n8n/onboarding.json` now saves the auto-generated EA provider password instead of discarding it
(root cause of the happynails password being unrecoverable — it was never persisted anywhere).
Migrated + shipped to the live homelab (`smb-db` schema, `smb-crm` rebuilt, confirmed healthy).
Happynails' (`C-0002`) reset EA password is recorded as `CR-1784116056728`.
Follow-ups (not yet done — all need you, not more code):
- [ ] Re-import the updated `n8n/onboarding.json` into the live n8n instance (the new "Save
credential" node). Needs the real `__CRM_TOKEN__`/`__EA_AUTH__` credential bindings re-attached
in the n8n UI — not safely scriptable from outside n8n.
- [ ] The happynails credential's `username` is still blank — confirm it in the EA admin UI and
fill it in via the dashboard's ✎ edit on the Credentials tab.
- [ ] Your call, no rush: encrypt `credentials.secret` at rest (e.g. pgcrypto) instead of
plaintext if the dashboard is ever exposed more broadly than Caddy basic-auth + host security.
## Deploy pipeline — trigger from the Gitea UI — DONE 2026-07-15 (backoffice only)
**Goal:** stop hand-running scp/ssh/docker-compose-build for every change (that's how the
credentials feature above originally shipped). Gitea Actions is now enabled on
`git.mivanchenko.de`, with a self-hosted runner (`homelab-runner`, labels `self-hosted`/
`homelab`) running natively on the homelab host as the `act-runner` systemd service (survives
reboots). `.gitea/workflows/deploy-backoffice.yml` is a `workflow_dispatch`-only job (manual
"Run workflow" button — deliberately not on push, since triggering it = deploying) that syncs
`backoffice/db` + `backoffice/app`, re-applies the idempotent schema, rebuilds/restarts
`smb-crm`, and health-checks it.
- [x] Runner registered — runs directly on the host with the same `docker`/filesystem access the
operator already has, so no SSH deploy key was needed for the runner itself.
- [x] Fixed along the way: the runner-token generator needs `docker exec -u git gitea …` (Gitea
refuses to run as root, `docker exec` defaults to root); the runner is registered against
Gitea's internal container IP (`http://172.24.0.2:3000`), not `git.mivanchenko.de`, because the
homelab can't reach its own public hostname (NAT hairpin) — `https://git.mivanchenko.de` calls
from the host itself just hang.
- [x] First real run failed twice, both fixed: (1) `actions/checkout@v4` needs Node.js, which the
intentionally-minimal host-mode runner doesn't have — replaced with a plain `git clone`;
(2) the repo is private, so that clone needs auth — added a `DEPLOY_TOKEN` Gitea Actions secret
(a `read:repository,write:repository`-scoped token under the admin account, stored only in
Gitea's encrypted secret store, stripped from the cloned workspace's `git remote` right after
checkout).
- [ ] **Needs you:** actually press "Run workflow" once in the Gitea UI (Actions tab →
Deploy backoffice (smb-crm)) as the real end-to-end test — I deliberately didn't mint a Gitea
API token to test-trigger it myself (that would've left a stray write-scoped credential behind).
- [ ] Extend the same pattern to the other `deploy/*` groups (`booking/`, `smb-demos/`,
`clients/<slug>/`) — straightforward once the backoffice one is confirmed working, since
they're all simpler (mostly just `docker compose up -d`, no DB migration step).
- [ ] Once you're adding collaborators: anyone with **write** access to this repo can trigger a
deploy. Worth being deliberate about who gets write vs. read/PR-only access — **your call**.
## Other
- [ ] Harden n8n auth: compose still has deprecated `N8N_BASIC_AUTH_*` with password `password`
(legacy/ignored by modern n8n owner-login). **Needs your call** — I can remove the dead env, but
whether the owner account itself is strong enough is a judgment only you can make.
- [ ] Orders: the pizzeria demo posts orders into the `Leads` tab via the lead-intake webhook as a
stop-gap. Build a proper **Orders** flow + sheet tab (items, total, mode, status) when the
order-management product is real.
## Done
- [x] 2026-06-25 — Latency fix (n8n executions 14 min → ~6 s; DNS + PostHog telemetry).
- [x] 2026-06-25 — Chooser reworded to client-friendly, jargon-free copy.
- [x] 2026-06-25 — Pizzeria demo with online ordering added.
- [x] 2026-06-25 — Barbershop "Google Kalender" dot-overlap styling fixed.
- [x] 2026-06-25 — All three demo pages neutralised (stack-agnostic, non-stale dates).
- [x] 2026-06-25 — DB-first back office: Postgres source of truth + Sheets mirror + CRUD + n8n
ingest swap (lead-intake/onboarding → DB API). Live at onboard.mivanchenko.de/crm.
- [x] 2026-07-14 — Shared self-hosted booking: Easy!Appointments stack (`deploy/booking/`),
auto-provisioning wired into the onboarding workflow, `booking-sync` workflow syncing bookings
to the CRM.
- [x] 2026-07-14 — Per-client deploy tooling: `deploy/clients/` isolated-stack model +
`new-client.sh` scaffolder; back-office updates.
- [x] 2026-07-14 — Booking-sync workflow, done differently than originally scoped: the planned
Tier-B-Cal.com-webhook / Tier-A-GCal-poll split was superseded by the tier-decoupling above —
one shared Easy!Appointments instance handles booking for every client instead.
- [x] 2026-07-15 — Credentials-in-the-CRM feature built and shipped live (see above).