Townhouse Infrastructure

Mega-schema over hela miljon KOMPLETT 2026-05-09

6 vyer - live-verifierad mot brain + monitor + townhouse-gitea + ai-rag + alla 4 webb-VPSes + Tailscale-status

VI - KOMPLETT inventering (det riktiga mega-schemat)

Detta ar groundtruth (senast verifierad 2026-05-09)

Det har ar diagrammet du bad om fran borjan. Alla 14 Caddy-subdomaner som egna noder, alla 23 Tailscale-enheter (aven offline), varje docker-container per host med image+port+status (47 containers totalt: brain 6, ai-rag 5, lyxlimo-webb 2, underbar-webb 6, monitor 5, townhouse-gitea 18, demopc 5+), alla extra brain /opt-mappar (townhouse-generator, headplane, mcp-hub, traefik-legacy), brain-lyxlimo-bot RESTARTING-status, alla 6 externa cloud-APIs.

Live-data hamtad via SSH till brain, monitor, townhouse-gitea (townhouse-gemma4-2-cred), ai-rag, lyxlimo-webb, underbar-webb. Tailscale-listan visar 23 enheter totalt - 7 aktiva tagged servers + 16 personliga klienter (varav manga offline). monitor-cax11 har en EGEN Caddy (cloudflare-build) som routar ops.ibo.se - inte brain Caddy som jag forst trodde. townhouse-gitea har 18 containers inklusive 2 swarm tasks fran Dokploy + 3 file-drop containers (pingvin + filebrowser + caddy).

I - Network Topology and Hosts

Forsta versionen - se KOMPLETT for groundtruth.

II - Application Flows

4 sequence-flow: Lyxlimo booking, Underbar review, CMS-deploy, Patch-approval.

III - Monitoring and Automation

Push-monitoring + execution + Stats-pipeline + Veo+YouTube+Pmax.

IV - n8n Workflows and ops Dashboards

37 workflows i 6 kategorier + 10 ops-sub-pages.

V - Service Catalog dashboard.imagesbyolofsson.se

41 tjanster i 5 sektioner enligt /srv/dashboard/index.html SECTIONS-array.

---
config:
  theme: dark
  layout: elk
