Root cause was the edge's own labelBackgroundColor style property,
left enabled with no explicit value in the app, distinct from the
label's HTML text styling entirely. Set it explicitly to #1C1D1E,
then stripped a competing plain-white background layer draw.io's
export was still injecting on top of it.
draw.io's export was auto-injecting its own adaptive-background CSS
onto edge labels regardless of source content, with a fallback that
didn't match the page. Stripped background-color entirely so labels
sit directly on the page background with nothing to mismatch.
Nextcloud's draw.io export was inconsistently applying its own
page-level adaptive-background variable to some edge labels instead
of the authored value, making them render darker than the rest.
Post-processed the exported SVG to normalize every label to the same
background value.
Add the Suricata dual-layer detail (DNS lockdown, Vault protection
each enforced independently at two layers) and honest live-backend
churn instead of a flat inventory list. Drop the disabled-LAN-frontend
TIP entirely — not worth keeping in the public doc.
Pulled every pfSense child note (Default Policy, Proxmox Firewall Usage,
VM-Level Firewalld Strategy, WAN Rules, Security Tooling, Failure Impact,
HAProxy, Aliases, DNS & Resolver Config, LAN Rules, NAT, VPN), not just
the originally-scoped subset. Live-verified firewalld defaults, SSH
rich-rule restrictions, and Vault's per-client rules directly against
the running hosts - all matched exactly.
Caught and fixed a real staleness bug in the KB itself: WAN Rules and
Security Tooling (both dated a week old) still described CrowdSec as
active, but the more recent Aliases note (updated yesterday, checked
against pfSense's actual config.xml) confirmed the package has been
fully purged. Fixed both stale notes and the public doc, which had
already inherited the wrong claim.
Folded in the staged PAM defense-in-depth note properly - found the
fuller, better story directly in the Ansible role's source comments
(the bug hit twice, on two different hosts, before the permanent fix).
Added the LAN lockdown narrative this doc was missing, the DNS
bypass-block defense-in-depth story, and the this-week port-alias
conversion. Cut two redundant Mermaid diagrams. Added real evidence:
four pfSense screenshots already validated for architecture-overview.md
reused here since they directly prove this doc's own claims, plus two
real firewall-cmd command outputs from live verification this session.
Source Trilium notes (Authentik/SSO Integration, Gitea Access Model)
live-verified and stamped this session - authentik_api_token confirmed
in vault.yml, automation-bot confirmed as a real non-admin Gitea account
via the API.
Cut the redundant Mermaid diagram (prose right below it said the same
thing). Restored the LDAP/superuser-outpost exception from the source
note as a compact callout rather than a full section, proportionate to
its actual weight. Added the real reasoning for centralizing auth and
the proxy-layer defense-in-depth argument, a table of the ~9 apps
running real native OIDC/SAML on top of the universal Proxy Provider
baseline (corrected after initially misstating it as either/or), the
provider-removal discipline, and two screenshots proving the Write/
Read-only access split instead of just asserting it.
Edge-label captions were using an ambient light-dark() background that
didn't match Gitea's actual dark theme. Fixed to a plain rgb(13,17,23),
matching the corrected CLAUDE.md diagram convention.
Source Trilium note (Secret Distribution Model) was live-verified and
stamped this session - Certificate Flow, App Secret Flow, Configuration
Distribution Flow, and the technitium_api_token Vault-KV exception all
confirmed against the real hosts, not just trusted from the KB.
Replaced the Mermaid diagram, which incorrectly showed the Configuration
Distribution Flow feeding into Vault directly, with a corrected version:
that flow is a control-plane gate on what Vault Agent renders, not a
peer data flow. Added a real Vault Agent log excerpt proving the full
redeploy chain (restart -> render -> reload_command -> container
recreated) actually happens, and restored the technitium_api_token
exception that was in the source note but missing from this doc.
Publishing a ranked list of a live system's own weak points - including
explicitly naming the "single highest-value target" - is a real security
problem for a public repo, not a portfolio concern. Removed the section
and the matching stat-table row. Left the fixed cert-renewal incident in
place since there's nothing exploitable in a resolved issue.
Access model now covers the narrowly-scoped read-only utility policies
(ansible sync roles, metrics-reader, pki-reader) alongside the
host-scoped svc-* pattern - the fuller picture confirmed live against
Vault's actual ACL policy list, not just the original narrower claim.
Added two real screenshots: the live policy list, and an actual audit
log entry showing a KV read with every secret value as hmac-sha256:...
instead of plaintext - proof for the audit-trail claim instead of just
an assertion of it.
Removed the before/after plaintext-secrets Mermaid diagram (the bullets
right below it already said the same thing more clearly) and the
Architecture flowchart (its per-host labels were silently missing
registry-token and Hawser-secret details that didn't fit in the boxes).
Replaced the flowchart with an accurate, complete "who gets what" table
sourced directly from Trilium, one sentence for the proxy-routing fact
it was trying to show, and a new sequence diagram for the actual secret
lifecycle (auth -> render -> read -> async rotation) - a genuinely
different kind of diagram since that's process/time-ordered information
a table can't represent well.
Also restored real specificity Trilium had that the doc had flattened
into vague language: the cert-renewal row now names Technitium
specifically and states the actual incident (an expired cert silently
served for 19 days before the auto-rebuild hook existed), and the audit
trail row now states the real rotation policy (weekly, 8 generations).
Removed the dependency-graph Mermaid diagram (had backwards Vault
dependency arrows, contradicted the documented ingress model) and the
four-boundary Mermaid diagram (fully redundant with the table below it).
Verified every remaining claim against Trilium's Traffic Flow Model and
fixed two that didn't hold up: the DNS-bypass failure behavior overstated
an unconditional SSO bypass, and the notifications failure behavior had
an unsourced claim about backlog retry.
Added the real story behind the trust-boundary hardening (three stages
of the same "trusted by default" flaw getting found and closed, not a
changelog of dated facts), a new trust-depth diagram showing that being
"inside the network" still enforces its own internal boundaries, and
real pfSense screenshots proving the LAN lockdown rules actually exist
in production, not just in the doc.
Two real, working scripts: an Ansible playbook run-menu (the same
pattern used identically across both the fleet-config and infra-
provisioning repos), and cuesplit, a frame-accurate FLAC/.cue
splitting utility. Sanitized paths only, logic untouched.
README gets a new "Scripts & Tooling" section pointing at it, placed
right after "What's Running" alongside the other repo-structure
pointer.
Adds the two Dockerfiles nextcloud's compose build: contexts were
already referencing but never had (custom smbclient support for the
main container, a documentserver base for euro-office) - both had
their real internal-CA-trust mechanism replaced with a comment
explaining what's there and why, per the standing rule against
publishing that detail.
Also fixes a compose bug: kodi-ma-bridge's `environment:` key had
been dropped, leaving KEY=value pairs nested under `env_file:` (which
expects file paths, not inline vars) - would have made docker-compose
try to open a file literally named `TZ=${TZ}`.
Rest of this commit is manual cleanup (comment tidying, dropped
personal media-library volume mounts, a couple of .env comment
trims).
Replaced 29 host-port values across 27 stacks with the app's own
well-known default (read directly from each compose file's own
container-side port mapping) instead of the arbitrary port numbers
this fleet actually assigns to avoid collisions across ~44 services
on one host. More recognizable to a reader, and removes the last
fleet-specific detail these files carried.
Also removes a stale adminer service from n8n's stack entirely
(compose service + .env var) - it's no longer actually deployed.
Adds cloudcommander and it-tools .env files. it-tools' private .env
had two real domains (it-tools.local.thesabear.net,
it-tools.thesabear.net) that the public compose file doesn't
reference at all, so dropped along with the unused DOCKER var.
This completes .env/.env.secrets.ctmpl coverage across every
category in stacks/ (identity-and-security, platform, media,
productivity, dev-tools) - every var cross-checked against its
actual public compose file throughout.
Adds real Vault Agent secret templates and non-secret env vars for
all 9 productivity stacks. Sanitized several real leaks: a real
residential public IP (ghost's UMAMI_IGNORE_IP), real personal
domains/email used across joplin/n8n/ghost/paperless-ngx, and a
real embedded OAuth client ID in paperless-ngx's private OIDC config
(dropped entirely - the public compose file doesn't wire up OIDC at
all, so none of that config was needed).
Also dropped n8n secrets referencing the out-of-scope Content
Pipeline system (nocodb-content, Postiz), and did not carry over a
real Redis password found sitting in plaintext in nextcloud's
private .env (unused by the public compose anyway).
Every var cross-checked against what its actual public compose file
references before writing.
Adds real Vault Agent secret templates and non-secret env vars for
all 15 media stacks. Sanitized real leaks found in the source data:
kodi's real username and real hostnames+IPs in KODI_INSTANCES,
tdarr's real IP and hostname reference, immich's real username -
replaced with generic values (IPs use the RFC 5737 documentation
range, 192.0.2.0/24, rather than another real-looking address).
Every var cross-checked against what its actual public compose file
references before writing, learned from the platform batch.
Adds real Vault Agent secret templates and non-secret env vars for
14 platform stacks (caddy, caddy-internal, dashy, docker-socket-
proxy, dockhand, gitea, grafana-stack, hawser, kestra, kopia,
mosquitto, nocodb, node-red, speedtest-tracker), sanitized per the
kv/host/... convention established for identity-and-security.
Also fixes 6 variable-name mismatches discovered while cross-checking
every new .env/.ctmpl against what its actual public compose file
references (the public compose files were already adapted from the
private originals during an earlier reorg pass, so names don't always
match 1:1): authentik (PG_USER/PG_DB/PG_PASS), gitea (added
GITEA_DOMAIN/GITEA_ROOT_URL, dropped an unused duplicate-instance pair
of vars), grafana-stack (GRAFANA_ADMIN_PASSWORD), kestra (added
USERNAME/PASSWORD), kopia (SECRET_PASSWORD), nocodb (JWT_SECRET).
Also dropped an unused DOCKER var across most stacks where the
compose file hardcodes its path instead of referencing it.
Adds the real Vault Agent secret-rendering templates and non-secret
env vars for authentik, vaultwarden, and wazuh - sanitized (real
domain/email/SMTP provider genericized, kv/<host>/... paths use a
generic 'host' placeholder instead of the real hostname). This is
the standing pattern for the rest of stacks/ going forward.
Removed: ollama (AI-related concerns), emby/qbittorrent/prowlarr
(media-download concerns), bookstack (being deprecated in the
homelab), and mariadb-latest/mysql-8/postgres-18-alpine/valkey-9.0-
alpine (build-only base-image factories - nothing connects to them,
they add no portfolio value on their own; real per-app DB usage,
e.g. Kodi's own mariadb service, already lives in that app's stack
folder and is unaffected).
Stack count updated 53 -> 44 in both README references.
- Heading capitalization consistent Title Case throughout
- Moved the three personal sections (What I Have Explored, What I'm
Focused On, Fun Fact) from between the two strongest technical
sections to after Systems/What's Running, as a closing note -
keeps momentum through the trust-boundary story into the diagram
and full Systems breakdown, and gives the page a real closing beat
instead of ending on a bare stack-count line
- Split the dense opening bio paragraph into two at its natural seam
Adds a real drawio-exported diagram to "Why Four Trust Boundaries,
Not One" showing the physical blast-radius split behind the routing-
authority claim in the text (previously prose-only). Sanitized -
verified no real hostnames/IPs made it into the export.
Also reorders all 14 Systems bullets alphabetically for easier
scanning.
Fixed one real inaccuracy (Gitea registry scope, Secret Distribution
path count) and replaced generic descriptions with true, specific
detail across all 14 Systems bullets - real incidents, real design
decisions, concrete numbers - each checked against Trilium and, where
relevant, the live Ansible roles rather than trusted at face value.
Also stages a verified-but-unintegrated PAM hardening finding in
docs/firewall-and-network.md for that file's own upcoming audit pass.
1.15em rendered fine (sanitizer allow-list is working, confirmed by
the stat-table numbers) but was too subtle a jump to actually look
bigger next to body text. 1.4em matches the visual weight of the
stat-strip numbers.
Gitea's default sanitizer strips style="" from every element; added a
markup.sanitizer allowlist for style on common tags (app.ini, this
Gitea instance) and restarted the service. Now that it's actually
respected: table centered via style="margin:0 auto" (the deprecated
align attribute never reliably did this), and the app-name font-size
bump from the last commit should finally show up too.
Plain markdown bold has no size control. Switched each app name to
<strong style="font-size:1.15em;"> so it stands out from the
description text instead of just being the same size in bold.
align="center" and a div wrapper both failed to actually center the
table under Gitea's markdown CSS. Rather than keep guessing, dropped
the centering attempt - table stays left-aligned with its per-cell
alignment intact.
align="center" on <table> itself gets ignored - Gitea's markdown CSS
forces tables to display:block for horizontal scroll, which breaks
that attribute. Wrapping in <div align="center"> actually centers it.
Table wasn't centered on the page (only text inside cells was) - added
align=\"center\" to the table itself. Icons were arbitrary; replaced
with ones that actually trace back to the related system's own icon
elsewhere in this doc (Docker whale for compose stacks, the brick wall
from Firewall & Network Segmentation for trust boundaries, the same
cycle icon Orchestration uses for automated maintenance flows).
Both paragraphs made the same 'this isn't a hobby, it's a platform'
point and duplicated tech mentions. Merged into one section: a
bulleted list naming the real technology behind every category
(Vault for secrets, Kestra for orchestration, Wazuh+Dashy for the
SOC, etc.) instead of one dense paragraph or abstract category names.
Caption claimed all dashed arrows were telemetry reporting, but two
of them ('proxies', 'auth') are traffic-routing relationships, not
telemetry - the old caption was written for the full 20-edge version
and never updated when the diagram got simplified.
No more Mermaid, per standing rule - the architecture diagram is now
a real image (assets/architecture-overview.svg), built and exported
from the Nextcloud Diagramming app, fetched via WebDAV and validated
as well-formed XML before committing. Ends the rendering-gap chase
entirely since there's no live renderer involved anymore, just a
static image.
Restructured so the edge proxy feeds forward into the app host
(EdgeProxy -.proxies.-> Apps) instead of the app host and SSO
pointing backward into the proxy - matches the real traffic
direction (browser hits the proxy first, which routes to the app)
and removes the long rank-spanning edges that were likely behind the
rendering gap. Full node set restored, colors restored.
7 nodes, strictly tree-shaped, no diamond merges (removed DNS-->Vault
and the EdgeProxy/SSO/InternalProxy branch entirely). If gap is gone,
the rejoining-path topology is the trigger, not raw node count.
Diagnostic bisection - if gap reappears here, it's a size/complexity
threshold. If not, the trigger is specifically in the observability
fan-in portion I removed for this test.
Diagnostic only - not a real content change. If the gap disappears
with this minimal diagram, it's genuinely about the real diagram's
content and I can bisect from here. If the gap is still there, it's
not about the diagram at all.
None of edge reduction, classDef removal, or multi-line label removal
fixed the gap - restoring the full diagram content since there's no
reason to keep it degraded for a fix that didn't work.
Compared node labels between this diagram and the non-gapped
architecture-overview.md one - this had 5 two-line labels, that one
only 2. Reduced to 1 remaining (App Host) to test whether multi-line
label height is what's breaking the container size calculation.
The docs/architecture-overview.md diagram has no custom colors and
renders with no gap; this one had classDef/class styling and did.
Real comparative evidence, not a blind guess this time - testing
whether custom fill/stroke styling is what's breaking Gitea's
container height calculation.
8 dotted 'reports to' edges converging on 2 nodes was likely forcing
Mermaid's layout engine to reserve extra vertical space for edge
routing in the LR diagram, leaving a large blank area below the
visible nodes. Reduced to 2 representative edges - can't verify the
actual render myself, this is a targeted guess to test.
Removed by request - it was the one unfinished system in the repo,
and it's the user's call on what represents them publicly. Removed
completely: the README section, docs/content-pipeline.md, and the
two stacks that only existed to back it (postiz, nocodb-content).
Fixed cross-links in dns-naming.md and firewall-and-network.md that
pointed at it (replaced the public-facing-app example with the
still-present Ghost blog), and notifications.md (dropped the
reference, also fixed a leftover 'documented rather than hidden'
phrase in the same paragraph). Stat strip and stack counts updated
(55->53 stacks, 16->15 systems).
The 'Every system, at a glance' table and the Systems section covered
the same 16 things in the same order - removed the table, kept the
narrative section since that's where the actual selling has to happen.
Rewrote every paragraph to lead with what the system does and its
real scope (what an employer needs to see) instead of a debugging
edge case or failure-mode trivia standing in for the description.
README is short/sharp facts, docs/*.md is full depth/process/reasoning
- that split had collapsed into either near-duplicate text in both
files or the fact missing from the README entirely. README now states
the OIDC zone-crossing fact in one crisp sentence; the full doc has
the complete step-by-step breakdown. The LDAP superuser workaround
claim is removed from both - the source material frames it as
contingency guidance for a scenario that may never have come up, not
documented deployed behavior, so it doesn't belong in either file.
The source material says header-based forward-auth is 'the viable
path instead, if SSO is wanted for an app like this' - conditional
contingency guidance for a scenario that may not have actually come
up, not a description of something built and running. Presenting it
as live, deployed behavior in both docs/sso-and-identity.md and the
README was a real accuracy error. Removed from both; the doc still
covers what's actually confirmed (Authentik, dual-zone registration,
native OIDC vs. proxy forward-auth split, least-privilege Git access).
The paragraphs were near-duplicates of each doc's own opening -
added nothing beyond the table row above them or the full doc below.
Rewrote every one to lead with a different angle/detail than its
doc (the before/after story, the concrete failure math, the vendor
limitation) instead of restating the same sentences shorter. Also
removed every remaining 'documented honestly / rather than hidden /
caught during an audit' self-referential phrasing - state the fact,
don't narrate the act of documenting it.
New opening frames the repo as a platform, not a media-server-plus-
extras, and draws the direct line to what an employer's platform/
security team actually owns. Added a condensed version of the
trust-boundary story (the single hardest decision in the repo) right
in the README, ahead of the systems table, so a reader gets a real
'this person thinks like an engineer' moment before they even scroll.
Diagram: switched to LR layout, color-coded by role (edge/core/
observability), dropped the repeated 'reports to' labels that were
clipping in the browser - same fix already applied to
architecture-overview.md. Added a stat strip (11 hosts, 55 stacks,
16 systems, 4 boundaries, 13 flows) and a scannable systems-at-a-
glance table above the detailed sections, so a reader gets the full
picture in a 10-second skim before the deeper 2-minute read.
Actually fixed the 4 problems named earlier instead of just naming
them: a numbers-at-a-glance strip up top, the trust-boundary diagram
is now color-coded by boundary type, added a concrete blast-radius
table showing exactly what stays up vs goes down per failure, and
replaced vague figures ('~40 stacks', 'every managed host') with
real ones (41 stacks, 11 fleet hosts).
Leads with the hardest actual decision (4 trust boundaries, each
calibrated to a different risk level, enforced by 3 separate routing
authorities) given full narrative depth instead of flat template
treatment. The other 6 traffic laws move to a compact reference
table linking to their own dedicated docs, avoiding redundancy with
content that already lives elsewhere in more depth.
Previously only delivered 2-3 of the 8 traffic categories it claimed
to cover. Now states every law (ingress, east-west, secrets, DNS,
observability, notifications, CI/CD, management) with its own
Law/Flow/Failure-behavior, plus the 4 global architectural laws that
apply across all of them.
Switch to LR layout, restore all 12 observability-reporting edges
(cut them by mistake, they belong in a dependency graph), drop the
repeated 'reports to' label text that was breaking legibility and
let line style (solid=dependency, dotted=async reporting) carry that
distinction instead. Also shortened edge labels on the trust-boundary
diagram to avoid the same clipping issue.
Completes the full depth pass across all 16 docs - real tech names,
why-this-vs-the-alternative reasoning, heavy cross-linking between
every doc, ranked/specific tradeoffs instead of generic hedges.
Same treatment as the security batch - real tech names, 'why this vs
the alternative' reasoning, heavy cross-linking, ranked/specific
tradeoffs instead of generic hedges.
Names real technology throughout (Vault, Authentik, pfSense,
firewalld, Wazuh) instead of staying generic, adds a 'why this
approach vs the alternative' section to every doc, cross-links
heavily between docs instead of treating each system in isolation.
Names real technology (Technitium) instead of staying vague, adds
explicit cross-links to every other system that actually depends on
DNS, explains why self-hosted DNS was chosen, and adds a 'what I'd
improve' section. Template for redoing the rest.
Replaces rocky-docker-lxc/, caddy-vm/, monitoring-vm/, and automation/
with a single stacks/ tree grouped by function (identity-and-security,
platform, media, productivity, dev-tools) - 55 stacks total, every one
a real directory in the private repos today.
Resolves all outstanding drift: adds Wazuh (previously entirely
missing), Firefly III and Postiz (never mirrored), the internal-zone
Caddy instance and the Cron Scheduler's separate NocoDB instance
(both previously conflated with their counterparts under one shared
folder); renames Calibre-Web to calibre-web-automated and splits
Kodi-MariaDB into kodi + mariadb-latest to match what the private
repos actually run now; drops the decommissioned NetBox stack.
Also removes automation/iac/old-content/ and
automation/ansible/old-playbooks/ in the same pass - both folders
were entirely stale content describing a superseded architecture
(GitLab instead of Gitea, HAProxy+mTLS SSO instead of the current
Caddy+Authentik setup), now replaced by docs/iac-pipeline.md.
Completes the docs/ set - all 16 systems now have a full deep-dive
page in the diagram+callout+table style. Architecture overview covers
the 4 trust boundaries and 2 traffic planes; IaC pipeline is a fresh
Packer->Terraform->Ansible writeup replacing the stale old-content/
snapshot; disaster recovery covers the 3 independent backup layers
and the deliberately decoupled OS/app upgrade systems.
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.
Mermaid diagrams up front, GFM alert callouts for key decisions and
gotchas, tables instead of bullet walls - replaces plain prose with
the visual template established for security-monitoring.md.