diff --git a/.github/workflows/generate-docs.yml b/.github/workflows/generate-docs.yml
index 242306a..4ed7be0 100644
--- a/.github/workflows/generate-docs.yml
+++ b/.github/workflows/generate-docs.yml
@@ -29,3 +29,17 @@ jobs:
run: |
test -d docs/public
test -n "$(find docs/public -mindepth 1 -print -quit)"
+ - name: Install Graphviz and MkDocs
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y graphviz
+ dot -V
+ python3 -m pip install --user mkdocs
+ - name: Validate public docs and diagrams
+ run: |
+ test -f docs/public/physical-topology.svg
+ test -f docs/public/docker-traefik-dynu.svg
+ ! rg -n "Graphviz dot not found" docs/public/*.svg
+ ! rg -n "lan\.ddnsgeek\.com" docs/public/*.svg docs/public/*.md
+ ! rg -n -i "password|token|api_key|secret" docs/public/*.svg
+ python3 -m mkdocs build -f mkdocs-public.yml --strict
diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml
index 337fc84..1757d6d 100644
--- a/.github/workflows/publish-docs.yml
+++ b/.github/workflows/publish-docs.yml
@@ -28,13 +28,26 @@ jobs:
test -d docs/public
test -n "$(find docs/public -mindepth 1 -print -quit)"
+ - name: Install Graphviz
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y graphviz
+ dot -V
+
+ - name: Validate sanitized diagram artifacts
+ run: |
+ test -f docs/public/physical-topology.svg
+ test -f docs/public/docker-traefik-dynu.svg
+ ! rg -n "Graphviz dot not found" docs/public/*.svg
+ ! rg -n "lan\.ddnsgeek\.com" docs/public/*.svg docs/public/*.md
+ ! rg -n -i "password|token|api_key|secret" docs/public/*.svg
+
- name: Install MkDocs
run: |
python3 -m pip install --user mkdocs
- name: Build public MkDocs site
run: |
-
python3 -m mkdocs build -f mkdocs-public.yml --strict
- name: Verify published content excludes internal/generated docs
diff --git a/docs/README.md b/docs/README.md
index 6716d57..7fdf925 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -2,21 +2,37 @@
## Local generation
+Install prerequisites:
+
+```bash
+sudo apt-get update
+sudo apt-get install -y graphviz
+```
+
+Then generate and validate public docs:
+
```bash
chmod +x scripts/docs/*.sh
scripts/docs/generate-all.sh
+python3 -m mkdocs build -f mkdocs-public.yml --strict
+```
+
+NixOS-friendly alternative:
+
+```bash
+nix shell nixpkgs#graphviz nixpkgs#python3 nixpkgs#python3Packages.pyyaml
```
This pipeline only runs `docker compose config` and static parsing. It does **not** start containers.
## CI behaviour
-GitHub Actions workflow `.github/workflows/generate-docs.yml` runs on pushes/PRs to `main` and manual dispatch. It generates docs and uploads them as the `generated-documentation` artifact.
+GitHub Actions workflow `.github/workflows/generate-docs.yml` validates committed public docs and diagrams and runs a strict public MkDocs build.
## Outputs
- `docs/generated`: resolved compose config and markdown inventories
-- `docs/diagrams`: DOT and SVG architecture diagram
+- `docs/diagrams`: generated DOT and SVG diagrams
- `docs/public`: sanitized copy for public sharing
## Publication safety
diff --git a/docs/diagrams/docker-compose.dot b/docs/diagrams/docker-compose.dot
index 487ec63..9c78dfa 100644
--- a/docs/diagrams/docker-compose.dot
+++ b/docs/diagrams/docker-compose.dot
@@ -1,6 +1,6 @@
digraph Compose {
rankdir=LR;
- node [fontname=Helvetica];
+ node [fontname="Helvetica"];
"svc:authelia" [label="authelia", shape=box, style=filled, fillcolor="#dfefff"];
"svc:crowdsec" [label="crowdsec", shape=box, style=filled, fillcolor="#dfefff"];
"svc:docker-socket-proxy" [label="docker-socket-proxy", shape=box, style=filled, fillcolor="#dfefff"];
diff --git a/docs/diagrams/docker-compose.svg b/docs/diagrams/docker-compose.svg
index cd8a7c6..1991277 100644
--- a/docs/diagrams/docker-compose.svg
+++ b/docs/diagrams/docker-compose.svg
@@ -1 +1,463 @@
-
+
+
+
+
+
diff --git a/docs/diagrams/docker-traefik-dynu.dot b/docs/diagrams/docker-traefik-dynu.dot
new file mode 100644
index 0000000..d8ce9bf
--- /dev/null
+++ b/docs/diagrams/docker-traefik-dynu.dot
@@ -0,0 +1,260 @@
+digraph DockerTraefikDynu {
+ graph [rankdir=LR, compound=true, splines=true, nodesep=0.5, ranksep=1.0, fontname="Helvetica"];
+ node [fontname="Helvetica", fontsize=10, style="rounded,filled"];
+ edge [fontname="Helvetica", fontsize=9];
+ "ext:dynu" [label="Dynu / Public DNS", shape=oval, fillcolor="#fde68a"];
+ "svc:traefik" [label="traefik", shape=box, fillcolor="#bfdbfe"];
+ "net:gramps" [label="gramps", shape=ellipse, fillcolor="#f3f4f6"];
+ "net:monitor" [label="monitor", shape=ellipse, fillcolor="#f3f4f6"];
+ "net:nextcloud" [label="nextcloud", shape=ellipse, fillcolor="#f3f4f6"];
+ "net:passbolt" [label="passbolt", shape=ellipse, fillcolor="#f3f4f6"];
+ "net:traefik" [label="traefik", shape=ellipse, fillcolor="#f3f4f6"];
+ "svc:authelia" [label="authelia", shape=box, fillcolor="#dcfce7"];
+ "svc:authelia" -> "net:traefik" [color="#6b7280"];
+ "router:authelia" [label="router:authelia\nentry:websecure tls:true", shape=diamond, fillcolor="#fbcfe8"];
+ "svc:traefik" -> "router:authelia";
+ "traefik-service:authelia" [label="service:authelia", shape=component, fillcolor="#fecaca"];
+ "router:authelia" -> "traefik-service:authelia";
+ "dns:service-1." [label="service-1.", shape=note, fillcolor="#fde68a"];
+ "ext:dynu" -> "dns:service-1.";
+ "dns:service-1." -> "router:authelia";
+ "svc:crowdsec" [label="crowdsec", shape=box, fillcolor="#dcfce7"];
+ "svc:crowdsec" -> "net:traefik" [color="#6b7280"];
+ "svc:docker-socket-proxy" [label="docker-socket-proxy", shape=box, fillcolor="#dcfce7"];
+ "svc:docker-socket-proxy" -> "net:monitor" [color="#6b7280"];
+ "svc:docker-socket-proxy" -> "net:traefik" [color="#6b7280"];
+ "svc:docker-update-exporter" [label="docker-update-exporter", shape=box, fillcolor="#dcfce7"];
+ "svc:docker-update-exporter" -> "net:monitor" [color="#6b7280"];
+ "svc:error-pages" [label="error-pages", shape=box, fillcolor="#dcfce7"];
+ "svc:error-pages" -> "net:traefik" [color="#6b7280"];
+ "router:error-pages-router" [label="router:error-pages-router\nentry:web tls:false", shape=diamond, fillcolor="#fbcfe8"];
+ "svc:traefik" -> "router:error-pages-router";
+ "traefik-service:error-pages" [label="service:error-pages", shape=component, fillcolor="#fecaca"];
+ "router:error-pages-router" -> "traefik-service:error-pages";
+ "mw:error-pages-middleware" [label="error-pages-middleware", shape=hexagon, fillcolor="#ddd6fe"];
+ "router:error-pages-router" -> "mw:error-pages-middleware" [style=dashed];
+ "traefik-service:error-pages-service" [label="service:error-pages-service\nport:8080", shape=component, fillcolor="#fecaca"];
+ "traefik-service:error-pages-service" -> "svc:error-pages";
+ "svc:gitea" [label="gitea", shape=box, fillcolor="#dcfce7"];
+ "svc:gitea" -> "net:traefik" [color="#6b7280"];
+ "router:gitea" [label="router:gitea\nentry:websecure tls:true", shape=diamond, fillcolor="#fbcfe8"];
+ "svc:traefik" -> "router:gitea";
+ "traefik-service:gitea" [label="service:gitea", shape=component, fillcolor="#fecaca"];
+ "router:gitea" -> "traefik-service:gitea";
+ "dns:service-2." [label="service-2.", shape=note, fillcolor="#fde68a"];
+ "ext:dynu" -> "dns:service-2.";
+ "dns:service-2." -> "router:gitea";
+ "traefik-service:gitea" [label="service:gitea\nport:3000", shape=component, fillcolor="#fecaca"];
+ "traefik-service:gitea" -> "svc:gitea";
+ "svc:gitea-runner" [label="gitea-runner", shape=box, fillcolor="#dcfce7"];
+ "svc:gitea-runner" -> "net:traefik" [color="#6b7280"];
+ "svc:gotify" [label="gotify", shape=box, fillcolor="#dcfce7"];
+ "svc:gotify" -> "net:traefik" [color="#6b7280"];
+ "router:gotify" [label="router:gotify\nentry:websecure tls:false", shape=diamond, fillcolor="#fbcfe8"];
+ "svc:traefik" -> "router:gotify";
+ "traefik-service:gotify" [label="service:gotify", shape=component, fillcolor="#fecaca"];
+ "router:gotify" -> "traefik-service:gotify";
+ "dns:service-3." [label="service-3.", shape=note, fillcolor="#fde68a"];
+ "ext:dynu" -> "dns:service-3.";
+ "dns:service-3." -> "router:gotify";
+ "traefik-service:gotify" [label="service:gotify\nport:80", shape=component, fillcolor="#fecaca"];
+ "traefik-service:gotify" -> "svc:gotify";
+ "svc:grafana" [label="grafana", shape=box, fillcolor="#dcfce7"];
+ "svc:grafana" -> "net:monitor" [color="#6b7280"];
+ "svc:grafana" -> "net:traefik" [color="#6b7280"];
+ "router:grafana" [label="router:grafana\nentry:websecure tls:false", shape=diamond, fillcolor="#fbcfe8"];
+ "svc:traefik" -> "router:grafana";
+ "traefik-service:grafana" [label="service:grafana", shape=component, fillcolor="#fecaca"];
+ "router:grafana" -> "traefik-service:grafana";
+ "dns:service-4." [label="service-4.", shape=note, fillcolor="#fde68a"];
+ "ext:dynu" -> "dns:service-4.";
+ "dns:service-4." -> "router:grafana";
+ "traefik-service:grafana" [label="service:grafana\nport:3000", shape=component, fillcolor="#fecaca"];
+ "traefik-service:grafana" -> "svc:grafana";
+ "svc:gramps-redis" [label="gramps-redis", shape=box, fillcolor="#dcfce7"];
+ "svc:gramps-redis" -> "net:gramps" [color="#6b7280"];
+ "svc:grampsweb" [label="grampsweb", shape=box, fillcolor="#dcfce7"];
+ "svc:grampsweb" -> "net:gramps" [color="#6b7280"];
+ "svc:grampsweb" -> "net:traefik" [color="#6b7280"];
+ "router:gramps" [label="router:gramps\nentry:websecure tls:false", shape=diamond, fillcolor="#fbcfe8"];
+ "svc:traefik" -> "router:gramps";
+ "traefik-service:grampsweb" [label="service:grampsweb", shape=component, fillcolor="#fecaca"];
+ "router:gramps" -> "traefik-service:grampsweb";
+ "dns:service-5." [label="service-5.", shape=note, fillcolor="#fde68a"];
+ "ext:dynu" -> "dns:service-5.";
+ "dns:service-5." -> "router:gramps";
+ "traefik-service:gramps" [label="service:gramps\nport:5000", shape=component, fillcolor="#fecaca"];
+ "traefik-service:gramps" -> "svc:grampsweb";
+ "svc:grampsweb_celery" [label="grampsweb_celery", shape=box, fillcolor="#dcfce7"];
+ "svc:grampsweb_celery" -> "net:gramps" [color="#6b7280"];
+ "svc:influxdb" [label="influxdb", shape=box, fillcolor="#dcfce7"];
+ "svc:influxdb" -> "net:monitor" [color="#6b7280"];
+ "svc:influxdb" -> "net:traefik" [color="#6b7280"];
+ "router:influxdb" [label="router:influxdb\nentry:websecure tls:false", shape=diamond, fillcolor="#fbcfe8"];
+ "svc:traefik" -> "router:influxdb";
+ "traefik-service:influxdb" [label="service:influxdb", shape=component, fillcolor="#fecaca"];
+ "router:influxdb" -> "traefik-service:influxdb";
+ "dns:service-6." [label="service-6.", shape=note, fillcolor="#fde68a"];
+ "ext:dynu" -> "dns:service-6.";
+ "dns:service-6." -> "router:influxdb";
+ "mw:authelia" [label="authelia", shape=hexagon, fillcolor="#ddd6fe"];
+ "router:influxdb" -> "mw:authelia" [style=dashed];
+ "traefik-service:influxdb" [label="service:influxdb\nport:8086", shape=component, fillcolor="#fecaca"];
+ "traefik-service:influxdb" -> "svc:influxdb";
+ "svc:monitor-kuma" [label="monitor-kuma", shape=box, fillcolor="#dcfce7"];
+ "svc:monitor-kuma" -> "net:monitor" [color="#6b7280"];
+ "svc:monitor-kuma" -> "net:traefik" [color="#6b7280"];
+ "router:monitor" [label="router:monitor\nentry:websecure tls:true", shape=diamond, fillcolor="#fbcfe8"];
+ "svc:traefik" -> "router:monitor";
+ "traefik-service:monitor-kuma" [label="service:monitor-kuma", shape=component, fillcolor="#fecaca"];
+ "router:monitor" -> "traefik-service:monitor-kuma";
+ "dns:service-7." [label="service-7.", shape=note, fillcolor="#fde68a"];
+ "ext:dynu" -> "dns:service-7.";
+ "dns:service-7." -> "router:monitor";
+ "traefik-service:monitor" [label="service:monitor\nport:3001", shape=component, fillcolor="#fecaca"];
+ "traefik-service:monitor" -> "svc:monitor-kuma";
+ "svc:mtls-bridge" [label="mtls-bridge", shape=box, fillcolor="#dcfce7"];
+ "svc:mtls-bridge" -> "net:monitor" [color="#6b7280"];
+ "svc:mtls-bridge" -> "net:traefik" [color="#6b7280"];
+ "router:mtls-bridge" [label="router:mtls-bridge\nentry:websecure tls:false", shape=diamond, fillcolor="#fbcfe8"];
+ "svc:traefik" -> "router:mtls-bridge";
+ "traefik-service:mtls-bridge" [label="service:mtls-bridge", shape=component, fillcolor="#fecaca"];
+ "router:mtls-bridge" -> "traefik-service:mtls-bridge";
+ "dns:service-8." [label="service-8.", shape=note, fillcolor="#fde68a"];
+ "ext:dynu" -> "dns:service-8.";
+ "dns:service-8." -> "router:mtls-bridge";
+ "mw:mtls-bridge-auth" [label="mtls-bridge-auth", shape=hexagon, fillcolor="#ddd6fe"];
+ "router:mtls-bridge" -> "mw:mtls-bridge-auth" [style=dashed];
+ "mw:mtls-bridge-cors" [label="mtls-bridge-cors", shape=hexagon, fillcolor="#ddd6fe"];
+ "router:mtls-bridge" -> "mw:mtls-bridge-cors" [style=dashed];
+ "router:mtls-bridge-preflight" [label="router:mtls-bridge-preflight\nentry:websecure tls:false", shape=diamond, fillcolor="#fbcfe8"];
+ "svc:traefik" -> "router:mtls-bridge-preflight";
+ "traefik-service:mtls-bridge" [label="service:mtls-bridge", shape=component, fillcolor="#fecaca"];
+ "router:mtls-bridge-preflight" -> "traefik-service:mtls-bridge";
+ "dns:service-8." [label="service-8.", shape=note, fillcolor="#fde68a"];
+ "ext:dynu" -> "dns:service-8.";
+ "dns:service-8." -> "router:mtls-bridge-preflight";
+ "mw:mtls-bridge-cors" [label="mtls-bridge-cors", shape=hexagon, fillcolor="#ddd6fe"];
+ "router:mtls-bridge-preflight" -> "mw:mtls-bridge-cors" [style=dashed];
+ "traefik-service:mtls-bridge" [label="service:mtls-bridge\nport:8080", shape=component, fillcolor="#fecaca"];
+ "traefik-service:mtls-bridge" -> "svc:mtls-bridge";
+ "svc:nextcloud-db" [label="nextcloud-db", shape=box, fillcolor="#dcfce7"];
+ "svc:nextcloud-db" -> "net:nextcloud" [color="#6b7280"];
+ "svc:nextcloud-redis" [label="nextcloud-redis", shape=box, fillcolor="#dcfce7"];
+ "svc:nextcloud-redis" -> "net:nextcloud" [color="#6b7280"];
+ "svc:nextcloud-webapp" [label="nextcloud-webapp", shape=box, fillcolor="#dcfce7"];
+ "svc:nextcloud-webapp" -> "net:nextcloud" [color="#6b7280"];
+ "svc:nextcloud-webapp" -> "net:traefik" [color="#6b7280"];
+ "router:nextcloud" [label="router:nextcloud\nentry:websecure tls:false", shape=diamond, fillcolor="#fbcfe8"];
+ "svc:traefik" -> "router:nextcloud";
+ "traefik-service:nextcloud-webapp" [label="service:nextcloud-webapp", shape=component, fillcolor="#fecaca"];
+ "router:nextcloud" -> "traefik-service:nextcloud-webapp";
+ "dns:service-9." [label="service-9.", shape=note, fillcolor="#fde68a"];
+ "ext:dynu" -> "dns:service-9.";
+ "dns:service-9." -> "router:nextcloud";
+ "mw:nextcloud-dav" [label="nextcloud-dav", shape=hexagon, fillcolor="#ddd6fe"];
+ "router:nextcloud" -> "mw:nextcloud-dav" [style=dashed];
+ "mw:nextcloud-webfinger" [label="nextcloud-webfinger", shape=hexagon, fillcolor="#ddd6fe"];
+ "router:nextcloud" -> "mw:nextcloud-webfinger" [style=dashed];
+ "svc:node-exporter" [label="node-exporter", shape=box, fillcolor="#dcfce7"];
+ "svc:node-exporter" -> "net:monitor" [color="#6b7280"];
+ "svc:node-red" [label="node-red", shape=box, fillcolor="#dcfce7"];
+ "svc:node-red" -> "net:monitor" [color="#6b7280"];
+ "svc:node-red" -> "net:traefik" [color="#6b7280"];
+ "router:node-red" [label="router:node-red\nentry:websecure tls:false", shape=diamond, fillcolor="#fbcfe8"];
+ "svc:traefik" -> "router:node-red";
+ "traefik-service:node-red" [label="service:node-red", shape=component, fillcolor="#fecaca"];
+ "router:node-red" -> "traefik-service:node-red";
+ "dns:service-10." [label="service-10.", shape=note, fillcolor="#fde68a"];
+ "ext:dynu" -> "dns:service-10.";
+ "dns:service-10." -> "router:node-red";
+ "mw:authelia" [label="authelia", shape=hexagon, fillcolor="#ddd6fe"];
+ "router:node-red" -> "mw:authelia" [style=dashed];
+ "traefik-service:node-red" [label="service:node-red\nport:1880", shape=component, fillcolor="#fecaca"];
+ "traefik-service:node-red" -> "svc:node-red";
+ "svc:passbolt-db" [label="passbolt-db", shape=box, fillcolor="#dcfce7"];
+ "svc:passbolt-db" -> "net:passbolt" [color="#6b7280"];
+ "svc:passbolt-webapp" [label="passbolt-webapp", shape=box, fillcolor="#dcfce7"];
+ "svc:passbolt-webapp" -> "net:passbolt" [color="#6b7280"];
+ "svc:passbolt-webapp" -> "net:traefik" [color="#6b7280"];
+ "router:passbolt" [label="router:passbolt\nentry:websecure tls:false", shape=diamond, fillcolor="#fbcfe8"];
+ "svc:traefik" -> "router:passbolt";
+ "traefik-service:passbolt-webapp" [label="service:passbolt-webapp", shape=component, fillcolor="#fecaca"];
+ "router:passbolt" -> "traefik-service:passbolt-webapp";
+ "dns:service-11." [label="service-11.", shape=note, fillcolor="#fde68a"];
+ "ext:dynu" -> "dns:service-11.";
+ "dns:service-11." -> "router:passbolt";
+ "svc:pihole-exporter" [label="pihole-exporter", shape=box, fillcolor="#dcfce7"];
+ "svc:pihole-exporter" -> "net:monitor" [color="#6b7280"];
+ "svc:portainer" [label="portainer", shape=box, fillcolor="#dcfce7"];
+ "svc:portainer" -> "net:traefik" [color="#6b7280"];
+ "router:portainer" [label="router:portainer\nentry:websecure tls:true", shape=diamond, fillcolor="#fbcfe8"];
+ "svc:traefik" -> "router:portainer";
+ "traefik-service:portainer" [label="service:portainer", shape=component, fillcolor="#fecaca"];
+ "router:portainer" -> "traefik-service:portainer";
+ "dns:service-12." [label="service-12.", shape=note, fillcolor="#fde68a"];
+ "ext:dynu" -> "dns:service-12.";
+ "dns:service-12." -> "router:portainer";
+ "traefik-service:portainer" [label="service:portainer\nport:9000", shape=component, fillcolor="#fecaca"];
+ "traefik-service:portainer" -> "svc:portainer";
+ "svc:prometheus" [label="prometheus", shape=box, fillcolor="#dcfce7"];
+ "svc:prometheus" -> "net:monitor" [color="#6b7280"];
+ "svc:prometheus" -> "net:traefik" [color="#6b7280"];
+ "router:prometheus" [label="router:prometheus\nentry:websecure tls:false", shape=diamond, fillcolor="#fbcfe8"];
+ "svc:traefik" -> "router:prometheus";
+ "traefik-service:prometheus" [label="service:prometheus", shape=component, fillcolor="#fecaca"];
+ "router:prometheus" -> "traefik-service:prometheus";
+ "dns:service-13." [label="service-13.", shape=note, fillcolor="#fde68a"];
+ "ext:dynu" -> "dns:service-13.";
+ "dns:service-13." -> "router:prometheus";
+ "mw:authelia" [label="authelia", shape=hexagon, fillcolor="#ddd6fe"];
+ "router:prometheus" -> "mw:authelia" [style=dashed];
+ "traefik-service:prometheus" [label="service:prometheus\nport:9090", shape=component, fillcolor="#fecaca"];
+ "traefik-service:prometheus" -> "svc:prometheus";
+ "svc:searxng-webapp" [label="searxng-webapp", shape=box, fillcolor="#dcfce7"];
+ "svc:searxng-webapp" -> "net:traefik" [color="#6b7280"];
+ "router:searxng" [label="router:searxng\nentry:websecure tls:false", shape=diamond, fillcolor="#fbcfe8"];
+ "svc:traefik" -> "router:searxng";
+ "traefik-service:searxng-webapp" [label="service:searxng-webapp", shape=component, fillcolor="#fecaca"];
+ "router:searxng" -> "traefik-service:searxng-webapp";
+ "dns:service-14." [label="service-14.", shape=note, fillcolor="#fde68a"];
+ "ext:dynu" -> "dns:service-14.";
+ "dns:service-14." -> "router:searxng";
+ "traefik-service:searxng" [label="service:searxng\nport:8080", shape=component, fillcolor="#fecaca"];
+ "traefik-service:searxng" -> "svc:searxng-webapp";
+ "svc:shift-recorder-web" [label="shift-recorder-web", shape=box, fillcolor="#dcfce7"];
+ "svc:shift-recorder-web" -> "net:traefik" [color="#6b7280"];
+ "router:shifts" [label="router:shifts\nentry:websecure tls:true", shape=diamond, fillcolor="#fbcfe8"];
+ "svc:traefik" -> "router:shifts";
+ "traefik-service:shift-recorder-web" [label="service:shift-recorder-web", shape=component, fillcolor="#fecaca"];
+ "router:shifts" -> "traefik-service:shift-recorder-web";
+ "dns:service-15." [label="service-15.", shape=note, fillcolor="#fde68a"];
+ "ext:dynu" -> "dns:service-15.";
+ "dns:service-15." -> "router:shifts";
+ "traefik-service:shifts" [label="service:shifts\nport:80", shape=component, fillcolor="#fecaca"];
+ "traefik-service:shifts" -> "svc:shift-recorder-web";
+ "svc:stockfill" [label="stockfill", shape=box, fillcolor="#dcfce7"];
+ "svc:stockfill" -> "net:traefik" [color="#6b7280"];
+ "router:stockfill" [label="router:stockfill\nentry:websecure tls:true", shape=diamond, fillcolor="#fbcfe8"];
+ "svc:traefik" -> "router:stockfill";
+ "traefik-service:stockfill" [label="service:stockfill", shape=component, fillcolor="#fecaca"];
+ "router:stockfill" -> "traefik-service:stockfill";
+ "dns:service-16." [label="service-16.", shape=note, fillcolor="#fde68a"];
+ "ext:dynu" -> "dns:service-16.";
+ "dns:service-16." -> "router:stockfill";
+ "traefik-service:stockfill" [label="service:stockfill\nport:80", shape=component, fillcolor="#fecaca"];
+ "traefik-service:stockfill" -> "svc:stockfill";
+ "svc:telegraf" [label="telegraf", shape=box, fillcolor="#dcfce7"];
+ "svc:telegraf" -> "net:monitor" [color="#6b7280"];
+ "svc:traefik" [label="traefik", shape=box, fillcolor="#dcfce7"];
+ "svc:traefik" -> "net:traefik" [color="#6b7280"];
+ "router:traefik" [label="router:traefik\nentry:websecure tls:false", shape=diamond, fillcolor="#fbcfe8"];
+ "svc:traefik" -> "router:traefik";
+ "traefik-service:api@internal" [label="service:api@internal", shape=component, fillcolor="#fecaca"];
+ "router:traefik" -> "traefik-service:api@internal";
+ "dns:service-17." [label="service-17.", shape=note, fillcolor="#fde68a"];
+ "ext:dynu" -> "dns:service-17.";
+ "dns:service-17." -> "router:traefik";
+ "mw:authelia" [label="authelia", shape=hexagon, fillcolor="#ddd6fe"];
+ "router:traefik" -> "mw:authelia" [style=dashed];
+}
diff --git a/docs/diagrams/docker-traefik-dynu.svg b/docs/diagrams/docker-traefik-dynu.svg
new file mode 100644
index 0000000..3011e6a
--- /dev/null
+++ b/docs/diagrams/docker-traefik-dynu.svg
@@ -0,0 +1,1560 @@
+
+
+
+
+
diff --git a/docs/diagrams/physical-topology.dot b/docs/diagrams/physical-topology.dot
new file mode 100644
index 0000000..476f76b
--- /dev/null
+++ b/docs/diagrams/physical-topology.dot
@@ -0,0 +1,79 @@
+digraph PhysicalTopology {
+ graph [rankdir=LR, compound=true, splines=ortho, nodesep=0.45, ranksep=0.75, fontname="Helvetica"];
+ node [fontname="Helvetica", fontsize=10, style="rounded,filled", fillcolor="#ffffff"];
+ edge [fontname="Helvetica", fontsize=9];
+ subgraph "cluster_docker" {
+ label="docker";
+ style="rounded,filled";
+ color="#c7d6f5";
+ fillcolor="#eef3ff";
+ "host:docker" [label="docker", shape=box3d, fillcolor="#d4e3ff"];
+ "svc:authelia" [label="authelia", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:authelia" [style=dashed, color="#6b7280"];
+ "svc:crowdsec" [label="crowdsec", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:crowdsec" [style=dashed, color="#6b7280"];
+ "svc:docker-socket-proxy" [label="docker-socket-proxy", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:docker-socket-proxy" [style=dashed, color="#6b7280"];
+ "svc:docker-update-exporter" [label="docker-update-exporter", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:docker-update-exporter" [style=dashed, color="#6b7280"];
+ "svc:error-pages" [label="error-pages", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:error-pages" [style=dashed, color="#6b7280"];
+ "svc:gitea" [label="gitea", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:gitea" [style=dashed, color="#6b7280"];
+ "svc:gitea-runner" [label="gitea-runner", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:gitea-runner" [style=dashed, color="#6b7280"];
+ "svc:gotify" [label="gotify", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:gotify" [style=dashed, color="#6b7280"];
+ "svc:grafana" [label="grafana", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:grafana" [style=dashed, color="#6b7280"];
+ "svc:gramps-redis" [label="gramps-redis", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:gramps-redis" [style=dashed, color="#6b7280"];
+ "svc:grampsweb" [label="grampsweb", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:grampsweb" [style=dashed, color="#6b7280"];
+ "svc:grampsweb_celery" [label="grampsweb_celery", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:grampsweb_celery" [style=dashed, color="#6b7280"];
+ "svc:influxdb" [label="influxdb", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:influxdb" [style=dashed, color="#6b7280"];
+ "svc:monitor-kuma" [label="monitor-kuma", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:monitor-kuma" [style=dashed, color="#6b7280"];
+ "svc:mtls-bridge" [label="mtls-bridge", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:mtls-bridge" [style=dashed, color="#6b7280"];
+ "svc:nextcloud-db" [label="nextcloud-db", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:nextcloud-db" [style=dashed, color="#6b7280"];
+ "svc:nextcloud-redis" [label="nextcloud-redis", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:nextcloud-redis" [style=dashed, color="#6b7280"];
+ "svc:nextcloud-webapp" [label="nextcloud-webapp", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:nextcloud-webapp" [style=dashed, color="#6b7280"];
+ "svc:node-exporter" [label="node-exporter", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:node-exporter" [style=dashed, color="#6b7280"];
+ "svc:node-red" [label="node-red", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:node-red" [style=dashed, color="#6b7280"];
+ "svc:passbolt-db" [label="passbolt-db", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:passbolt-db" [style=dashed, color="#6b7280"];
+ "svc:passbolt-webapp" [label="passbolt-webapp", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:passbolt-webapp" [style=dashed, color="#6b7280"];
+ "svc:portainer" [label="portainer", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:portainer" [style=dashed, color="#6b7280"];
+ "svc:prometheus" [label="prometheus", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:prometheus" [style=dashed, color="#6b7280"];
+ "svc:searxng-webapp" [label="searxng-webapp", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:searxng-webapp" [style=dashed, color="#6b7280"];
+ "svc:shift-recorder-web" [label="shift-recorder-web", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:shift-recorder-web" [style=dashed, color="#6b7280"];
+ "svc:stockfill" [label="stockfill", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:stockfill" [style=dashed, color="#6b7280"];
+ "svc:telegraf" [label="telegraf", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:telegraf" [style=dashed, color="#6b7280"];
+ "svc:traefik" [label="traefik", shape=box, fillcolor="#dff2e1"];
+ "host:docker" -> "svc:traefik" [style=dashed, color="#6b7280"];
+ }
+ subgraph "cluster_raspberrypi" {
+ label="raspberrypi";
+ style="rounded,filled";
+ color="#c7d6f5";
+ fillcolor="#eef3ff";
+ "host:raspberrypi" [label="raspberrypi", shape=box3d, fillcolor="#d4e3ff"];
+ "svc:pihole-exporter" [label="pihole-exporter", shape=box, fillcolor="#dff2e1"];
+ "host:raspberrypi" -> "svc:pihole-exporter" [style=dashed, color="#6b7280"];
+ }
+}
diff --git a/docs/diagrams/physical-topology.svg b/docs/diagrams/physical-topology.svg
new file mode 100644
index 0000000..77daceb
--- /dev/null
+++ b/docs/diagrams/physical-topology.svg
@@ -0,0 +1,401 @@
+
+
+
+
+
diff --git a/docs/public/diagrams.md b/docs/public/diagrams.md
new file mode 100644
index 0000000..334e03b
--- /dev/null
+++ b/docs/public/diagrams.md
@@ -0,0 +1,9 @@
+# Infrastructure diagrams
+
+## Physical / virtual topology
+
+
+
+## Docker, Traefik and Dynu routing
+
+
diff --git a/docs/public/docker-compose.svg b/docs/public/docker-compose.svg
index cd8a7c6..1991277 100644
--- a/docs/public/docker-compose.svg
+++ b/docs/public/docker-compose.svg
@@ -1 +1,463 @@
-
+
+
+
+
+
diff --git a/docs/public/docker-traefik-dynu.svg b/docs/public/docker-traefik-dynu.svg
new file mode 100644
index 0000000..3011e6a
--- /dev/null
+++ b/docs/public/docker-traefik-dynu.svg
@@ -0,0 +1,1560 @@
+
+
+
+
+
diff --git a/docs/public/index.md b/docs/public/index.md
index f600c19..9d57d8e 100644
--- a/docs/public/index.md
+++ b/docs/public/index.md
@@ -1,12 +1,20 @@
# Public Infrastructure Summary
-This folder contains sanitized documentation generated from the infrastructure repository.
+This documentation is generated from the infrastructure repository. Sensitive values are redacted.
-Sensitive values such as internal domain names, private IP addresses, tokens, passwords, and secrets are redacted.
+## Infrastructure diagrams
+
+### Physical / virtual topology
+
+
+
+### Docker, Traefik and Dynu routing
+
+
## Documents
+- [Diagrams](diagrams.md)
- [Compose Inventory](compose-inventory.md)
- [Traefik Routes](traefik-routes.md)
- [Prometheus Rules](prometheus-rules.md)
-- [Docker Compose Diagram](docker-compose.svg)
diff --git a/docs/public/physical-topology.svg b/docs/public/physical-topology.svg
new file mode 100644
index 0000000..77daceb
--- /dev/null
+++ b/docs/public/physical-topology.svg
@@ -0,0 +1,401 @@
+
+
+
+
+
diff --git a/mkdocs-public.yml b/mkdocs-public.yml
index 9315745..77a2e2e 100644
--- a/mkdocs-public.yml
+++ b/mkdocs-public.yml
@@ -7,6 +7,7 @@ site_dir: site-public
nav:
- Home: index.md
+ - Diagrams: diagrams.md
- Compose Inventory: compose-inventory.md
- Traefik Routes: traefik-routes.md
- Prometheus Rules: prometheus-rules.md
diff --git a/scripts/docs/generate-all.sh b/scripts/docs/generate-all.sh
index 7690e59..65806ee 100755
--- a/scripts/docs/generate-all.sh
+++ b/scripts/docs/generate-all.sh
@@ -8,5 +8,5 @@ python3 scripts/docs/generate-compose-inventory.py docs/generated/docker-compose
python3 scripts/docs/generate-traefik-routes.py docs/generated/docker-compose.resolved.yml docs/generated/traefik-routes.md
python3 scripts/docs/generate-prometheus-rules.py docs/generated/prometheus-rules.md
python3 scripts/docs/generate-docs-index.py docs/generated/index.md
-python3 scripts/docs/generate-diagrams.py docs/generated/docker-compose.resolved.yml docs/diagrams/docker-compose.dot docs/diagrams/docker-compose.svg
+python3 scripts/docs/generate-diagrams.py --compose docs/generated/docker-compose.resolved.yml --out-dir docs/diagrams
python3 scripts/docs/sanitize-public-docs.py docs/generated docs/diagrams docs/public
diff --git a/scripts/docs/generate-diagrams.py b/scripts/docs/generate-diagrams.py
index d1690d9..7945c71 100644
--- a/scripts/docs/generate-diagrams.py
+++ b/scripts/docs/generate-diagrams.py
@@ -1,18 +1,231 @@
#!/usr/bin/env python3
-import sys,yaml,subprocess,shutil
-inp,dotf,svgf=sys.argv[1],sys.argv[2],sys.argv[3]
-with open(inp) as f:c=yaml.safe_load(f) or {}
-svcs=c.get('services') or {}
-lines=["digraph Compose {"," rankdir=LR;"," node [fontname=Helvetica];"]
-for s in svcs: lines.append(f' "svc:{s}" [label="{s}", shape=box, style=filled, fillcolor="#dfefff"];')
-for n in (c.get('networks') or {}).keys(): lines.append(f' "net:{n}" [label="{n}", shape=ellipse, style=filled, fillcolor="#f4f4f4"];')
-for s,sv in svcs.items():
- ns=sv.get('networks') or []
- if isinstance(ns,dict): ns=ns.keys()
- for n in ns: lines.append(f' "svc:{s}" -> "net:{n}";')
-lines.append("}")
-open(dotf,'w').write('\n'.join(lines)+'\n')
-if shutil.which('dot'):
- subprocess.run(['dot','-Tsvg',dotf,'-o',svgf],check=True)
-else:
- open(svgf,'w').write('\n')
+import argparse
+import re
+import subprocess
+import shutil
+from pathlib import Path
+import yaml
+
+INTERNAL_DOMAIN_RE = re.compile(r"\b[a-zA-Z0-9.-]+\.lan\.ddnsgeek\.com\b")
+HOST_MATCH_RE = re.compile(r"Host\(([^)]*)\)")
+TICKED_HOST_RE = re.compile(r"`([^`]+)`")
+
+
+def require_dot() -> None:
+ if not shutil.which("dot"):
+ raise SystemExit(
+ "Graphviz 'dot' not found in environment. Install graphviz before running docs generation."
+ )
+
+
+def load_compose(path: Path) -> dict:
+ with path.open() as handle:
+ return yaml.safe_load(handle) or {}
+
+
+def sanitize_domain(value: str, known: dict[str, str]) -> str:
+ if value in known:
+ return known[value]
+ if INTERNAL_DOMAIN_RE.search(value):
+ mapped = f"service-{len(known)+1}."
+ known[value] = mapped
+ return mapped
+ return value
+
+
+def parse_labels(service: dict) -> dict[str, str]:
+ labels = service.get("labels") or {}
+ if isinstance(labels, list):
+ mapped = {}
+ for item in labels:
+ if "=" in str(item):
+ k, v = str(item).split("=", 1)
+ mapped[k] = v
+ return mapped
+ if isinstance(labels, dict):
+ return {str(k): str(v) for k, v in labels.items()}
+ return {}
+
+
+def extract_hosts(rule: str) -> list[str]:
+ hosts: list[str] = []
+ for m in HOST_MATCH_RE.findall(rule or ""):
+ for host in TICKED_HOST_RE.findall(m):
+ hosts.append(host.strip())
+ return hosts
+
+
+def render_svg(dot_path: Path, svg_path: Path) -> None:
+ subprocess.run(["dot", "-Tsvg", str(dot_path), "-o", str(svg_path)], check=True)
+
+
+def write_dot(path: Path, lines: list[str]) -> None:
+ path.write_text("\n".join(lines) + "\n")
+
+
+def infer_host(service_name: str, service: dict) -> str:
+ labels = parse_labels(service)
+ for key in ("com.docker.compose.project", "infra.host", "host"):
+ value = labels.get(key)
+ if value:
+ return value.lower()
+ s = service_name.lower()
+ if "raspi" in s or "pi" in s:
+ return "raspberrypi"
+ if "proxmox" in s:
+ return "proxmox"
+ return "docker"
+
+
+def generate_physical_topology(compose: dict, out_dot: Path, out_svg: Path) -> None:
+ services = compose.get("services") or {}
+ hosts: dict[str, list[str]] = {}
+ for name, svc in services.items():
+ hosts.setdefault(infer_host(name, svc), []).append(name)
+
+ lines = [
+ "digraph PhysicalTopology {",
+ " graph [rankdir=LR, compound=true, splines=ortho, nodesep=0.45, ranksep=0.75, fontname=\"Helvetica\"];",
+ " node [fontname=\"Helvetica\", fontsize=10, style=\"rounded,filled\", fillcolor=\"#ffffff\"];",
+ " edge [fontname=\"Helvetica\", fontsize=9];",
+ ]
+
+ for host, host_services in sorted(hosts.items()):
+ lines.extend(
+ [
+ f' subgraph "cluster_{host}" {{',
+ f' label="{host}";',
+ ' style="rounded,filled";',
+ ' color="#c7d6f5";',
+ ' fillcolor="#eef3ff";',
+ f' "host:{host}" [label="{host}", shape=box3d, fillcolor="#d4e3ff"];',
+ ]
+ )
+ for service in sorted(host_services):
+ lines.append(
+ f' "svc:{service}" [label="{service}", shape=box, fillcolor="#dff2e1"];'
+ )
+ lines.append(f' "host:{host}" -> "svc:{service}" [style=dashed, color="#6b7280"];')
+ lines.append(" }")
+
+ lines.append("}")
+ write_dot(out_dot, lines)
+ render_svg(out_dot, out_svg)
+
+
+def generate_docker_traefik_dynu(compose: dict, out_dot: Path, out_svg: Path) -> None:
+ services = compose.get("services") or {}
+ networks = compose.get("networks") or {}
+ known_domains: dict[str, str] = {}
+
+ lines = [
+ "digraph DockerTraefikDynu {",
+ " graph [rankdir=LR, compound=true, splines=true, nodesep=0.5, ranksep=1.0, fontname=\"Helvetica\"];",
+ " node [fontname=\"Helvetica\", fontsize=10, style=\"rounded,filled\"];",
+ " edge [fontname=\"Helvetica\", fontsize=9];",
+ ' "ext:dynu" [label="Dynu / Public DNS", shape=oval, fillcolor="#fde68a"];',
+ ' "svc:traefik" [label="traefik", shape=box, fillcolor="#bfdbfe"];',
+ ]
+
+ for net in sorted(networks.keys()):
+ lines.append(f' "net:{net}" [label="{net}", shape=ellipse, fillcolor="#f3f4f6"];')
+
+ for svc_name, svc in sorted(services.items()):
+ lines.append(f' "svc:{svc_name}" [label="{svc_name}", shape=box, fillcolor="#dcfce7"];')
+ svc_nets = svc.get("networks") or []
+ if isinstance(svc_nets, dict):
+ svc_nets = svc_nets.keys()
+ for net in svc_nets:
+ lines.append(f' "svc:{svc_name}" -> "net:{net}" [color="#6b7280"];')
+
+ labels = parse_labels(svc)
+ router_prefix = "traefik.http.routers."
+ service_prefix = "traefik.http.services."
+
+ routers = sorted({k[len(router_prefix):].split(".", 1)[0] for k in labels if k.startswith(router_prefix)})
+ for router in routers:
+ rule = labels.get(f"{router_prefix}{router}.rule", "")
+ router_service = labels.get(f"{router_prefix}{router}.service", svc_name)
+ middlewares = labels.get(f"{router_prefix}{router}.middlewares", "")
+ entrypoints = labels.get(f"{router_prefix}{router}.entrypoints", "")
+ tls = labels.get(f"{router_prefix}{router}.tls", "false")
+ lines.append(f' "router:{router}" [label="router:{router}\\nentry:{entrypoints} tls:{tls}", shape=diamond, fillcolor="#fbcfe8"];')
+ lines.append(f' "svc:traefik" -> "router:{router}";')
+ target_node = f"traefik-service:{router_service}"
+ lines.append(f' "{target_node}" [label="service:{router_service}", shape=component, fillcolor="#fecaca"];')
+ lines.append(f' "router:{router}" -> "{target_node}";')
+
+ for host in extract_hosts(rule):
+ clean = sanitize_domain(host, known_domains)
+ lines.append(f' "dns:{clean}" [label="{clean}", shape=note, fillcolor="#fde68a"];')
+ lines.append(f' "ext:dynu" -> "dns:{clean}";')
+ lines.append(f' "dns:{clean}" -> "router:{router}";')
+
+ for middleware in [m.strip() for m in middlewares.split(",") if m.strip()]:
+ lines.append(f' "mw:{middleware}" [label="{middleware}", shape=hexagon, fillcolor="#ddd6fe"];')
+ lines.append(f' "router:{router}" -> "mw:{middleware}" [style=dashed];')
+
+ lb_services = sorted({k[len(service_prefix):].split(".", 1)[0] for k in labels if k.startswith(service_prefix)})
+ for lb in lb_services:
+ port = labels.get(f"{service_prefix}{lb}.loadbalancer.server.port", "")
+ lines.append(f' "traefik-service:{lb}" [label="service:{lb}\\nport:{port}", shape=component, fillcolor="#fecaca"];')
+ lines.append(f' "traefik-service:{lb}" -> "svc:{svc_name}";')
+
+ lines.append("}")
+ write_dot(out_dot, lines)
+ render_svg(out_dot, out_svg)
+
+
+def generate_compose_topology(compose: dict, out_dot: Path, out_svg: Path) -> None:
+ services = compose.get("services") or {}
+ networks = compose.get("networks") or {}
+ lines = [
+ "digraph Compose {",
+ " rankdir=LR;",
+ ' node [fontname="Helvetica"];',
+ ]
+ for service in sorted(services):
+ lines.append(f' "svc:{service}" [label="{service}", shape=box, style=filled, fillcolor="#dfefff"];')
+ for net in sorted(networks):
+ lines.append(f' "net:{net}" [label="{net}", shape=ellipse, style=filled, fillcolor="#f4f4f4"];')
+
+ for service, svc in sorted(services.items()):
+ svc_nets = svc.get("networks") or []
+ if isinstance(svc_nets, dict):
+ svc_nets = svc_nets.keys()
+ for net in svc_nets:
+ lines.append(f' "svc:{service}" -> "net:{net}";')
+
+ lines.append("}")
+ write_dot(out_dot, lines)
+ render_svg(out_dot, out_svg)
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser()
+ parser.add_argument("legacy", nargs="*")
+ parser.add_argument("--compose")
+ parser.add_argument("--out-dir")
+ args = parser.parse_args()
+
+ require_dot()
+
+ if args.compose and args.out_dir:
+ compose_path = Path(args.compose)
+ out_dir = Path(args.out_dir)
+ elif len(args.legacy) == 3:
+ compose_path = Path(args.legacy[0])
+ out_dir = Path(args.legacy[1]).parent
+ else:
+ raise SystemExit("Usage: generate-diagrams.py --compose --out-dir ")
+
+ out_dir.mkdir(parents=True, exist_ok=True)
+ compose = load_compose(compose_path)
+
+ generate_docker_traefik_dynu(compose, out_dir / "docker-traefik-dynu.dot", out_dir / "docker-traefik-dynu.svg")
+ generate_physical_topology(compose, out_dir / "physical-topology.dot", out_dir / "physical-topology.svg")
+ generate_compose_topology(compose, out_dir / "docker-compose.dot", out_dir / "docker-compose.svg")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/docs/sanitize-public-docs.py b/scripts/docs/sanitize-public-docs.py
index 78012cb..4a1a6e2 100644
--- a/scripts/docs/sanitize-public-docs.py
+++ b/scripts/docs/sanitize-public-docs.py
@@ -20,27 +20,50 @@ def sanitize_text(content: str) -> str:
content = re.sub(r'(?m)^([A-Z0-9_]*(?:PASSWORD|TOKEN|API_KEY|SECRET)[A-Z0-9_]*)\s*[:=]\s*.*$', r'\1=', content)
return content
+
for name in ['compose-inventory.md', 'traefik-routes.md', 'prometheus-rules.md']:
src = src_generated / name
if src.exists():
(out_dir / name).write_text(sanitize_text(src.read_text(errors='ignore')))
-svg_src = src_diagrams / 'docker-compose.svg'
-if svg_src.exists():
- (out_dir / 'docker-compose.svg').write_text(sanitize_text(svg_src.read_text(errors='ignore')))
+for svg_name in ['docker-compose.svg', 'physical-topology.svg', 'docker-traefik-dynu.svg']:
+ src = src_diagrams / svg_name
+ if src.exists():
+ (out_dir / svg_name).write_text(sanitize_text(src.read_text(errors='ignore')))
(out_dir / 'index.md').write_text(
"""# Public Infrastructure Summary
-This folder contains sanitized documentation generated from the infrastructure repository.
+This documentation is generated from the infrastructure repository. Sensitive values are redacted.
-Sensitive values such as internal domain names, private IP addresses, tokens, passwords, and secrets are redacted.
+## Infrastructure diagrams
+
+### Physical / virtual topology
+
+
+
+### Docker, Traefik and Dynu routing
+
+
## Documents
+- [Diagrams](diagrams.md)
- [Compose Inventory](compose-inventory.md)
- [Traefik Routes](traefik-routes.md)
- [Prometheus Rules](prometheus-rules.md)
-- [Docker Compose Diagram](docker-compose.svg)
+"""
+)
+
+(out_dir / 'diagrams.md').write_text(
+ """# Infrastructure diagrams
+
+## Physical / virtual topology
+
+
+
+## Docker, Traefik and Dynu routing
+
+
"""
)