title: Townhouse Infra — Network Topology & Hosts (2026-05-07)
---
flowchart LR
    %% ============ EXTERNAL ============
    subgraph EXT["INTERNET / EXTERNAL"]
        direction TB
        USR["End-Users<br/>browsers, mobile"]
        CF["Cloudflare<br/>DNS + Proxy + WAF<br/>orange-cloud for public<br/>DNS-only for admin"]
        GOOG["Google APIs<br/>Ads, GA4, Calendar,<br/>Gmail, Sheets, Veo, Gemini"]
        DISCORD["Discord<br/>Webhooks for alerts"]
    end

    %% ============ HETZNER ============
    subgraph HZ["HETZNER CLOUD - hel1-dc2 - 5 VPS - 32 EUR/mo"]
        direction TB

        subgraph BRAIN["BRAIN - CX33<br/>pub 204.168.246.143<br/>ts 100.91.241.68"]
            BR_CADDY["Caddy host-installed<br/>:80 :443<br/>TLS-termination ALL subdomains<br/>Let's Encrypt + DNS-01"]
            BR_N8N["n8n :5678<br/>27 workflows, 6 active<br/>Postgres + Redis + Workers"]
            BR_PG["Postgres<br/>monitoring DB + lyxlimo_stats<br/>+ lyxlimo_bookings"]
            BR_REDIS["Redis cache"]
            BR_EXEC["brain-approve-exec<br/>:9991 docker container<br/>SSH-out to 7 hosts"]
            BR_STATS["Flask stats.imagesbyolofsson.se<br/>systemd timer 06:00 daily"]
            BR_VEO["/srv/lyxlimo-veo<br/>veo_client.py + ffmpeg"]
        end

        subgraph AIRAG["AI-RAG - CX33<br/>pub 89.167.42.116<br/>ts 100.120.67.52"]
            AR_API["FastAPI :8000<br/>Acrylic-Nexus RAG"]
            AR_CHROMA["ChromaDB<br/>vector store"]
            AR_MCP["townhouse-mcp :1337<br/>aggregator"]
            AR_DIUN["Diun cron 6,18<br/>21 image watchers"]
            AR_VER["mon-service-versions.py<br/>cron 30 6,18"]
        end

        subgraph LYXWEBB["LYXLIMO-WEBB - CX23<br/>pub 204.168.245.109<br/>ts 100.75.14.44<br/>label underbar-4gb sic"]
            LX_NGINX["nginx :80 :443<br/>lyxlimo.se + www<br/>+ lyxlimo.imagesbyolofsson.se"]
            LX_HTML["/srv/lyxlimo/www/index.html<br/>direct-edit no Astro build<br/>5-step booking form"]
        end

        subgraph UNDERBARWEBB["UNDERBAR-WEBB - CX23<br/>pub 204.168.249.189<br/>NOT on Tailscale"]
            UB_NGINX["underbar-nginx container<br/>:80 :443<br/>3 server blocks<br/>bind-mount /srv/underbar/www-stage"]
            UB_CONTENT["underbar-content-api<br/>:8002 FastAPI<br/>products + images + notices"]
            UB_REVIEWS["underbar-reviews-api<br/>:8001 FastAPI<br/>+ Gemini auto-validation"]
            UB_PG_C["underbar-content-postgres"]
            UB_PG_R["underbar-reviews-postgres"]
        end

        subgraph MONITOR["MONITOR-CAX11 - ARM<br/>pub 65.21.252.57<br/>ts 100.123.194.33<br/>hostname montior-cax11 sic"]
            MO_BESZEL["Beszel hub :8090<br/>6 agents"]
            MO_GLANCES["Glances :61208<br/>PyPI v3.4.0.5"]
            MO_KUMA["Uptime Kuma"]
            MO_HEALTH["Caddy health-staging<br/>+ approve-SPA"]
        end
    end

    %% ============ LAPTOPS ============
    subgraph LAPS["LOCAL LAPTOPS - on Tailscale"]
        direction TB

        subgraph TLLM["TOWNHOUSE-LLM - i7-1265U<br/>ts 100.71.126.116<br/>laptop"]
            OLLAMA["Ollama :11434<br/>Qwen3-4B Q4_K_M<br/>Gemma-4-E4B Q4_K_M<br/>OpenAI-compat /v1/"]
        end

        subgraph TGITEA["TOWNHOUSE-GITEA - Core Ultra 5<br/>ts 100.83.211.41<br/>laptop"]
            GITEA["Gitea :3000<br/>1.22.6<br/>Actions runner + Packages"]
            ONEDEV["OneDev :6610<br/>5 repos in Townhouse hierarchy"]
            DOKPLOY["Dokploy :3001<br/>swarm orchestrator"]
            SB["SilverBullet :3030<br/>3 areas Linus/Claude/Gemini"]
            AUTH["Authelia"]
            FILEDROP["file-drop"]
            CMS_SWARM["underbar-underbaradminui-j6sver<br/>swarm task nixpacks staticfile"]
        end

        DEMOPC["DEMOPC-TOWNHOUSE<br/>ts 100.75.223.96<br/>Windows laptop<br/>Ollama Desktop shared"]
    end

    %% ============ EDGE / VPN ============
    subgraph EDGE["EDGE / VPN INFRA"]
        OPNS["OPNsense<br/>ts 100.127.190.86<br/>FreeBSD<br/>exit-node + Unbound DNS<br/>Web UI :4443"]
        OPNS_DASH["vps-opnsense-dashboard<br/>ts 100.103.201.116"]
        TS_CTRL["Tailscale Coordinator<br/>CGNAT 100.64.0.0/10<br/>tag townhouse + ACL"]
    end

    %% ============ HARDWARE OFF-LINE ============
    Z10["z10PE-D16-WS<br/>OFFLINE 2026-05-06<br/>2 x Xeon E5 v4 + 1TB ECC<br/>17 disks + GTX 1070 Ti<br/>no-POST diag pending"]

    %% ============ FLOWS ============
    USR -->|HTTPS 443| CF
    CF -->|HTTPS to origin| BR_CADDY
    CF -->|DNS-only admin paths<br/>via Tailscale ACL| BR_CADDY

    BR_CADDY -->|reverse_proxy<br/>lyxlimo.se| LX_NGINX
    BR_CADDY -->|reverse_proxy<br/>underbar.site<br/>via VPS-net 10.0.0.5| UB_NGINX
    BR_CADDY -->|reverse_proxy<br/>reviews.imagesbyolofsson.se| UB_REVIEWS
    BR_CADDY -->|/api proxy<br/>admin.underbar.site| UB_CONTENT
    BR_CADDY -->|reverse_proxy<br/>admin.underbar.site default| DOKPLOY
    BR_CADDY -->|reverse_proxy<br/>n8n.imagesbyolofsson.se| BR_N8N
    BR_CADDY -->|reverse_proxy<br/>onedev.imagesbyolofsson.se| ONEDEV
    BR_CADDY -->|reverse_proxy<br/>silverbullet.ibo.se| SB
    BR_CADDY -->|reverse_proxy<br/>stats.ibo.se Tailscale-only| BR_STATS
    BR_CADDY -->|reverse_proxy<br/>ops.ibo.se Tailscale-only| MO_HEALTH

    UB_NGINX -.->|reads bind-mount| UB_CONTENT
    UB_REVIEWS -->|Postgres| UB_PG_R
    UB_CONTENT -->|Postgres| UB_PG_C
    UB_REVIEWS -->|HTTPS| GOOG

    LX_NGINX -.->|POST booking| BR_N8N
    BR_N8N -->|Postgres| BR_PG
    BR_N8N -->|HTTPS| GOOG
    BR_N8N -->|webhook| DISCORD
    BR_N8N -->|HTTP| BR_EXEC
    BR_N8N -->|Ollama API| OLLAMA
    BR_EXEC -->|SSH out| AIRAG
    BR_EXEC -->|SSH out| LYXWEBB
    BR_EXEC -->|SSH out| UNDERBARWEBB
    BR_EXEC -->|SSH out| MONITOR
    BR_EXEC -->|SSH out| TGITEA

    BR_STATS -->|reads| BR_PG
    BR_VEO -->|HTTPS| GOOG

    DOKPLOY -->|deploys swarm task| CMS_SWARM
    DOKPLOY -.->|pulls source| ONEDEV
    GITEA -.->|repos mirror eval| ONEDEV

    AR_DIUN -->|webhook| BR_N8N
    AR_VER -->|webhook| BR_N8N

    MO_BESZEL -->|TCP agents| BRAIN
    MO_BESZEL -->|TCP agents| AIRAG
    MO_BESZEL -->|TCP agents| LYXWEBB
    MO_BESZEL -->|TCP agents| UNDERBARWEBB
    MO_BESZEL -->|TCP agents| TGITEA
    MO_GLANCES -.->|HTTP polls| BRAIN
    MO_GLANCES -.->|HTTP polls| AIRAG

    DEMOPC -.->|Tailscale<br/>SSH+VS Code| BRAIN
    DEMOPC -.->|Tailscale<br/>SSH+VS Code| LYXWEBB
    DEMOPC -.->|Tailscale<br/>SSH+VS Code| UNDERBARWEBB

    OPNS -.->|exit-node + DNS<br/>CGNAT rebind override| TS_CTRL
    TS_CTRL -.->|tag townhouse ACL| BRAIN
    TS_CTRL -.->|tag townhouse ACL| AIRAG
    TS_CTRL -.->|tag townhouse ACL| LYXWEBB
    TS_CTRL -.->|tag townhouse ACL| MONITOR
    TS_CTRL -.->|tag townhouse ACL| TLLM
    TS_CTRL -.->|tag townhouse ACL| TGITEA
    TS_CTRL -.->|tag townhouse ACL| DEMOPC

    classDef brainNode fill:#4a148c,stroke:#fff,color:#fff
    classDef dataNode fill:#1b5e20,stroke:#fff,color:#fff
    classDef webNode fill:#0d47a1,stroke:#fff,color:#fff
    classDef adminNode fill:#f57f17,stroke:#000,color:#000
    classDef aiNode fill:#b71c1c,stroke:#fff,color:#fff
    classDef extNode fill:#37474f,stroke:#fff,color:#fff
    classDef offlineNode fill:#424242,stroke:#f00,stroke-width:3px,color:#fff,stroke-dasharray: 5 5

    class BR_CADDY,BR_N8N,BR_EXEC,BR_STATS,BR_VEO brainNode
    class BR_PG,BR_REDIS,UB_PG_C,UB_PG_R,AR_CHROMA dataNode
    class LX_NGINX,LX_HTML,UB_NGINX,UB_CONTENT,UB_REVIEWS webNode
    class GITEA,ONEDEV,DOKPLOY,SB,AUTH,FILEDROP,CMS_SWARM,MO_HEALTH,MO_BESZEL,MO_GLANCES,MO_KUMA adminNode
    class OLLAMA aiNode
    class USR,CF,GOOG,DISCORD,OPNS,OPNS_DASH,TS_CTRL,DEMOPC extNode
    class Z10 offlineNode
---
config:
  theme: dark
