6b7d47fa4c
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>
125 lines
9.3 KiB
Markdown
125 lines
9.3 KiB
Markdown
# 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 1–4 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).
|