From c10ff81d47f0db6f67de2255c359bf1c9b1d4c81 Mon Sep 17 00:00:00 2001 From: thesabear Date: Sat, 22 Aug 2026 07:36:47 +0300 Subject: [PATCH] docs: add orchestration, registry, dashy, cron, and content pipeline write-ups Completes the orchestration/registry/content batch - Kestra's check-pause-approve-apply pattern, the self-hosted build pipeline, the SOC dashboard's 14-source aggregation, the cron heatmap, and the in-progress content pipeline, all in the diagram+callout+table style. --- docs/content-pipeline.md | 41 +++++++++++++++++++++ docs/cron-scheduler.md | 32 ++++++++++++++++ docs/dashy-soc-board.md | 75 ++++++++++++++++++++++++++++++++++++++ docs/orchestration.md | 50 +++++++++++++++++++++++++ docs/registry-and-build.md | 50 +++++++++++++++++++++++++ 5 files changed, 248 insertions(+) create mode 100644 docs/content-pipeline.md create mode 100644 docs/cron-scheduler.md create mode 100644 docs/dashy-soc-board.md create mode 100644 docs/orchestration.md create mode 100644 docs/registry-and-build.md diff --git a/docs/content-pipeline.md b/docs/content-pipeline.md new file mode 100644 index 0000000..2c192d9 --- /dev/null +++ b/docs/content-pipeline.md @@ -0,0 +1,41 @@ +# ๐Ÿ“ Content Pipeline + +> [!WARNING] +> Still under active development โ€” this covers the current shape, not a finished architecture. Included deliberately rather than only showcasing finished work. + +## The intended shape + +```mermaid +flowchart LR + Idea["Idea"] --> Draft["Drafting"] + Draft --> Review["Review"] + Review --> Approved["Approved"] + Approved --> Published["Published"] + + Approved -.glue via automation tool.-> Storage["Draft/asset staging"] + Approved -.glue via automation tool.-> Blog["Published blog"] + Approved -.planned, not yet wired.-> Social["Social platforms"] +``` + +A structured content tracker moves ideas through a status pipeline, with a workflow-automation tool gluing the stages together: approved content flows through to a file-storage staging area and out to a public blog. A separate, still-incomplete leg is meant to fan approved posts out to social platforms automatically. + +## A deliberate constraint, not a limitation stumbled into + +> [!TIP] +> The social-publishing tool is **single-origin by design** โ€” its frontend/backend URLs are baked to one specific public hostname at configuration time, so it deliberately has no presence in either internal DNS zone described in [`docs/dns-naming.md`](dns-naming.md); any other hostname hitting it fails CORS. This was a conscious choice, not a workaround: the tool is meant to be reached exactly one way. + +## A real surprise in scale + +> [!IMPORTANT] +> The social-publishing tool's real deployment footprint is 8 containers, not the app+database+cache most people would guess โ€” it bundles its own workflow engine (mandatory for all of its background jobs, including scheduled posting itself) plus that engine's own database and search-based visibility store. A useful reminder that a tool's advertised simplicity and its actual operational footprint can be very different things. + +## What's actually built vs. what's still wiring up + +| Piece | Status | +|---|---| +| Content tracker โ†’ automation โ†’ draft storage โ†’ blog | Most built-out, working end to end | +| AI-assisted drafting step | Still being wired up | +| Social-publishing fan-out | Still being wired up | + +--- +Related stacks: [`stacks/productivity/postiz/`](../stacks/productivity/postiz/), [`stacks/productivity/nocodb-content/`](../stacks/productivity/nocodb-content/), [`stacks/productivity/ghost/`](../stacks/productivity/ghost/) diff --git a/docs/cron-scheduler.md b/docs/cron-scheduler.md new file mode 100644 index 0000000..d404bef --- /dev/null +++ b/docs/cron-scheduler.md @@ -0,0 +1,32 @@ +# โฑ๏ธ Cron Scheduler + +> [!NOTE] +> A fleet-wide inventory of every known scheduled job โ€” orchestration flows, system timers, automation-managed cron, app-internal schedules โ€” visualized as a heatmap so a new job's time can be picked without colliding with existing load. + +## The heatmap + +```mermaid +flowchart LR + Sources["Job sources
flows ยท timers ยท cron ยท app schedules"] --> Table["Structured inventory
name, host, category,
schedule, duration, resource tags"] + Table --> Weight["Weighted load calculation"] + Weight --> Grid["7ร—24 grid
day ร— hour"] + Grid --> Bands["4 color bands
quieter โ†’ busier"] +``` + +Each cell's color reflects a **computed load weight**, not a raw job count: a base weight per job, plus a duration-based weight, plus resource-intensity weight (disk/CPU/network, summed if a job carries multiple tags), plus an extra weight for anything tagged as a backup job. Jobs whose time can't be resolved to a specific day and hour are excluded from the grid and listed separately rather than forced into a misleading cell. + +> [!TIP] +> Clicking a populated cell expands the full job list for that slot โ€” name, host, category, and whether that job's schedule can actually be moved or is fixed by whatever app owns it. + +## Honest about its own limitation + +> [!WARNING] +> **This is a manually-maintained inventory, not an auto-refreshed one.** The schema has a "last synced" column, but nothing currently populates it โ€” no automated process re-scans the fleet to keep this current. Entries get added or edited by hand as jobs are created or changed. Documented as a real limitation rather than presented as more automated than it actually is. + +## A real, unresolved bug this system surfaced + +> [!IMPORTANT] +> One backup job's prune step targets a datastore name that doesn't actually exist โ€” not a scheduling collision, an orphaned config that's been silently failing. Two fixes are on the table (drop the dead job and let a different retention mechanism be authoritative, or repoint it at the real datastore name and let its richer retention rules take over) โ€” deliberately not resolved yet, tracked openly rather than silently patched over with a guess. + +--- +Related stacks: [`stacks/platform/dashy/`](../stacks/platform/dashy/), [`stacks/platform/nocodb/`](../stacks/platform/nocodb/) diff --git a/docs/dashy-soc-board.md b/docs/dashy-soc-board.md new file mode 100644 index 0000000..28a32f4 --- /dev/null +++ b/docs/dashy-soc-board.md @@ -0,0 +1,75 @@ +# ๐Ÿ–ฅ๏ธ Dashy SOC Board + +> [!NOTE] +> The homelab's "one pane of glass" โ€” a single dashboard page backed by one large custom function, aggregating roughly a dozen distinct data sources that used to be scattered across separate pages into one glanceable board. + +## Fourteen data sources, one board + +```mermaid +flowchart TD + subgraph Sources["Data sources"] + direction LR + Wazuh["SOC indexer
+ compliance API"] + Loki["Log aggregator
24h error counts"] + Prom["Metrics
CPU/RAM/disk"] + Slack["Alert channels
per app + per host"] + Orch["Orchestration APIs
execution health"] + Audit["Fleet config-drift
audit feed"] + Hyper["Hypervisor API
VM/LXC tree"] + Vault["Secrets platform
seal state, lease counts"] + SSO["SSO provider
login/failure stats"] + DNS["DNS server
query stats, blocklist"] + Backup["Backup server
job age/status"] + FW["Edge firewall
state table, top rules"] + end + + Sources --> Function["One aggregation function"] + Function --> Board["SOC Board
client-rendered"] +``` + +## Layout + +A top row shows a 6-tier infrastructure dependency ladder alongside fleet-wide 24-hour KPIs. Below that, an "Active Issues" strip surfaces the worst findings fleet-wide, followed by a grid of per-host tiles โ€” worst-severity badge, error sparkline, sorted consistently. Clicking a tile expands a full detail panel: compliance and resource gauges, that host's folded-in specialty widget where relevant, and findings grouped by category. + +> [!TIP] +> **Deliberately scoped**: security/error/warning signal only. Per-app uptime is handled separately by the dashboard's own native status checks on individual tiles โ€” the only up/down signal inside this board is *infrastructure-tier* reachability, not per-app availability. Keeping the scope narrow is what keeps the board actually scannable. + +## Acknowledge mechanism: client-side, no backend + +```mermaid +flowchart LR + Finding["Finding
stable content-derived key"] --> Ack["User acknowledges"] + Ack --> Storage["Browser localStorage
flat map, per-browser"] + Storage --> Recompute["Live re-derive:
badges, issue strip, gauges"] + Recompute -.never touches.-> Upstream["Wazuh / Slack / any
upstream source"] +``` + +> [!IMPORTANT] +> Acknowledging a finding is a **display filter only** โ€” it never writes back to any upstream source, is scoped per-browser rather than fleet-wide, and a full data set for every host is always kept in memory so the recompute has everything it needs regardless of which panel happens to be open. A reload in a different browser shows the finding again โ€” a deliberate tradeoff favoring simplicity (no backend state to manage) over persistence. + +## Real integration gotchas hit building this + +> [!WARNING] +> A few non-obvious lessons from actually building this, in case they save someone else the debugging time: +> - Embedding via `fetch` + `innerHTML` means injected `