title: Townhouse — Application Flows (Lyxlimo Booking + Underbar Reviews + Deploy)
---
sequenceDiagram
    autonumber
    actor User as End-User
    participant CF as Cloudflare
    participant Caddy as Caddy on brain
    participant LXN as lyxlimo-webb nginx
    participant N8N as n8n on brain
    participant PG as brain Postgres
    participant Sheet as Google Sheets
    participant Cal as Google Calendar
    participant Gmail as Gmail
    participant Disc as Discord
    participant Approver as Approver web SPA
    participant UBN as underbar-webb nginx
    participant RevAPI as Reviews API 8001
    participant Gemini as Google Gemini
    participant CMS as admin.underbar.site CMS
    participant ContAPI as Content API 8002
    participant OneDev as OneDev
    participant Dokploy as Dokploy
    participant Swarm as docker swarm task

    Note over User,Sheet: FLOW A — Lyxlimo booking lyxlimo.se
    User->>CF: GET lyxlimo.se 5-step form
    CF->>Caddy: HTTPS to origin
    Caddy->>LXN: reverse_proxy
    LXN-->>User: HTML + GA4 + Maps JS
    User->>LXN: POST form fields
    LXN->>N8N: POST /webhook/lyxlimo-booking
    N8N->>N8N: Normalize + parse message
    par Storage and observability
        N8N->>PG: INSERT booking
        N8N->>Sheet: append row 26 cols
        N8N->>Disc: webhook notify
    end
    N8N-->>User: 202 Accepted thank-you
    Note over Approver,Cal: Manual approval gate
    N8N->>Approver: generate UUID-token<br/>POST approve.ibo.se URL to Discord
    Approver->>N8N: GET /api/approval/:token
    Approver->>N8N: POST /api/submit decision
    alt approved
        N8N->>Cal: createEvent OSA-invite
        N8N->>Sheet: write calendar_event_link
        N8N->>Gmail: send confirmation
    else rejected
        N8N->>Sheet: status=rejected
    end

    Note over User,Gemini: FLOW B — Underbar review submit underbar.site
    User->>CF: GET underbar.site/leksaker.html
    CF->>Caddy: HTTPS
    Caddy->>UBN: via VPS-net 10.0.0.5
    UBN-->>User: HTML + product-modal.js v=20260505-1
    User->>UBN: open product modal click
    UBN-->>User: render reviews from localStorage
    User->>RevAPI: OPTIONS preflight
    RevAPI-->>User: CORS headers underbar.site OK
    User->>RevAPI: POST /api/reviews body
    RevAPI->>RevAPI: Pydantic validate 5-1000 chars
    RevAPI->>PG: INSERT status=pending
    Note right of RevAPI: stored in underbar-reviews-postgres
    RevAPI->>Gemini: POST validate review
    Gemini-->>RevAPI: approve OR reject + reason
    RevAPI->>RevAPI: UPDATE status + llm_reason
    opt admin notify
        RevAPI->>Disc: webhook
    end
    RevAPI-->>User: 202 Accepted

    Note over User,Swarm: FLOW C — CMS deploy admin.underbar.site
    User->>CF: GET admin.underbar.site
    CF->>Caddy: HTTPS DNS-only
    Caddy->>Caddy: Tailscale ACL 100.64.0.0/10
    Note right of Caddy: path-routing<br/>/api/admin/reviews -> RevAPI<br/>/api + /uploads -> ContAPI<br/>default -> Dokploy nginx
    Caddy->>Swarm: serve CMS index.html
    User->>CMS: edit product or upload image
    CMS->>ContAPI: POST /api with admin-auth
    ContAPI->>PG: write underbar-content-postgres
    Note over OneDev,Swarm: When code changes
    User->>OneDev: git push to townhouse/underbar-admin-ui
    OneDev-->>Dokploy: webhook
    Dokploy->>OneDev: pull source nixpacks build
    Dokploy->>Swarm: rolling update underbar-underbaradminui-j6sver
    Swarm-->>CMS: new image live ~1.4s

    Note over User,Disc: FLOW D — Patch approval health-staging
    User->>Caddy: GET ops.ibo.se/health-staging
    Caddy->>Caddy: Tailscale-only OPNsense override
    Caddy-->>User: serve dashboard JSON
    User->>Caddy: click Approve action
    Caddy->>N8N: POST /webhook/mon/approve-action
    N8N->>N8N: validate type host target
    N8N->>RevAPI: out of scope
    N8N->>PG: INSERT approval_log
    N8N-->>User: 200 ok
---
config:
  theme: dark
  layout: elk
title: Townhouse — Monitoring & Automation Pipeline (2026-05-07)
---
flowchart TB
    subgraph SRC["DATA SOURCES — push-cron from each host"]
        direction LR
        S1["mon-patch-report.sh<br/>cron 09:30 UTC daily<br/>6/7 hosts<br/>missing: townhouse-llm"]
        S2["mon-image-local.sh<br/>cron 06:00 18:00 UTC<br/>6 hosts docker digest scan"]
        S3["Diun on ai-rag<br/>cron 06:00 18:00<br/>21 public-registry images"]
        S4["mon-service-versions.py<br/>cron 06:30 18:30<br/>HTTP-version probes<br/>+ GitHub Releases"]
        S5["mon-cve-image.sh<br/>per-image trivy<br/>triggered after image change"]
        S6["mon-cve-fleet-sweep.sh<br/>weekly cron Sun 04:17"]
        S7["mon-disk-report.sh<br/>existing pre-2026-05"]
    end

    subgraph N8N["N8N WEBHOOK RECEIVERS — on brain :5678"]
        direction TB
        W1["[MON] Patch webhook receiver<br/>Pzu8d4DTU0W6YplC<br/>→ Fetch prev state<br/>→ Parse + classify<br/>→ NOT IN auto-resolve<br/>→ alert_needed gate"]
        W2["[MON] ImageLocal webhook<br/>tiw9OuuBsIYnMmWw"]
        W3["[MON] Image webhook receiver<br/>737UwzzYVCOfGd5z<br/>(Diun source)"]
        W4["[MON] ServiceVersion webhook<br/>PcXY3Z0EeXvKMZbM"]
        W5["[MON] Approve action<br/>5ynBxmhR5HlfRoH2<br/>tightened ok contract<br/>single-read-then-parse"]
        W6["[MON] CVE webhook<br/>existing"]
        W7["[MON] Disk webhook<br/>existing"]
    end

    subgraph STO["BRAIN POSTGRES — monitoring DB"]
        T1[("patch_packages<br/>NOT IN auto-resolve")]
        T2[("patch_host_state<br/>+ patch_host_summary view")]
        T3[("image_status<br/>Diun events")]
        T4[("image_local<br/>per-host digest")]
        T5[("service_versions")]
        T6[("cve_findings")]
        T7[("approval_log<br/>recent_actions exposed")]
        T8[("disk_history")]
    end

    subgraph DASH["HEALTH DASHBOARD pipeline"]
        DH["discover-health.py on brain<br/>cron */15 min<br/>queries Postgres<br/>scp JSON to monitor"]
        JSON["/data/health-staging.json<br/>+ recent_actions 30min window"]
        UI["ops.imagesbyolofsson.se/health-staging<br/>graf-vy + list-mode<br/>pulse-overlay<br/>apis-staging styling"]
    end

    subgraph ACT["EXECUTION + ALERTING"]
        EXEC["brain-approve-exec :9991<br/>Docker container on brain_default net<br/>SSH out via /root/.ssh<br/>UserKnownHostsFile /dev/null"]
        REBOOT["systemctl reboot --no-block<br/>via SSH"]
        APT["apt-get install pkg<br/>via SSH"]
        DC["docker compose up -d --force-recreate<br/>via SSH"]
        DALERT["Discord #alerts-bezzel<br/>+ patch-channel<br/>state-changed gated"]
    end

    subgraph SEC["SECONDARY MONITORING on monitor-cax11"]
        BES["Beszel hub :8090<br/>6 agents<br/>cannot self-monitor"]
        GLA["Glances :61208<br/>PyPI v3.4.0.5"]
        KUMA["Uptime Kuma<br/>HTTPS endpoints"]
        DALERT2["Discord webhooks<br/>18 alerts heavy/lite"]
    end

    subgraph STATS["LYXLIMO STATS pipeline"]
        FETCH["fetch_stats.py on brain<br/>systemd timer 06:00 daily"]
        ADS["Google Ads API<br/>r5zEiVt-a028 dev token<br/>customer 8324523542"]
        GA4["GA4 Data API<br/>property 440588771"]
        SPG[("brain Postgres<br/>lyxlimo_stats DB")]
        FLASK["Flask stats.ibo.se<br/>Tailscale-only<br/>Caddy DNS-01 cert"]
    end

    subgraph VIDEO["LYXLIMO VEO PIPELINE on brain"]
        VEO["veo_client.py<br/>generate_for_pmax 16s<br/>2 x 8s ffmpeg concat"]
        FF["ffmpeg<br/>concat horizontal + square<br/>+ vertical via 9:16 Veo"]
        YT["YouTube Data API v3<br/>youtube.upload scope<br/>10000 units/day"]
        ADS_LINK["Google Ads SDK<br/>asset_group_asset link<br/>required: ≥10s"]
    end

    subgraph BHA["BRAIN-HANDS-APPROVER planned"]
        GEN["Generator on townhouse-llm<br/>cron Sunday 23:00<br/>Ollama Qwen3-4B + Gemma-4-E4B"]
        PROP[("ad_proposals table<br/>NOT YET CREATED")]
        APPR["Approver UI<br/>NOT YET BUILT"]
    end

    S1 --> W1
    S2 --> W2
    S3 --> W3
    S4 --> W4
    S5 --> W6
    S6 --> W6
    S7 --> W7

    W1 --> T1
    W1 --> T2
    W2 --> T4
    W3 --> T3
    W4 --> T5
    W6 --> T6
    W7 --> T8
    W5 --> T7

    W1 -->|state-changed gate| DALERT
    W3 -->|new digest| DALERT
    W6 -->|critical CVE| DALERT
    W7 -->|threshold| DALERT

    T1 --> DH
    T2 --> DH
    T3 --> DH
    T4 --> DH
    T5 --> DH
    T6 --> DH
    T7 --> DH
    T8 --> DH

    DH --> JSON
    JSON -->|scp Tailscale| UI

    UI -->|user clicks Approve| W5
    W5 -->|HTTP /exec| EXEC
    EXEC -->|reboot-host| REBOOT
    EXEC -->|patch-package| APT
    EXEC -->|image upgrade| DC
    EXEC -->|trigger CVE rescan async| S5

    BES -->|TCP agent| DALERT2
    GLA -.->|HTTP polls| BES
    KUMA --> DALERT2

    FETCH -->|read| ADS
    FETCH -->|read| GA4
    FETCH -->|write| SPG
    FLASK -->|read| SPG

    VEO --> FF
    FF -->|MP4 16s| YT
    YT -->|video_id| ADS_LINK
    VEO -.->|future autonomous trigger| GEN

    GEN -.->|future| PROP
    PROP -.->|future| APPR
    APPR -.->|future| ADS_LINK

    classDef srcNode fill:#1a237e,stroke:#fff,color:#fff
    classDef n8nNode fill:#4a148c,stroke:#fff,color:#fff
    classDef storeNode fill:#1b5e20,stroke:#fff,color:#fff
    classDef execNode fill:#b71c1c,stroke:#fff,color:#fff
    classDef alertNode fill:#e65100,stroke:#fff,color:#fff
    classDef futureNode fill:#424242,stroke:#fff,stroke-dasharray: 5 5,color:#fff

    class S1,S2,S3,S4,S5,S6,S7 srcNode
    class W1,W2,W3,W4,W5,W6,W7 n8nNode
    class T1,T2,T3,T4,T5,T6,T7,T8,SPG storeNode
    class EXEC,REBOOT,APT,DC,VEO,FF,YT,ADS_LINK,FETCH execNode
    class DALERT,DALERT2 alertNode
    class GEN,PROP,APPR futureNode
