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

9.3 KiB
Raw Blame History

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:

  • 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.
  • 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.

  • Read: Leads & Clients tables, served by smb-crm straight from Postgres.
  • Add: "+ Neu" modal per entity; auto-IDs (C-#### / L-epoch), renewal auto-compute.
  • Edit: per-row ✎ modal, token-protected PATCH.
  • Delete: per-row 🗑 (token-protected), audit-logged — replaces the scratchpad cleanup.
  • Behind Caddy basic-auth; mutations gated by CRM_API_TOKEN; all changes audit-logged.
  • One-way DB→Sheets mirror keeps the Sheet overview current after every change.
  • 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.
  • 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.

  • 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.
  • 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.
  • 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

  • 2026-06-25 — Latency fix (n8n executions 14 min → ~6 s; DNS + PostHog telemetry).
  • 2026-06-25 — Chooser reworded to client-friendly, jargon-free copy.
  • 2026-06-25 — Pizzeria demo with online ordering added.
  • 2026-06-25 — Barbershop "Google Kalender" dot-overlap styling fixed.
  • 2026-06-25 — All three demo pages neutralised (stack-agnostic, non-stale dates).
  • 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.
  • 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.
  • 2026-07-14 — Per-client deploy tooling: deploy/clients/ isolated-stack model + new-client.sh scaffolder; back-office updates.
  • 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.
  • 2026-07-15 — Credentials-in-the-CRM feature built and shipped live (see above).