---
config:
  theme: dark
  layout: elk
title: Townhouse — n8n Workflows & ops Dashboard Ecosystem (2026-05-07)
---
flowchart TB
    subgraph OPS["ops.imagesbyolofsson.se — Tailscale-only via OPNsense override"]
        direction TB
        OPS_INDEX["index.html<br/>landing page"]
        OPS_TOPO["/topology<br/>5 VPS visual map<br/>served from brain"]
        OPS_MEM["/memory<br/>memory-projekt dashboard<br/>served from brain"]
        OPS_WF["/workflows<br/>n8n Workflow Map<br/>SVG canvas + zoom + filters<br/>data: /data/workflows.json<br/>37 workflows · 11 active"]
        OPS_WFS["/workflows-staging<br/>same UI · staging tag"]
        OPS_API["/apis-staging<br/>API Endpoint Map<br/>24 integrations registered<br/>shares workflows.json"]
        OPS_HEALTH["/health-staging<br/>Patch+CVE+Disk+Image dashboard<br/>graf-mode + list-mode<br/>data: /data/health-staging.json<br/>recent_actions 30min pulse"]
        OPS_ARCH["/architecture<br/>Per-host mermaid views<br/>overview + 7 host-specific<br/>15 embedded mermaid diagrams"]
        OPS_BHA["/architecture-bha<br/>Brain-Hands-Approver design<br/>6 mermaid diagrams"]
        OPS_APPR["/health/alerts/approve<br/>SPA preview-then-confirm<br/>POST /webhook/mon/approve-action"]
    end

    subgraph N8N_MON["n8n MONITORING — 7 active 2 off"]
        direction TB
        WF_PATCH["[MON] Patch webhook receiver<br/>Pzu8d4DTU0W6YplC<br/>/webhook/mon/patch-report<br/>postgres+http<br/>10 nodes"]
        WF_CVE["[MON] CVE webhook receiver<br/>d5tZRc7O2ychQH42<br/>/webhook/mon/cve-report<br/>postgres+http+discord<br/>8 nodes"]
        WF_DISK["[MON] Disk webhook receiver<br/>xbovPyA7v4Y540pQ<br/>/webhook/mon/disk-report<br/>postgres+http+discord<br/>9 nodes"]
        WF_IMG["[MON] Image webhook receiver<br/>737UwzzYVCOfGd5z<br/>/webhook/mon/image-report<br/>Diun source<br/>postgres+http"]
        WF_IMGL["[MON] ImageLocal webhook receiver<br/>tiw9OuuBsIYnMmWw<br/>/webhook/mon/image-local<br/>postgres+http"]
        WF_SVC["[MON] ServiceVersion webhook receiver<br/>PcXY3Z0EeXvKMZbM<br/>/webhook/mon/service-version<br/>HTTP probes + GitHub Releases"]
        WF_APPR["[MON] Approve action<br/>5ynBxmhR5HlfRoH2<br/>/webhook/mon/approve-action<br/>tightened ok contract<br/>5 nodes"]
        WF_CERT["[MON] Cert expiry sweep<br/>OFF — scheduled<br/>exec+postgres+http+discord"]
        WF_TREND["[MON] Disk-space trend<br/>OFF — scheduled<br/>postgres+http+discord+exec"]
    end

    subgraph N8N_LYX["n8n LYXLIMO — 2 active 5 off legacy copies"]
        direction TB
        WF_BOOK["Lyxlimo Booking v2 Sheets-observability<br/>q288IvsYAzfoRG53<br/>/webhook/lyxlimo-booking<br/>postgres+discord+sheets<br/>7 nodes"]
        WF_AVAIL["Lyxlimo Availability Check<br/>yaJGhiwmbTU7SdoL<br/>/webhook/lyxlimo-availability<br/>postgres<br/>4 nodes"]
        WF_LYX_OFF["5 inaktiva booking-kopior<br/>only-short-info, copy mail response,<br/>Booking Apr 19, copy 2 - copy copy,<br/>original Booking — rollback-safe"]
    end

    subgraph N8N_APR["n8n APPROVAL — 2 active"]
        direction TB
        WF_APR_GET["Approval API GET /api/approval/:token<br/>bAeLGXMCmVvVJfCb<br/>sheets only · 4 nodes"]
        WF_APR_POST["Approval API POST /api/submit<br/>wqL1CewpyAHEfprg<br/>sheets+discord+gmail+gcal<br/>13 nodes — calendar invite live"]
    end

    subgraph N8N_CAL["n8n CALENDAR — both off"]
        direction TB
        WF_CAL_AUTO["Add event to Google Calendar automatically<br/>OFF · gcal manual"]
        WF_CAL_BOOK["Calendar-Booking<br/>fsonz9WA77EM1hr9<br/>OFF · gcal+gmail+sheets+discord"]
    end

    subgraph N8N_MKT["n8n MARKETING — all 3 off pending Standard-access"]
        direction TB
        WF_MKT_GADS["[Marketing] Automate Google Ads search term analysis<br/>qhPTLsrHjJjIxPi0 · 22 nodes<br/>gemini+http+discord"]
        WF_MKT_GA4["[Marketing] Google Analytics<br/>p9P0VsiHJB6sRgzx · 16 nodes<br/>gemini+gmail+discord"]
        WF_MKT_WEEK["Online Marketing Weekly Report<br/>16 nodes · facebook+http+gemini"]
    end

    subgraph N8N_JUNK["n8n JUNK — 14 inactive imports/templates"]
        direction TB
        WF_JUNK["My workflow 1-5 + SpaceX +<br/>Brave Search Goggles + SEO audits +<br/>2x Veo TikTok + 2x SSL monitoring +<br/>Google Ads MCP examples — cleanup-candidates"]
    end

    subgraph SVC["BACKING SERVICES — on brain unless noted"]
        direction LR
        SVC_PG[("Postgres<br/>monitoring + lyxlimo + stats")]
        SVC_REDIS[("Redis cache")]
        SVC_EXEC["brain-approve-exec :9991<br/>SSH-out container"]
        SVC_DISC["Discord webhooks<br/>#alerts-bezzel + #patch-channel"]
        SVC_GOOG["Google Workspace APIs<br/>Sheets · Calendar · Gmail<br/>SA: townhouse-mcp-sa@<br/>gen-lang-client-0056474916"]
        SVC_ADS["Google Ads API<br/>customer 8324523542<br/>dev token r5zEiVt-a028"]
        SVC_GA4["GA4 Data API<br/>property 440588771"]
        SVC_GEMINI["Google Gemini<br/>review validation +<br/>marketing analysis"]
        SVC_OLLAMA["Ollama on townhouse-llm<br/>100.71.126.116:11434"]
    end

    BROWSER["Linus + Agents<br/>browser via Tailscale"]
    OPNS["OPNsense override<br/>routes ops.ibo.se<br/>to monitor-cax11"]

    BROWSER --> OPNS
    OPNS -->|Tailscale 100.x| OPS_INDEX
    OPS_INDEX --> OPS_TOPO
    OPS_INDEX --> OPS_MEM
    OPS_INDEX --> OPS_WF
    OPS_INDEX --> OPS_WFS
    OPS_INDEX --> OPS_API
    OPS_INDEX --> OPS_HEALTH
    OPS_INDEX --> OPS_ARCH
    OPS_INDEX --> OPS_BHA
    OPS_HEALTH --> OPS_APPR

    OPS_WF -.->|reads| WF_DATA[/"/data/workflows.json<br/>generated daily<br/>n8n REST scan on brain"/]
    OPS_WFS -.->|reads| WF_DATA
    OPS_API -.->|reads| WF_DATA
    OPS_HEALTH -.->|reads| HEALTH_DATA[/"/data/health-staging.json<br/>discover-health.py */15min<br/>scp from brain to monitor"/]
    OPS_ARCH -.->|embeds| MERMAID_EMB[/"15 mermaid diagrams<br/>per-host views<br/>+ overview"/]
    OPS_BHA -.->|embeds| MERMAID_BHA[/"6 mermaid diagrams<br/>BHA design"/]

    OPS_APPR -->|POST| WF_APPR
    WF_APPR --> SVC_EXEC
    WF_APPR --> SVC_PG
    SVC_EXEC -.->|SSH| HOSTS[("7 SSH targets:<br/>brain ai-rag lyx-webb<br/>ub-webb monitor gitea")]

    HOSTS -.->|cron push| WF_PATCH
    HOSTS -.->|cron push| WF_DISK
    HOSTS -.->|cron push| WF_IMG
    HOSTS -.->|cron push| WF_IMGL
    HOSTS -.->|cron push| WF_SVC
    HOSTS -.->|trivy push| WF_CVE
    WF_PATCH --> SVC_PG
    WF_CVE --> SVC_PG
    WF_DISK --> SVC_PG
    WF_IMG --> SVC_PG
    WF_IMGL --> SVC_PG
    WF_SVC --> SVC_PG
    WF_CVE --> SVC_DISC
    WF_DISK --> SVC_DISC
    WF_PATCH -->|state-changed gate| SVC_DISC

    LYX_FORM[/"lyxlimo.se 5-step form<br/>POST"/] --> WF_BOOK
    WF_BOOK --> SVC_PG
    WF_BOOK --> SVC_GOOG
    WF_BOOK --> SVC_DISC
    WF_BOOK -->|generate token<br/>post URL| SVC_DISC
    LYX_FORM --> WF_AVAIL
    WF_AVAIL --> SVC_PG

    APPR_SPA[/"approve.imagesbyolofsson.se<br/>React SPA"/] -->|GET| WF_APR_GET
    APPR_SPA -->|POST decision| WF_APR_POST
    WF_APR_GET --> SVC_GOOG
    WF_APR_POST --> SVC_GOOG
    WF_APR_POST --> SVC_DISC

    WF_MKT_GADS -.->|reads| SVC_ADS
    WF_MKT_GA4 -.->|reads| SVC_GA4
    WF_MKT_GADS -.->|analysis| SVC_GEMINI
    WF_MKT_GA4 -.->|analysis| SVC_GEMINI
    WF_MKT_GA4 -.->|report| SVC_GOOG
    WF_MKT_WEEK -.->|consolidated| SVC_DISC

    WF_APPR -.->|reboot-host| HOSTS
    WF_CERT -.->|when on| SVC_EXEC
    WF_TREND -.->|when on| SVC_EXEC

    classDef opsNode fill:#1a1a2e,stroke:#C9A84C,color:#C9A84C
    classDef monNode fill:#4a148c,stroke:#fff,color:#fff
    classDef lyxNode fill:#2e7d32,stroke:#fff,color:#fff
    classDef apprNode fill:#0d47a1,stroke:#fff,color:#fff
    classDef calNode fill:#37474f,stroke:#aaa,color:#aaa,stroke-dasharray: 3 3
    classDef mktNode fill:#bf360c,stroke:#aaa,color:#fff,stroke-dasharray: 3 3
    classDef junkNode fill:#212121,stroke:#666,color:#888,stroke-dasharray: 5 5
    classDef svcNode fill:#1b5e20,stroke:#fff,color:#fff
    classDef dataNode fill:#3e2723,stroke:#C9A84C,color:#fff

    class OPS_INDEX,OPS_TOPO,OPS_MEM,OPS_WF,OPS_WFS,OPS_API,OPS_HEALTH,OPS_ARCH,OPS_BHA,OPS_APPR opsNode
    class WF_PATCH,WF_CVE,WF_DISK,WF_IMG,WF_IMGL,WF_SVC,WF_APPR monNode
    class WF_CERT,WF_TREND,WF_CAL_AUTO,WF_CAL_BOOK calNode
    class WF_BOOK,WF_AVAIL lyxNode
    class WF_LYX_OFF junkNode
    class WF_APR_GET,WF_APR_POST apprNode
    class WF_MKT_GADS,WF_MKT_GA4,WF_MKT_WEEK mktNode
    class WF_JUNK junkNode
    class SVC_PG,SVC_REDIS,SVC_EXEC,SVC_DISC,SVC_GOOG,SVC_ADS,SVC_GA4,SVC_GEMINI,SVC_OLLAMA svcNode
    class WF_DATA,HEALTH_DATA,MERMAID_EMB,MERMAID_BHA dataNode
---
config:
  theme: dark
  layout: elk
title: Townhouse — Service Catalog from dashboard.imagesbyolofsson.se
---
flowchart TB
    subgraph DASH["dashboard.imagesbyolofsson.se — service catalog<br/>Tailscale-only · /srv/dashboard on brain · 5 sections · 41 services"]
        DBINDEX["Townhouse · Dashboard<br/>SECTIONS data structure<br/>live HEAD-probes per card<br/>tags: public · tailscale · internal · localhost"]
    end

    subgraph SEC1["SEKTION 01 — OPERATIONS"]
        direction TB
        S1A["n8n<br/>brain · public<br/>11 active 26 inactive"]
        S1B["Approval system<br/>approve.ibo.se<br/>brain · public · token-gated"]
        S1C["Approve-Exec service<br/>brain · internal<br/>:9991 SSH-out"]
        S1D["Uptime Kuma<br/>status.ibo.se<br/>monitor · public"]
        S1E["Beszel hub<br/>monitor :8090 · tailscale<br/>Discord alerts"]
        S1F["Glances per nod<br/>:61208 on all 9 nodes<br/>tailscale"]
        S1G["Patch+CVE pipeline<br/>ops.ibo.se · public<br/>7/7 reporting"]
        S1H["Dokploy<br/>dokploy.ibo.se · public<br/>townhouse-gitea container deploy"]
    end

    subgraph SEC2["SEKTION 02 — AI"]
        direction TB
        S2A["LobeChat<br/>chat.ibo.se · tailscale<br/>multi-provider AI chat<br/>Casdoor-auth · demopc dual-GPU"]
        S2B["Ollama @ demopc<br/>100.75.223.96:11434<br/>16 GB VRAM RTX 2070 + GTX 1070 Ti<br/>qwen2.5:7b/3b · gemma4:e2b · 3x hermes"]
        S2C["Ollama @ townhouse-llm<br/>100.71.126.116:11434<br/>CPU-only · Qwen3-4B + gemma-4-E4B"]
        S2D["townhouse-mcp aggregator<br/>ai-rag :1337 · tailscale<br/>Hetzner 14 + GitHub 26 + GA4 5 tools<br/>n8n-bridge FAIL"]
        S2E["RAG-API<br/>ai-rag :8000 · tailscale<br/>FastAPI + ChromaDB"]
        S2F["BHA Generator<br/>planned · status unknown<br/>demopc / townhouse-llm"]
        S2G["BHA Publisher<br/>planned · brain<br/>Google Ads API push"]
        S2H["BHA Approver<br/>approve.ibo.se · public<br/>weekly review"]
    end

    subgraph SEC3["SEKTION 03 — DATA"]
        direction TB
        S3A["Stats Lyxlimo<br/>stats.ibo.se · tailscale<br/>brain · daily fetch 06:00<br/>Postgres + Flask"]
        S3B["Underbar Reviews<br/>reviews.ibo.se · public<br/>underbar-webb 10.0.0.5:8001"]
        S3C["Underbar CMS Admin<br/>admin.underbar.site · tailscale<br/>products + categories + hero + audit"]
        S3D["Postgres x 7<br/>brain n8n + monitoring · gitea ·<br/>onedev · dokploy · underbar x 2 · lobechat"]
        S3E["ChromaDB<br/>ai-rag · internal<br/>RAG embeddings"]
        S3F["MinIO LobeChat<br/>demopc :9001 · localhost<br/>S3-compat · A:/AI/lobechat/data"]
        S3G["Cloudflare R2<br/>cloud · public<br/>bf4df5...r2.cloudflarestorage.com"]
        S3H["Google Ads<br/>cloud · public<br/>2 accounts OAuth2<br/>Lyxlimo + Townhouse-MCC"]
        S3I["Google Analytics 4<br/>cloud · public<br/>property 440588771"]
        S3J["AdLoop MCP<br/>demopc · localhost<br/>Vanliga datorn"]
    end

    subgraph SEC4["SEKTION 04 — TOOLS"]
        direction TB
        S4A["Gitea<br/>gitea.ibo.se · public<br/>14 repos · 3 orgs · Actions runner"]
        S4B["OneDev<br/>onedev.ibo.se · public<br/>3 Underbar repos · CI"]
        S4C["SilverBullet<br/>silverbullet.ibo.se · public<br/>3 ytor · method-aware ACL"]
        S4D["Pingvin Share<br/>townhouse-gitea · public<br/>self-hosted file share"]
        S4E["File Browser<br/>townhouse-gitea · internal<br/>filesystem browser"]
        S4F["Authelia<br/>auth.ibo.se · public<br/>SSO · OIDC-provider"]
        S4G["Casdoor LobeChat<br/>demopc :8000 · localhost<br/>OAuth-provider · admin/123 BYTAS"]
        S4H["Tailscale ACL<br/>tailnet · internal<br/>tag:townhouse + 100.64.0.0/10"]
    end

    subgraph EDGE["edge — already mapped in diagram 1"]
        BR["brain"]
        AIR["ai-rag"]
        LXW["lyxlimo-webb"]
        UBW["underbar-webb"]
        MON["monitor"]
        TGT["townhouse-gitea"]
        TLLM["townhouse-llm"]
        DPC["demopc"]
        IPHONE["iphone laptop edge devices<br/>9 nodes total per dashboard"]
    end

    DBINDEX --> SEC1
    DBINDEX --> SEC2
    DBINDEX --> SEC3
    DBINDEX --> SEC4

    S1A -.host.-> BR
    S1B -.host.-> BR
    S1C -.host.-> BR
    S1D -.host.-> MON
    S1E -.host.-> MON
    S1F -.host.-> EDGE
    S1G -.host.-> MON
    S1H -.host.-> TGT

    S2A -.host.-> DPC
    S2B -.host.-> DPC
    S2C -.host.-> TLLM
    S2D -.host.-> AIR
    S2E -.host.-> AIR
    S2F -.host.-> DPC
    S2G -.host.-> BR
    S2H -.host.-> BR

    S3A -.host.-> BR
    S3B -.host.-> UBW
    S3C -.host.-> UBW
    S3D -.distributed.-> EDGE
    S3E -.host.-> AIR
    S3F -.host.-> DPC
    S3J -.host.-> DPC

    S4A -.host.-> TGT
    S4B -.host.-> TGT
    S4C -.host.-> TGT
    S4D -.host.-> TGT
    S4E -.host.-> TGT
    S4F -.host.-> TGT
    S4G -.host.-> DPC

    classDef catNode fill:#1a1a2e,stroke:#C9A84C,color:#C9A84C,stroke-width:2px
    classDef opsNode fill:#4a148c,stroke:#fff,color:#fff
    classDef aiNode fill:#b71c1c,stroke:#fff,color:#fff
    classDef dataNode fill:#1b5e20,stroke:#fff,color:#fff
    classDef toolNode fill:#0d47a1,stroke:#fff,color:#fff
    classDef plannedNode fill:#424242,stroke:#aaa,color:#aaa,stroke-dasharray: 5 5
    classDef edgeNode fill:#37474f,stroke:#fff,color:#fff

    class DBINDEX catNode
    class S1A,S1B,S1C,S1D,S1E,S1F,S1G,S1H opsNode
    class S2A,S2B,S2C,S2D,S2E aiNode
    class S2F,S2G,S2H plannedNode
    class S3A,S3B,S3C,S3D,S3E,S3F,S3G,S3H,S3I,S3J dataNode
    class S4A,S4B,S4C,S4D,S4E,S4F,S4G,S4H toolNode
    class BR,AIR,LXW,UBW,MON,TGT,TLLM,DPC,IPHONE edgeNode
---
config:
  theme: dark
  layout: elk
title: Townhouse — KOMPLETT inventering 2026-05-07 (alla 23 Tailscale-enheter, alla 14 subdomäner, alla containers per host)
---
flowchart LR
    subgraph EXT["INTERNET"]
        USR["End-Users + Linus + Agents"]
        CF["Cloudflare<br/>DNS + WAF + R2 storage"]
        GOOG["Google APIs<br/>Ads + GA4 + Cal + Gmail<br/>Sheets + Veo + Gemini + YouTube"]
        DISC["Discord<br/>2 webhooks alerts + patch"]
    end

    subgraph CADDY["brain Caddy — 14 subdomans (TLS via CF DNS-01)"]
        direction TB
        D1["n8n.ibo.se → brain :5678"]
        D2["approve.ibo.se → brain /srv/approve/www + n8n proxy<br/>Lyxlimo SPA"]
        D3["auth.ibo.se → townhouse-gitea :9091<br/>Authelia OIDC SSO"]
        D4["status.ibo.se → monitor :3001<br/>Uptime Kuma"]
        D5["stats.ibo.se → brain :8050<br/>Lyxlimo Flask · Tailscale-only"]
        D6["onedev.ibo.se → townhouse-gitea :6610"]
        D7["silverbullet.ibo.se → townhouse-gitea :3030<br/>method-aware ACL FUNGERAR 2026-05-07"]
        D8["reviews.ibo.se → underbar-webb 10.0.0.5:8001"]
        D9["dokploy.ibo.se → townhouse-gitea :3001"]
        D10["final-stage.ibo.se → townhouse-gitea :8084 + Content API<br/>Tailscale-only preview"]
        D11["underbar-admin.ibo.se → townhouse-gitea :80 + APIs<br/>LEGACY parallell admin · Tailscale-only"]
        D12["admin.underbar.site → swarm CMS + APIs<br/>Tailscale-only"]
        D13["chat.ibo.se → demopc LobeChat"]
        D14["dashboard.ibo.se → brain /srv/dashboard<br/>Tailscale-only · 41 services index"]
    end

    subgraph BRAIN["BRAIN cx33 · 204.168.246.143 · ts 100.91.241.68"]
        direction TB
        BR_CADDY["Caddy host-installed<br/>14 server-blocks · Cloudflare DNS-01"]
        BR_N8N["brain-n8n container<br/>:5678 · 37 workflows · 11 active"]
        BR_PG["brain-postgres :5432<br/>monitoring + lyxlimo + lyxlimo_stats"]
        BR_RED["brain-redis :6379"]
        BR_EXEC["brain-approve-exec :9991<br/>SSH-out container · healthy"]
        BR_BES["beszel-agent v0.18.7"]
        BR_BOT["brain-lyxlimo-bot<br/>RESTARTING (1) BROKEN"]
        BR_OPT["/opt/townhouse-generator<br/>BHA Generator-stub"]
        BR_HP["/opt/headplane<br/>Headscale-related?"]
        BR_MCP["/opt/mcp-hub"]
        BR_STA["/opt/stats — Flask + systemd timer 06:00"]
        BR_VEO["/srv/lyxlimo-veo<br/>veo_client.py + ffmpeg"]
        BR_DASH["/srv/dashboard/index.html<br/>SECTIONS catalog"]
        BR_OPS["/srv/ops/www<br/>topology + workflows + memory + index"]
        BR_APR["/srv/approve/www<br/>React SPA"]
        BR_TRAE["/opt/traefik · pre-Caddy legacy"]
    end

    subgraph AIRAG["AI-RAG cx33 · 89.167.42.116 · ts 100.120.67.52"]
        AR_RAG["rag-api :8000 · FastAPI"]
        AR_CHR["rag-chromadb :8000 internal · 0.5.15"]
        AR_MCP["townhouse-mcp :1337 · aggregator<br/>Hetzner 14 + GitHub 26 + GA4 5 tools"]
        AR_DIUN["diun · cron 6,18 watch 21 images"]
        AR_BES["beszel-agent v0.18.7"]
    end

    subgraph LYXW["LYXLIMO-WEBB cx23 · 204.168.245.109 · ts 100.75.14.44 · webb1"]
        LX_NGX["lyxlimo-nginx :80 :443"]
        LX_HTML["/srv/lyxlimo/www/index.html<br/>direct-edit no-Astro"]
        LX_BES["beszel-agent v0.18.7"]
    end

    subgraph UBW["UNDERBAR-WEBB cx23 · 204.168.249.189 · ts 100.119.68.48 · webb2"]
        UB_NGX["underbar-nginx :80 :443<br/>3 server-blocks"]
        UB_CONT["underbar-content-api 10.0.0.5:8002 · healthy"]
        UB_REV["underbar-reviews-api 10.0.0.5:8001<br/>Gemini auto-validate"]
        UB_PG_C["underbar-content-postgres pg16-alpine"]
        UB_PG_R["underbar-reviews-postgres pg16-alpine"]
        UB_BES["beszel-agent v0.18.7"]
    end

    subgraph MON["MONITOR-CAX11 ARM · 65.21.252.57 · ts 100.123.194.33"]
        MO_BES["monitor-beszel hub :8090<br/>Discord 18 alerts"]
        MO_BESA["beszel-agent latest"]
        MO_KUMA["monitor-uptime-kuma :3001 · healthy"]
        MO_HOMEP["monitor-homepage :3000 · healthy"]
        MO_CADDY["monitor-caddy cloudflare-build :80 :443<br/>routes ops.ibo.se"]
        MO_OPS["/opt/monitor/www<br/>10 sub-pages: workflows + workflows-staging + apis-staging<br/>+ health-staging + architecture + architecture-bha<br/>+ topology + memory + index + alerts/approve"]
    end

    subgraph TGT["TOWNHOUSE-GITEA Core Ultra 5 · ts 100.83.211.41 · laptop"]
        direction TB
        TG_GITEA["gitea :3000 :2222 · 1.22 · 14 repos 3 orgs"]
        TG_GITEA_PG["gitea-postgres pg16"]
        TG_RUN["gitea-runner act_runner"]
        TG_OD["onedev :6610-6611 · 1dev/server"]
        TG_OD_PG["onedev-postgres pg16-alpine"]
        TG_DOK["dokploy :3001 swarm v0.29.2 · healthy"]
        TG_DOK_PG["dokploy-postgres pg16"]
        TG_DOK_RED["dokploy-redis :7"]
        TG_SB["silverbullet :3030 · healthy"]
        TG_AUTH["authelia :9091 · 4.38 · healthy"]
        TG_AUTH_RED["authelia-redis"]
        TG_PINGVIN["file-drop-pingvinshare<br/>self-hosted file-share · healthy"]
        TG_FB["file-drop-filebrowser · healthy"]
        TG_FDC["file-drop-caddy :80 :8080"]
        TG_BES["beszel-agent latest"]
        TG_SWARM_CMS["underbar-underbaradminui-j6sver swarm task<br/>nixpacks staticfile · :80 default"]
        TG_SWARM_FS["underbar-finalstage-hgj4fl swarm task<br/>:8084 preview"]
        TG_STAGE["underbar-stage-nginx :8081"]
    end

    subgraph TLLM["TOWNHOUSE-LLM i7-1265U · ts 100.71.126.116 · laptop"]
        TL_OLL["Ollama :11434<br/>Qwen3-4B Q4_K_M · gemma-4-E4B Q4_K_M<br/>OpenAI-compat /v1/"]
    end

    subgraph DPC["DEMOPC-TOWNHOUSE Win11 · ts 100.75.223.96 · dual-GPU"]
        DPC_OLL["Ollama Desktop :11434<br/>RTX 2070 + GTX 1070 Ti = 16 GB VRAM<br/>qwen2.5:7b/3b · gemma4:e2b · 3x hermes"]
        DPC_LOBE["LobeChat<br/>multi-provider AI chat"]
        DPC_CASD["Casdoor :8000<br/>OAuth-provider · admin/123 BYTAS"]
        DPC_MIN["MinIO :9001<br/>S3-compat A:/AI/lobechat/data"]
        DPC_LOBE_PG["LobeChat Postgres"]
        DPC_AL["AdLoop MCP<br/>Vanliga datorn"]
        DPC_DOCK["demopc-docker-desktop ts 100.98.15.107"]
    end

    subgraph EDGE_TS["EDGE on Tailscale · 23 enheter totalt"]
        direction TB
        TS_OPN["opnsense ts 100.127.190.86<br/>FreeBSD exit-node + Unbound DNS<br/>OFFLINE 9d"]
        TS_OPNDB["vps-opnsense-dashboard ts 100.103.201.116"]
        TS_IVER["iver-7vrk6m3 ts 100.114.112.107<br/>Linus jobbdator · OFFLIMITS"]
        TS_PHONES["mobile/iPad: demo-phone · ipad157 · iphone-13<br/>+ moa.biserud@: moasdator + samsung-sm-s921b"]
        TS_OFFLINE["Offline: parrot 7d · winmachine 94d<br/>desktop-6sgh53h 8h · desktop-igva7s9 175d<br/>townhouse 17d · ubuntulaptop 3d · iphone-13 144d"]
    end

    subgraph CLOUD["EXTERNAL CLOUD"]
        CL_R2["Cloudflare R2<br/>bf4df5...r2.cloudflarestorage.com"]
        CL_GADS["Google Ads · 2 OAuth2 accounts<br/>Lyxlimo + Townhouse-MCC<br/>customer 8324523542"]
        CL_GA4["GA4 property 440588771"]
        CL_GEMI["Gemini API"]
        CL_VEO["Veo 3.1 preview API"]
        CL_YT["YouTube Data API v3<br/>10000 units/day"]
    end

    OFFLINE["z10PE-D16-WS workstation<br/>OFFLINE 2026-05-06 · no-POST<br/>2x Xeon E5 v4 + 1TB ECC + 17 disks"]

    USR --> CF
    CF --> CADDY
    CADDY --> BRAIN
    CADDY -.proxy.-> AIRAG
    CADDY -.proxy.-> LYXW
    CADDY -.proxy.-> UBW
    CADDY -.proxy.-> MON
    CADDY -.proxy.-> TGT
    CADDY -.proxy.-> DPC

    BR_N8N --> BR_PG
    BR_N8N --> BR_RED
    BR_N8N --> BR_EXEC
    BR_N8N --> DISC
    BR_N8N --> GOOG
    BR_N8N -.calls.-> TL_OLL
    BR_N8N -.calls.-> DPC_OLL
    BR_EXEC -.SSH.-> AIRAG
    BR_EXEC -.SSH.-> LYXW
    BR_EXEC -.SSH.-> UBW
    BR_EXEC -.SSH.-> MON
    BR_EXEC -.SSH.-> TGT
    BR_STA --> CL_GADS
    BR_STA --> CL_GA4
    BR_VEO --> CL_VEO
    BR_VEO --> CL_YT
    BR_VEO --> CL_GADS
    BR_BOT -.broken.-> BR_N8N

    AR_RAG --> AR_CHR
    AR_MCP -.tools.-> CL_GADS
    AR_MCP -.tools.-> CL_GA4
    AR_DIUN --> BR_N8N

    LX_NGX --> LX_HTML
    LX_NGX -.POST booking.-> BR_N8N

    UB_NGX -.bind-mount.-> UB_CONT
    UB_REV --> UB_PG_R
    UB_CONT --> UB_PG_C
    UB_REV --> CL_GEMI
    UB_REV --> DISC

    MO_OPS -.reads.-> BR_PG
    MO_BES --> DISC
    MO_KUMA --> DISC
    MO_BESA -.collect.-> MO_BES
    BR_BES -.collect.-> MO_BES
    AR_BES -.collect.-> MO_BES
    LX_BES -.collect.-> MO_BES
    UB_BES -.collect.-> MO_BES
    TG_BES -.collect.-> MO_BES

    TG_GITEA --> TG_GITEA_PG
    TG_OD --> TG_OD_PG
    TG_DOK --> TG_DOK_PG
    TG_DOK --> TG_DOK_RED
    TG_DOK -->|deploys| TG_SWARM_CMS
    TG_DOK -->|deploys| TG_SWARM_FS
    TG_AUTH --> TG_AUTH_RED
    TG_OD -.git source.-> TG_DOK
    TG_GITEA -.mirror eval.-> TG_OD
    TG_RUN -.runner.-> TG_GITEA

    DPC_LOBE --> DPC_OLL
    DPC_LOBE --> DPC_CASD
    DPC_LOBE --> DPC_MIN
    DPC_LOBE --> DPC_LOBE_PG
    DPC_LOBE --> CL_R2

    TS_OPN -.exit-node.-> EDGE_TS

    classDef brainNode fill:#4a148c,stroke:#fff,color:#fff
    classDef webNode fill:#0d47a1,stroke:#fff,color:#fff
    classDef dataNode fill:#1b5e20,stroke:#fff,color:#fff
    classDef adminNode fill:#f57f17,stroke:#000,color:#000
    classDef aiNode fill:#b71c1c,stroke:#fff,color:#fff
    classDef extNode fill:#37474f,stroke:#fff,color:#fff
    classDef brokenNode fill:#7f1d1d,stroke:#f87171,color:#fff,stroke-width:3px
    classDef offlineNode fill:#212121,stroke:#666,color:#888,stroke-dasharray: 5 5
    classDef cloudNode fill:#3e2723,stroke:#C9A84C,color:#fff
    classDef caddyNode fill:#1a1a2e,stroke:#C9A84C,color:#C9A84C

    class BR_CADDY,BR_N8N,BR_EXEC,BR_STA,BR_VEO,BR_DASH,BR_OPS,BR_APR,BR_OPT,BR_MCP,BR_HP,BR_TRAE brainNode
    class BR_PG,BR_RED,UB_PG_C,UB_PG_R,AR_CHR,TG_GITEA_PG,TG_OD_PG,TG_DOK_PG,TG_AUTH_RED,TG_DOK_RED,DPC_LOBE_PG,DPC_MIN dataNode
    class LX_NGX,LX_HTML,UB_NGX,UB_CONT,UB_REV,AR_RAG,AR_MCP webNode
    class TG_GITEA,TG_OD,TG_DOK,TG_SB,TG_AUTH,TG_PINGVIN,TG_FB,TG_FDC,TG_RUN,TG_SWARM_CMS,TG_SWARM_FS,TG_STAGE,MO_BES,MO_KUMA,MO_HOMEP,MO_CADDY,MO_OPS,BR_BES,AR_BES,LX_BES,UB_BES,TG_BES,MO_BESA adminNode
    class TL_OLL,DPC_OLL,DPC_LOBE,DPC_CASD,DPC_AL aiNode
    class USR,CF,GOOG,DISC,TS_OPN,TS_OPNDB,TS_IVER,TS_PHONES,DPC_DOCK extNode
    class BR_BOT brokenNode
    class TS_OFFLINE,OFFLINE offlineNode
    class CL_R2,CL_GADS,CL_GA4,CL_GEMI,CL_VEO,CL_YT cloudNode
    class D1,D2,D3,D4,D5,D6,D7,D8,D9,D10,D11,D12,D13,D14 caddyNode