Compare commits
91 Commits
f0aa9941d8
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| c89b8129ec | |||
| c98b2d8232 | |||
| 3a9b3786c5 | |||
| 1131eceb94 | |||
| 586be50567 | |||
| d0e08b8fc1 | |||
| 487117b31a | |||
| 82fa90d843 | |||
| ef1e7432ee | |||
| 427ea20e43 | |||
| 3298219ccf | |||
| b30c7f3a8a | |||
| 374511c123 | |||
| 313f7f1c21 | |||
| 6aa78525c2 | |||
| 5e0de23ff7 | |||
| 3c655dabcf | |||
| 60e59f95a8 | |||
| cdf37fb2c9 | |||
| f56b734cb4 | |||
| 10c4373c0e | |||
| 2619d86dc1 | |||
| 1b679a4f09 | |||
| 02033cd3f9 | |||
| d932bc57d0 | |||
| c4cfa8081f | |||
| 90c9094a6f | |||
| 36c5f2b1e3 | |||
| 7bc2729ef6 | |||
| 9d79f828e4 | |||
| 22b3659cdf | |||
| 4440fddb2b | |||
| d74000fbd0 | |||
| 9efdb5c781 | |||
| e7fd52616d | |||
| b7b4b4d36d | |||
| 40f5a3ce0d | |||
| 8e43118661 | |||
| 0e76f3cef1 | |||
| 8d18ab7059 | |||
| 6a959d85c3 | |||
| 79c583eca3 | |||
| 7fcafed3f9 | |||
| 0b67830d7f | |||
| d878a0f9b8 | |||
| 9c38910a67 | |||
| 4a38a9421d | |||
| 5d32693925 | |||
| 696cecfecb | |||
| c0360a14b9 | |||
| 5589594d2c | |||
| 0bd41b2fb1 | |||
| 5f10d0366e | |||
| 9f98101c5d | |||
| 0c20676590 | |||
| 59e2c2b9a3 | |||
| 61d49e85a3 | |||
| 8f84ed6d83 | |||
| 24cbb02bff | |||
| 685a472572 | |||
| 9de1bee542 | |||
| 306e2c14db | |||
| 63b47b59b5 | |||
| 52bd2d9fa2 | |||
| 034ad17cf9 | |||
| ef2846562b | |||
| 52e2102f93 | |||
| a9b21912db | |||
| c9e4aeb8d6 | |||
| 96a44d5da6 | |||
| 0d936677e5 | |||
| 5c38b92bc8 | |||
| fc5c882193 | |||
| a4501d4034 | |||
| c0958d8f02 | |||
| 2a97864a06 | |||
| bd23339edd | |||
| f8091a5c76 | |||
| 6a2639f931 | |||
| fae5e119d1 | |||
| 872038d0c9 | |||
| 7a6db9fcfd | |||
| d6a8979d55 | |||
| a9a8a708d3 | |||
| 749c0d500d | |||
| 8f112af65b | |||
| 580e9b9aed | |||
| c77db36865 | |||
| e11dc22999 | |||
| 862ddd42f8 | |||
| d0e7e52150 |
@@ -0,0 +1,71 @@
|
||||
name: Generate Docs
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 */6 * * *"
|
||||
|
||||
jobs:
|
||||
generate:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DOCKER_HOST: tcp://docker-socket-proxy:2375
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install docker CLI
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y docker.io docker-compose
|
||||
|
||||
- name: Generate docs
|
||||
run: |
|
||||
scripts/docs/generate-all.sh
|
||||
|
||||
- name: Validate generated docs
|
||||
run: |
|
||||
set -e
|
||||
|
||||
test -s docs/generated/docker-compose.resolved.yml
|
||||
test -s docs/generated/host-topology.md
|
||||
test -s docs/public/physical-topology.svg
|
||||
test -s docs/public/docker-traefik-dynu.svg
|
||||
|
||||
! grep -R "Host inventory JSON not found" docs/public docs/diagrams
|
||||
! grep -R "Generate terraform inventory" docs/public docs/diagrams
|
||||
|
||||
# Ensure no obvious secrets leaked
|
||||
! grep -R -E -i "password|token|api[_-]?key|secret" docs/public \
|
||||
|| (echo "Secret-like string detected"; exit 1)
|
||||
|
||||
- name: Commit changes
|
||||
run: |
|
||||
git config user.name "docs-bot"
|
||||
git config user.email "docs-bot@local"
|
||||
|
||||
git add docs/generated docs/diagrams docs/public data/terraform/proxmox-inventory.json || true
|
||||
|
||||
if git diff --cached --quiet; then
|
||||
echo "No changes to commit"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git commit -m "docs: regenerate documentation artifacts"
|
||||
|
||||
- name: Push to Gitea
|
||||
run: |
|
||||
git push origin HEAD:main
|
||||
|
||||
- name: Push to GitHub mirror
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_PUSH_TOKEN }}
|
||||
GITHUB_MIRROR_REPO: ${{ vars.GITHUB_MIRROR_REPO }}
|
||||
run: |
|
||||
test -n "$GITHUB_TOKEN"
|
||||
test -n "$GITHUB_MIRROR_REPO"
|
||||
git remote add github "https://$GITHUB_TOKEN@github.com/$GITHUB_MIRROR_REPO.git" || true
|
||||
git push github HEAD:main
|
||||
@@ -0,0 +1,24 @@
|
||||
name: Validate Docs (Gitea)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- "docs/**"
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Validate docs
|
||||
run: |
|
||||
set -e
|
||||
|
||||
test -d docs/public
|
||||
test -s docs/public/physical-topology.svg
|
||||
|
||||
! grep -R "Host inventory JSON not found" docs/public
|
||||
! grep -R "Generate terraform inventory" docs/public
|
||||
@@ -0,0 +1,35 @@
|
||||
name: Validate committed public docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
validate-public-docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Ensure committed docs/public exists
|
||||
run: |
|
||||
test -d docs/public
|
||||
test -n "$(find docs/public -mindepth 1 -print -quit)"
|
||||
- name: Install MkDocs
|
||||
run: |
|
||||
python3 -m pip install --user mkdocs
|
||||
- name: Validate docs content
|
||||
run: |
|
||||
set -e
|
||||
test -s docs/public/physical-topology.svg
|
||||
test -s docs/public/docker-traefik-dynu.svg
|
||||
! grep -R "Host inventory JSON not found" docs/public
|
||||
! grep -R "Generate terraform inventory" docs/public
|
||||
! rg -n -i "password|token|api[_-]?key|secret" docs/public
|
||||
- name: Build MkDocs site
|
||||
run: |
|
||||
python3 -m mkdocs build -f mkdocs-public.yml --strict
|
||||
@@ -0,0 +1,84 @@
|
||||
name: Publish documentation site
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: github-pages
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Ensure committed docs/public exists
|
||||
run: |
|
||||
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
|
||||
run: |
|
||||
test -d site-public
|
||||
test ! -e site-public/generated
|
||||
test ! -e site-public/docker
|
||||
|
||||
- name: Verify expected 404-only paths are not generated
|
||||
run: |
|
||||
test ! -e site-public/generated/compose-inventory/index.html
|
||||
test ! -e site-public/generated/prometheus-rules/index.html
|
||||
test ! -e site-public/docker/index.html
|
||||
|
||||
- name: Configure GitHub Pages
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
- name: Upload GitHub Pages artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: site-public
|
||||
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
+23
@@ -4,6 +4,7 @@
|
||||
#!**/Dockerfile
|
||||
#!docker-compose.yml
|
||||
**/data/
|
||||
apps/gitea/runner-data/
|
||||
**/db/
|
||||
**/database/
|
||||
apps/nextcloud/config/
|
||||
@@ -28,3 +29,25 @@ secrets/*
|
||||
!.env.example
|
||||
core/traefik/certs/*
|
||||
!core/traefik/certs/.gitkeep
|
||||
site/
|
||||
|
||||
# Docs generation artifacts intentionally tracked
|
||||
!data/terraform/proxmox-inventory.json
|
||||
!infrastructure/terraform/dynu/generated/
|
||||
!infrastructure/terraform/dynu/generated/dynu_dns_records_inventory.json
|
||||
!docs/generated/
|
||||
!docs/generated/docker-compose.resolved.yml
|
||||
!docs/generated/host-topology.md
|
||||
!docs/diagrams/
|
||||
!docs/diagrams/*.svg
|
||||
!docs/public/
|
||||
!docs/public/*.md
|
||||
!docs/public/*.svg
|
||||
|
||||
# Terraform local/state artifacts
|
||||
**/.terraform/
|
||||
**/.terraform.lock.hcl
|
||||
*.tfstate
|
||||
*.tfstate.*
|
||||
*.tfvars
|
||||
*.tfvars.json
|
||||
|
||||
@@ -17,11 +17,20 @@ If you only read one section, read **[Source-of-truth boundaries](docs/source-of
|
||||
- Docker environment composition and `services-up.sh`: [docs/docker-environment.md](docs/docker-environment.md)
|
||||
- Terraform workflows (brownfield import/reconciliation): [docs/terraform-workflows.md](docs/terraform-workflows.md)
|
||||
- Infrastructure inventory intent and outputs: [docs/infrastructure-inventory.md](docs/infrastructure-inventory.md)
|
||||
- Dynu DNS read-only inventory workflow: [docs/dynu-dns-inventory.md](docs/dynu-dns-inventory.md)
|
||||
- Generated host topology doc: [docs/generated/host-topology.md](docs/generated/host-topology.md)
|
||||
- Ansible bootstrap workflows: [docs/ansible-workflows.md](docs/ansible-workflows.md)
|
||||
- Deployment prerequisites and secrets setup: [docs/deployment-prerequisites.md](docs/deployment-prerequisites.md)
|
||||
- Secrets inventory: [docs/security-secrets.md](docs/security-secrets.md)
|
||||
|
||||
Terraform subtrees:
|
||||
Codex helper scripts:
|
||||
|
||||
- Initial Codex environment/bootstrap setup: [scripts/codex-setup.sh](scripts/codex-setup.sh)
|
||||
- Codex environment maintenance/refresh: [scripts/codex-maintenance.sh](scripts/codex-maintenance.sh)
|
||||
|
||||
Infrastructure subtrees:
|
||||
|
||||
- Ansible foundation docs: [infrastructure/ansible/README.md](infrastructure/ansible/README.md)
|
||||
- Terraform root docs: [infrastructure/terraform/README.md](infrastructure/terraform/README.md)
|
||||
- Terraform Docker mirror: [infrastructure/terraform/docker/README.md](infrastructure/terraform/docker/README.md)
|
||||
- Terraform Proxmox inventory: [infrastructure/terraform/proxmox/README.md](infrastructure/terraform/proxmox/README.md)
|
||||
@@ -36,6 +45,13 @@ Terraform subtrees:
|
||||
- `services-up.sh` composes the environment by discovering compose files and applying common env/network inputs.
|
||||
- For service runtime behavior, start from Compose files and `services-up.sh` (not Terraform).
|
||||
|
||||
|
||||
### Ansible (bootstrap foundation)
|
||||
|
||||
- Ansible under `infrastructure/ansible/` is a phase-1 foundation for inventory/configuration scaffolding.
|
||||
- It supports safe validation (inventory parsing and playbook syntax checks) while hosts/devices are onboarded gradually.
|
||||
- It does not replace Compose runtime authority or Terraform reconciliation authority at this stage.
|
||||
|
||||
### Terraform (inventory and reconciliation authority)
|
||||
|
||||
- Terraform under `infrastructure/terraform/` is used to codify and reconcile existing infrastructure.
|
||||
@@ -103,3 +119,70 @@ flowchart TB
|
||||
```
|
||||
|
||||
For request-flow and network detail, see [docs/architecture.md](docs/architecture.md).
|
||||
|
||||
## Public docs publication workflow
|
||||
|
||||
Public docs are generated on the Docker host and committed to this repository. GitHub Actions only publishes committed content from `docs/public`.
|
||||
|
||||
1. Generate public docs locally from the repository root:
|
||||
|
||||
```bash
|
||||
./scripts/generate-public-docs.sh
|
||||
```
|
||||
|
||||
2. Inspect the generated changes:
|
||||
|
||||
```bash
|
||||
git diff -- docs/public docs/generated docs/diagrams
|
||||
```
|
||||
|
||||
3. Commit the generated public docs (and any supporting generated files you intend to version):
|
||||
|
||||
```bash
|
||||
git add docs/public docs/generated docs/diagrams
|
||||
git commit -m "docs: regenerate public docs"
|
||||
```
|
||||
|
||||
4. Push your branch:
|
||||
|
||||
```bash
|
||||
git push
|
||||
```
|
||||
|
||||
Only files under `docs/public` are published by GitHub Pages. Internal/generated documentation is not published unless it is deliberately copied/sanitized into `docs/public`.
|
||||
|
||||
### Regenerating architecture docs (Prometheus + Dynu DNS)
|
||||
|
||||
```bash
|
||||
# Refresh Dynu live inventory and generated resources/import helpers
|
||||
cd infrastructure/terraform/dynu
|
||||
terraform apply -refresh-only
|
||||
python3 scripts/generate-brownfield-records.py --overwrite
|
||||
|
||||
# Regenerate architecture docs from Prometheus + Dynu inventory
|
||||
cd ../../..
|
||||
python3 scripts/render_prometheus_docs.py \
|
||||
--inventory-file docs/runtime/prometheus-inventory.json \
|
||||
--dynu-dns-inventory-file infrastructure/terraform/dynu/generated/dynu_dns_records_inventory.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Codex setup and maintenance scripts
|
||||
|
||||
The repository includes helper scripts for Codex sessions that need local tooling and safe placeholder secret material for validation-only workflows:
|
||||
|
||||
- `scripts/codex-setup.sh`
|
||||
- Installs baseline CLI dependencies (shell/yaml/terraform/ansible tooling).
|
||||
- Prepares `secrets/stack-secrets.env` from templates and creates dummy file-based secret placeholders based on `secrets/inventory.json`.
|
||||
- Installs/refreshed baseline Ansible collections when `infrastructure/ansible/collections/requirements.yml` is present.
|
||||
- Runs safe Ansible bootstrap checks (version, inventory parse, playbook syntax check) without live connectivity operations.
|
||||
- Prints installed tool versions for quick verification.
|
||||
|
||||
- `scripts/codex-maintenance.sh`
|
||||
- Refreshes Python-based linting/automation tooling.
|
||||
- Reconciles placeholder secret files against current `secrets/inventory.json` (creates missing, removes stale).
|
||||
- Rebuilds `secrets/stack-secrets.env` with dummy values for compose-config validation.
|
||||
- Refreshes Ansible collections and repeats safe inventory/syntax validation checks.
|
||||
|
||||
Both scripts are intended for local validation environments and should not be treated as production provisioning automation.
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# Gitea
|
||||
|
||||
## Gitea Actions
|
||||
|
||||
Gitea Actions is enabled by setting:
|
||||
|
||||
- `GITEA__actions__ENABLED=true`
|
||||
|
||||
## Runner service
|
||||
|
||||
The repository includes a dedicated Gitea Actions runner service named:
|
||||
|
||||
- `gitea-runner`
|
||||
|
||||
The runner uses Docker through the existing Docker socket proxy:
|
||||
|
||||
- `DOCKER_HOST=tcp://docker-socket-proxy:2375`
|
||||
|
||||
The runner intentionally **does not** mount:
|
||||
|
||||
- `/var/run/docker.sock`
|
||||
|
||||
## Registration token
|
||||
|
||||
Generate a runner registration token from the Gitea UI:
|
||||
|
||||
- Site Administration → Actions → Runners
|
||||
- or Repo → Settings → Actions → Runners
|
||||
|
||||
Put the token in your env/secrets file:
|
||||
|
||||
- `GITEA_RUNNER_REGISTRATION_TOKEN=...`
|
||||
|
||||
## Start the runner
|
||||
|
||||
- `./services-up.sh --profile gitea up -d gitea-runner`
|
||||
- or `./services-up.sh --profile all up -d gitea-runner`
|
||||
|
||||
## Logs
|
||||
|
||||
- `docker logs -f gitea-runner`
|
||||
|
||||
## Labels
|
||||
|
||||
Common workflow label:
|
||||
|
||||
- `runs-on: ubuntu-latest`
|
||||
|
||||
This should match the configured labels, for example:
|
||||
|
||||
- `GITEA_RUNNER_LABELS=ubuntu-latest:docker://node:20-bookworm,...`
|
||||
|
||||
## Security note
|
||||
|
||||
The runner can control Docker through `docker-socket-proxy`. This is safer than mounting the raw Docker socket directly, but workflows still have meaningful control over Docker. Only trusted repositories/users should be allowed to run workflows on this runner.
|
||||
@@ -9,6 +9,7 @@ services:
|
||||
- USER_GID=${GITEA_USER_GID}
|
||||
- GITEA__database__DB_TYPE=${GITEA_DB_TYPE}
|
||||
- GITEA__server__ROOT_URL=${GITEA_ROOT_URL}
|
||||
- GITEA__actions__ENABLED=true
|
||||
volumes:
|
||||
- ${PROJECT_ROOT}/apps/gitea/data:/data
|
||||
networks:
|
||||
@@ -31,6 +32,25 @@ services:
|
||||
retries: 6
|
||||
start_period: 120s
|
||||
|
||||
gitea-runner:
|
||||
profiles: ["apps","all","gitea","ci"]
|
||||
container_name: gitea-runner
|
||||
image: gitea/act_runner:latest
|
||||
restart: always
|
||||
depends_on:
|
||||
- gitea
|
||||
- docker-socket-proxy
|
||||
environment:
|
||||
- GITEA_INSTANCE_URL=${GITEA_ROOT_URL}
|
||||
- GITEA_RUNNER_REGISTRATION_TOKEN=${GITEA_RUNNER_REGISTRATION_TOKEN}
|
||||
- GITEA_RUNNER_NAME=${GITEA_RUNNER_NAME}
|
||||
- GITEA_RUNNER_LABELS=${GITEA_RUNNER_LABELS}
|
||||
- DOCKER_HOST=${DOCKER_SOCKET_PROXY_HOST}
|
||||
volumes:
|
||||
- ${PROJECT_ROOT}/apps/gitea/runner-data:/data
|
||||
networks:
|
||||
- traefik
|
||||
|
||||
#volumes:
|
||||
# gitea_data:
|
||||
|
||||
|
||||
@@ -13,6 +13,11 @@ GITEA_USER_UID=1000
|
||||
GITEA_USER_GID=1000
|
||||
GITEA_DB_TYPE=sqlite3
|
||||
GITEA_ROOT_URL=https://gitea.lan.ddnsgeek.com/
|
||||
# Generate a token in Gitea: Site Administration → Actions → Runners
|
||||
# or Repo → Settings → Actions → Runners
|
||||
GITEA_RUNNER_REGISTRATION_TOKEN=vYDNxzMvayREkXoaAR3x3UREkxQB2PU4eORzmkZ9
|
||||
GITEA_RUNNER_NAME=docker-runner-01
|
||||
GITEA_RUNNER_LABELS=ubuntu-latest:docker://node:20-bookworm,ubuntu-22.04:docker://node:20-bookworm,linux:docker://node:20-bookworm,docker:docker://docker:cli
|
||||
|
||||
# Grafana
|
||||
GRAFANA_ROOT_URL=https://grafana.lan.ddnsgeek.com/
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# Generated Documentation
|
||||
|
||||
## 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` validates committed public docs and diagrams and runs a strict public MkDocs build.
|
||||
|
||||
## Outputs
|
||||
|
||||
- `docs/generated`: resolved compose config and markdown inventories
|
||||
- `docs/diagrams`: generated DOT and SVG diagrams
|
||||
- `docs/public`: sanitized copy for public sharing
|
||||
|
||||
## Publication safety
|
||||
|
||||
- `docs/public` is intended for public sharing after sanitization.
|
||||
- `docs/generated` and `docs/diagrams` may include internal details and should be treated as internal by default.
|
||||
@@ -0,0 +1,72 @@
|
||||
# Ansible Workflows (Bootstrap / Phase 1)
|
||||
|
||||
Ansible is being introduced as a minimal, maintainable foundation for host/device inventory and future configuration workflows.
|
||||
|
||||
## Why introduce Ansible now
|
||||
|
||||
- The repository already has strong runtime and infrastructure boundaries (Compose + Terraform).
|
||||
- A small Ansible baseline allows gradual host onboarding without forcing immediate large-scale automation.
|
||||
- It enables safe validation workflows (`inventory --list`, playbook syntax checks) before real execution.
|
||||
|
||||
## What Ansible is for in this repository (right now)
|
||||
|
||||
- YAML inventory structure for hosts/devices to be onboarded over time.
|
||||
- Group and host variable scaffolding for future incremental adoption.
|
||||
- Validation-oriented starter playbook and local tooling checks.
|
||||
|
||||
## What Ansible is not for yet
|
||||
|
||||
- Replacing Docker Compose runtime authority.
|
||||
- Replacing Terraform inventory/reconciliation authority.
|
||||
- Becoming the current source of truth for NixOS host management.
|
||||
- Becoming the current source of truth for all network automation.
|
||||
|
||||
## Directory layout
|
||||
|
||||
- `infrastructure/ansible/ansible.cfg`
|
||||
- `infrastructure/ansible/inventory/hosts.yml`
|
||||
- `infrastructure/ansible/inventory/group_vars/`
|
||||
- `infrastructure/ansible/inventory/host_vars/`
|
||||
- `infrastructure/ansible/playbooks/ping.yml`
|
||||
- `infrastructure/ansible/collections/requirements.yml`
|
||||
|
||||
## Add a host (gradual onboarding)
|
||||
|
||||
1. Open `infrastructure/ansible/inventory/hosts.yml`.
|
||||
2. Add the host under an appropriate group (`linux`, `network`, `virtualization`, or `nixos`).
|
||||
3. Add non-sensitive defaults under group vars only when shared across hosts.
|
||||
4. Add host-specific values in `inventory/host_vars/<hostname>.yml`.
|
||||
5. Keep secrets out of committed files.
|
||||
|
||||
Example pattern:
|
||||
|
||||
```yaml
|
||||
linux:
|
||||
hosts:
|
||||
my-host:
|
||||
ansible_host: my-host.local
|
||||
```
|
||||
|
||||
## Validation commands
|
||||
|
||||
Run from repository root:
|
||||
|
||||
```bash
|
||||
ansible --version
|
||||
ansible-lint --version
|
||||
ansible-inventory -i infrastructure/ansible/inventory/hosts.yml --list
|
||||
ansible-playbook -i infrastructure/ansible/inventory/hosts.yml infrastructure/ansible/playbooks/ping.yml --syntax-check
|
||||
```
|
||||
|
||||
Install/update baseline collections:
|
||||
|
||||
```bash
|
||||
ansible-galaxy collection install -r infrastructure/ansible/collections/requirements.yml -p infrastructure/ansible/collections
|
||||
```
|
||||
|
||||
## Guardrails for future expansion
|
||||
|
||||
- Keep changes incremental (one host/group/playbook change at a time).
|
||||
- Prefer simple playbooks before introducing roles.
|
||||
- Add network-platform/NixOS-specific logic only when those boundaries are explicitly adopted.
|
||||
- Keep documentation aligned with source-of-truth boundaries when Ansible authority evolves.
|
||||
@@ -88,6 +88,7 @@ Use architecture docs together with:
|
||||
- [docs/source-of-truth.md](source-of-truth.md)
|
||||
- [docs/terraform-workflows.md](terraform-workflows.md)
|
||||
- [docs/infrastructure-inventory.md](infrastructure-inventory.md)
|
||||
- [docs/generated/host-topology.md](generated/host-topology.md)
|
||||
|
||||
## Notes on runtime vs declared state
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# Automation
|
||||
|
||||
This section describes automation systems associated with this environment.
|
||||
|
||||
Current state:
|
||||
|
||||
- CI documentation generation in GitHub Actions.
|
||||
- Static parsing and rendering from repository configuration.
|
||||
- No live service interaction during documentation generation.
|
||||
|
||||
Future content can document:
|
||||
|
||||
- Docker update automation.
|
||||
- Node-RED automation flows and operational patterns.
|
||||
@@ -0,0 +1,75 @@
|
||||
digraph Compose {
|
||||
rankdir=LR;
|
||||
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"];
|
||||
"svc:docker-update-exporter" [label="docker-update-exporter", shape=box, style=filled, fillcolor="#dfefff"];
|
||||
"svc:error-pages" [label="error-pages", shape=box, style=filled, fillcolor="#dfefff"];
|
||||
"svc:gitea" [label="gitea", shape=box, style=filled, fillcolor="#dfefff"];
|
||||
"svc:gitea-runner" [label="gitea-runner", shape=box, style=filled, fillcolor="#dfefff"];
|
||||
"svc:gotify" [label="gotify", shape=box, style=filled, fillcolor="#dfefff"];
|
||||
"svc:grafana" [label="grafana", shape=box, style=filled, fillcolor="#dfefff"];
|
||||
"svc:gramps-redis" [label="gramps-redis", shape=box, style=filled, fillcolor="#dfefff"];
|
||||
"svc:grampsweb" [label="grampsweb", shape=box, style=filled, fillcolor="#dfefff"];
|
||||
"svc:grampsweb_celery" [label="grampsweb_celery", shape=box, style=filled, fillcolor="#dfefff"];
|
||||
"svc:influxdb" [label="influxdb", shape=box, style=filled, fillcolor="#dfefff"];
|
||||
"svc:monitor-kuma" [label="monitor-kuma", shape=box, style=filled, fillcolor="#dfefff"];
|
||||
"svc:mtls-bridge" [label="mtls-bridge", shape=box, style=filled, fillcolor="#dfefff"];
|
||||
"svc:nextcloud-db" [label="nextcloud-db", shape=box, style=filled, fillcolor="#dfefff"];
|
||||
"svc:nextcloud-redis" [label="nextcloud-redis", shape=box, style=filled, fillcolor="#dfefff"];
|
||||
"svc:nextcloud-webapp" [label="nextcloud-webapp", shape=box, style=filled, fillcolor="#dfefff"];
|
||||
"svc:node-exporter" [label="node-exporter", shape=box, style=filled, fillcolor="#dfefff"];
|
||||
"svc:node-red" [label="node-red", shape=box, style=filled, fillcolor="#dfefff"];
|
||||
"svc:passbolt-db" [label="passbolt-db", shape=box, style=filled, fillcolor="#dfefff"];
|
||||
"svc:passbolt-webapp" [label="passbolt-webapp", shape=box, style=filled, fillcolor="#dfefff"];
|
||||
"svc:pihole-exporter" [label="pihole-exporter", shape=box, style=filled, fillcolor="#dfefff"];
|
||||
"svc:portainer" [label="portainer", shape=box, style=filled, fillcolor="#dfefff"];
|
||||
"svc:prometheus" [label="prometheus", shape=box, style=filled, fillcolor="#dfefff"];
|
||||
"svc:searxng-webapp" [label="searxng-webapp", shape=box, style=filled, fillcolor="#dfefff"];
|
||||
"svc:telegraf" [label="telegraf", shape=box, style=filled, fillcolor="#dfefff"];
|
||||
"svc:traefik" [label="traefik", shape=box, style=filled, fillcolor="#dfefff"];
|
||||
"net:gramps" [label="gramps", shape=ellipse, style=filled, fillcolor="#f4f4f4"];
|
||||
"net:monitor" [label="monitor", shape=ellipse, style=filled, fillcolor="#f4f4f4"];
|
||||
"net:nextcloud" [label="nextcloud", shape=ellipse, style=filled, fillcolor="#f4f4f4"];
|
||||
"net:passbolt" [label="passbolt", shape=ellipse, style=filled, fillcolor="#f4f4f4"];
|
||||
"net:traefik" [label="traefik", shape=ellipse, style=filled, fillcolor="#f4f4f4"];
|
||||
"svc:authelia" -> "net:traefik";
|
||||
"svc:crowdsec" -> "net:traefik";
|
||||
"svc:docker-socket-proxy" -> "net:monitor";
|
||||
"svc:docker-socket-proxy" -> "net:traefik";
|
||||
"svc:docker-update-exporter" -> "net:monitor";
|
||||
"svc:error-pages" -> "net:traefik";
|
||||
"svc:gitea" -> "net:traefik";
|
||||
"svc:gitea-runner" -> "net:traefik";
|
||||
"svc:gotify" -> "net:traefik";
|
||||
"svc:grafana" -> "net:monitor";
|
||||
"svc:grafana" -> "net:traefik";
|
||||
"svc:gramps-redis" -> "net:gramps";
|
||||
"svc:grampsweb" -> "net:gramps";
|
||||
"svc:grampsweb" -> "net:traefik";
|
||||
"svc:grampsweb_celery" -> "net:gramps";
|
||||
"svc:influxdb" -> "net:monitor";
|
||||
"svc:influxdb" -> "net:traefik";
|
||||
"svc:monitor-kuma" -> "net:monitor";
|
||||
"svc:monitor-kuma" -> "net:traefik";
|
||||
"svc:mtls-bridge" -> "net:monitor";
|
||||
"svc:mtls-bridge" -> "net:traefik";
|
||||
"svc:nextcloud-db" -> "net:nextcloud";
|
||||
"svc:nextcloud-redis" -> "net:nextcloud";
|
||||
"svc:nextcloud-webapp" -> "net:nextcloud";
|
||||
"svc:nextcloud-webapp" -> "net:traefik";
|
||||
"svc:node-exporter" -> "net:monitor";
|
||||
"svc:node-red" -> "net:monitor";
|
||||
"svc:node-red" -> "net:traefik";
|
||||
"svc:passbolt-db" -> "net:passbolt";
|
||||
"svc:passbolt-webapp" -> "net:passbolt";
|
||||
"svc:passbolt-webapp" -> "net:traefik";
|
||||
"svc:pihole-exporter" -> "net:monitor";
|
||||
"svc:portainer" -> "net:traefik";
|
||||
"svc:prometheus" -> "net:monitor";
|
||||
"svc:prometheus" -> "net:traefik";
|
||||
"svc:searxng-webapp" -> "net:traefik";
|
||||
"svc:telegraf" -> "net:monitor";
|
||||
"svc:traefik" -> "net:traefik";
|
||||
}
|
||||
@@ -0,0 +1,439 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 2.43.0 (0)
|
||||
-->
|
||||
<!-- Title: Compose Pages: 1 -->
|
||||
<svg width="334pt" height="1502pt"
|
||||
viewBox="0.00 0.00 334.49 1502.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1498)">
|
||||
<title>Compose</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-1498 330.49,-1498 330.49,4 -4,4"/>
|
||||
<!-- svc:authelia -->
|
||||
<g id="node1" class="node">
|
||||
<title>svc:authelia</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="126,-738 54,-738 54,-702 126,-702 126,-738"/>
|
||||
<text text-anchor="middle" x="90" y="-716.3" font-family="Helvetica,sans-Serif" font-size="14.00">authelia</text>
|
||||
</g>
|
||||
<!-- net:traefik -->
|
||||
<g id="node33" class="node">
|
||||
<title>net:traefik</title>
|
||||
<ellipse fill="#f4f4f4" stroke="black" cx="271.25" cy="-774" rx="40.09" ry="18"/>
|
||||
<text text-anchor="middle" x="271.25" y="-770.3" font-family="Helvetica,sans-Serif" font-size="14.00">traefik</text>
|
||||
</g>
|
||||
<!-- svc:authelia->net:traefik -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>svc:authelia->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M126.41,-730.67C155.5,-739.43 196.8,-751.87 227.69,-761.18"/>
|
||||
<polygon fill="black" stroke="black" points="226.74,-764.55 237.32,-764.08 228.76,-757.85 226.74,-764.55"/>
|
||||
</g>
|
||||
<!-- svc:crowdsec -->
|
||||
<g id="node2" class="node">
|
||||
<title>svc:crowdsec</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="130.5,-684 49.5,-684 49.5,-648 130.5,-648 130.5,-684"/>
|
||||
<text text-anchor="middle" x="90" y="-662.3" font-family="Helvetica,sans-Serif" font-size="14.00">crowdsec</text>
|
||||
</g>
|
||||
<!-- svc:crowdsec->net:traefik -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>svc:crowdsec->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M130.61,-674.06C146.59,-678.3 164.81,-684.44 180,-693 206.25,-707.78 231.35,-731.35 248.38,-749.26"/>
|
||||
<polygon fill="black" stroke="black" points="246.24,-752.1 255.62,-757.03 251.37,-747.33 246.24,-752.1"/>
|
||||
</g>
|
||||
<!-- svc:docker-socket-proxy -->
|
||||
<g id="node3" class="node">
|
||||
<title>svc:docker-socket-proxy</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="167.5,-954 12.5,-954 12.5,-918 167.5,-918 167.5,-954"/>
|
||||
<text text-anchor="middle" x="90" y="-932.3" font-family="Helvetica,sans-Serif" font-size="14.00">docker-socket-proxy</text>
|
||||
</g>
|
||||
<!-- net:monitor -->
|
||||
<g id="node30" class="node">
|
||||
<title>net:monitor</title>
|
||||
<ellipse fill="#f4f4f4" stroke="black" cx="271.25" cy="-1206" rx="46.29" ry="18"/>
|
||||
<text text-anchor="middle" x="271.25" y="-1202.3" font-family="Helvetica,sans-Serif" font-size="14.00">monitor</text>
|
||||
</g>
|
||||
<!-- svc:docker-socket-proxy->net:monitor -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>svc:docker-socket-proxy->net:monitor</title>
|
||||
<path fill="none" stroke="black" d="M167.53,-953.71C172.02,-956.38 176.24,-959.46 180,-963 242.3,-1021.6 261.83,-1127.12 267.77,-1177.6"/>
|
||||
<polygon fill="black" stroke="black" points="264.31,-1178.21 268.86,-1187.78 271.27,-1177.46 264.31,-1178.21"/>
|
||||
</g>
|
||||
<!-- svc:docker-socket-proxy->net:traefik -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>svc:docker-socket-proxy->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M165.31,-917.9C170.5,-915.32 175.46,-912.37 180,-909 218.01,-880.82 244.97,-831.56 259.03,-800.98"/>
|
||||
<polygon fill="black" stroke="black" points="262.27,-802.31 263.14,-791.76 255.87,-799.46 262.27,-802.31"/>
|
||||
</g>
|
||||
<!-- svc:docker-update-exporter -->
|
||||
<g id="node4" class="node">
|
||||
<title>svc:docker-update-exporter</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="180,-1494 0,-1494 0,-1458 180,-1458 180,-1494"/>
|
||||
<text text-anchor="middle" x="90" y="-1472.3" font-family="Helvetica,sans-Serif" font-size="14.00">docker-update-exporter</text>
|
||||
</g>
|
||||
<!-- svc:docker-update-exporter->net:monitor -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>svc:docker-update-exporter->net:monitor</title>
|
||||
<path fill="none" stroke="black" d="M168.37,-1457.79C172.55,-1455.23 176.47,-1452.32 180,-1449 242.3,-1390.4 261.83,-1284.88 267.77,-1234.4"/>
|
||||
<polygon fill="black" stroke="black" points="271.27,-1234.54 268.86,-1224.22 264.31,-1233.79 271.27,-1234.54"/>
|
||||
</g>
|
||||
<!-- svc:error-pages -->
|
||||
<g id="node5" class="node">
|
||||
<title>svc:error-pages</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="137.5,-630 42.5,-630 42.5,-594 137.5,-594 137.5,-630"/>
|
||||
<text text-anchor="middle" x="90" y="-608.3" font-family="Helvetica,sans-Serif" font-size="14.00">error-pages</text>
|
||||
</g>
|
||||
<!-- svc:error-pages->net:traefik -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>svc:error-pages->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M137.71,-619.76C152.25,-623.79 167.68,-629.86 180,-639 218.01,-667.18 244.97,-716.44 259.03,-747.02"/>
|
||||
<polygon fill="black" stroke="black" points="255.87,-748.54 263.14,-756.24 262.27,-745.69 255.87,-748.54"/>
|
||||
</g>
|
||||
<!-- svc:gitea -->
|
||||
<g id="node6" class="node">
|
||||
<title>svc:gitea</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="117,-576 63,-576 63,-540 117,-540 117,-576"/>
|
||||
<text text-anchor="middle" x="90" y="-554.3" font-family="Helvetica,sans-Serif" font-size="14.00">gitea</text>
|
||||
</g>
|
||||
<!-- svc:gitea->net:traefik -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>svc:gitea->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M117.07,-560.28C136.41,-563.2 162.37,-569.85 180,-585 229.98,-627.95 254.42,-704.8 264.44,-746.04"/>
|
||||
<polygon fill="black" stroke="black" points="261.07,-747 266.73,-755.95 267.88,-745.42 261.07,-747"/>
|
||||
</g>
|
||||
<!-- svc:gitea-runner -->
|
||||
<g id="node7" class="node">
|
||||
<title>svc:gitea-runner</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="142,-522 38,-522 38,-486 142,-486 142,-522"/>
|
||||
<text text-anchor="middle" x="90" y="-500.3" font-family="Helvetica,sans-Serif" font-size="14.00">gitea-runner</text>
|
||||
</g>
|
||||
<!-- svc:gitea-runner->net:traefik -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>svc:gitea-runner->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M142.31,-511.04C155.9,-515.03 169.65,-521.26 180,-531 242.3,-589.6 261.83,-695.12 267.77,-745.6"/>
|
||||
<polygon fill="black" stroke="black" points="264.31,-746.21 268.86,-755.78 271.27,-745.46 264.31,-746.21"/>
|
||||
</g>
|
||||
<!-- svc:gotify -->
|
||||
<g id="node8" class="node">
|
||||
<title>svc:gotify</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="118,-468 62,-468 62,-432 118,-432 118,-468"/>
|
||||
<text text-anchor="middle" x="90" y="-446.3" font-family="Helvetica,sans-Serif" font-size="14.00">gotify</text>
|
||||
</g>
|
||||
<!-- svc:gotify->net:traefik -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>svc:gotify->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M118.17,-451.6C137.81,-454.16 163.67,-460.68 180,-477 254.99,-551.96 268.02,-687.13 270.03,-745.69"/>
|
||||
<polygon fill="black" stroke="black" points="266.53,-745.79 270.29,-755.69 273.53,-745.61 266.53,-745.79"/>
|
||||
</g>
|
||||
<!-- svc:grafana -->
|
||||
<g id="node9" class="node">
|
||||
<title>svc:grafana</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="125.5,-1278 54.5,-1278 54.5,-1242 125.5,-1242 125.5,-1278"/>
|
||||
<text text-anchor="middle" x="90" y="-1256.3" font-family="Helvetica,sans-Serif" font-size="14.00">grafana</text>
|
||||
</g>
|
||||
<!-- svc:grafana->net:monitor -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>svc:grafana->net:monitor</title>
|
||||
<path fill="none" stroke="black" d="M125.56,-1249.59C153.74,-1241.1 193.78,-1229.04 224.6,-1219.75"/>
|
||||
<polygon fill="black" stroke="black" points="225.69,-1223.08 234.26,-1216.84 223.67,-1216.38 225.69,-1223.08"/>
|
||||
</g>
|
||||
<!-- svc:grafana->net:traefik -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>svc:grafana->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M125.54,-1257.93C144.23,-1254.97 166.25,-1248.21 180,-1233 238.17,-1168.67 262.1,-892.17 268.43,-802.32"/>
|
||||
<polygon fill="black" stroke="black" points="271.93,-802.3 269.13,-792.08 264.95,-801.82 271.93,-802.3"/>
|
||||
</g>
|
||||
<!-- svc:gramps-redis -->
|
||||
<g id="node10" class="node">
|
||||
<title>svc:gramps-redis</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="144.5,-360 35.5,-360 35.5,-324 144.5,-324 144.5,-360"/>
|
||||
<text text-anchor="middle" x="90" y="-338.3" font-family="Helvetica,sans-Serif" font-size="14.00">gramps-redis</text>
|
||||
</g>
|
||||
<!-- net:gramps -->
|
||||
<g id="node29" class="node">
|
||||
<title>net:gramps</title>
|
||||
<ellipse fill="#f4f4f4" stroke="black" cx="271.25" cy="-342" rx="45.49" ry="18"/>
|
||||
<text text-anchor="middle" x="271.25" y="-338.3" font-family="Helvetica,sans-Serif" font-size="14.00">gramps</text>
|
||||
</g>
|
||||
<!-- svc:gramps-redis->net:gramps -->
|
||||
<g id="edge12" class="edge">
|
||||
<title>svc:gramps-redis->net:gramps</title>
|
||||
<path fill="none" stroke="black" d="M144.78,-342C167.14,-342 193.05,-342 215.51,-342"/>
|
||||
<polygon fill="black" stroke="black" points="215.56,-345.5 225.56,-342 215.56,-338.5 215.56,-345.5"/>
|
||||
</g>
|
||||
<!-- svc:grampsweb -->
|
||||
<g id="node11" class="node">
|
||||
<title>svc:grampsweb</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="139,-414 41,-414 41,-378 139,-378 139,-414"/>
|
||||
<text text-anchor="middle" x="90" y="-392.3" font-family="Helvetica,sans-Serif" font-size="14.00">grampsweb</text>
|
||||
</g>
|
||||
<!-- svc:grampsweb->net:gramps -->
|
||||
<g id="edge13" class="edge">
|
||||
<title>svc:grampsweb->net:gramps</title>
|
||||
<path fill="none" stroke="black" d="M139.03,-381.53C165.7,-373.5 198.68,-363.56 224.9,-355.66"/>
|
||||
<polygon fill="black" stroke="black" points="226.03,-358.98 234.59,-352.74 224.01,-352.27 226.03,-358.98"/>
|
||||
</g>
|
||||
<!-- svc:grampsweb->net:traefik -->
|
||||
<g id="edge14" class="edge">
|
||||
<title>svc:grampsweb->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M139.27,-401.34C154.08,-405.21 169.28,-411.81 180,-423 225.12,-470.09 256.34,-671.08 266.59,-745.84"/>
|
||||
<polygon fill="black" stroke="black" points="263.14,-746.48 267.95,-755.92 270.08,-745.55 263.14,-746.48"/>
|
||||
</g>
|
||||
<!-- svc:grampsweb_celery -->
|
||||
<g id="node12" class="node">
|
||||
<title>svc:grampsweb_celery</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="163.5,-306 16.5,-306 16.5,-270 163.5,-270 163.5,-306"/>
|
||||
<text text-anchor="middle" x="90" y="-284.3" font-family="Helvetica,sans-Serif" font-size="14.00">grampsweb_celery</text>
|
||||
</g>
|
||||
<!-- svc:grampsweb_celery->net:gramps -->
|
||||
<g id="edge15" class="edge">
|
||||
<title>svc:grampsweb_celery->net:gramps</title>
|
||||
<path fill="none" stroke="black" d="M151.18,-306.13C175.28,-313.39 202.57,-321.61 224.94,-328.35"/>
|
||||
<polygon fill="black" stroke="black" points="223.94,-331.71 234.52,-331.24 225.96,-325 223.94,-331.71"/>
|
||||
</g>
|
||||
<!-- svc:influxdb -->
|
||||
<g id="node13" class="node">
|
||||
<title>svc:influxdb</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="127,-1224 53,-1224 53,-1188 127,-1188 127,-1224"/>
|
||||
<text text-anchor="middle" x="90" y="-1202.3" font-family="Helvetica,sans-Serif" font-size="14.00">influxdb</text>
|
||||
</g>
|
||||
<!-- svc:influxdb->net:monitor -->
|
||||
<g id="edge16" class="edge">
|
||||
<title>svc:influxdb->net:monitor</title>
|
||||
<path fill="none" stroke="black" d="M127.27,-1206C152.33,-1206 186.13,-1206 214.57,-1206"/>
|
||||
<polygon fill="black" stroke="black" points="214.79,-1209.5 224.79,-1206 214.79,-1202.5 214.79,-1209.5"/>
|
||||
</g>
|
||||
<!-- svc:influxdb->net:traefik -->
|
||||
<g id="edge17" class="edge">
|
||||
<title>svc:influxdb->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M127.17,-1203.5C145.42,-1200.36 166.5,-1193.56 180,-1179 231.57,-1123.4 259.36,-885.3 267.6,-802.51"/>
|
||||
<polygon fill="black" stroke="black" points="271.1,-802.62 268.59,-792.33 264.14,-801.94 271.1,-802.62"/>
|
||||
</g>
|
||||
<!-- svc:monitor-kuma -->
|
||||
<g id="node14" class="node">
|
||||
<title>svc:monitor-kuma</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="146.5,-1170 33.5,-1170 33.5,-1134 146.5,-1134 146.5,-1170"/>
|
||||
<text text-anchor="middle" x="90" y="-1148.3" font-family="Helvetica,sans-Serif" font-size="14.00">monitor-kuma</text>
|
||||
</g>
|
||||
<!-- svc:monitor-kuma->net:monitor -->
|
||||
<g id="edge18" class="edge">
|
||||
<title>svc:monitor-kuma->net:monitor</title>
|
||||
<path fill="none" stroke="black" d="M146.73,-1168.79C171.73,-1176.32 200.85,-1185.1 224.54,-1192.23"/>
|
||||
<polygon fill="black" stroke="black" points="223.75,-1195.65 234.33,-1195.18 225.77,-1188.94 223.75,-1195.65"/>
|
||||
</g>
|
||||
<!-- svc:monitor-kuma->net:traefik -->
|
||||
<g id="edge19" class="edge">
|
||||
<title>svc:monitor-kuma->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M146.69,-1144.49C159.02,-1140.46 171.06,-1134.33 180,-1125 225.12,-1077.91 256.34,-876.92 266.59,-802.16"/>
|
||||
<polygon fill="black" stroke="black" points="270.08,-802.45 267.95,-792.08 263.14,-801.52 270.08,-802.45"/>
|
||||
</g>
|
||||
<!-- svc:mtls-bridge -->
|
||||
<g id="node15" class="node">
|
||||
<title>svc:mtls-bridge</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="138.5,-1116 41.5,-1116 41.5,-1080 138.5,-1080 138.5,-1116"/>
|
||||
<text text-anchor="middle" x="90" y="-1094.3" font-family="Helvetica,sans-Serif" font-size="14.00">mtls-bridge</text>
|
||||
</g>
|
||||
<!-- svc:mtls-bridge->net:monitor -->
|
||||
<g id="edge20" class="edge">
|
||||
<title>svc:mtls-bridge->net:monitor</title>
|
||||
<path fill="none" stroke="black" d="M138.76,-1108.34C152.58,-1112.41 167.34,-1117.87 180,-1125 206.25,-1139.78 231.35,-1163.35 248.38,-1181.26"/>
|
||||
<polygon fill="black" stroke="black" points="246.24,-1184.1 255.62,-1189.03 251.37,-1179.33 246.24,-1184.1"/>
|
||||
</g>
|
||||
<!-- svc:mtls-bridge->net:traefik -->
|
||||
<g id="edge21" class="edge">
|
||||
<title>svc:mtls-bridge->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M138.53,-1092.51C153.46,-1088.63 168.92,-1082.07 180,-1071 254.99,-996.04 268.02,-860.87 270.03,-802.31"/>
|
||||
<polygon fill="black" stroke="black" points="273.53,-802.39 270.29,-792.31 266.53,-802.21 273.53,-802.39"/>
|
||||
</g>
|
||||
<!-- svc:nextcloud-db -->
|
||||
<g id="node16" class="node">
|
||||
<title>svc:nextcloud-db</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="144,-90 36,-90 36,-54 144,-54 144,-90"/>
|
||||
<text text-anchor="middle" x="90" y="-68.3" font-family="Helvetica,sans-Serif" font-size="14.00">nextcloud-db</text>
|
||||
</g>
|
||||
<!-- net:nextcloud -->
|
||||
<g id="node31" class="node">
|
||||
<title>net:nextcloud</title>
|
||||
<ellipse fill="#f4f4f4" stroke="black" cx="271.25" cy="-180" rx="55.49" ry="18"/>
|
||||
<text text-anchor="middle" x="271.25" y="-176.3" font-family="Helvetica,sans-Serif" font-size="14.00">nextcloud</text>
|
||||
</g>
|
||||
<!-- svc:nextcloud-db->net:nextcloud -->
|
||||
<g id="edge22" class="edge">
|
||||
<title>svc:nextcloud-db->net:nextcloud</title>
|
||||
<path fill="none" stroke="black" d="M144.33,-84.04C156.48,-87.9 169.03,-92.82 180,-99 206.09,-113.7 231.04,-137.06 248.07,-154.93"/>
|
||||
<polygon fill="black" stroke="black" points="245.93,-157.77 255.31,-162.7 251.06,-153 245.93,-157.77"/>
|
||||
</g>
|
||||
<!-- svc:nextcloud-redis -->
|
||||
<g id="node17" class="node">
|
||||
<title>svc:nextcloud-redis</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="152,-198 28,-198 28,-162 152,-162 152,-198"/>
|
||||
<text text-anchor="middle" x="90" y="-176.3" font-family="Helvetica,sans-Serif" font-size="14.00">nextcloud-redis</text>
|
||||
</g>
|
||||
<!-- svc:nextcloud-redis->net:nextcloud -->
|
||||
<g id="edge23" class="edge">
|
||||
<title>svc:nextcloud-redis->net:nextcloud</title>
|
||||
<path fill="none" stroke="black" d="M152.18,-180C169.48,-180 188.35,-180 205.83,-180"/>
|
||||
<polygon fill="black" stroke="black" points="205.94,-183.5 215.94,-180 205.94,-176.5 205.94,-183.5"/>
|
||||
</g>
|
||||
<!-- svc:nextcloud-webapp -->
|
||||
<g id="node18" class="node">
|
||||
<title>svc:nextcloud-webapp</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="162.5,-252 17.5,-252 17.5,-216 162.5,-216 162.5,-252"/>
|
||||
<text text-anchor="middle" x="90" y="-230.3" font-family="Helvetica,sans-Serif" font-size="14.00">nextcloud-webapp</text>
|
||||
</g>
|
||||
<!-- svc:nextcloud-webapp->net:nextcloud -->
|
||||
<g id="edge24" class="edge">
|
||||
<title>svc:nextcloud-webapp->net:nextcloud</title>
|
||||
<path fill="none" stroke="black" d="M151.18,-215.87C173.57,-209.12 198.72,-201.55 220.11,-195.1"/>
|
||||
<polygon fill="black" stroke="black" points="221.4,-198.37 229.97,-192.13 219.38,-191.67 221.4,-198.37"/>
|
||||
</g>
|
||||
<!-- svc:nextcloud-webapp->net:traefik -->
|
||||
<g id="edge25" class="edge">
|
||||
<title>svc:nextcloud-webapp->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M162.89,-247.71C169.31,-251.2 175.18,-255.56 180,-261 212.66,-297.85 255.07,-643.36 267,-745.62"/>
|
||||
<polygon fill="black" stroke="black" points="263.55,-746.27 268.18,-755.8 270.51,-745.47 263.55,-746.27"/>
|
||||
</g>
|
||||
<!-- svc:node-exporter -->
|
||||
<g id="node19" class="node">
|
||||
<title>svc:node-exporter</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="148,-1440 32,-1440 32,-1404 148,-1404 148,-1440"/>
|
||||
<text text-anchor="middle" x="90" y="-1418.3" font-family="Helvetica,sans-Serif" font-size="14.00">node-exporter</text>
|
||||
</g>
|
||||
<!-- svc:node-exporter->net:monitor -->
|
||||
<g id="edge26" class="edge">
|
||||
<title>svc:node-exporter->net:monitor</title>
|
||||
<path fill="none" stroke="black" d="M148.25,-1412.27C159.68,-1408.33 170.94,-1402.79 180,-1395 229.98,-1352.05 254.42,-1275.2 264.44,-1233.96"/>
|
||||
<polygon fill="black" stroke="black" points="267.88,-1234.58 266.73,-1224.05 261.07,-1233 267.88,-1234.58"/>
|
||||
</g>
|
||||
<!-- svc:node-red -->
|
||||
<g id="node20" class="node">
|
||||
<title>svc:node-red</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="129.5,-1062 50.5,-1062 50.5,-1026 129.5,-1026 129.5,-1062"/>
|
||||
<text text-anchor="middle" x="90" y="-1040.3" font-family="Helvetica,sans-Serif" font-size="14.00">node-red</text>
|
||||
</g>
|
||||
<!-- svc:node-red->net:monitor -->
|
||||
<g id="edge27" class="edge">
|
||||
<title>svc:node-red->net:monitor</title>
|
||||
<path fill="none" stroke="black" d="M129.57,-1049.69C146.29,-1053.6 165.34,-1060.13 180,-1071 218.01,-1099.18 244.97,-1148.44 259.03,-1179.02"/>
|
||||
<polygon fill="black" stroke="black" points="255.87,-1180.54 263.14,-1188.24 262.27,-1177.69 255.87,-1180.54"/>
|
||||
</g>
|
||||
<!-- svc:node-red->net:traefik -->
|
||||
<g id="edge28" class="edge">
|
||||
<title>svc:node-red->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M129.95,-1040.03C147.14,-1036.46 166.48,-1029.72 180,-1017 242.3,-958.4 261.83,-852.88 267.77,-802.4"/>
|
||||
<polygon fill="black" stroke="black" points="271.27,-802.54 268.86,-792.22 264.31,-801.79 271.27,-802.54"/>
|
||||
</g>
|
||||
<!-- svc:passbolt-db -->
|
||||
<g id="node21" class="node">
|
||||
<title>svc:passbolt-db</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="139,-36 41,-36 41,0 139,0 139,-36"/>
|
||||
<text text-anchor="middle" x="90" y="-14.3" font-family="Helvetica,sans-Serif" font-size="14.00">passbolt-db</text>
|
||||
</g>
|
||||
<!-- net:passbolt -->
|
||||
<g id="node32" class="node">
|
||||
<title>net:passbolt</title>
|
||||
<ellipse fill="#f4f4f4" stroke="black" cx="271.25" cy="-72" rx="48.99" ry="18"/>
|
||||
<text text-anchor="middle" x="271.25" y="-68.3" font-family="Helvetica,sans-Serif" font-size="14.00">passbolt</text>
|
||||
</g>
|
||||
<!-- svc:passbolt-db->net:passbolt -->
|
||||
<g id="edge29" class="edge">
|
||||
<title>svc:passbolt-db->net:passbolt</title>
|
||||
<path fill="none" stroke="black" d="M139.03,-32.47C165.15,-40.34 197.32,-50.03 223.27,-57.85"/>
|
||||
<polygon fill="black" stroke="black" points="222.3,-61.21 232.88,-60.74 224.32,-54.51 222.3,-61.21"/>
|
||||
</g>
|
||||
<!-- svc:passbolt-webapp -->
|
||||
<g id="node22" class="node">
|
||||
<title>svc:passbolt-webapp</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="157.5,-144 22.5,-144 22.5,-108 157.5,-108 157.5,-144"/>
|
||||
<text text-anchor="middle" x="90" y="-122.3" font-family="Helvetica,sans-Serif" font-size="14.00">passbolt-webapp</text>
|
||||
</g>
|
||||
<!-- svc:passbolt-webapp->net:passbolt -->
|
||||
<g id="edge30" class="edge">
|
||||
<title>svc:passbolt-webapp->net:passbolt</title>
|
||||
<path fill="none" stroke="black" d="M151.18,-107.87C174.64,-100.8 201.13,-92.82 223.16,-86.19"/>
|
||||
<polygon fill="black" stroke="black" points="224.37,-89.48 232.94,-83.24 222.35,-82.77 224.37,-89.48"/>
|
||||
</g>
|
||||
<!-- svc:passbolt-webapp->net:traefik -->
|
||||
<g id="edge31" class="edge">
|
||||
<title>svc:passbolt-webapp->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M157.85,-136.93C166.26,-140.81 174,-146.02 180,-153 189.88,-164.49 250.79,-625.33 266.53,-745.56"/>
|
||||
<polygon fill="black" stroke="black" points="263.1,-746.33 267.87,-755.8 270.04,-745.43 263.1,-746.33"/>
|
||||
</g>
|
||||
<!-- svc:pihole-exporter -->
|
||||
<g id="node23" class="node">
|
||||
<title>svc:pihole-exporter</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="151.5,-1386 28.5,-1386 28.5,-1350 151.5,-1350 151.5,-1386"/>
|
||||
<text text-anchor="middle" x="90" y="-1364.3" font-family="Helvetica,sans-Serif" font-size="14.00">pihole-exporter</text>
|
||||
</g>
|
||||
<!-- svc:pihole-exporter->net:monitor -->
|
||||
<g id="edge32" class="edge">
|
||||
<title>svc:pihole-exporter->net:monitor</title>
|
||||
<path fill="none" stroke="black" d="M151.62,-1355.78C161.68,-1352.07 171.57,-1347.25 180,-1341 218.01,-1312.82 244.97,-1263.56 259.03,-1232.98"/>
|
||||
<polygon fill="black" stroke="black" points="262.27,-1234.31 263.14,-1223.76 255.87,-1231.46 262.27,-1234.31"/>
|
||||
</g>
|
||||
<!-- svc:portainer -->
|
||||
<g id="node24" class="node">
|
||||
<title>svc:portainer</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="130,-900 50,-900 50,-864 130,-864 130,-900"/>
|
||||
<text text-anchor="middle" x="90" y="-878.3" font-family="Helvetica,sans-Serif" font-size="14.00">portainer</text>
|
||||
</g>
|
||||
<!-- svc:portainer->net:traefik -->
|
||||
<g id="edge33" class="edge">
|
||||
<title>svc:portainer->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M130.17,-874.06C146.25,-869.82 164.67,-863.64 180,-855 206.25,-840.22 231.35,-816.65 248.38,-798.74"/>
|
||||
<polygon fill="black" stroke="black" points="251.37,-800.67 255.62,-790.97 246.24,-795.9 251.37,-800.67"/>
|
||||
</g>
|
||||
<!-- svc:prometheus -->
|
||||
<g id="node25" class="node">
|
||||
<title>svc:prometheus</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="140,-1008 40,-1008 40,-972 140,-972 140,-1008"/>
|
||||
<text text-anchor="middle" x="90" y="-986.3" font-family="Helvetica,sans-Serif" font-size="14.00">prometheus</text>
|
||||
</g>
|
||||
<!-- svc:prometheus->net:monitor -->
|
||||
<g id="edge34" class="edge">
|
||||
<title>svc:prometheus->net:monitor</title>
|
||||
<path fill="none" stroke="black" d="M140.37,-997.26C154.39,-1001.24 168.86,-1007.42 180,-1017 229.98,-1059.95 254.42,-1136.8 264.44,-1178.04"/>
|
||||
<polygon fill="black" stroke="black" points="261.07,-1179 266.73,-1187.95 267.88,-1177.42 261.07,-1179"/>
|
||||
</g>
|
||||
<!-- svc:prometheus->net:traefik -->
|
||||
<g id="edge35" class="edge">
|
||||
<title>svc:prometheus->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M140.37,-982.74C154.39,-978.76 168.86,-972.58 180,-963 229.98,-920.05 254.42,-843.2 264.44,-801.96"/>
|
||||
<polygon fill="black" stroke="black" points="267.88,-802.58 266.73,-792.05 261.07,-801 267.88,-802.58"/>
|
||||
</g>
|
||||
<!-- svc:searxng-webapp -->
|
||||
<g id="node26" class="node">
|
||||
<title>svc:searxng-webapp</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="156,-846 24,-846 24,-810 156,-810 156,-846"/>
|
||||
<text text-anchor="middle" x="90" y="-824.3" font-family="Helvetica,sans-Serif" font-size="14.00">searxng-webapp</text>
|
||||
</g>
|
||||
<!-- svc:searxng-webapp->net:traefik -->
|
||||
<g id="edge36" class="edge">
|
||||
<title>svc:searxng-webapp->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M151.18,-809.87C176.26,-802.32 204.79,-793.72 227.63,-786.84"/>
|
||||
<polygon fill="black" stroke="black" points="228.81,-790.14 237.37,-783.9 226.79,-783.44 228.81,-790.14"/>
|
||||
</g>
|
||||
<!-- svc:telegraf -->
|
||||
<g id="node27" class="node">
|
||||
<title>svc:telegraf</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="125.5,-1332 54.5,-1332 54.5,-1296 125.5,-1296 125.5,-1332"/>
|
||||
<text text-anchor="middle" x="90" y="-1310.3" font-family="Helvetica,sans-Serif" font-size="14.00">telegraf</text>
|
||||
</g>
|
||||
<!-- svc:telegraf->net:monitor -->
|
||||
<g id="edge37" class="edge">
|
||||
<title>svc:telegraf->net:monitor</title>
|
||||
<path fill="none" stroke="black" d="M125.8,-1307.18C142.85,-1302.93 163.26,-1296.43 180,-1287 206.25,-1272.22 231.35,-1248.65 248.38,-1230.74"/>
|
||||
<polygon fill="black" stroke="black" points="251.37,-1232.67 255.62,-1222.97 246.24,-1227.9 251.37,-1232.67"/>
|
||||
</g>
|
||||
<!-- svc:traefik -->
|
||||
<g id="node28" class="node">
|
||||
<title>svc:traefik</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="121,-792 59,-792 59,-756 121,-756 121,-792"/>
|
||||
<text text-anchor="middle" x="90" y="-770.3" font-family="Helvetica,sans-Serif" font-size="14.00">traefik</text>
|
||||
</g>
|
||||
<!-- svc:traefik->net:traefik -->
|
||||
<g id="edge38" class="edge">
|
||||
<title>svc:traefik->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M121,-774C148.16,-774 188.69,-774 220.66,-774"/>
|
||||
<polygon fill="black" stroke="black" points="220.71,-777.5 230.71,-774 220.71,-770.5 220.71,-777.5"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 24 KiB |
@@ -0,0 +1,118 @@
|
||||
digraph DockerTraefikDynu {
|
||||
graph [rankdir=LR, compound=true, splines=polyline, nodesep=0.9, ranksep=1.6, fontname="Helvetica", concentrate=true, newrank=true];
|
||||
node [fontname="Helvetica", fontsize=11, style="rounded,filled"];
|
||||
edge [fontname="Helvetica", fontsize=9, color="#334155"];
|
||||
"dynu" [label="Dynu / Public DNS", shape=box, fillcolor="#fde68a"];
|
||||
"svc:traefik" [label="Traefik", shape=box, fillcolor="#bfdbfe"];
|
||||
"dynu" -> "svc:traefik" [penwidth=1.6];
|
||||
"svc:authelia" [label="authelia
|
||||
[TLS]", shape=box, fillcolor="#dcfce7"];
|
||||
"svc:traefik" -> "svc:authelia" [penwidth=1.4];
|
||||
"dns:auth.<domain>" [label="auth.<domain>", shape=note, fillcolor="#fef3c7"];
|
||||
"dns:auth.<domain>" -> "dynu";
|
||||
"svc:gitea" [label="gitea
|
||||
:3000
|
||||
[TLS]", shape=box, fillcolor="#dcfce7"];
|
||||
"svc:traefik" -> "svc:gitea" [penwidth=1.4];
|
||||
"dns:gitea.<domain>" [label="gitea.<domain>", shape=note, fillcolor="#fef3c7"];
|
||||
"dns:gitea.<domain>" -> "dynu";
|
||||
"svc:gotify" [label="gotify
|
||||
:80", shape=box, fillcolor="#dcfce7"];
|
||||
"svc:traefik" -> "svc:gotify" [penwidth=1.4];
|
||||
"dns:gotify.<domain>" [label="gotify.<domain>", shape=note, fillcolor="#fef3c7"];
|
||||
"dns:gotify.<domain>" -> "dynu";
|
||||
"svc:grafana" [label="grafana
|
||||
:3000", shape=box, fillcolor="#dcfce7"];
|
||||
"svc:traefik" -> "svc:grafana" [penwidth=1.4];
|
||||
"dns:grafana.<domain>" [label="grafana.<domain>", shape=note, fillcolor="#fef3c7"];
|
||||
"dns:grafana.<domain>" -> "dynu";
|
||||
"svc:grampsweb" [label="grampsweb", shape=box, fillcolor="#dcfce7"];
|
||||
"svc:traefik" -> "svc:grampsweb" [penwidth=1.4];
|
||||
"dns:familytree.<domain>" [label="familytree.<domain>", shape=note, fillcolor="#fef3c7"];
|
||||
"dns:familytree.<domain>" -> "dynu";
|
||||
"svc:influxdb" [label="influxdb
|
||||
:8086
|
||||
[authelia]", shape=box, fillcolor="#dcfce7"];
|
||||
"svc:traefik" -> "svc:influxdb" [penwidth=1.4];
|
||||
"dns:influxdb.<domain>" [label="influxdb.<domain>", shape=note, fillcolor="#fef3c7"];
|
||||
"dns:influxdb.<domain>" -> "dynu";
|
||||
"svc:monitor-kuma" [label="monitor-kuma
|
||||
[TLS]", shape=box, fillcolor="#dcfce7"];
|
||||
"svc:traefik" -> "svc:monitor-kuma" [penwidth=1.4];
|
||||
"dns:monitor-kuma.<domain>" [label="monitor-kuma.<domain>", shape=note, fillcolor="#fef3c7"];
|
||||
"dns:monitor-kuma.<domain>" -> "dynu";
|
||||
"svc:mtls-bridge" [label="mtls-bridge
|
||||
:8080
|
||||
[mTLS]", shape=box, fillcolor="#dcfce7"];
|
||||
"svc:traefik" -> "svc:mtls-bridge" [penwidth=1.4];
|
||||
"dns:mtls-bridge.<domain>" [label="mtls-bridge.<domain>", shape=note, fillcolor="#fef3c7"];
|
||||
"dns:mtls-bridge.<domain>" -> "dynu";
|
||||
"svc:nextcloud-webapp" [label="nextcloud-webapp", shape=box, fillcolor="#dcfce7"];
|
||||
"svc:traefik" -> "svc:nextcloud-webapp" [penwidth=1.4];
|
||||
"dns:nextcloud.<domain>" [label="nextcloud.<domain>", shape=note, fillcolor="#fef3c7"];
|
||||
"dns:nextcloud.<domain>" -> "dynu";
|
||||
"svc:node-red" [label="node-red
|
||||
:1880
|
||||
[authelia]", shape=box, fillcolor="#dcfce7"];
|
||||
"svc:traefik" -> "svc:node-red" [penwidth=1.4];
|
||||
"dns:node-red.<domain>" [label="node-red.<domain>", shape=note, fillcolor="#fef3c7"];
|
||||
"dns:node-red.<domain>" -> "dynu";
|
||||
"svc:passbolt-webapp" [label="passbolt-webapp", shape=box, fillcolor="#dcfce7"];
|
||||
"svc:traefik" -> "svc:passbolt-webapp" [penwidth=1.4];
|
||||
"dns:passbolt.<domain>" [label="passbolt.<domain>", shape=note, fillcolor="#fef3c7"];
|
||||
"dns:passbolt.<domain>" -> "dynu";
|
||||
"svc:portainer" [label="portainer
|
||||
:9000
|
||||
[TLS]", shape=box, fillcolor="#dcfce7"];
|
||||
"svc:traefik" -> "svc:portainer" [penwidth=1.4];
|
||||
"dns:portainer.<domain>" [label="portainer.<domain>", shape=note, fillcolor="#fef3c7"];
|
||||
"dns:portainer.<domain>" -> "dynu";
|
||||
"svc:prometheus" [label="prometheus
|
||||
:9090
|
||||
[authelia]", shape=box, fillcolor="#dcfce7"];
|
||||
"svc:traefik" -> "svc:prometheus" [penwidth=1.4];
|
||||
"dns:prometheus.<domain>" [label="prometheus.<domain>", shape=note, fillcolor="#fef3c7"];
|
||||
"dns:prometheus.<domain>" -> "dynu";
|
||||
"svc:searxng-webapp" [label="searxng-webapp", shape=box, fillcolor="#dcfce7"];
|
||||
"svc:traefik" -> "svc:searxng-webapp" [penwidth=1.4];
|
||||
"dns:searxng.<domain>" [label="searxng.<domain>", shape=note, fillcolor="#fef3c7"];
|
||||
"dns:searxng.<domain>" -> "dynu";
|
||||
"svc:traefik" [label="traefik
|
||||
[authelia]", shape=box, fillcolor="#dcfce7"];
|
||||
"svc:traefik" -> "svc:traefik" [penwidth=1.4];
|
||||
"dns:traefik.<domain>" [label="traefik.<domain>", shape=note, fillcolor="#fef3c7"];
|
||||
"dns:traefik.<domain>" -> "dynu";
|
||||
{ rank=same; "dns:auth.<domain>"; "dns:familytree.<domain>"; "dns:gitea.<domain>"; "dns:gotify.<domain>"; "dns:grafana.<domain>"; "dns:influxdb.<domain>"; "dns:monitor-kuma.<domain>"; "dns:mtls-bridge.<domain>"; "dns:nextcloud.<domain>"; "dns:node-red.<domain>"; "dns:passbolt.<domain>"; "dns:portainer.<domain>"; "dns:prometheus.<domain>"; "dns:searxng.<domain>"; "dns:traefik.<domain>"; }
|
||||
subgraph "cluster_networks" {
|
||||
label="Docker backend networks"; style="rounded,dashed"; color="#d1d5db";
|
||||
"net:gramps" [label="gramps", shape=ellipse, fillcolor="#f8fafc"];
|
||||
"net:monitor" [label="monitor", shape=ellipse, fillcolor="#f8fafc"];
|
||||
"net:nextcloud" [label="nextcloud", shape=ellipse, fillcolor="#f8fafc"];
|
||||
"net:passbolt" [label="passbolt", shape=ellipse, fillcolor="#f8fafc"];
|
||||
"net:traefik" [label="traefik", shape=ellipse, fillcolor="#f8fafc"];
|
||||
}
|
||||
"svc:authelia" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
|
||||
"svc:gitea" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
|
||||
"svc:gotify" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
|
||||
"svc:grafana" -> "net:monitor" [style=dashed, color="#94a3b8", arrowsize=0.7];
|
||||
"svc:grafana" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
|
||||
"svc:grampsweb" -> "net:gramps" [style=dashed, color="#94a3b8", arrowsize=0.7];
|
||||
"svc:grampsweb" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
|
||||
"svc:influxdb" -> "net:monitor" [style=dashed, color="#94a3b8", arrowsize=0.7];
|
||||
"svc:influxdb" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
|
||||
"svc:monitor-kuma" -> "net:monitor" [style=dashed, color="#94a3b8", arrowsize=0.7];
|
||||
"svc:monitor-kuma" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
|
||||
"svc:mtls-bridge" -> "net:monitor" [style=dashed, color="#94a3b8", arrowsize=0.7];
|
||||
"svc:mtls-bridge" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
|
||||
"svc:nextcloud-webapp" -> "net:nextcloud" [style=dashed, color="#94a3b8", arrowsize=0.7];
|
||||
"svc:nextcloud-webapp" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
|
||||
"svc:node-red" -> "net:monitor" [style=dashed, color="#94a3b8", arrowsize=0.7];
|
||||
"svc:node-red" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
|
||||
"svc:passbolt-webapp" -> "net:passbolt" [style=dashed, color="#94a3b8", arrowsize=0.7];
|
||||
"svc:passbolt-webapp" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
|
||||
"svc:portainer" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
|
||||
"svc:prometheus" -> "net:monitor" [style=dashed, color="#94a3b8", arrowsize=0.7];
|
||||
"svc:prometheus" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
|
||||
"svc:searxng-webapp" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
|
||||
"svc:traefik" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
|
||||
}
|
||||
@@ -0,0 +1,611 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 2.43.0 (0)
|
||||
-->
|
||||
<!-- Title: DockerTraefikDynu Pages: 1 -->
|
||||
<svg width="1113pt" height="1536pt"
|
||||
viewBox="0.00 0.00 1113.39 1536.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1532)">
|
||||
<title>DockerTraefikDynu</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-1532 1109.39,-1532 1109.39,4 -4,4"/>
|
||||
<g id="clust2" class="cluster">
|
||||
<title>cluster_networks</title>
|
||||
<path fill="none" stroke="#d1d5db" stroke-dasharray="5,2" d="M922,-650C922,-650 1093.39,-650 1093.39,-650 1099.39,-650 1105.39,-656 1105.39,-662 1105.39,-662 1105.39,-1117 1105.39,-1117 1105.39,-1123 1099.39,-1129 1093.39,-1129 1093.39,-1129 922,-1129 922,-1129 916,-1129 910,-1123 910,-1117 910,-1117 910,-662 910,-662 910,-656 916,-650 922,-650"/>
|
||||
<text text-anchor="middle" x="1007.69" y="-1113.8" font-family="Helvetica,sans-Serif" font-size="14.00">Docker backend networks</text>
|
||||
</g>
|
||||
<!-- dynu -->
|
||||
<g id="node1" class="node">
|
||||
<title>dynu</title>
|
||||
<path fill="#fde68a" stroke="black" d="M374,-789C374,-789 282,-789 282,-789 276,-789 270,-783 270,-777 270,-777 270,-765 270,-765 270,-759 276,-753 282,-753 282,-753 374,-753 374,-753 380,-753 386,-759 386,-765 386,-765 386,-777 386,-777 386,-783 380,-789 374,-789"/>
|
||||
<text text-anchor="middle" x="328" y="-768.2" font-family="Helvetica,sans-Serif" font-size="11.00">Dynu / Public DNS</text>
|
||||
</g>
|
||||
<!-- svc:traefik -->
|
||||
<g id="node2" class="node">
|
||||
<title>svc:traefik</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M559,-789C559,-789 513,-789 513,-789 507,-789 501,-783 501,-777 501,-777 501,-765 501,-765 501,-759 507,-753 513,-753 513,-753 559,-753 559,-753 565,-753 571,-759 571,-765 571,-765 571,-777 571,-777 571,-783 565,-789 559,-789"/>
|
||||
<text text-anchor="middle" x="536" y="-774.2" font-family="Helvetica,sans-Serif" font-size="11.00">traefik</text>
|
||||
<text text-anchor="middle" x="536" y="-762.2" font-family="Helvetica,sans-Serif" font-size="11.00">[authelia]</text>
|
||||
</g>
|
||||
<!-- dynu->svc:traefik -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>dynu->svc:traefik</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.6" d="M386.11,-771C419.13,-771 460.06,-771 490.64,-771"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.6" points="491,-774.5 501,-771 491,-767.5 491,-774.5"/>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:traefik -->
|
||||
<g id="edge30" class="edge">
|
||||
<title>svc:traefik->svc:traefik</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M511.86,-789.35C485.91,-817.82 493.95,-854 536,-854 574.1,-854 584.29,-824.29 566.55,-797.52"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="569.07,-795.06 560.14,-789.35 563.55,-799.38 569.07,-795.06"/>
|
||||
</g>
|
||||
<!-- svc:authelia -->
|
||||
<g id="node3" class="node">
|
||||
<title>svc:authelia</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M763,-789C763,-789 726,-789 726,-789 720,-789 714,-783 714,-777 714,-777 714,-765 714,-765 714,-759 720,-753 726,-753 726,-753 763,-753 763,-753 769,-753 775,-759 775,-765 775,-765 775,-777 775,-777 775,-783 769,-789 763,-789"/>
|
||||
<text text-anchor="middle" x="744.5" y="-774.2" font-family="Helvetica,sans-Serif" font-size="11.00">authelia</text>
|
||||
<text text-anchor="middle" x="744.5" y="-762.2" font-family="Helvetica,sans-Serif" font-size="11.00">[TLS]</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:authelia -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>svc:traefik->svc:authelia</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M571.1,-771C607.43,-771 664.91,-771 703.39,-771"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="703.71,-774.5 713.71,-771 703.71,-767.5 703.71,-774.5"/>
|
||||
</g>
|
||||
<!-- svc:gitea -->
|
||||
<g id="node5" class="node">
|
||||
<title>svc:gitea</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M759.5,-688C759.5,-688 729.5,-688 729.5,-688 723.5,-688 717.5,-682 717.5,-676 717.5,-676 717.5,-656 717.5,-656 717.5,-650 723.5,-644 729.5,-644 729.5,-644 759.5,-644 759.5,-644 765.5,-644 771.5,-650 771.5,-656 771.5,-656 771.5,-676 771.5,-676 771.5,-682 765.5,-688 759.5,-688"/>
|
||||
<text text-anchor="middle" x="744.5" y="-675.2" font-family="Helvetica,sans-Serif" font-size="11.00">gitea</text>
|
||||
<text text-anchor="middle" x="744.5" y="-663.2" font-family="Helvetica,sans-Serif" font-size="11.00">:3000</text>
|
||||
<text text-anchor="middle" x="744.5" y="-651.2" font-family="Helvetica,sans-Serif" font-size="11.00">[TLS]</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:gitea -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>svc:traefik->svc:gitea</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M571.1,-753.66C608.91,-734.44 669.61,-703.57 707.98,-684.06"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="709.89,-687.01 717.22,-679.36 706.72,-680.77 709.89,-687.01"/>
|
||||
</g>
|
||||
<!-- svc:gotify -->
|
||||
<g id="node7" class="node">
|
||||
<title>svc:gotify</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M759.5,-579C759.5,-579 729.5,-579 729.5,-579 723.5,-579 717.5,-573 717.5,-567 717.5,-567 717.5,-555 717.5,-555 717.5,-549 723.5,-543 729.5,-543 729.5,-543 759.5,-543 759.5,-543 765.5,-543 771.5,-549 771.5,-555 771.5,-555 771.5,-567 771.5,-567 771.5,-573 765.5,-579 759.5,-579"/>
|
||||
<text text-anchor="middle" x="744.5" y="-564.2" font-family="Helvetica,sans-Serif" font-size="11.00">gotify</text>
|
||||
<text text-anchor="middle" x="744.5" y="-552.2" font-family="Helvetica,sans-Serif" font-size="11.00">:80</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:gotify -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>svc:traefik->svc:gotify</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M553.8,-752.96C592.77,-711.11 686,-611 686,-611 686,-611 700.43,-598.45 714.83,-585.93"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="717.49,-588.25 722.74,-579.05 712.9,-582.97 717.49,-588.25"/>
|
||||
</g>
|
||||
<!-- svc:grafana -->
|
||||
<g id="node9" class="node">
|
||||
<title>svc:grafana</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M762,-999C762,-999 727,-999 727,-999 721,-999 715,-993 715,-987 715,-987 715,-975 715,-975 715,-969 721,-963 727,-963 727,-963 762,-963 762,-963 768,-963 774,-969 774,-975 774,-975 774,-987 774,-987 774,-993 768,-999 762,-999"/>
|
||||
<text text-anchor="middle" x="744.5" y="-984.2" font-family="Helvetica,sans-Serif" font-size="11.00">grafana</text>
|
||||
<text text-anchor="middle" x="744.5" y="-972.2" font-family="Helvetica,sans-Serif" font-size="11.00">:3000</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:grafana -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>svc:traefik->svc:grafana</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M553.8,-789.04C592.77,-830.89 686,-931 686,-931 686,-931 700.43,-943.55 714.83,-956.07"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="712.9,-959.03 722.74,-962.95 717.49,-953.75 712.9,-959.03"/>
|
||||
</g>
|
||||
<!-- svc:grampsweb -->
|
||||
<g id="node11" class="node">
|
||||
<title>svc:grampsweb</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M772.5,-1528C772.5,-1528 716.5,-1528 716.5,-1528 710.5,-1528 704.5,-1522 704.5,-1516 704.5,-1516 704.5,-1504 704.5,-1504 704.5,-1498 710.5,-1492 716.5,-1492 716.5,-1492 772.5,-1492 772.5,-1492 778.5,-1492 784.5,-1498 784.5,-1504 784.5,-1504 784.5,-1516 784.5,-1516 784.5,-1522 778.5,-1528 772.5,-1528"/>
|
||||
<text text-anchor="middle" x="744.5" y="-1507.2" font-family="Helvetica,sans-Serif" font-size="11.00">grampsweb</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:grampsweb -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>svc:traefik->svc:grampsweb</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M540.91,-789.07C564.42,-897.78 686,-1460 686,-1460 686,-1460 700.43,-1472.55 714.83,-1485.07"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="712.9,-1488.03 722.74,-1491.95 717.49,-1482.75 712.9,-1488.03"/>
|
||||
</g>
|
||||
<!-- svc:influxdb -->
|
||||
<g id="node13" class="node">
|
||||
<title>svc:influxdb</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M767.5,-898C767.5,-898 721.5,-898 721.5,-898 715.5,-898 709.5,-892 709.5,-886 709.5,-886 709.5,-866 709.5,-866 709.5,-860 715.5,-854 721.5,-854 721.5,-854 767.5,-854 767.5,-854 773.5,-854 779.5,-860 779.5,-866 779.5,-866 779.5,-886 779.5,-886 779.5,-892 773.5,-898 767.5,-898"/>
|
||||
<text text-anchor="middle" x="744.5" y="-885.2" font-family="Helvetica,sans-Serif" font-size="11.00">influxdb</text>
|
||||
<text text-anchor="middle" x="744.5" y="-873.2" font-family="Helvetica,sans-Serif" font-size="11.00">:8086</text>
|
||||
<text text-anchor="middle" x="744.5" y="-861.2" font-family="Helvetica,sans-Serif" font-size="11.00">[authelia]</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:influxdb -->
|
||||
<g id="edge12" class="edge">
|
||||
<title>svc:traefik->svc:influxdb</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M571.1,-788.34C606.37,-806.27 661.57,-834.34 699.97,-853.87"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="698.85,-857.22 709.35,-858.64 702.02,-850.98 698.85,-857.22"/>
|
||||
</g>
|
||||
<!-- svc:monitor-kuma -->
|
||||
<g id="node15" class="node">
|
||||
<title>svc:monitor-kuma</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M778.5,-1427C778.5,-1427 710.5,-1427 710.5,-1427 704.5,-1427 698.5,-1421 698.5,-1415 698.5,-1415 698.5,-1403 698.5,-1403 698.5,-1397 704.5,-1391 710.5,-1391 710.5,-1391 778.5,-1391 778.5,-1391 784.5,-1391 790.5,-1397 790.5,-1403 790.5,-1403 790.5,-1415 790.5,-1415 790.5,-1421 784.5,-1427 778.5,-1427"/>
|
||||
<text text-anchor="middle" x="744.5" y="-1412.2" font-family="Helvetica,sans-Serif" font-size="11.00">monitor-kuma</text>
|
||||
<text text-anchor="middle" x="744.5" y="-1400.2" font-family="Helvetica,sans-Serif" font-size="11.00">[TLS]</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:monitor-kuma -->
|
||||
<g id="edge14" class="edge">
|
||||
<title>svc:traefik->svc:monitor-kuma</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M541.62,-789.24C566.77,-888.49 686,-1359 686,-1359 686,-1359 700.43,-1371.55 714.83,-1384.07"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="712.9,-1387.03 722.74,-1390.95 717.49,-1381.75 712.9,-1387.03"/>
|
||||
</g>
|
||||
<!-- svc:mtls-bridge -->
|
||||
<g id="node17" class="node">
|
||||
<title>svc:mtls-bridge</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M772,-1326C772,-1326 717,-1326 717,-1326 711,-1326 705,-1320 705,-1314 705,-1314 705,-1294 705,-1294 705,-1288 711,-1282 717,-1282 717,-1282 772,-1282 772,-1282 778,-1282 784,-1288 784,-1294 784,-1294 784,-1314 784,-1314 784,-1320 778,-1326 772,-1326"/>
|
||||
<text text-anchor="middle" x="744.5" y="-1313.2" font-family="Helvetica,sans-Serif" font-size="11.00">mtls-bridge</text>
|
||||
<text text-anchor="middle" x="744.5" y="-1301.2" font-family="Helvetica,sans-Serif" font-size="11.00">:8080</text>
|
||||
<text text-anchor="middle" x="744.5" y="-1289.2" font-family="Helvetica,sans-Serif" font-size="11.00">[mTLS]</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:mtls-bridge -->
|
||||
<g id="edge16" class="edge">
|
||||
<title>svc:traefik->svc:mtls-bridge</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M542.66,-789.19C569.88,-876.69 686,-1250 686,-1250 686,-1250 698.77,-1262 712.28,-1274.68"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="710.1,-1277.43 719.78,-1281.72 714.89,-1272.33 710.1,-1277.43"/>
|
||||
</g>
|
||||
<!-- svc:nextcloud-webapp -->
|
||||
<g id="node19" class="node">
|
||||
<title>svc:nextcloud-webapp</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M791,-137C791,-137 698,-137 698,-137 692,-137 686,-131 686,-125 686,-125 686,-113 686,-113 686,-107 692,-101 698,-101 698,-101 791,-101 791,-101 797,-101 803,-107 803,-113 803,-113 803,-125 803,-125 803,-131 797,-137 791,-137"/>
|
||||
<text text-anchor="middle" x="744.5" y="-116.2" font-family="Helvetica,sans-Serif" font-size="11.00">nextcloud-webapp</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:nextcloud-webapp -->
|
||||
<g id="edge18" class="edge">
|
||||
<title>svc:traefik->svc:nextcloud-webapp</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M541.62,-752.85C566.77,-654.11 686,-186 686,-186 686,-186 704.96,-163.91 721.1,-145.1"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="723.93,-147.18 727.79,-137.31 718.62,-142.62 723.93,-147.18"/>
|
||||
</g>
|
||||
<!-- svc:node-red -->
|
||||
<g id="node21" class="node">
|
||||
<title>svc:node-red</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M767.5,-1217C767.5,-1217 721.5,-1217 721.5,-1217 715.5,-1217 709.5,-1211 709.5,-1205 709.5,-1205 709.5,-1185 709.5,-1185 709.5,-1179 715.5,-1173 721.5,-1173 721.5,-1173 767.5,-1173 767.5,-1173 773.5,-1173 779.5,-1179 779.5,-1185 779.5,-1185 779.5,-1205 779.5,-1205 779.5,-1211 773.5,-1217 767.5,-1217"/>
|
||||
<text text-anchor="middle" x="744.5" y="-1204.2" font-family="Helvetica,sans-Serif" font-size="11.00">node-red</text>
|
||||
<text text-anchor="middle" x="744.5" y="-1192.2" font-family="Helvetica,sans-Serif" font-size="11.00">:1880</text>
|
||||
<text text-anchor="middle" x="744.5" y="-1180.2" font-family="Helvetica,sans-Serif" font-size="11.00">[authelia]</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:node-red -->
|
||||
<g id="edge20" class="edge">
|
||||
<title>svc:traefik->svc:node-red</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M544.29,-789.1C574.2,-863.38 686,-1141 686,-1141 686,-1141 698.77,-1153 712.28,-1165.68"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="710.1,-1168.43 719.78,-1172.72 714.89,-1163.33 710.1,-1168.43"/>
|
||||
</g>
|
||||
<!-- svc:passbolt-webapp -->
|
||||
<g id="node23" class="node">
|
||||
<title>svc:passbolt-webapp</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M787.5,-36C787.5,-36 701.5,-36 701.5,-36 695.5,-36 689.5,-30 689.5,-24 689.5,-24 689.5,-12 689.5,-12 689.5,-6 695.5,0 701.5,0 701.5,0 787.5,0 787.5,0 793.5,0 799.5,-6 799.5,-12 799.5,-12 799.5,-24 799.5,-24 799.5,-30 793.5,-36 787.5,-36"/>
|
||||
<text text-anchor="middle" x="744.5" y="-15.2" font-family="Helvetica,sans-Serif" font-size="11.00">passbolt-webapp</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:passbolt-webapp -->
|
||||
<g id="edge22" class="edge">
|
||||
<title>svc:traefik->svc:passbolt-webapp</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M540.83,-752.92C564.15,-642.88 686,-68 686,-68 686,-68 700.43,-55.45 714.83,-42.93"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="717.49,-45.25 722.74,-36.05 712.9,-39.97 717.49,-45.25"/>
|
||||
</g>
|
||||
<!-- svc:portainer -->
|
||||
<g id="node25" class="node">
|
||||
<title>svc:portainer</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M766,-478C766,-478 723,-478 723,-478 717,-478 711,-472 711,-466 711,-466 711,-446 711,-446 711,-440 717,-434 723,-434 723,-434 766,-434 766,-434 772,-434 778,-440 778,-446 778,-446 778,-466 778,-466 778,-472 772,-478 766,-478"/>
|
||||
<text text-anchor="middle" x="744.5" y="-465.2" font-family="Helvetica,sans-Serif" font-size="11.00">portainer</text>
|
||||
<text text-anchor="middle" x="744.5" y="-453.2" font-family="Helvetica,sans-Serif" font-size="11.00">:9000</text>
|
||||
<text text-anchor="middle" x="744.5" y="-441.2" font-family="Helvetica,sans-Serif" font-size="11.00">[TLS]</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:portainer -->
|
||||
<g id="edge24" class="edge">
|
||||
<title>svc:traefik->svc:portainer</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M547.48,-752.65C581.39,-693.24 686,-510 686,-510 686,-510 698.77,-498 712.28,-485.32"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="714.89,-487.67 719.78,-478.28 710.1,-482.57 714.89,-487.67"/>
|
||||
</g>
|
||||
<!-- svc:prometheus -->
|
||||
<g id="node27" class="node">
|
||||
<title>svc:prometheus</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M774,-1108C774,-1108 715,-1108 715,-1108 709,-1108 703,-1102 703,-1096 703,-1096 703,-1076 703,-1076 703,-1070 709,-1064 715,-1064 715,-1064 774,-1064 774,-1064 780,-1064 786,-1070 786,-1076 786,-1076 786,-1096 786,-1096 786,-1102 780,-1108 774,-1108"/>
|
||||
<text text-anchor="middle" x="744.5" y="-1095.2" font-family="Helvetica,sans-Serif" font-size="11.00">prometheus</text>
|
||||
<text text-anchor="middle" x="744.5" y="-1083.2" font-family="Helvetica,sans-Serif" font-size="11.00">:9090</text>
|
||||
<text text-anchor="middle" x="744.5" y="-1071.2" font-family="Helvetica,sans-Serif" font-size="11.00">[authelia]</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:prometheus -->
|
||||
<g id="edge26" class="edge">
|
||||
<title>svc:traefik->svc:prometheus</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M547.48,-789.35C581.39,-848.76 686,-1032 686,-1032 686,-1032 698.77,-1044 712.28,-1056.68"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="710.1,-1059.43 719.78,-1063.72 714.89,-1054.33 710.1,-1059.43"/>
|
||||
</g>
|
||||
<!-- svc:searxng-webapp -->
|
||||
<g id="node29" class="node">
|
||||
<title>svc:searxng-webapp</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M786,-369C786,-369 703,-369 703,-369 697,-369 691,-363 691,-357 691,-357 691,-345 691,-345 691,-339 697,-333 703,-333 703,-333 786,-333 786,-333 792,-333 798,-339 798,-345 798,-345 798,-357 798,-357 798,-363 792,-369 786,-369"/>
|
||||
<text text-anchor="middle" x="744.5" y="-348.2" font-family="Helvetica,sans-Serif" font-size="11.00">searxng-webapp</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:searxng-webapp -->
|
||||
<g id="edge28" class="edge">
|
||||
<title>svc:traefik->svc:searxng-webapp</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M544.29,-752.9C574.2,-678.62 686,-401 686,-401 686,-401 700.43,-388.45 714.83,-375.93"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="717.49,-378.25 722.74,-369.05 712.9,-372.97 717.49,-378.25"/>
|
||||
</g>
|
||||
<!-- net:traefik -->
|
||||
<g id="node36" class="node">
|
||||
<title>net:traefik</title>
|
||||
<ellipse fill="#f8fafc" stroke="black" cx="1007.69" cy="-878" rx="31.04" ry="18"/>
|
||||
<text text-anchor="middle" x="1007.69" y="-875.2" font-family="Helvetica,sans-Serif" font-size="11.00">traefik</text>
|
||||
</g>
|
||||
<!-- svc:traefik->net:traefik -->
|
||||
<g id="edge55" class="edge">
|
||||
<title>svc:traefik->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M542.75,-752.82C570.13,-666.27 686,-300 686,-300 686,-300 803,-300 803,-300 803,-300 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
|
||||
</g>
|
||||
<!-- svc:authelia->net:traefik -->
|
||||
<g id="edge32" class="edge">
|
||||
<title>svc:authelia->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M775.21,-783.17C824.86,-803.51 924.25,-844.23 975.12,-865.06"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="974.26,-867.36 981.67,-867.75 976.12,-862.83 974.26,-867.36"/>
|
||||
</g>
|
||||
<!-- dns:auth.<domain> -->
|
||||
<g id="node4" class="node">
|
||||
<title>dns:auth.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="123.5,-1496 25.5,-1496 25.5,-1460 129.5,-1460 129.5,-1490 123.5,-1496"/>
|
||||
<polyline fill="none" stroke="black" points="123.5,-1496 123.5,-1490 "/>
|
||||
<polyline fill="none" stroke="black" points="129.5,-1490 123.5,-1490 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-1475.2" font-family="Helvetica,sans-Serif" font-size="11.00">auth.<domain></text>
|
||||
</g>
|
||||
<!-- dns:auth.<domain>->dynu -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>dns:auth.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M106.12,-1459.95C128.03,-1445.63 155,-1428 155,-1428 155,-1428 286.65,-925.11 319.6,-799.28"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="323.07,-799.82 322.22,-789.26 316.3,-798.05 323.07,-799.82"/>
|
||||
</g>
|
||||
<!-- svc:gitea->net:traefik -->
|
||||
<g id="edge33" class="edge">
|
||||
<title>svc:gitea->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M768.56,-688.05C784.55,-703.36 803,-721 803,-721 803,-721 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
|
||||
</g>
|
||||
<!-- dns:gitea.<domain> -->
|
||||
<g id="node6" class="node">
|
||||
<title>dns:gitea.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="125,-1395 24,-1395 24,-1359 131,-1359 131,-1389 125,-1395"/>
|
||||
<polyline fill="none" stroke="black" points="125,-1395 125,-1389 "/>
|
||||
<polyline fill="none" stroke="black" points="131,-1389 125,-1389 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-1374.2" font-family="Helvetica,sans-Serif" font-size="11.00">gitea.<domain></text>
|
||||
</g>
|
||||
<!-- dns:gitea.<domain>->dynu -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>dns:gitea.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M106.12,-1358.95C128.03,-1344.63 155,-1327 155,-1327 155,-1327 283.58,-911.36 318.4,-798.82"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="321.76,-799.77 321.38,-789.18 315.08,-797.7 321.76,-799.77"/>
|
||||
</g>
|
||||
<!-- svc:gotify->net:traefik -->
|
||||
<g id="edge34" class="edge">
|
||||
<title>svc:gotify->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-579.05C782.73,-593.37 803,-611 803,-611 803,-611 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
|
||||
</g>
|
||||
<!-- dns:gotify.<domain> -->
|
||||
<g id="node8" class="node">
|
||||
<title>dns:gotify.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="126,-1294 23,-1294 23,-1258 132,-1258 132,-1288 126,-1294"/>
|
||||
<polyline fill="none" stroke="black" points="126,-1294 126,-1288 "/>
|
||||
<polyline fill="none" stroke="black" points="132,-1288 126,-1288 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-1273.2" font-family="Helvetica,sans-Serif" font-size="11.00">gotify.<domain></text>
|
||||
</g>
|
||||
<!-- dns:gotify.<domain>->dynu -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>dns:gotify.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M106.12,-1257.95C128.03,-1243.63 155,-1226 155,-1226 155,-1226 279.21,-897.41 316.53,-798.71"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="319.89,-799.71 320.15,-789.12 313.34,-797.23 319.89,-799.71"/>
|
||||
</g>
|
||||
<!-- net:monitor -->
|
||||
<g id="node33" class="node">
|
||||
<title>net:monitor</title>
|
||||
<ellipse fill="#f8fafc" stroke="black" cx="1007.69" cy="-979" rx="35.46" ry="18"/>
|
||||
<text text-anchor="middle" x="1007.69" y="-976.2" font-family="Helvetica,sans-Serif" font-size="11.00">monitor</text>
|
||||
</g>
|
||||
<!-- svc:grafana->net:monitor -->
|
||||
<g id="edge35" class="edge">
|
||||
<title>svc:grafana->net:monitor</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M774.2,-980.78C820.4,-980.43 911.47,-979.73 964.89,-979.32"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="965.19,-981.77 972.17,-979.26 965.15,-976.87 965.19,-981.77"/>
|
||||
</g>
|
||||
<!-- svc:grafana->net:traefik -->
|
||||
<g id="edge36" class="edge">
|
||||
<title>svc:grafana->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M774.2,-969.68C823.41,-950.28 923.53,-910.8 974.83,-890.57"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="975.82,-892.81 981.44,-887.96 974.03,-888.25 975.82,-892.81"/>
|
||||
</g>
|
||||
<!-- dns:grafana.<domain> -->
|
||||
<g id="node10" class="node">
|
||||
<title>dns:grafana.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="132,-1193 17,-1193 17,-1157 138,-1157 138,-1187 132,-1193"/>
|
||||
<polyline fill="none" stroke="black" points="132,-1193 132,-1187 "/>
|
||||
<polyline fill="none" stroke="black" points="138,-1187 132,-1187 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-1172.2" font-family="Helvetica,sans-Serif" font-size="11.00">grafana.<domain></text>
|
||||
</g>
|
||||
<!-- dns:grafana.<domain>->dynu -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>dns:grafana.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M106.12,-1156.95C128.03,-1142.63 155,-1125 155,-1125 155,-1125 273.61,-880.89 313.84,-798.09"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="317.01,-799.57 318.23,-789.04 310.72,-796.51 317.01,-799.57"/>
|
||||
</g>
|
||||
<!-- net:gramps -->
|
||||
<g id="node32" class="node">
|
||||
<title>net:gramps</title>
|
||||
<ellipse fill="#f8fafc" stroke="black" cx="1007.69" cy="-1080" rx="34.76" ry="18"/>
|
||||
<text text-anchor="middle" x="1007.69" y="-1077.2" font-family="Helvetica,sans-Serif" font-size="11.00">gramps</text>
|
||||
</g>
|
||||
<!-- svc:grampsweb->net:gramps -->
|
||||
<g id="edge37" class="edge">
|
||||
<title>svc:grampsweb->net:gramps</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-1491.95C782.73,-1477.63 803,-1460 803,-1460 803,-1460 949.23,-1187.21 993.89,-1103.88"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="996.27,-1104.64 997.41,-1097.32 991.95,-1102.33 996.27,-1104.64"/>
|
||||
</g>
|
||||
<!-- svc:grampsweb->net:traefik -->
|
||||
<g id="edge38" class="edge">
|
||||
<title>svc:grampsweb->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-1491.95C782.73,-1477.63 803,-1460 803,-1460 803,-1460 910,-929 910,-929 910,-929 949.05,-908.4 977.52,-893.39"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="978.73,-895.52 983.78,-890.09 976.45,-891.18 978.73,-895.52"/>
|
||||
</g>
|
||||
<!-- dns:familytree.<domain> -->
|
||||
<g id="node12" class="node">
|
||||
<title>dns:familytree.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="139,-1092 10,-1092 10,-1056 145,-1056 145,-1086 139,-1092"/>
|
||||
<polyline fill="none" stroke="black" points="139,-1092 139,-1086 "/>
|
||||
<polyline fill="none" stroke="black" points="145,-1086 139,-1086 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-1071.2" font-family="Helvetica,sans-Serif" font-size="11.00">familytree.<domain></text>
|
||||
</g>
|
||||
<!-- dns:familytree.<domain>->dynu -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>dns:familytree.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M106.12,-1055.95C128.03,-1041.63 155,-1024 155,-1024 155,-1024 264.64,-862.73 308.84,-797.71"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="311.9,-799.43 314.63,-789.2 306.11,-795.5 311.9,-799.43"/>
|
||||
</g>
|
||||
<!-- svc:influxdb->net:monitor -->
|
||||
<g id="edge39" class="edge">
|
||||
<title>svc:influxdb->net:monitor</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M779.65,-889.47C829.7,-909.2 922.46,-945.78 972.53,-965.53"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="971.89,-967.91 979.3,-968.2 973.69,-963.35 971.89,-967.91"/>
|
||||
</g>
|
||||
<!-- svc:influxdb->net:traefik -->
|
||||
<g id="edge40" class="edge">
|
||||
<title>svc:influxdb->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M779.65,-876.26C828.54,-876.64 918.2,-877.32 968.99,-877.71"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="969.17,-880.16 976.19,-877.77 969.21,-875.26 969.17,-880.16"/>
|
||||
</g>
|
||||
<!-- dns:influxdb.<domain> -->
|
||||
<g id="node14" class="node">
|
||||
<title>dns:influxdb.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="132.5,-991 16.5,-991 16.5,-955 138.5,-955 138.5,-985 132.5,-991"/>
|
||||
<polyline fill="none" stroke="black" points="132.5,-991 132.5,-985 "/>
|
||||
<polyline fill="none" stroke="black" points="138.5,-985 132.5,-985 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-970.2" font-family="Helvetica,sans-Serif" font-size="11.00">influxdb.<domain></text>
|
||||
</g>
|
||||
<!-- dns:influxdb.<domain>->dynu -->
|
||||
<g id="edge13" class="edge">
|
||||
<title>dns:influxdb.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M106.12,-954.95C128.03,-940.63 155,-923 155,-923 155,-923 250.14,-838.92 298.91,-795.83"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="301.42,-798.28 306.59,-789.03 296.78,-793.03 301.42,-798.28"/>
|
||||
</g>
|
||||
<!-- svc:monitor-kuma->net:monitor -->
|
||||
<g id="edge41" class="edge">
|
||||
<title>svc:monitor-kuma->net:monitor</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-1390.95C782.73,-1376.63 803,-1359 803,-1359 803,-1359 910,-1030 910,-1030 910,-1030 947.73,-1010.1 976.05,-995.16"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="977.25,-997.3 982.29,-991.87 974.96,-992.97 977.25,-997.3"/>
|
||||
</g>
|
||||
<!-- svc:monitor-kuma->net:traefik -->
|
||||
<g id="edge42" class="edge">
|
||||
<title>svc:monitor-kuma->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-1390.95C782.73,-1376.63 803,-1359 803,-1359 803,-1359 910,-929 910,-929 910,-929 949.05,-908.4 977.52,-893.39"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="978.73,-895.52 983.78,-890.09 976.45,-891.18 978.73,-895.52"/>
|
||||
</g>
|
||||
<!-- dns:monitor-kuma.<domain> -->
|
||||
<g id="node16" class="node">
|
||||
<title>dns:monitor-kuma.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="149,-890 0,-890 0,-854 155,-854 155,-884 149,-890"/>
|
||||
<polyline fill="none" stroke="black" points="149,-890 149,-884 "/>
|
||||
<polyline fill="none" stroke="black" points="155,-884 149,-884 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-869.2" font-family="Helvetica,sans-Serif" font-size="11.00">monitor-kuma.<domain></text>
|
||||
</g>
|
||||
<!-- dns:monitor-kuma.<domain>->dynu -->
|
||||
<g id="edge15" class="edge">
|
||||
<title>dns:monitor-kuma.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M122.93,-853.94C165.02,-836.83 228.32,-811.11 273.25,-792.85"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="274.59,-796.08 282.54,-789.07 271.96,-789.59 274.59,-796.08"/>
|
||||
</g>
|
||||
<!-- svc:mtls-bridge->net:monitor -->
|
||||
<g id="edge43" class="edge">
|
||||
<title>svc:mtls-bridge->net:monitor</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M769.22,-1281.72C785.06,-1266.85 803,-1250 803,-1250 803,-1250 910,-1030 910,-1030 910,-1030 947.73,-1010.1 976.05,-995.16"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="977.25,-997.3 982.29,-991.87 974.96,-992.97 977.25,-997.3"/>
|
||||
</g>
|
||||
<!-- svc:mtls-bridge->net:traefik -->
|
||||
<g id="edge44" class="edge">
|
||||
<title>svc:mtls-bridge->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M769.22,-1281.72C785.06,-1266.85 803,-1250 803,-1250 803,-1250 910,-929 910,-929 910,-929 949.05,-908.4 977.52,-893.39"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="978.73,-895.52 983.78,-890.09 976.45,-891.18 978.73,-895.52"/>
|
||||
</g>
|
||||
<!-- dns:mtls-bridge.<domain> -->
|
||||
<g id="node18" class="node">
|
||||
<title>dns:mtls-bridge.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="142,-789 7,-789 7,-753 148,-753 148,-783 142,-789"/>
|
||||
<polyline fill="none" stroke="black" points="142,-789 142,-783 "/>
|
||||
<polyline fill="none" stroke="black" points="148,-783 142,-783 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-768.2" font-family="Helvetica,sans-Serif" font-size="11.00">mtls-bridge.<domain></text>
|
||||
</g>
|
||||
<!-- dns:mtls-bridge.<domain>->dynu -->
|
||||
<g id="edge17" class="edge">
|
||||
<title>dns:mtls-bridge.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M148.05,-771C182.94,-771 225.03,-771 259.61,-771"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="259.62,-774.5 269.62,-771 259.62,-767.5 259.62,-774.5"/>
|
||||
</g>
|
||||
<!-- net:nextcloud -->
|
||||
<g id="node34" class="node">
|
||||
<title>net:nextcloud</title>
|
||||
<ellipse fill="#f8fafc" stroke="black" cx="1007.69" cy="-777" rx="42.89" ry="18"/>
|
||||
<text text-anchor="middle" x="1007.69" y="-774.2" font-family="Helvetica,sans-Serif" font-size="11.00">nextcloud</text>
|
||||
</g>
|
||||
<!-- svc:nextcloud-webapp->net:nextcloud -->
|
||||
<g id="edge45" class="edge">
|
||||
<title>svc:nextcloud-webapp->net:nextcloud</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M761.21,-137.31C778.24,-157.15 803,-186 803,-186 803,-186 910,-727 910,-727 910,-727 945.48,-745.35 973.46,-759.81"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="972.67,-762.17 980.02,-763.2 974.92,-757.81 972.67,-762.17"/>
|
||||
</g>
|
||||
<!-- svc:nextcloud-webapp->net:traefik -->
|
||||
<g id="edge46" class="edge">
|
||||
<title>svc:nextcloud-webapp->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M761.21,-137.31C778.24,-157.15 803,-186 803,-186 803,-186 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
|
||||
</g>
|
||||
<!-- dns:nextcloud.<domain> -->
|
||||
<g id="node20" class="node">
|
||||
<title>dns:nextcloud.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="138,-688 11,-688 11,-652 144,-652 144,-682 138,-688"/>
|
||||
<polyline fill="none" stroke="black" points="138,-688 138,-682 "/>
|
||||
<polyline fill="none" stroke="black" points="144,-682 138,-682 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-667.2" font-family="Helvetica,sans-Serif" font-size="11.00">nextcloud.<domain></text>
|
||||
</g>
|
||||
<!-- dns:nextcloud.<domain>->dynu -->
|
||||
<g id="edge19" class="edge">
|
||||
<title>dns:nextcloud.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M122.93,-688.06C165.02,-705.17 228.32,-730.89 273.25,-749.15"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="271.96,-752.41 282.54,-752.93 274.59,-745.92 271.96,-752.41"/>
|
||||
</g>
|
||||
<!-- svc:node-red->net:monitor -->
|
||||
<g id="edge47" class="edge">
|
||||
<title>svc:node-red->net:monitor</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M769.22,-1172.72C785.06,-1157.85 803,-1141 803,-1141 803,-1141 910,-1030 910,-1030 910,-1030 947.73,-1010.1 976.05,-995.16"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="977.25,-997.3 982.29,-991.87 974.96,-992.97 977.25,-997.3"/>
|
||||
</g>
|
||||
<!-- svc:node-red->net:traefik -->
|
||||
<g id="edge48" class="edge">
|
||||
<title>svc:node-red->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M769.22,-1172.72C785.06,-1157.85 803,-1141 803,-1141 803,-1141 910,-929 910,-929 910,-929 949.05,-908.4 977.52,-893.39"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="978.73,-895.52 983.78,-890.09 976.45,-891.18 978.73,-895.52"/>
|
||||
</g>
|
||||
<!-- dns:node-red.<domain> -->
|
||||
<g id="node22" class="node">
|
||||
<title>dns:node-red.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="135.5,-587 13.5,-587 13.5,-551 141.5,-551 141.5,-581 135.5,-587"/>
|
||||
<polyline fill="none" stroke="black" points="135.5,-587 135.5,-581 "/>
|
||||
<polyline fill="none" stroke="black" points="141.5,-581 135.5,-581 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-566.2" font-family="Helvetica,sans-Serif" font-size="11.00">node-red.<domain></text>
|
||||
</g>
|
||||
<!-- dns:node-red.<domain>->dynu -->
|
||||
<g id="edge21" class="edge">
|
||||
<title>dns:node-red.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M106.12,-587.05C128.03,-601.37 155,-619 155,-619 155,-619 250.14,-703.08 298.91,-746.17"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="296.78,-748.97 306.59,-752.97 301.42,-743.72 296.78,-748.97"/>
|
||||
</g>
|
||||
<!-- net:passbolt -->
|
||||
<g id="node35" class="node">
|
||||
<title>net:passbolt</title>
|
||||
<ellipse fill="#f8fafc" stroke="black" cx="1007.69" cy="-676" rx="37.77" ry="18"/>
|
||||
<text text-anchor="middle" x="1007.69" y="-673.2" font-family="Helvetica,sans-Serif" font-size="11.00">passbolt</text>
|
||||
</g>
|
||||
<!-- svc:passbolt-webapp->net:passbolt -->
|
||||
<g id="edge49" class="edge">
|
||||
<title>svc:passbolt-webapp->net:passbolt</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-36.05C782.73,-50.37 803,-68 803,-68 803,-68 960.4,-537.83 998.48,-651.47"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="996.16,-652.27 1000.71,-658.13 1000.81,-650.72 996.16,-652.27"/>
|
||||
</g>
|
||||
<!-- svc:passbolt-webapp->net:traefik -->
|
||||
<g id="edge50" class="edge">
|
||||
<title>svc:passbolt-webapp->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-36.05C782.73,-50.37 803,-68 803,-68 803,-68 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
|
||||
</g>
|
||||
<!-- dns:passbolt.<domain> -->
|
||||
<g id="node24" class="node">
|
||||
<title>dns:passbolt.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="134,-486 15,-486 15,-450 140,-450 140,-480 134,-486"/>
|
||||
<polyline fill="none" stroke="black" points="134,-486 134,-480 "/>
|
||||
<polyline fill="none" stroke="black" points="140,-480 134,-480 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-465.2" font-family="Helvetica,sans-Serif" font-size="11.00">passbolt.<domain></text>
|
||||
</g>
|
||||
<!-- dns:passbolt.<domain>->dynu -->
|
||||
<g id="edge23" class="edge">
|
||||
<title>dns:passbolt.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M106.12,-486.05C128.03,-500.37 155,-518 155,-518 155,-518 264.64,-679.27 308.84,-744.29"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="306.11,-746.5 314.63,-752.8 311.9,-742.57 306.11,-746.5"/>
|
||||
</g>
|
||||
<!-- svc:portainer->net:traefik -->
|
||||
<g id="edge51" class="edge">
|
||||
<title>svc:portainer->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M769.22,-478.28C785.06,-493.15 803,-510 803,-510 803,-510 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
|
||||
</g>
|
||||
<!-- dns:portainer.<domain> -->
|
||||
<g id="node26" class="node">
|
||||
<title>dns:portainer.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="135.5,-385 13.5,-385 13.5,-349 141.5,-349 141.5,-379 135.5,-385"/>
|
||||
<polyline fill="none" stroke="black" points="135.5,-385 135.5,-379 "/>
|
||||
<polyline fill="none" stroke="black" points="141.5,-379 135.5,-379 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-364.2" font-family="Helvetica,sans-Serif" font-size="11.00">portainer.<domain></text>
|
||||
</g>
|
||||
<!-- dns:portainer.<domain>->dynu -->
|
||||
<g id="edge25" class="edge">
|
||||
<title>dns:portainer.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M106.12,-385.05C128.03,-399.37 155,-417 155,-417 155,-417 273.61,-661.11 313.84,-743.91"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="310.72,-745.49 318.23,-752.96 317.01,-742.43 310.72,-745.49"/>
|
||||
</g>
|
||||
<!-- svc:prometheus->net:monitor -->
|
||||
<g id="edge52" class="edge">
|
||||
<title>svc:prometheus->net:monitor</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M786.03,-1069.4C837.48,-1048.32 925.31,-1012.34 973.18,-992.73"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="974.11,-995 979.66,-990.07 972.26,-990.46 974.11,-995"/>
|
||||
</g>
|
||||
<!-- svc:prometheus->net:traefik -->
|
||||
<g id="edge53" class="edge">
|
||||
<title>svc:prometheus->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M769.22,-1063.72C785.06,-1048.85 803,-1032 803,-1032 803,-1032 910,-929 910,-929 910,-929 949.05,-908.4 977.52,-893.39"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="978.73,-895.52 983.78,-890.09 976.45,-891.18 978.73,-895.52"/>
|
||||
</g>
|
||||
<!-- dns:prometheus.<domain> -->
|
||||
<g id="node28" class="node">
|
||||
<title>dns:prometheus.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="144,-284 5,-284 5,-248 150,-248 150,-278 144,-284"/>
|
||||
<polyline fill="none" stroke="black" points="144,-284 144,-278 "/>
|
||||
<polyline fill="none" stroke="black" points="150,-278 144,-278 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-263.2" font-family="Helvetica,sans-Serif" font-size="11.00">prometheus.<domain></text>
|
||||
</g>
|
||||
<!-- dns:prometheus.<domain>->dynu -->
|
||||
<g id="edge27" class="edge">
|
||||
<title>dns:prometheus.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M106.12,-284.05C128.03,-298.37 155,-316 155,-316 155,-316 279.21,-644.59 316.53,-743.29"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="313.34,-744.77 320.15,-752.88 319.89,-742.29 313.34,-744.77"/>
|
||||
</g>
|
||||
<!-- svc:searxng-webapp->net:traefik -->
|
||||
<g id="edge54" class="edge">
|
||||
<title>svc:searxng-webapp->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-369.05C782.73,-383.37 803,-401 803,-401 803,-401 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
|
||||
</g>
|
||||
<!-- dns:searxng.<domain> -->
|
||||
<g id="node30" class="node">
|
||||
<title>dns:searxng.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="133,-183 16,-183 16,-147 139,-147 139,-177 133,-183"/>
|
||||
<polyline fill="none" stroke="black" points="133,-183 133,-177 "/>
|
||||
<polyline fill="none" stroke="black" points="139,-177 133,-177 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-162.2" font-family="Helvetica,sans-Serif" font-size="11.00">searxng.<domain></text>
|
||||
</g>
|
||||
<!-- dns:searxng.<domain>->dynu -->
|
||||
<g id="edge29" class="edge">
|
||||
<title>dns:searxng.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M106.12,-183.05C128.03,-197.37 155,-215 155,-215 155,-215 283.58,-630.64 318.4,-743.18"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="315.08,-744.3 321.38,-752.82 321.76,-742.23 315.08,-744.3"/>
|
||||
</g>
|
||||
<!-- dns:traefik.<domain> -->
|
||||
<g id="node31" class="node">
|
||||
<title>dns:traefik.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="128.5,-82 20.5,-82 20.5,-46 134.5,-46 134.5,-76 128.5,-82"/>
|
||||
<polyline fill="none" stroke="black" points="128.5,-82 128.5,-76 "/>
|
||||
<polyline fill="none" stroke="black" points="134.5,-76 128.5,-76 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-61.2" font-family="Helvetica,sans-Serif" font-size="11.00">traefik.<domain></text>
|
||||
</g>
|
||||
<!-- dns:traefik.<domain>->dynu -->
|
||||
<g id="edge31" class="edge">
|
||||
<title>dns:traefik.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M106.12,-82.05C128.03,-96.37 155,-114 155,-114 155,-114 286.65,-616.89 319.6,-742.72"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="316.3,-743.95 322.22,-752.74 323.07,-742.18 316.3,-743.95"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 41 KiB |
@@ -0,0 +1,4 @@
|
||||
digraph PhysicalTopology {
|
||||
graph [rankdir=LR, fontname="Helvetica", nodesep=1.0, ranksep=1.5];
|
||||
"placeholder:inventory" [shape=note, style="filled", fillcolor="#fef3c7", label="Host inventory JSON not found.\nGenerate terraform inventory and rerun scripts/docs/generate-all.sh\n(--host-inventory <path>)."];
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 2.43.0 (0)
|
||||
-->
|
||||
<!-- Title: PhysicalTopology Pages: 1 -->
|
||||
<svg width="514pt" height="61pt"
|
||||
viewBox="0.00 0.00 514.00 61.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 57)">
|
||||
<title>PhysicalTopology</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-57 510,-57 510,4 -4,4"/>
|
||||
<!-- placeholder:inventory -->
|
||||
<g id="node1" class="node">
|
||||
<title>placeholder:inventory</title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="500,-53 0,-53 0,0 506,0 506,-47 500,-53"/>
|
||||
<polyline fill="none" stroke="black" points="500,-53 500,-47 "/>
|
||||
<polyline fill="none" stroke="black" points="506,-47 500,-47 "/>
|
||||
<text text-anchor="middle" x="253" y="-37.8" font-family="Times,serif" font-size="14.00">Host inventory JSON not found.</text>
|
||||
<text text-anchor="middle" x="253" y="-22.8" font-family="Times,serif" font-size="14.00">Generate terraform inventory and rerun scripts/docs/generate-all.sh</text>
|
||||
<text text-anchor="middle" x="253" y="-7.8" font-family="Times,serif" font-size="14.00">(--host-inventory <path>).</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,13 @@
|
||||
# Docker Environment
|
||||
|
||||
This environment is orchestrated from central Docker Compose definitions committed in this repository.
|
||||
|
||||
Compose source files are rendered into a resolved configuration during docs generation, then summarized into generated markdown and diagrams.
|
||||
|
||||
Generated outputs:
|
||||
|
||||
- [Compose Inventory](generated/compose-inventory.md)
|
||||
- [Resolved Compose Config](generated/docker-compose.resolved.yml)
|
||||
- [Docker Compose Diagram](diagrams/docker-compose.svg)
|
||||
|
||||
Generated documentation is produced by CI from repository files only. Documentation generation does not start containers.
|
||||
@@ -0,0 +1,139 @@
|
||||
# Dynu DNS Read-Only Inventory
|
||||
|
||||
This repository includes a **read-only** Dynu DNS inventory workflow for `lan.ddnsgeek.com`.
|
||||
|
||||
> This integration is intentionally read-only. No Dynu mutations are permitted in this repo at this stage.
|
||||
|
||||
## Scope
|
||||
|
||||
- Fetch live DNS/domain data from Dynu using **GET requests only**.
|
||||
- Correlate Dynu hostnames with Traefik `Host(...)` rules found in compose files.
|
||||
- Generate local inventory artifacts for documentation.
|
||||
|
||||
## Safety Guard Rails
|
||||
|
||||
- Scripts fail unless `DYNU_READ_ONLY=true`.
|
||||
- No Dynu write methods (`POST`, `PUT`, `PATCH`, `DELETE`) are implemented.
|
||||
- No Terraform Dynu provider/resources/modules are introduced.
|
||||
- No Ansible Dynu mutation tasks are introduced.
|
||||
- API secrets are read from environment variables and are never logged.
|
||||
|
||||
## Correlation logic
|
||||
|
||||
`scripts/dynu/correlate_dynu_with_traefik.py` uses compose files as the source of truth and parses them as YAML.
|
||||
|
||||
It supports both common label formats:
|
||||
|
||||
- list style:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.http.routers.app.rule=Host(`app.lan.ddnsgeek.com`)"
|
||||
```
|
||||
|
||||
- map style:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
traefik.http.routers.app.rule: "Host(`app.lan.ddnsgeek.com`)"
|
||||
```
|
||||
|
||||
The parser extracts hostnames from router rules such as:
|
||||
|
||||
- `Host(`a`)`
|
||||
- `Host("a")`
|
||||
- `Host('a')`
|
||||
- multi-host rules (comma-delimited)
|
||||
- combined expressions such as `Host(...) && PathPrefix(...)`
|
||||
|
||||
## Route metadata in inventory
|
||||
|
||||
Each discovered hostname mapping includes:
|
||||
|
||||
- fqdn
|
||||
- compose service name
|
||||
- compose file path
|
||||
- stack area (`apps`, `monitoring`, `core`)
|
||||
- router label key(s)
|
||||
- raw router rule
|
||||
- `uses_tls`
|
||||
- `tls_options`
|
||||
- `middlewares`
|
||||
- `uses_mtls`
|
||||
- `uses_authelia`
|
||||
|
||||
mTLS is metadata only and **never blocks mapping**.
|
||||
|
||||
## Validation model
|
||||
|
||||
The generated JSON/Markdown include a top-level `validation` section with:
|
||||
|
||||
- `allowed_unmapped_hostnames`
|
||||
- `unexpected_unmapped_hostnames`
|
||||
- `duplicate_hostnames`
|
||||
- `ambiguous_hostnames`
|
||||
- `validation_ok`
|
||||
|
||||
Current policy:
|
||||
|
||||
- `edge.lan.ddnsgeek.com` is the only allowed unmapped DNS hostname.
|
||||
- every other `*.lan.ddnsgeek.com` DNS hostname should map to a compose/Traefik-discovered service.
|
||||
|
||||
Optional strict mode:
|
||||
|
||||
- Set `DYNU_ENFORCE_VALIDATION=true` to make the correlate script exit non-zero when unexpected unmapped hostnames exist.
|
||||
|
||||
## Required Environment Variables
|
||||
|
||||
- `DYNU_API_KEY` (required for fetch)
|
||||
- `DYNU_BASE_URL` (optional, defaults to `https://api.dynu.com`)
|
||||
- `DYNU_READ_ONLY` (**must** be `true`)
|
||||
|
||||
Recommended local secrets file (not committed): `secrets/dynu.env`
|
||||
|
||||
```bash
|
||||
DYNU_API_KEY=replace-with-real-api-key
|
||||
DYNU_READ_ONLY=true
|
||||
DYNU_BASE_URL=https://api.dynu.com
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Keep values unquoted unless required by your shell.
|
||||
- `scripts/dynu/build_dns_inventory.sh` will auto-load `secrets/dynu.env` when present.
|
||||
|
||||
## Commands
|
||||
|
||||
Run directly:
|
||||
|
||||
```bash
|
||||
DYNU_READ_ONLY=true DYNU_API_KEY=... python3 scripts/dynu/fetch_dynu_dns.py
|
||||
DYNU_READ_ONLY=true python3 scripts/dynu/correlate_dynu_with_traefik.py
|
||||
```
|
||||
|
||||
Or run the wrapper:
|
||||
|
||||
```bash
|
||||
scripts/dynu/build_dns_inventory.sh
|
||||
```
|
||||
|
||||
## Artifacts
|
||||
|
||||
- `data/dns/dynu_live.json` (generated, untracked by default due to repo `data/` ignore)
|
||||
- `data/dns/dynu_traefik_inventory.json` (generated, untracked by default)
|
||||
- `docs/generated/dns-inventory.md` (generated documentation artifact)
|
||||
|
||||
Because `data/` is gitignored in this repository, JSON outputs are intentionally local-only unless ignore behavior changes in the future.
|
||||
|
||||
## Ansible Wrapper (Read-Only)
|
||||
|
||||
A syntax-safe wrapper playbook is provided at:
|
||||
|
||||
- `infrastructure/ansible/playbooks/dns-inventory.yml`
|
||||
|
||||
It only executes the local read-only scripts and does not call write-capable Dynu APIs.
|
||||
|
||||
## Not Managed Yet
|
||||
|
||||
Dynu DNS records are **not** managed by Terraform or Ansible in this repository at this stage.
|
||||
No configuration in this repository sends Dynu mutation requests.
|
||||
@@ -0,0 +1,26 @@
|
||||
default-network.yml
|
||||
apps/gitea/docker-compose.yml
|
||||
apps/gramps/docker-compose.yml
|
||||
apps/nextcloud/docker-compose.yml
|
||||
apps/passbolt/docker-compose.yml
|
||||
apps/searxng/docker-compose.yml
|
||||
apps/shift-recorder/docker-compose.yml
|
||||
apps/stockfill/docker-compose.yml
|
||||
core/authelia/docker-compose.yml
|
||||
core/crowdsec/docker-compose.yml
|
||||
core/error-pages/docker-compose.yml
|
||||
core/test/docker-compose.yml
|
||||
core/traefik/docker-compose.yml
|
||||
monitoring/docker-exporter/docker-compose.yml
|
||||
monitoring/docker-socket-proxy/docker-compose.yml
|
||||
monitoring/gotify/docker-compose.yml
|
||||
monitoring/grafana/docker-compose.yml
|
||||
monitoring/influxdb/docker-compose.yml
|
||||
monitoring/mtls-bridge/docker-compose.yml
|
||||
monitoring/node-exporter/docker-compose.yml
|
||||
monitoring/node-red/docker-compose.yml
|
||||
monitoring/pihole-exporter/docker-compose.yml
|
||||
monitoring/portainer/docker-compose.yml
|
||||
monitoring/prometheus/docker-compose.yml
|
||||
monitoring/telegraf/docker-compose.yml
|
||||
monitoring/uptime-kuma/docker-compose.yml
|
||||
@@ -0,0 +1,61 @@
|
||||
# Docker Compose Inventory
|
||||
|
||||
Source fingerprint: `0fad36c3fed6`
|
||||
|
||||
## Summary
|
||||
|
||||
| Item | Count |
|
||||
|---|---:|
|
||||
| Services | 30 |
|
||||
| Networks | 5 |
|
||||
| Volumes | 0 |
|
||||
|
||||
## Services
|
||||
|
||||
| Service | Container | Image | Build | Profiles | Networks | Ports | Restart |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| authelia | authelia | authelia/authelia | /home/nixos/docker/core/authelia | core, all, authelia, traefik | traefik | | always |
|
||||
| crowdsec | crowdsec | | /home/nixos/docker/core/crowdsec | core, all, crowdsec, traefik | traefik | | always |
|
||||
| docker-socket-proxy | docker-socket-proxy | tecnativa/docker-socket-proxy:latest | | monitoring, all, docker-socket-proxy, core, traefik, prometheus | monitor, traefik | | unless-stopped |
|
||||
| docker-update-exporter | docker-update-exporter | | /home/nixos/docker/monitoring/docker-exporter | monitoring, all, docker-exporter, prometheus | monitor | | unless-stopped |
|
||||
| error-pages | error-pages | tarampampam/error-pages:3 | | core, all, error-pages, traefik | traefik | | always |
|
||||
| gitea | gitea | gitea/gitea:latest | | apps, all, gitea | traefik | | always |
|
||||
| gitea-runner | gitea-runner | gitea/act_runner:latest | | apps, all, gitea, ci | traefik | | always |
|
||||
| gotify | gotify | gotify/server:latest | | monitoring, all, gotify | traefik | | always |
|
||||
| grafana | grafana | grafana/grafana:latest | | monitoring, all, grafana | monitor, traefik | | unless-stopped |
|
||||
| gramps-redis | gramps-redis | valkey/valkey:8-alpine | | apps, all, gramps | gramps | | always |
|
||||
| grampsweb | gramps-web | ghcr.io/gramps-project/grampsweb:latest | | apps, all, gramps | gramps, traefik | | always |
|
||||
| grampsweb_celery | gramps-web-celery | ghcr.io/gramps-project/grampsweb:latest | | apps, all, gramps | gramps | | always |
|
||||
| influxdb | influxdb | influxdb:2.7 | | monitoring, all, influxdb, prometheus | monitor, traefik | | unless-stopped |
|
||||
| monitor-kuma | monitor-kuma | louislam/uptime-kuma:2.1.1 | | monitoring, all, uptime-kuma | monitor, traefik | | always |
|
||||
| mtls-bridge | mtls-bridge | | /home/nixos/docker/monitoring/mtls-bridge | monitoring, all, mtls-bridge | monitor, traefik | | unless-stopped |
|
||||
| nextcloud-db | nextcloud-db | mariadb:11.4 | | apps, all, nextcloud | nextcloud | | always |
|
||||
| nextcloud-redis | nextcloud-redis | redis | | apps, all, nextcloud | nextcloud | | always |
|
||||
| nextcloud-webapp | nextcloud-webapp | | /home/nixos/docker/apps/nextcloud | apps, all, nextcloud | nextcloud, traefik | | always |
|
||||
| node-exporter | node-exporter | prom/node-exporter:latest | | monitoring, all, node-exporter, prometheus | monitor | | unless-stopped |
|
||||
| node-red | node-red | | /home/nixos/docker/monitoring/node-red | monitoring, all, node-red | monitor, traefik | | unless-stopped |
|
||||
| passbolt-db | passbolt-db | mariadb:12 | | apps, all, passbolt | passbolt | | always |
|
||||
| passbolt-webapp | passbolt-webapp | passbolt/passbolt:latest-ce | | apps, all, passbolt | passbolt, traefik | | always |
|
||||
| pihole-exporter | pihole-exporter | ekofr/pihole-exporter:latest | | monitoring, all, pihole-exporter, prometheus | monitor | {'mode': 'ingress', 'target': 9617, 'published': '9617', 'protocol': 'tcp'} | unless-stopped |
|
||||
| portainer | portainer | portainer/portainer-ce:latest | | monitoring, all, portainer | traefik | | unless-stopped |
|
||||
| prometheus | prometheus | prom/prometheus:latest | | monitoring, all, prometheus | monitor, traefik | | unless-stopped |
|
||||
| searxng-webapp | searxng-webapp | searxng/searxng | | apps, all, searxng | traefik | | always |
|
||||
| shift-recorder-web | shift-recorder | | /home/nixos/docker/apps/shift-recorder | apps, all, shift-recorder | traefik | | unless-stopped |
|
||||
| stockfill | stockfill | | /home/nixos/docker/apps/stockfill | apps, all, stockfill | traefik | | unless-stopped |
|
||||
| telegraf | telegraf | telegraf:latest | | monitoring, all, telegraf, prometheus | monitor | | unless-stopped |
|
||||
| traefik | traefik | traefik:3 | /home/nixos/docker/core | core, all, traefik | traefik | {'mode': 'ingress', 'target': 80, 'published': '80', 'protocol': 'tcp'}, {'mode': 'ingress', 'target': 443, 'published': '443', 'protocol': 'tcp'} | always |
|
||||
|
||||
## Networks
|
||||
|
||||
| Network | Driver | External |
|
||||
|---|---|---|
|
||||
| gramps | | False |
|
||||
| monitor | | False |
|
||||
| nextcloud | | False |
|
||||
| passbolt | | False |
|
||||
| traefik | bridge | False |
|
||||
|
||||
## Volumes
|
||||
|
||||
| Volume | External |
|
||||
|---|---|
|
||||
@@ -0,0 +1,65 @@
|
||||
# DNS Inventory (Dynu + Traefik)
|
||||
|
||||
> This integration is intentionally read-only. No Dynu mutations are permitted in this repo at this stage.
|
||||
|
||||
- Base domain: `lan.ddnsgeek.com`
|
||||
- Dynu fetched at: `2026-04-21T04:18:38+00:00`
|
||||
- Inventory generated at: `2026-04-21T04:18:39+00:00`
|
||||
|
||||
## Summary
|
||||
|
||||
- Traefik hostnames discovered: **17**
|
||||
- Dynu hostnames discovered: **20**
|
||||
- Mapped hostnames: **17**
|
||||
- DNS-only hostnames: **3**
|
||||
- Traefik-only hostnames: **0**
|
||||
- Ambiguous hostnames: **0**
|
||||
|
||||
## Validation
|
||||
|
||||
- Validation ok: **false**
|
||||
- Allowed unmapped hostnames: `edge.lan.ddnsgeek.com`
|
||||
- Unexpected unmapped hostnames: **1**
|
||||
- Duplicate hostnames: **1**
|
||||
- Ambiguous hostnames: **0**
|
||||
|
||||
### Allowed unmapped hostnames
|
||||
|
||||
- `edge.lan.ddnsgeek.com`
|
||||
|
||||
### Unexpected unmapped hostnames
|
||||
|
||||
- `kuma.lan.ddnsgeek.com`
|
||||
|
||||
### Duplicate hostnames
|
||||
|
||||
- `mtls-bridge.lan.ddnsgeek.com`
|
||||
|
||||
### Ambiguous hostnames
|
||||
|
||||
_None._
|
||||
|
||||
## Correlation
|
||||
|
||||
| Hostname | Status | Reasons | Service(s) | Route metadata | DNS records |
|
||||
|---|---|---|---|---|---|
|
||||
| `auth.lan.ddnsgeek.com` | `mapped` | `mapped` | core/authelia | authelia [tls=true, mtls=false, authelia=false, tls_options=-, middlewares=-] | A: |
|
||||
| `edge.lan.ddnsgeek.com` | `allowed_unmapped` | `allowed_unmapped, dns_only` | - | - | A: |
|
||||
| `familytree.lan.ddnsgeek.com` | `mapped` | `mapped` | apps/grampsweb | gramps [tls=true, mtls=false, authelia=false, tls_options=-, middlewares=-] | A: |
|
||||
| `gitea.lan.ddnsgeek.com` | `mapped` | `mapped` | apps/gitea | gitea [tls=true, mtls=false, authelia=false, tls_options=-, middlewares=-] | A: |
|
||||
| `gotify.lan.ddnsgeek.com` | `mapped` | `mapped` | monitoring/gotify | gotify [tls=true, mtls=true, authelia=false, tls_options=mtls-private-admin@file, middlewares=-] | A: |
|
||||
| `grafana.lan.ddnsgeek.com` | `mapped` | `mapped` | monitoring/grafana | grafana [tls=true, mtls=true, authelia=false, tls_options=mtls-private-admin@file, middlewares=-] | A: |
|
||||
| `influxdb.lan.ddnsgeek.com` | `mapped` | `mapped` | monitoring/influxdb | influxdb [tls=true, mtls=true, authelia=true, tls_options=mtls-private-admin@file, middlewares=authelia] | A: |
|
||||
| `kuma.lan.ddnsgeek.com` | `unexpected_unmapped` | `unexpected_unmapped, dns_only` | - | - | A:120.155.63.223 |
|
||||
| `lan.ddnsgeek.com` | `dns_only` | `dns_only` | - | - | SOA: |
|
||||
| `monitor-kuma.lan.ddnsgeek.com` | `mapped` | `mapped` | monitoring/monitor-kuma | monitor [tls=true, mtls=true, authelia=false, tls_options=mtls-private-admin@file, middlewares=-] | A: |
|
||||
| `mtls-bridge.lan.ddnsgeek.com` | `mapped` | `mapped` | monitoring/mtls-bridge | mtls-bridge [tls=true, mtls=true, authelia=false, tls_options=-, middlewares=mtls-bridge-auth,mtls-bridge-cors]<br>mtls-bridge-preflight [tls=true, mtls=true, authelia=false, tls_options=-, middlewares=mtls-bridge-cors] | A: |
|
||||
| `nextcloud.lan.ddnsgeek.com` | `mapped` | `mapped` | apps/nextcloud-webapp | nextcloud [tls=true, mtls=false, authelia=false, tls_options=-, middlewares=nextcloud-dav,nextcloud-webfinger] | A: |
|
||||
| `node-red.lan.ddnsgeek.com` | `mapped` | `mapped` | monitoring/node-red | node-red [tls=true, mtls=true, authelia=true, tls_options=mtls-private-admin@file, middlewares=authelia] | A: |
|
||||
| `passbolt.lan.ddnsgeek.com` | `mapped` | `mapped` | apps/passbolt-webapp | passbolt [tls=true, mtls=false, authelia=false, tls_options=-, middlewares=-] | A: |
|
||||
| `portainer.lan.ddnsgeek.com` | `mapped` | `mapped` | monitoring/portainer | portainer [tls=true, mtls=true, authelia=false, tls_options=mtls-private-admin@file, middlewares=-] | A: |
|
||||
| `prometheus.lan.ddnsgeek.com` | `mapped` | `mapped` | monitoring/prometheus | prometheus [tls=true, mtls=true, authelia=true, tls_options=mtls-private-admin@file, middlewares=authelia] | A: |
|
||||
| `searxng.lan.ddnsgeek.com` | `mapped` | `mapped` | apps/searxng-webapp | searxng [tls=true, mtls=false, authelia=false, tls_options=-, middlewares=-] | A: |
|
||||
| `shifts.lan.ddnsgeek.com` | `mapped` | `mapped` | apps/shift-recorder-web | shifts [tls=true, mtls=false, authelia=false, tls_options=-, middlewares=-] | A: |
|
||||
| `stockfill.lan.ddnsgeek.com` | `mapped` | `mapped` | apps/stockfill | stockfill [tls=true, mtls=false, authelia=false, tls_options=-, middlewares=-] | A: |
|
||||
| `traefik.lan.ddnsgeek.com` | `mapped` | `mapped` | core/traefik | traefik [tls=true, mtls=true, authelia=true, tls_options=mtls-private-admin@file, middlewares=authelia] | A: |
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
||||
# Host Topology
|
||||
|
||||
> Generated by `scripts/docs/generate_host_topology.py` on 2026-05-12T18:32:09+00:00.
|
||||
|
||||
## Topology Diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
phys_pve["pve\nphysical"]
|
||||
phys_raspberrypi["raspberrypi\nphysical"]
|
||||
virt_docker["docker\nvirtual"]
|
||||
phys_pve --> virt_docker
|
||||
virt_nix_cache["nix-cache\nvirtual"]
|
||||
phys_pve --> virt_nix_cache
|
||||
virt_pbs["pbs\nvirtual"]
|
||||
phys_pve --> virt_pbs
|
||||
virt_pihole["pihole\nvirtual"]
|
||||
phys_pve --> virt_pihole
|
||||
virt_server_nixos["server-nixos\nvirtual"]
|
||||
phys_pve --> virt_server_nixos
|
||||
```
|
||||
|
||||
## Physical Hosts
|
||||
|
||||
| Name | Type | Role | Management | OS | Hypervisor | Location | Notes |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| pve | physical | proxmox | pve.sweet.home | debian | proxmox | home | Primary Proxmox VE host |
|
||||
| raspberrypi | physical | edge | raspberrypi.tail13f623.ts.net | debian | | riverglades | Raspberry Pi host |
|
||||
|
||||
## Virtual Hosts
|
||||
|
||||
| Name | Type | Role | Parent/Node | Management | OS | Notes |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| docker | virtual | docker-host | pve | | linux | Primary Docker VM |
|
||||
| nix-cache | virtual | cache | pve | | linux | Nix binary cache VM |
|
||||
| pbs | virtual | backup | pve | | linux | Proxmox Backup Server VM |
|
||||
| pihole | virtual | dns | pve | | linux | DNS filtering VM |
|
||||
| server-nixos | virtual | nixos-server | pve | | nixos | General-purpose NixOS VM |
|
||||
@@ -0,0 +1,11 @@
|
||||
# Generated Documentation
|
||||
|
||||
This directory contains documentation generated automatically from repository configuration.
|
||||
|
||||
## Files
|
||||
|
||||
- [Compose file list](compose-files.txt)
|
||||
- [Resolved Docker Compose config](docker-compose.resolved.yml)
|
||||
- [Compose inventory](compose-inventory.md)
|
||||
- [Traefik routes](traefik-routes.md)
|
||||
- [Docker Compose diagram](../diagrams/docker-compose.svg)
|
||||
@@ -0,0 +1,23 @@
|
||||
# Traefik Routes
|
||||
|
||||
| Service | Router | Rule | Entrypoints | TLS | Middlewares | Target Port |
|
||||
|---|---|---|---|---|---|---|
|
||||
| authelia | authelia | Host(`auth.lan.ddnsgeek.com`) | websecure | true | | |
|
||||
| error-pages | error-pages-router | HostRegexp(`{host:.+}`) | web | | error-pages-middleware | |
|
||||
| gitea | gitea | Host(`gitea.lan.ddnsgeek.com`) | websecure | true | | 3000 |
|
||||
| gotify | gotify | Host(`gotify.lan.ddnsgeek.com`) | websecure | | | 80 |
|
||||
| grafana | grafana | Host(`grafana.lan.ddnsgeek.com`) | websecure | | | 3000 |
|
||||
| grampsweb | gramps | Host(`familytree.lan.ddnsgeek.com`) | websecure | | | 5000 |
|
||||
| influxdb | influxdb | Host(`influxdb.lan.ddnsgeek.com`) | websecure | | authelia | 8086 |
|
||||
| monitor-kuma | monitor | Host(`monitor-kuma.lan.ddnsgeek.com`) | websecure | true | | 3001 |
|
||||
| mtls-bridge | mtls-bridge | Host(`mtls-bridge.lan.ddnsgeek.com`) | websecure | | mtls-bridge-auth,mtls-bridge-cors | 8080 |
|
||||
| mtls-bridge | mtls-bridge-preflight | Host(`mtls-bridge.lan.ddnsgeek.com`) && Method(`OPTIONS`) | websecure | | mtls-bridge-cors | |
|
||||
| nextcloud-webapp | nextcloud | Host(`nextcloud.lan.ddnsgeek.com`) | websecure | | nextcloud-dav, nextcloud-webfinger | |
|
||||
| node-red | node-red | Host(`node-red.lan.ddnsgeek.com`) | websecure | | authelia | 1880 |
|
||||
| passbolt-webapp | passbolt | Host(`passbolt.lan.ddnsgeek.com`) | websecure | | | |
|
||||
| portainer | portainer | Host(`portainer.lan.ddnsgeek.com`) | websecure | true | | 9000 |
|
||||
| prometheus | prometheus | Host(`prometheus.lan.ddnsgeek.com`) | websecure | | authelia | 9090 |
|
||||
| searxng-webapp | searxng | Host(`searxng.lan.ddnsgeek.com`) | websecure | | | 8080 |
|
||||
| shift-recorder-web | shifts | Host(`shifts.lan.ddnsgeek.com`) | websecure | true | | 80 |
|
||||
| stockfill | stockfill | Host(`stockfill.lan.ddnsgeek.com`) | websecure | true | | 80 |
|
||||
| traefik | traefik | Host(`traefik.lan.ddnsgeek.com`) | websecure | | authelia | |
|
||||
@@ -0,0 +1,29 @@
|
||||
# Infrastructure Documentation
|
||||
|
||||
This documentation describes the Docker-based infrastructure, reverse proxy configuration, monitoring stack, automation services, and generated inventory for this environment.
|
||||
|
||||
Some sections are manually written. Other sections are generated automatically by GitHub Actions from the repository configuration.
|
||||
|
||||
## Sections
|
||||
|
||||
- [Docker Environment](docker.md)
|
||||
- [Networking and Reverse Proxy](networking.md)
|
||||
- [Monitoring and Alerting](monitoring.md)
|
||||
- [Automation](automation.md)
|
||||
- [Operations](operations.md)
|
||||
- [Public Showcase](showcase.md)
|
||||
|
||||
## Generated Documentation
|
||||
|
||||
- [Compose Inventory](generated/compose-inventory.md)
|
||||
- [Traefik Routes](generated/traefik-routes.md)
|
||||
- [Resolved Compose Config](generated/docker-compose.resolved.yml)
|
||||
- [Docker Compose Diagram](diagrams/docker-compose.svg)
|
||||
|
||||
## Public-safe Output
|
||||
|
||||
Sanitized documentation intended for public sharing is generated under:
|
||||
|
||||
```text
|
||||
docs/public/
|
||||
```
|
||||
@@ -25,14 +25,37 @@ This is currently the most structured host/VM inventory in the repo.
|
||||
|
||||
These resources should match existing running containers, not redefine runtime composition strategy.
|
||||
|
||||
### 3) Compose runtime definitions
|
||||
|
||||
### 3) Ansible bootstrap layer
|
||||
|
||||
`infrastructure/ansible/` provides an emerging inventory/configuration scaffold for hosts and devices.
|
||||
|
||||
Current scope is intentionally limited to structure, variables scaffolding, and safe validation workflows.
|
||||
|
||||
### 4) Compose runtime definitions
|
||||
|
||||
Compose files define intended service runtime composition, networking, labels, and integration.
|
||||
|
||||
### 4) Architecture docs
|
||||
### 5) Architecture docs
|
||||
|
||||
`docs/architecture.md` provides a human-readable topology view based on repository configuration and observed runtime signals.
|
||||
|
||||
### 6) Dynu DNS read-only inventory
|
||||
|
||||
`scripts/dynu/` and `docs/dynu-dns-inventory.md` provide a strictly read-only DNS inventory workflow:
|
||||
|
||||
- fetch Dynu DNS data with GET-only API usage,
|
||||
- correlate Dynu hostnames with Traefik `Host(...)` labels in Compose sources,
|
||||
- generate local JSON and markdown artifacts for documentation pipelines.
|
||||
|
||||
Dynu write operations are intentionally blocked in this repository stage.
|
||||
|
||||
### 7) Terraform Dynu DNS layer
|
||||
|
||||
`infrastructure/terraform/dynu/` is the brownfield Terraform DNS mirror/reconciliation root for Dynu domain/record inventory outputs.
|
||||
|
||||
At this stage it is primarily documentation-oriented catalog output, ready for one-object-at-a-time imports.
|
||||
|
||||
## Output shaping expectations
|
||||
|
||||
When adding Terraform outputs for documentation/tooling:
|
||||
@@ -44,8 +67,10 @@ When adding Terraform outputs for documentation/tooling:
|
||||
|
||||
## Limitations today
|
||||
|
||||
- Generated host topology document: `docs/generated/host-topology.md` (via `scripts/docs/build_host_topology.sh`).
|
||||
- No full generated inventory document pipeline is present yet.
|
||||
- Some Terraform files still include generated boilerplate comments requiring ongoing cleanup.
|
||||
- Ansible/NixOS operational layers are not yet implemented in a way that provides authoritative inventory in this repo.
|
||||
- Ansible is currently a bootstrap inventory/configuration layer and is not authoritative for full operations yet.
|
||||
- NixOS operational management is not yet implemented as an Ansible authority in this repo.
|
||||
|
||||
These limitations are expected for the current adoption stage.
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# Monitoring and Alerting
|
||||
|
||||
Monitoring documentation is generated from static rule files committed in this repository.
|
||||
|
||||
- Prometheus rule files are parsed statically.
|
||||
- Live Prometheus APIs are not queried.
|
||||
- Future improvements can include Grafana dashboard and alert contact-point summaries.
|
||||
|
||||
Generated asset:
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# Networking and Reverse Proxy
|
||||
|
||||
Networking and reverse-proxy documentation is generated from Docker Compose metadata.
|
||||
|
||||
- Traefik labels are parsed statically from Compose definitions.
|
||||
- Service-to-network relationships are represented in generated diagrams.
|
||||
- Sensitive internal values are redacted in public-facing output.
|
||||
|
||||
Related generated assets:
|
||||
|
||||
- [Traefik Routes](generated/traefik-routes.md)
|
||||
- [Docker Compose Diagram](diagrams/docker-compose.svg)
|
||||
@@ -0,0 +1,30 @@
|
||||
# Operations
|
||||
|
||||
## Local docs generation
|
||||
|
||||
```bash
|
||||
chmod +x scripts/docs/*.sh
|
||||
scripts/docs/generate-all.sh
|
||||
```
|
||||
|
||||
## Inspect generated changes
|
||||
|
||||
```bash
|
||||
git status -- docs/generated docs/diagrams docs/public
|
||||
```
|
||||
|
||||
## GitHub Actions artifacts
|
||||
|
||||
Use the **Generate documentation** workflow run and download the `generated-documentation` artifact from the run summary.
|
||||
|
||||
## Commit behavior
|
||||
|
||||
- Pull requests generate docs and upload artifacts only.
|
||||
- Pushes to `main` generate docs, upload artifacts, and commit generated docs when changes exist.
|
||||
- Manual workflow runs can commit generated docs when enabled through workflow input.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Confirm required tooling (`python3`, `jq`, `graphviz`, `docker compose`) is available.
|
||||
- Re-run generation after documentation script changes.
|
||||
- Review `docs/public` output for redaction coverage before sharing.
|
||||
@@ -0,0 +1,59 @@
|
||||
# Docker Compose Inventory
|
||||
|
||||
Source fingerprint: `232be78ef441`
|
||||
|
||||
## Summary
|
||||
|
||||
| Item | Count |
|
||||
|---|---:|
|
||||
| Services | 28 |
|
||||
| Networks | 5 |
|
||||
| Volumes | 0 |
|
||||
|
||||
## Services
|
||||
|
||||
| Service | Container | Image | Build | Profiles | Networks | Ports | Restart |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| authelia | authelia | authelia/authelia | /home/nixos/docker/core/authelia | core, all, authelia, traefik | traefik | | always |
|
||||
| crowdsec | crowdsec | | /home/nixos/docker/core/crowdsec | core, all, crowdsec, traefik | traefik | | always |
|
||||
| docker-socket-proxy | docker-socket-proxy | tecnativa/docker-socket-proxy:latest | | monitoring, all, docker-socket-proxy, core, traefik, prometheus | monitor, traefik | | unless-stopped |
|
||||
| docker-update-exporter | docker-update-exporter | | /home/nixos/docker/monitoring/docker-exporter | monitoring, all, docker-exporter, prometheus | monitor | | unless-stopped |
|
||||
| error-pages | error-pages | tarampampam/error-pages:3 | | core, all, error-pages, traefik | traefik | | always |
|
||||
| gitea | gitea | gitea/gitea:latest | | apps, all, gitea | traefik | | always |
|
||||
| gitea-runner | gitea-runner | gitea/act_runner:latest | | apps, all, gitea, ci | traefik | | always |
|
||||
| gotify | gotify | gotify/server:latest | | monitoring, all, gotify | traefik | | always |
|
||||
| grafana | grafana | grafana/grafana:latest | | monitoring, all, grafana | monitor, traefik | | unless-stopped |
|
||||
| gramps-redis | gramps-redis | valkey/valkey:8-alpine | | apps, all, gramps | gramps | | always |
|
||||
| grampsweb | gramps-web | ghcr.io/gramps-project/grampsweb:latest | | apps, all, gramps | gramps, traefik | | always |
|
||||
| grampsweb_celery | gramps-web-celery | ghcr.io/gramps-project/grampsweb:latest | | apps, all, gramps | gramps | | always |
|
||||
| influxdb | influxdb | influxdb:2.7 | | monitoring, all, influxdb, prometheus | monitor, traefik | | unless-stopped |
|
||||
| monitor-kuma | monitor-kuma | louislam/uptime-kuma:2.1.1 | | monitoring, all, uptime-kuma | monitor, traefik | | always |
|
||||
| mtls-bridge | mtls-bridge | | /home/nixos/docker/monitoring/mtls-bridge | monitoring, all, mtls-bridge | monitor, traefik | | unless-stopped |
|
||||
| nextcloud-db | nextcloud-db | mariadb:11.4 | | apps, all, nextcloud | nextcloud | | always |
|
||||
| nextcloud-redis | nextcloud-redis | redis | | apps, all, nextcloud | nextcloud | | always |
|
||||
| nextcloud-webapp | nextcloud-webapp | | /home/nixos/docker/apps/nextcloud | apps, all, nextcloud | nextcloud, traefik | | always |
|
||||
| node-exporter | node-exporter | prom/node-exporter:latest | | monitoring, all, node-exporter, prometheus | monitor | | unless-stopped |
|
||||
| node-red | node-red | | /home/nixos/docker/monitoring/node-red | monitoring, all, node-red | monitor, traefik | | unless-stopped |
|
||||
| passbolt-db | passbolt-db | mariadb:12 | | apps, all, passbolt | passbolt | | always |
|
||||
| passbolt-webapp | passbolt-webapp | passbolt/passbolt:latest-ce | | apps, all, passbolt | passbolt, traefik | | always |
|
||||
| pihole-exporter | pihole-exporter | ekofr/pihole-exporter:latest | | monitoring, all, pihole-exporter, prometheus | monitor | {'mode': 'ingress', 'target': 9617, 'published': '9617', 'protocol': 'tcp'} | unless-stopped |
|
||||
| portainer | portainer | portainer/portainer-ce:latest | | monitoring, all, portainer | traefik | | unless-stopped |
|
||||
| prometheus | prometheus | prom/prometheus:latest | | monitoring, all, prometheus | monitor, traefik | | unless-stopped |
|
||||
| searxng-webapp | searxng-webapp | searxng/searxng | | apps, all, searxng | traefik | | always |
|
||||
| telegraf | telegraf | telegraf:latest | | monitoring, all, telegraf, prometheus | monitor | | unless-stopped |
|
||||
| traefik | traefik | traefik:3 | /home/nixos/docker/core | core, all, traefik | traefik | {'mode': 'ingress', 'target': 80, 'published': '80', 'protocol': 'tcp'}, {'mode': 'ingress', 'target': 443, 'published': '443', 'protocol': 'tcp'} | always |
|
||||
|
||||
## Networks
|
||||
|
||||
| Network | Driver | External |
|
||||
|---|---|---|
|
||||
| gramps | | False |
|
||||
| monitor | | False |
|
||||
| nextcloud | | False |
|
||||
| passbolt | | False |
|
||||
| traefik | bridge | False |
|
||||
|
||||
## Volumes
|
||||
|
||||
| Volume | External |
|
||||
|---|---|
|
||||
@@ -0,0 +1,19 @@
|
||||
# Infrastructure diagrams
|
||||
|
||||
## Physical / virtual topology
|
||||
|
||||
This view groups containers by inferred host and service role (edge/proxy/auth, monitoring, automation, apps, and supporting storage/services).
|
||||
|
||||
<div class="diagram-wrap">
|
||||
<img src="physical-topology.svg" alt="Physical topology">
|
||||
</div>
|
||||
|
||||
## Docker, Traefik and Dynu routing
|
||||
|
||||
This view shows sanitised public DNS names flowing to Traefik, then to exposed Docker services, with backend Docker network membership shown as secondary context.
|
||||
|
||||
_Diagrams are generated from Compose data and Traefik labels._
|
||||
|
||||
<div class="diagram-wrap">
|
||||
<img src="docker-traefik-dynu.svg" alt="Docker Traefik Dynu">
|
||||
</div>
|
||||
@@ -0,0 +1,439 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 2.43.0 (0)
|
||||
-->
|
||||
<!-- Title: Compose Pages: 1 -->
|
||||
<svg width="334pt" height="1502pt"
|
||||
viewBox="0.00 0.00 334.49 1502.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1498)">
|
||||
<title>Compose</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-1498 330.49,-1498 330.49,4 -4,4"/>
|
||||
<!-- svc:authelia -->
|
||||
<g id="node1" class="node">
|
||||
<title>svc:authelia</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="126,-738 54,-738 54,-702 126,-702 126,-738"/>
|
||||
<text text-anchor="middle" x="90" y="-716.3" font-family="Helvetica,sans-Serif" font-size="14.00">authelia</text>
|
||||
</g>
|
||||
<!-- net:traefik -->
|
||||
<g id="node33" class="node">
|
||||
<title>net:traefik</title>
|
||||
<ellipse fill="#f4f4f4" stroke="black" cx="271.25" cy="-774" rx="40.09" ry="18"/>
|
||||
<text text-anchor="middle" x="271.25" y="-770.3" font-family="Helvetica,sans-Serif" font-size="14.00">traefik</text>
|
||||
</g>
|
||||
<!-- svc:authelia->net:traefik -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>svc:authelia->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M126.41,-730.67C155.5,-739.43 196.8,-751.87 227.69,-761.18"/>
|
||||
<polygon fill="black" stroke="black" points="226.74,-764.55 237.32,-764.08 228.76,-757.85 226.74,-764.55"/>
|
||||
</g>
|
||||
<!-- svc:crowdsec -->
|
||||
<g id="node2" class="node">
|
||||
<title>svc:crowdsec</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="130.5,-684 49.5,-684 49.5,-648 130.5,-648 130.5,-684"/>
|
||||
<text text-anchor="middle" x="90" y="-662.3" font-family="Helvetica,sans-Serif" font-size="14.00">crowdsec</text>
|
||||
</g>
|
||||
<!-- svc:crowdsec->net:traefik -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>svc:crowdsec->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M130.61,-674.06C146.59,-678.3 164.81,-684.44 180,-693 206.25,-707.78 231.35,-731.35 248.38,-749.26"/>
|
||||
<polygon fill="black" stroke="black" points="246.24,-752.1 255.62,-757.03 251.37,-747.33 246.24,-752.1"/>
|
||||
</g>
|
||||
<!-- svc:docker-socket-proxy -->
|
||||
<g id="node3" class="node">
|
||||
<title>svc:docker-socket-proxy</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="167.5,-954 12.5,-954 12.5,-918 167.5,-918 167.5,-954"/>
|
||||
<text text-anchor="middle" x="90" y="-932.3" font-family="Helvetica,sans-Serif" font-size="14.00">docker-socket-proxy</text>
|
||||
</g>
|
||||
<!-- net:monitor -->
|
||||
<g id="node30" class="node">
|
||||
<title>net:monitor</title>
|
||||
<ellipse fill="#f4f4f4" stroke="black" cx="271.25" cy="-1206" rx="46.29" ry="18"/>
|
||||
<text text-anchor="middle" x="271.25" y="-1202.3" font-family="Helvetica,sans-Serif" font-size="14.00">monitor</text>
|
||||
</g>
|
||||
<!-- svc:docker-socket-proxy->net:monitor -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>svc:docker-socket-proxy->net:monitor</title>
|
||||
<path fill="none" stroke="black" d="M167.53,-953.71C172.02,-956.38 176.24,-959.46 180,-963 242.3,-1021.6 261.83,-1127.12 267.77,-1177.6"/>
|
||||
<polygon fill="black" stroke="black" points="264.31,-1178.21 268.86,-1187.78 271.27,-1177.46 264.31,-1178.21"/>
|
||||
</g>
|
||||
<!-- svc:docker-socket-proxy->net:traefik -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>svc:docker-socket-proxy->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M165.31,-917.9C170.5,-915.32 175.46,-912.37 180,-909 218.01,-880.82 244.97,-831.56 259.03,-800.98"/>
|
||||
<polygon fill="black" stroke="black" points="262.27,-802.31 263.14,-791.76 255.87,-799.46 262.27,-802.31"/>
|
||||
</g>
|
||||
<!-- svc:docker-update-exporter -->
|
||||
<g id="node4" class="node">
|
||||
<title>svc:docker-update-exporter</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="180,-1494 0,-1494 0,-1458 180,-1458 180,-1494"/>
|
||||
<text text-anchor="middle" x="90" y="-1472.3" font-family="Helvetica,sans-Serif" font-size="14.00">docker-update-exporter</text>
|
||||
</g>
|
||||
<!-- svc:docker-update-exporter->net:monitor -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>svc:docker-update-exporter->net:monitor</title>
|
||||
<path fill="none" stroke="black" d="M168.37,-1457.79C172.55,-1455.23 176.47,-1452.32 180,-1449 242.3,-1390.4 261.83,-1284.88 267.77,-1234.4"/>
|
||||
<polygon fill="black" stroke="black" points="271.27,-1234.54 268.86,-1224.22 264.31,-1233.79 271.27,-1234.54"/>
|
||||
</g>
|
||||
<!-- svc:error-pages -->
|
||||
<g id="node5" class="node">
|
||||
<title>svc:error-pages</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="137.5,-630 42.5,-630 42.5,-594 137.5,-594 137.5,-630"/>
|
||||
<text text-anchor="middle" x="90" y="-608.3" font-family="Helvetica,sans-Serif" font-size="14.00">error-pages</text>
|
||||
</g>
|
||||
<!-- svc:error-pages->net:traefik -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>svc:error-pages->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M137.71,-619.76C152.25,-623.79 167.68,-629.86 180,-639 218.01,-667.18 244.97,-716.44 259.03,-747.02"/>
|
||||
<polygon fill="black" stroke="black" points="255.87,-748.54 263.14,-756.24 262.27,-745.69 255.87,-748.54"/>
|
||||
</g>
|
||||
<!-- svc:gitea -->
|
||||
<g id="node6" class="node">
|
||||
<title>svc:gitea</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="117,-576 63,-576 63,-540 117,-540 117,-576"/>
|
||||
<text text-anchor="middle" x="90" y="-554.3" font-family="Helvetica,sans-Serif" font-size="14.00">gitea</text>
|
||||
</g>
|
||||
<!-- svc:gitea->net:traefik -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>svc:gitea->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M117.07,-560.28C136.41,-563.2 162.37,-569.85 180,-585 229.98,-627.95 254.42,-704.8 264.44,-746.04"/>
|
||||
<polygon fill="black" stroke="black" points="261.07,-747 266.73,-755.95 267.88,-745.42 261.07,-747"/>
|
||||
</g>
|
||||
<!-- svc:gitea-runner -->
|
||||
<g id="node7" class="node">
|
||||
<title>svc:gitea-runner</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="142,-522 38,-522 38,-486 142,-486 142,-522"/>
|
||||
<text text-anchor="middle" x="90" y="-500.3" font-family="Helvetica,sans-Serif" font-size="14.00">gitea-runner</text>
|
||||
</g>
|
||||
<!-- svc:gitea-runner->net:traefik -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>svc:gitea-runner->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M142.31,-511.04C155.9,-515.03 169.65,-521.26 180,-531 242.3,-589.6 261.83,-695.12 267.77,-745.6"/>
|
||||
<polygon fill="black" stroke="black" points="264.31,-746.21 268.86,-755.78 271.27,-745.46 264.31,-746.21"/>
|
||||
</g>
|
||||
<!-- svc:gotify -->
|
||||
<g id="node8" class="node">
|
||||
<title>svc:gotify</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="118,-468 62,-468 62,-432 118,-432 118,-468"/>
|
||||
<text text-anchor="middle" x="90" y="-446.3" font-family="Helvetica,sans-Serif" font-size="14.00">gotify</text>
|
||||
</g>
|
||||
<!-- svc:gotify->net:traefik -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>svc:gotify->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M118.17,-451.6C137.81,-454.16 163.67,-460.68 180,-477 254.99,-551.96 268.02,-687.13 270.03,-745.69"/>
|
||||
<polygon fill="black" stroke="black" points="266.53,-745.79 270.29,-755.69 273.53,-745.61 266.53,-745.79"/>
|
||||
</g>
|
||||
<!-- svc:grafana -->
|
||||
<g id="node9" class="node">
|
||||
<title>svc:grafana</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="125.5,-1278 54.5,-1278 54.5,-1242 125.5,-1242 125.5,-1278"/>
|
||||
<text text-anchor="middle" x="90" y="-1256.3" font-family="Helvetica,sans-Serif" font-size="14.00">grafana</text>
|
||||
</g>
|
||||
<!-- svc:grafana->net:monitor -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>svc:grafana->net:monitor</title>
|
||||
<path fill="none" stroke="black" d="M125.56,-1249.59C153.74,-1241.1 193.78,-1229.04 224.6,-1219.75"/>
|
||||
<polygon fill="black" stroke="black" points="225.69,-1223.08 234.26,-1216.84 223.67,-1216.38 225.69,-1223.08"/>
|
||||
</g>
|
||||
<!-- svc:grafana->net:traefik -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>svc:grafana->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M125.54,-1257.93C144.23,-1254.97 166.25,-1248.21 180,-1233 238.17,-1168.67 262.1,-892.17 268.43,-802.32"/>
|
||||
<polygon fill="black" stroke="black" points="271.93,-802.3 269.13,-792.08 264.95,-801.82 271.93,-802.3"/>
|
||||
</g>
|
||||
<!-- svc:gramps-redis -->
|
||||
<g id="node10" class="node">
|
||||
<title>svc:gramps-redis</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="144.5,-360 35.5,-360 35.5,-324 144.5,-324 144.5,-360"/>
|
||||
<text text-anchor="middle" x="90" y="-338.3" font-family="Helvetica,sans-Serif" font-size="14.00">gramps-redis</text>
|
||||
</g>
|
||||
<!-- net:gramps -->
|
||||
<g id="node29" class="node">
|
||||
<title>net:gramps</title>
|
||||
<ellipse fill="#f4f4f4" stroke="black" cx="271.25" cy="-342" rx="45.49" ry="18"/>
|
||||
<text text-anchor="middle" x="271.25" y="-338.3" font-family="Helvetica,sans-Serif" font-size="14.00">gramps</text>
|
||||
</g>
|
||||
<!-- svc:gramps-redis->net:gramps -->
|
||||
<g id="edge12" class="edge">
|
||||
<title>svc:gramps-redis->net:gramps</title>
|
||||
<path fill="none" stroke="black" d="M144.78,-342C167.14,-342 193.05,-342 215.51,-342"/>
|
||||
<polygon fill="black" stroke="black" points="215.56,-345.5 225.56,-342 215.56,-338.5 215.56,-345.5"/>
|
||||
</g>
|
||||
<!-- svc:grampsweb -->
|
||||
<g id="node11" class="node">
|
||||
<title>svc:grampsweb</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="139,-414 41,-414 41,-378 139,-378 139,-414"/>
|
||||
<text text-anchor="middle" x="90" y="-392.3" font-family="Helvetica,sans-Serif" font-size="14.00">grampsweb</text>
|
||||
</g>
|
||||
<!-- svc:grampsweb->net:gramps -->
|
||||
<g id="edge13" class="edge">
|
||||
<title>svc:grampsweb->net:gramps</title>
|
||||
<path fill="none" stroke="black" d="M139.03,-381.53C165.7,-373.5 198.68,-363.56 224.9,-355.66"/>
|
||||
<polygon fill="black" stroke="black" points="226.03,-358.98 234.59,-352.74 224.01,-352.27 226.03,-358.98"/>
|
||||
</g>
|
||||
<!-- svc:grampsweb->net:traefik -->
|
||||
<g id="edge14" class="edge">
|
||||
<title>svc:grampsweb->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M139.27,-401.34C154.08,-405.21 169.28,-411.81 180,-423 225.12,-470.09 256.34,-671.08 266.59,-745.84"/>
|
||||
<polygon fill="black" stroke="black" points="263.14,-746.48 267.95,-755.92 270.08,-745.55 263.14,-746.48"/>
|
||||
</g>
|
||||
<!-- svc:grampsweb_celery -->
|
||||
<g id="node12" class="node">
|
||||
<title>svc:grampsweb_celery</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="163.5,-306 16.5,-306 16.5,-270 163.5,-270 163.5,-306"/>
|
||||
<text text-anchor="middle" x="90" y="-284.3" font-family="Helvetica,sans-Serif" font-size="14.00">grampsweb_celery</text>
|
||||
</g>
|
||||
<!-- svc:grampsweb_celery->net:gramps -->
|
||||
<g id="edge15" class="edge">
|
||||
<title>svc:grampsweb_celery->net:gramps</title>
|
||||
<path fill="none" stroke="black" d="M151.18,-306.13C175.28,-313.39 202.57,-321.61 224.94,-328.35"/>
|
||||
<polygon fill="black" stroke="black" points="223.94,-331.71 234.52,-331.24 225.96,-325 223.94,-331.71"/>
|
||||
</g>
|
||||
<!-- svc:influxdb -->
|
||||
<g id="node13" class="node">
|
||||
<title>svc:influxdb</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="127,-1224 53,-1224 53,-1188 127,-1188 127,-1224"/>
|
||||
<text text-anchor="middle" x="90" y="-1202.3" font-family="Helvetica,sans-Serif" font-size="14.00">influxdb</text>
|
||||
</g>
|
||||
<!-- svc:influxdb->net:monitor -->
|
||||
<g id="edge16" class="edge">
|
||||
<title>svc:influxdb->net:monitor</title>
|
||||
<path fill="none" stroke="black" d="M127.27,-1206C152.33,-1206 186.13,-1206 214.57,-1206"/>
|
||||
<polygon fill="black" stroke="black" points="214.79,-1209.5 224.79,-1206 214.79,-1202.5 214.79,-1209.5"/>
|
||||
</g>
|
||||
<!-- svc:influxdb->net:traefik -->
|
||||
<g id="edge17" class="edge">
|
||||
<title>svc:influxdb->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M127.17,-1203.5C145.42,-1200.36 166.5,-1193.56 180,-1179 231.57,-1123.4 259.36,-885.3 267.6,-802.51"/>
|
||||
<polygon fill="black" stroke="black" points="271.1,-802.62 268.59,-792.33 264.14,-801.94 271.1,-802.62"/>
|
||||
</g>
|
||||
<!-- svc:monitor-kuma -->
|
||||
<g id="node14" class="node">
|
||||
<title>svc:monitor-kuma</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="146.5,-1170 33.5,-1170 33.5,-1134 146.5,-1134 146.5,-1170"/>
|
||||
<text text-anchor="middle" x="90" y="-1148.3" font-family="Helvetica,sans-Serif" font-size="14.00">monitor-kuma</text>
|
||||
</g>
|
||||
<!-- svc:monitor-kuma->net:monitor -->
|
||||
<g id="edge18" class="edge">
|
||||
<title>svc:monitor-kuma->net:monitor</title>
|
||||
<path fill="none" stroke="black" d="M146.73,-1168.79C171.73,-1176.32 200.85,-1185.1 224.54,-1192.23"/>
|
||||
<polygon fill="black" stroke="black" points="223.75,-1195.65 234.33,-1195.18 225.77,-1188.94 223.75,-1195.65"/>
|
||||
</g>
|
||||
<!-- svc:monitor-kuma->net:traefik -->
|
||||
<g id="edge19" class="edge">
|
||||
<title>svc:monitor-kuma->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M146.69,-1144.49C159.02,-1140.46 171.06,-1134.33 180,-1125 225.12,-1077.91 256.34,-876.92 266.59,-802.16"/>
|
||||
<polygon fill="black" stroke="black" points="270.08,-802.45 267.95,-792.08 263.14,-801.52 270.08,-802.45"/>
|
||||
</g>
|
||||
<!-- svc:mtls-bridge -->
|
||||
<g id="node15" class="node">
|
||||
<title>svc:mtls-bridge</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="138.5,-1116 41.5,-1116 41.5,-1080 138.5,-1080 138.5,-1116"/>
|
||||
<text text-anchor="middle" x="90" y="-1094.3" font-family="Helvetica,sans-Serif" font-size="14.00">mtls-bridge</text>
|
||||
</g>
|
||||
<!-- svc:mtls-bridge->net:monitor -->
|
||||
<g id="edge20" class="edge">
|
||||
<title>svc:mtls-bridge->net:monitor</title>
|
||||
<path fill="none" stroke="black" d="M138.76,-1108.34C152.58,-1112.41 167.34,-1117.87 180,-1125 206.25,-1139.78 231.35,-1163.35 248.38,-1181.26"/>
|
||||
<polygon fill="black" stroke="black" points="246.24,-1184.1 255.62,-1189.03 251.37,-1179.33 246.24,-1184.1"/>
|
||||
</g>
|
||||
<!-- svc:mtls-bridge->net:traefik -->
|
||||
<g id="edge21" class="edge">
|
||||
<title>svc:mtls-bridge->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M138.53,-1092.51C153.46,-1088.63 168.92,-1082.07 180,-1071 254.99,-996.04 268.02,-860.87 270.03,-802.31"/>
|
||||
<polygon fill="black" stroke="black" points="273.53,-802.39 270.29,-792.31 266.53,-802.21 273.53,-802.39"/>
|
||||
</g>
|
||||
<!-- svc:nextcloud-db -->
|
||||
<g id="node16" class="node">
|
||||
<title>svc:nextcloud-db</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="144,-90 36,-90 36,-54 144,-54 144,-90"/>
|
||||
<text text-anchor="middle" x="90" y="-68.3" font-family="Helvetica,sans-Serif" font-size="14.00">nextcloud-db</text>
|
||||
</g>
|
||||
<!-- net:nextcloud -->
|
||||
<g id="node31" class="node">
|
||||
<title>net:nextcloud</title>
|
||||
<ellipse fill="#f4f4f4" stroke="black" cx="271.25" cy="-180" rx="55.49" ry="18"/>
|
||||
<text text-anchor="middle" x="271.25" y="-176.3" font-family="Helvetica,sans-Serif" font-size="14.00">nextcloud</text>
|
||||
</g>
|
||||
<!-- svc:nextcloud-db->net:nextcloud -->
|
||||
<g id="edge22" class="edge">
|
||||
<title>svc:nextcloud-db->net:nextcloud</title>
|
||||
<path fill="none" stroke="black" d="M144.33,-84.04C156.48,-87.9 169.03,-92.82 180,-99 206.09,-113.7 231.04,-137.06 248.07,-154.93"/>
|
||||
<polygon fill="black" stroke="black" points="245.93,-157.77 255.31,-162.7 251.06,-153 245.93,-157.77"/>
|
||||
</g>
|
||||
<!-- svc:nextcloud-redis -->
|
||||
<g id="node17" class="node">
|
||||
<title>svc:nextcloud-redis</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="152,-198 28,-198 28,-162 152,-162 152,-198"/>
|
||||
<text text-anchor="middle" x="90" y="-176.3" font-family="Helvetica,sans-Serif" font-size="14.00">nextcloud-redis</text>
|
||||
</g>
|
||||
<!-- svc:nextcloud-redis->net:nextcloud -->
|
||||
<g id="edge23" class="edge">
|
||||
<title>svc:nextcloud-redis->net:nextcloud</title>
|
||||
<path fill="none" stroke="black" d="M152.18,-180C169.48,-180 188.35,-180 205.83,-180"/>
|
||||
<polygon fill="black" stroke="black" points="205.94,-183.5 215.94,-180 205.94,-176.5 205.94,-183.5"/>
|
||||
</g>
|
||||
<!-- svc:nextcloud-webapp -->
|
||||
<g id="node18" class="node">
|
||||
<title>svc:nextcloud-webapp</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="162.5,-252 17.5,-252 17.5,-216 162.5,-216 162.5,-252"/>
|
||||
<text text-anchor="middle" x="90" y="-230.3" font-family="Helvetica,sans-Serif" font-size="14.00">nextcloud-webapp</text>
|
||||
</g>
|
||||
<!-- svc:nextcloud-webapp->net:nextcloud -->
|
||||
<g id="edge24" class="edge">
|
||||
<title>svc:nextcloud-webapp->net:nextcloud</title>
|
||||
<path fill="none" stroke="black" d="M151.18,-215.87C173.57,-209.12 198.72,-201.55 220.11,-195.1"/>
|
||||
<polygon fill="black" stroke="black" points="221.4,-198.37 229.97,-192.13 219.38,-191.67 221.4,-198.37"/>
|
||||
</g>
|
||||
<!-- svc:nextcloud-webapp->net:traefik -->
|
||||
<g id="edge25" class="edge">
|
||||
<title>svc:nextcloud-webapp->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M162.89,-247.71C169.31,-251.2 175.18,-255.56 180,-261 212.66,-297.85 255.07,-643.36 267,-745.62"/>
|
||||
<polygon fill="black" stroke="black" points="263.55,-746.27 268.18,-755.8 270.51,-745.47 263.55,-746.27"/>
|
||||
</g>
|
||||
<!-- svc:node-exporter -->
|
||||
<g id="node19" class="node">
|
||||
<title>svc:node-exporter</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="148,-1440 32,-1440 32,-1404 148,-1404 148,-1440"/>
|
||||
<text text-anchor="middle" x="90" y="-1418.3" font-family="Helvetica,sans-Serif" font-size="14.00">node-exporter</text>
|
||||
</g>
|
||||
<!-- svc:node-exporter->net:monitor -->
|
||||
<g id="edge26" class="edge">
|
||||
<title>svc:node-exporter->net:monitor</title>
|
||||
<path fill="none" stroke="black" d="M148.25,-1412.27C159.68,-1408.33 170.94,-1402.79 180,-1395 229.98,-1352.05 254.42,-1275.2 264.44,-1233.96"/>
|
||||
<polygon fill="black" stroke="black" points="267.88,-1234.58 266.73,-1224.05 261.07,-1233 267.88,-1234.58"/>
|
||||
</g>
|
||||
<!-- svc:node-red -->
|
||||
<g id="node20" class="node">
|
||||
<title>svc:node-red</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="129.5,-1062 50.5,-1062 50.5,-1026 129.5,-1026 129.5,-1062"/>
|
||||
<text text-anchor="middle" x="90" y="-1040.3" font-family="Helvetica,sans-Serif" font-size="14.00">node-red</text>
|
||||
</g>
|
||||
<!-- svc:node-red->net:monitor -->
|
||||
<g id="edge27" class="edge">
|
||||
<title>svc:node-red->net:monitor</title>
|
||||
<path fill="none" stroke="black" d="M129.57,-1049.69C146.29,-1053.6 165.34,-1060.13 180,-1071 218.01,-1099.18 244.97,-1148.44 259.03,-1179.02"/>
|
||||
<polygon fill="black" stroke="black" points="255.87,-1180.54 263.14,-1188.24 262.27,-1177.69 255.87,-1180.54"/>
|
||||
</g>
|
||||
<!-- svc:node-red->net:traefik -->
|
||||
<g id="edge28" class="edge">
|
||||
<title>svc:node-red->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M129.95,-1040.03C147.14,-1036.46 166.48,-1029.72 180,-1017 242.3,-958.4 261.83,-852.88 267.77,-802.4"/>
|
||||
<polygon fill="black" stroke="black" points="271.27,-802.54 268.86,-792.22 264.31,-801.79 271.27,-802.54"/>
|
||||
</g>
|
||||
<!-- svc:passbolt-db -->
|
||||
<g id="node21" class="node">
|
||||
<title>svc:passbolt-db</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="139,-36 41,-36 41,0 139,0 139,-36"/>
|
||||
<text text-anchor="middle" x="90" y="-14.3" font-family="Helvetica,sans-Serif" font-size="14.00">passbolt-db</text>
|
||||
</g>
|
||||
<!-- net:passbolt -->
|
||||
<g id="node32" class="node">
|
||||
<title>net:passbolt</title>
|
||||
<ellipse fill="#f4f4f4" stroke="black" cx="271.25" cy="-72" rx="48.99" ry="18"/>
|
||||
<text text-anchor="middle" x="271.25" y="-68.3" font-family="Helvetica,sans-Serif" font-size="14.00">passbolt</text>
|
||||
</g>
|
||||
<!-- svc:passbolt-db->net:passbolt -->
|
||||
<g id="edge29" class="edge">
|
||||
<title>svc:passbolt-db->net:passbolt</title>
|
||||
<path fill="none" stroke="black" d="M139.03,-32.47C165.15,-40.34 197.32,-50.03 223.27,-57.85"/>
|
||||
<polygon fill="black" stroke="black" points="222.3,-61.21 232.88,-60.74 224.32,-54.51 222.3,-61.21"/>
|
||||
</g>
|
||||
<!-- svc:passbolt-webapp -->
|
||||
<g id="node22" class="node">
|
||||
<title>svc:passbolt-webapp</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="157.5,-144 22.5,-144 22.5,-108 157.5,-108 157.5,-144"/>
|
||||
<text text-anchor="middle" x="90" y="-122.3" font-family="Helvetica,sans-Serif" font-size="14.00">passbolt-webapp</text>
|
||||
</g>
|
||||
<!-- svc:passbolt-webapp->net:passbolt -->
|
||||
<g id="edge30" class="edge">
|
||||
<title>svc:passbolt-webapp->net:passbolt</title>
|
||||
<path fill="none" stroke="black" d="M151.18,-107.87C174.64,-100.8 201.13,-92.82 223.16,-86.19"/>
|
||||
<polygon fill="black" stroke="black" points="224.37,-89.48 232.94,-83.24 222.35,-82.77 224.37,-89.48"/>
|
||||
</g>
|
||||
<!-- svc:passbolt-webapp->net:traefik -->
|
||||
<g id="edge31" class="edge">
|
||||
<title>svc:passbolt-webapp->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M157.85,-136.93C166.26,-140.81 174,-146.02 180,-153 189.88,-164.49 250.79,-625.33 266.53,-745.56"/>
|
||||
<polygon fill="black" stroke="black" points="263.1,-746.33 267.87,-755.8 270.04,-745.43 263.1,-746.33"/>
|
||||
</g>
|
||||
<!-- svc:pihole-exporter -->
|
||||
<g id="node23" class="node">
|
||||
<title>svc:pihole-exporter</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="151.5,-1386 28.5,-1386 28.5,-1350 151.5,-1350 151.5,-1386"/>
|
||||
<text text-anchor="middle" x="90" y="-1364.3" font-family="Helvetica,sans-Serif" font-size="14.00">pihole-exporter</text>
|
||||
</g>
|
||||
<!-- svc:pihole-exporter->net:monitor -->
|
||||
<g id="edge32" class="edge">
|
||||
<title>svc:pihole-exporter->net:monitor</title>
|
||||
<path fill="none" stroke="black" d="M151.62,-1355.78C161.68,-1352.07 171.57,-1347.25 180,-1341 218.01,-1312.82 244.97,-1263.56 259.03,-1232.98"/>
|
||||
<polygon fill="black" stroke="black" points="262.27,-1234.31 263.14,-1223.76 255.87,-1231.46 262.27,-1234.31"/>
|
||||
</g>
|
||||
<!-- svc:portainer -->
|
||||
<g id="node24" class="node">
|
||||
<title>svc:portainer</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="130,-900 50,-900 50,-864 130,-864 130,-900"/>
|
||||
<text text-anchor="middle" x="90" y="-878.3" font-family="Helvetica,sans-Serif" font-size="14.00">portainer</text>
|
||||
</g>
|
||||
<!-- svc:portainer->net:traefik -->
|
||||
<g id="edge33" class="edge">
|
||||
<title>svc:portainer->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M130.17,-874.06C146.25,-869.82 164.67,-863.64 180,-855 206.25,-840.22 231.35,-816.65 248.38,-798.74"/>
|
||||
<polygon fill="black" stroke="black" points="251.37,-800.67 255.62,-790.97 246.24,-795.9 251.37,-800.67"/>
|
||||
</g>
|
||||
<!-- svc:prometheus -->
|
||||
<g id="node25" class="node">
|
||||
<title>svc:prometheus</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="140,-1008 40,-1008 40,-972 140,-972 140,-1008"/>
|
||||
<text text-anchor="middle" x="90" y="-986.3" font-family="Helvetica,sans-Serif" font-size="14.00">prometheus</text>
|
||||
</g>
|
||||
<!-- svc:prometheus->net:monitor -->
|
||||
<g id="edge34" class="edge">
|
||||
<title>svc:prometheus->net:monitor</title>
|
||||
<path fill="none" stroke="black" d="M140.37,-997.26C154.39,-1001.24 168.86,-1007.42 180,-1017 229.98,-1059.95 254.42,-1136.8 264.44,-1178.04"/>
|
||||
<polygon fill="black" stroke="black" points="261.07,-1179 266.73,-1187.95 267.88,-1177.42 261.07,-1179"/>
|
||||
</g>
|
||||
<!-- svc:prometheus->net:traefik -->
|
||||
<g id="edge35" class="edge">
|
||||
<title>svc:prometheus->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M140.37,-982.74C154.39,-978.76 168.86,-972.58 180,-963 229.98,-920.05 254.42,-843.2 264.44,-801.96"/>
|
||||
<polygon fill="black" stroke="black" points="267.88,-802.58 266.73,-792.05 261.07,-801 267.88,-802.58"/>
|
||||
</g>
|
||||
<!-- svc:searxng-webapp -->
|
||||
<g id="node26" class="node">
|
||||
<title>svc:searxng-webapp</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="156,-846 24,-846 24,-810 156,-810 156,-846"/>
|
||||
<text text-anchor="middle" x="90" y="-824.3" font-family="Helvetica,sans-Serif" font-size="14.00">searxng-webapp</text>
|
||||
</g>
|
||||
<!-- svc:searxng-webapp->net:traefik -->
|
||||
<g id="edge36" class="edge">
|
||||
<title>svc:searxng-webapp->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M151.18,-809.87C176.26,-802.32 204.79,-793.72 227.63,-786.84"/>
|
||||
<polygon fill="black" stroke="black" points="228.81,-790.14 237.37,-783.9 226.79,-783.44 228.81,-790.14"/>
|
||||
</g>
|
||||
<!-- svc:telegraf -->
|
||||
<g id="node27" class="node">
|
||||
<title>svc:telegraf</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="125.5,-1332 54.5,-1332 54.5,-1296 125.5,-1296 125.5,-1332"/>
|
||||
<text text-anchor="middle" x="90" y="-1310.3" font-family="Helvetica,sans-Serif" font-size="14.00">telegraf</text>
|
||||
</g>
|
||||
<!-- svc:telegraf->net:monitor -->
|
||||
<g id="edge37" class="edge">
|
||||
<title>svc:telegraf->net:monitor</title>
|
||||
<path fill="none" stroke="black" d="M125.8,-1307.18C142.85,-1302.93 163.26,-1296.43 180,-1287 206.25,-1272.22 231.35,-1248.65 248.38,-1230.74"/>
|
||||
<polygon fill="black" stroke="black" points="251.37,-1232.67 255.62,-1222.97 246.24,-1227.9 251.37,-1232.67"/>
|
||||
</g>
|
||||
<!-- svc:traefik -->
|
||||
<g id="node28" class="node">
|
||||
<title>svc:traefik</title>
|
||||
<polygon fill="#dfefff" stroke="black" points="121,-792 59,-792 59,-756 121,-756 121,-792"/>
|
||||
<text text-anchor="middle" x="90" y="-770.3" font-family="Helvetica,sans-Serif" font-size="14.00">traefik</text>
|
||||
</g>
|
||||
<!-- svc:traefik->net:traefik -->
|
||||
<g id="edge38" class="edge">
|
||||
<title>svc:traefik->net:traefik</title>
|
||||
<path fill="none" stroke="black" d="M121,-774C148.16,-774 188.69,-774 220.66,-774"/>
|
||||
<polygon fill="black" stroke="black" points="220.71,-777.5 230.71,-774 220.71,-770.5 220.71,-777.5"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 24 KiB |
@@ -0,0 +1,611 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 2.43.0 (0)
|
||||
-->
|
||||
<!-- Title: DockerTraefikDynu Pages: 1 -->
|
||||
<svg width="1113pt" height="1536pt"
|
||||
viewBox="0.00 0.00 1113.39 1536.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1532)">
|
||||
<title>DockerTraefikDynu</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-1532 1109.39,-1532 1109.39,4 -4,4"/>
|
||||
<g id="clust2" class="cluster">
|
||||
<title>cluster_networks</title>
|
||||
<path fill="none" stroke="#d1d5db" stroke-dasharray="5,2" d="M922,-650C922,-650 1093.39,-650 1093.39,-650 1099.39,-650 1105.39,-656 1105.39,-662 1105.39,-662 1105.39,-1117 1105.39,-1117 1105.39,-1123 1099.39,-1129 1093.39,-1129 1093.39,-1129 922,-1129 922,-1129 916,-1129 910,-1123 910,-1117 910,-1117 910,-662 910,-662 910,-656 916,-650 922,-650"/>
|
||||
<text text-anchor="middle" x="1007.69" y="-1113.8" font-family="Helvetica,sans-Serif" font-size="14.00">Docker backend networks</text>
|
||||
</g>
|
||||
<!-- dynu -->
|
||||
<g id="node1" class="node">
|
||||
<title>dynu</title>
|
||||
<path fill="#fde68a" stroke="black" d="M374,-789C374,-789 282,-789 282,-789 276,-789 270,-783 270,-777 270,-777 270,-765 270,-765 270,-759 276,-753 282,-753 282,-753 374,-753 374,-753 380,-753 386,-759 386,-765 386,-765 386,-777 386,-777 386,-783 380,-789 374,-789"/>
|
||||
<text text-anchor="middle" x="328" y="-768.2" font-family="Helvetica,sans-Serif" font-size="11.00">Dynu / Public DNS</text>
|
||||
</g>
|
||||
<!-- svc:traefik -->
|
||||
<g id="node2" class="node">
|
||||
<title>svc:traefik</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M559,-789C559,-789 513,-789 513,-789 507,-789 501,-783 501,-777 501,-777 501,-765 501,-765 501,-759 507,-753 513,-753 513,-753 559,-753 559,-753 565,-753 571,-759 571,-765 571,-765 571,-777 571,-777 571,-783 565,-789 559,-789"/>
|
||||
<text text-anchor="middle" x="536" y="-774.2" font-family="Helvetica,sans-Serif" font-size="11.00">traefik</text>
|
||||
<text text-anchor="middle" x="536" y="-762.2" font-family="Helvetica,sans-Serif" font-size="11.00">[authelia]</text>
|
||||
</g>
|
||||
<!-- dynu->svc:traefik -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>dynu->svc:traefik</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.6" d="M386.11,-771C419.13,-771 460.06,-771 490.64,-771"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.6" points="491,-774.5 501,-771 491,-767.5 491,-774.5"/>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:traefik -->
|
||||
<g id="edge30" class="edge">
|
||||
<title>svc:traefik->svc:traefik</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M511.86,-789.35C485.91,-817.82 493.95,-854 536,-854 574.1,-854 584.29,-824.29 566.55,-797.52"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="569.07,-795.06 560.14,-789.35 563.55,-799.38 569.07,-795.06"/>
|
||||
</g>
|
||||
<!-- svc:authelia -->
|
||||
<g id="node3" class="node">
|
||||
<title>svc:authelia</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M763,-789C763,-789 726,-789 726,-789 720,-789 714,-783 714,-777 714,-777 714,-765 714,-765 714,-759 720,-753 726,-753 726,-753 763,-753 763,-753 769,-753 775,-759 775,-765 775,-765 775,-777 775,-777 775,-783 769,-789 763,-789"/>
|
||||
<text text-anchor="middle" x="744.5" y="-774.2" font-family="Helvetica,sans-Serif" font-size="11.00">authelia</text>
|
||||
<text text-anchor="middle" x="744.5" y="-762.2" font-family="Helvetica,sans-Serif" font-size="11.00">[TLS]</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:authelia -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>svc:traefik->svc:authelia</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M571.1,-771C607.43,-771 664.91,-771 703.39,-771"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="703.71,-774.5 713.71,-771 703.71,-767.5 703.71,-774.5"/>
|
||||
</g>
|
||||
<!-- svc:gitea -->
|
||||
<g id="node5" class="node">
|
||||
<title>svc:gitea</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M759.5,-688C759.5,-688 729.5,-688 729.5,-688 723.5,-688 717.5,-682 717.5,-676 717.5,-676 717.5,-656 717.5,-656 717.5,-650 723.5,-644 729.5,-644 729.5,-644 759.5,-644 759.5,-644 765.5,-644 771.5,-650 771.5,-656 771.5,-656 771.5,-676 771.5,-676 771.5,-682 765.5,-688 759.5,-688"/>
|
||||
<text text-anchor="middle" x="744.5" y="-675.2" font-family="Helvetica,sans-Serif" font-size="11.00">gitea</text>
|
||||
<text text-anchor="middle" x="744.5" y="-663.2" font-family="Helvetica,sans-Serif" font-size="11.00">:3000</text>
|
||||
<text text-anchor="middle" x="744.5" y="-651.2" font-family="Helvetica,sans-Serif" font-size="11.00">[TLS]</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:gitea -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>svc:traefik->svc:gitea</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M571.1,-753.66C608.91,-734.44 669.61,-703.57 707.98,-684.06"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="709.89,-687.01 717.22,-679.36 706.72,-680.77 709.89,-687.01"/>
|
||||
</g>
|
||||
<!-- svc:gotify -->
|
||||
<g id="node7" class="node">
|
||||
<title>svc:gotify</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M759.5,-579C759.5,-579 729.5,-579 729.5,-579 723.5,-579 717.5,-573 717.5,-567 717.5,-567 717.5,-555 717.5,-555 717.5,-549 723.5,-543 729.5,-543 729.5,-543 759.5,-543 759.5,-543 765.5,-543 771.5,-549 771.5,-555 771.5,-555 771.5,-567 771.5,-567 771.5,-573 765.5,-579 759.5,-579"/>
|
||||
<text text-anchor="middle" x="744.5" y="-564.2" font-family="Helvetica,sans-Serif" font-size="11.00">gotify</text>
|
||||
<text text-anchor="middle" x="744.5" y="-552.2" font-family="Helvetica,sans-Serif" font-size="11.00">:80</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:gotify -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>svc:traefik->svc:gotify</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M553.8,-752.96C592.77,-711.11 686,-611 686,-611 686,-611 700.43,-598.45 714.83,-585.93"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="717.49,-588.25 722.74,-579.05 712.9,-582.97 717.49,-588.25"/>
|
||||
</g>
|
||||
<!-- svc:grafana -->
|
||||
<g id="node9" class="node">
|
||||
<title>svc:grafana</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M762,-999C762,-999 727,-999 727,-999 721,-999 715,-993 715,-987 715,-987 715,-975 715,-975 715,-969 721,-963 727,-963 727,-963 762,-963 762,-963 768,-963 774,-969 774,-975 774,-975 774,-987 774,-987 774,-993 768,-999 762,-999"/>
|
||||
<text text-anchor="middle" x="744.5" y="-984.2" font-family="Helvetica,sans-Serif" font-size="11.00">grafana</text>
|
||||
<text text-anchor="middle" x="744.5" y="-972.2" font-family="Helvetica,sans-Serif" font-size="11.00">:3000</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:grafana -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>svc:traefik->svc:grafana</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M553.8,-789.04C592.77,-830.89 686,-931 686,-931 686,-931 700.43,-943.55 714.83,-956.07"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="712.9,-959.03 722.74,-962.95 717.49,-953.75 712.9,-959.03"/>
|
||||
</g>
|
||||
<!-- svc:grampsweb -->
|
||||
<g id="node11" class="node">
|
||||
<title>svc:grampsweb</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M772.5,-1528C772.5,-1528 716.5,-1528 716.5,-1528 710.5,-1528 704.5,-1522 704.5,-1516 704.5,-1516 704.5,-1504 704.5,-1504 704.5,-1498 710.5,-1492 716.5,-1492 716.5,-1492 772.5,-1492 772.5,-1492 778.5,-1492 784.5,-1498 784.5,-1504 784.5,-1504 784.5,-1516 784.5,-1516 784.5,-1522 778.5,-1528 772.5,-1528"/>
|
||||
<text text-anchor="middle" x="744.5" y="-1507.2" font-family="Helvetica,sans-Serif" font-size="11.00">grampsweb</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:grampsweb -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>svc:traefik->svc:grampsweb</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M540.91,-789.07C564.42,-897.78 686,-1460 686,-1460 686,-1460 700.43,-1472.55 714.83,-1485.07"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="712.9,-1488.03 722.74,-1491.95 717.49,-1482.75 712.9,-1488.03"/>
|
||||
</g>
|
||||
<!-- svc:influxdb -->
|
||||
<g id="node13" class="node">
|
||||
<title>svc:influxdb</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M767.5,-898C767.5,-898 721.5,-898 721.5,-898 715.5,-898 709.5,-892 709.5,-886 709.5,-886 709.5,-866 709.5,-866 709.5,-860 715.5,-854 721.5,-854 721.5,-854 767.5,-854 767.5,-854 773.5,-854 779.5,-860 779.5,-866 779.5,-866 779.5,-886 779.5,-886 779.5,-892 773.5,-898 767.5,-898"/>
|
||||
<text text-anchor="middle" x="744.5" y="-885.2" font-family="Helvetica,sans-Serif" font-size="11.00">influxdb</text>
|
||||
<text text-anchor="middle" x="744.5" y="-873.2" font-family="Helvetica,sans-Serif" font-size="11.00">:8086</text>
|
||||
<text text-anchor="middle" x="744.5" y="-861.2" font-family="Helvetica,sans-Serif" font-size="11.00">[authelia]</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:influxdb -->
|
||||
<g id="edge12" class="edge">
|
||||
<title>svc:traefik->svc:influxdb</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M571.1,-788.34C606.37,-806.27 661.57,-834.34 699.97,-853.87"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="698.85,-857.22 709.35,-858.64 702.02,-850.98 698.85,-857.22"/>
|
||||
</g>
|
||||
<!-- svc:monitor-kuma -->
|
||||
<g id="node15" class="node">
|
||||
<title>svc:monitor-kuma</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M778.5,-1427C778.5,-1427 710.5,-1427 710.5,-1427 704.5,-1427 698.5,-1421 698.5,-1415 698.5,-1415 698.5,-1403 698.5,-1403 698.5,-1397 704.5,-1391 710.5,-1391 710.5,-1391 778.5,-1391 778.5,-1391 784.5,-1391 790.5,-1397 790.5,-1403 790.5,-1403 790.5,-1415 790.5,-1415 790.5,-1421 784.5,-1427 778.5,-1427"/>
|
||||
<text text-anchor="middle" x="744.5" y="-1412.2" font-family="Helvetica,sans-Serif" font-size="11.00">monitor-kuma</text>
|
||||
<text text-anchor="middle" x="744.5" y="-1400.2" font-family="Helvetica,sans-Serif" font-size="11.00">[TLS]</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:monitor-kuma -->
|
||||
<g id="edge14" class="edge">
|
||||
<title>svc:traefik->svc:monitor-kuma</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M541.62,-789.24C566.77,-888.49 686,-1359 686,-1359 686,-1359 700.43,-1371.55 714.83,-1384.07"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="712.9,-1387.03 722.74,-1390.95 717.49,-1381.75 712.9,-1387.03"/>
|
||||
</g>
|
||||
<!-- svc:mtls-bridge -->
|
||||
<g id="node17" class="node">
|
||||
<title>svc:mtls-bridge</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M772,-1326C772,-1326 717,-1326 717,-1326 711,-1326 705,-1320 705,-1314 705,-1314 705,-1294 705,-1294 705,-1288 711,-1282 717,-1282 717,-1282 772,-1282 772,-1282 778,-1282 784,-1288 784,-1294 784,-1294 784,-1314 784,-1314 784,-1320 778,-1326 772,-1326"/>
|
||||
<text text-anchor="middle" x="744.5" y="-1313.2" font-family="Helvetica,sans-Serif" font-size="11.00">mtls-bridge</text>
|
||||
<text text-anchor="middle" x="744.5" y="-1301.2" font-family="Helvetica,sans-Serif" font-size="11.00">:8080</text>
|
||||
<text text-anchor="middle" x="744.5" y="-1289.2" font-family="Helvetica,sans-Serif" font-size="11.00">[mTLS]</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:mtls-bridge -->
|
||||
<g id="edge16" class="edge">
|
||||
<title>svc:traefik->svc:mtls-bridge</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M542.66,-789.19C569.88,-876.69 686,-1250 686,-1250 686,-1250 698.77,-1262 712.28,-1274.68"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="710.1,-1277.43 719.78,-1281.72 714.89,-1272.33 710.1,-1277.43"/>
|
||||
</g>
|
||||
<!-- svc:nextcloud-webapp -->
|
||||
<g id="node19" class="node">
|
||||
<title>svc:nextcloud-webapp</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M791,-137C791,-137 698,-137 698,-137 692,-137 686,-131 686,-125 686,-125 686,-113 686,-113 686,-107 692,-101 698,-101 698,-101 791,-101 791,-101 797,-101 803,-107 803,-113 803,-113 803,-125 803,-125 803,-131 797,-137 791,-137"/>
|
||||
<text text-anchor="middle" x="744.5" y="-116.2" font-family="Helvetica,sans-Serif" font-size="11.00">nextcloud-webapp</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:nextcloud-webapp -->
|
||||
<g id="edge18" class="edge">
|
||||
<title>svc:traefik->svc:nextcloud-webapp</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M541.62,-752.85C566.77,-654.11 686,-186 686,-186 686,-186 704.96,-163.91 721.1,-145.1"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="723.93,-147.18 727.79,-137.31 718.62,-142.62 723.93,-147.18"/>
|
||||
</g>
|
||||
<!-- svc:node-red -->
|
||||
<g id="node21" class="node">
|
||||
<title>svc:node-red</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M767.5,-1217C767.5,-1217 721.5,-1217 721.5,-1217 715.5,-1217 709.5,-1211 709.5,-1205 709.5,-1205 709.5,-1185 709.5,-1185 709.5,-1179 715.5,-1173 721.5,-1173 721.5,-1173 767.5,-1173 767.5,-1173 773.5,-1173 779.5,-1179 779.5,-1185 779.5,-1185 779.5,-1205 779.5,-1205 779.5,-1211 773.5,-1217 767.5,-1217"/>
|
||||
<text text-anchor="middle" x="744.5" y="-1204.2" font-family="Helvetica,sans-Serif" font-size="11.00">node-red</text>
|
||||
<text text-anchor="middle" x="744.5" y="-1192.2" font-family="Helvetica,sans-Serif" font-size="11.00">:1880</text>
|
||||
<text text-anchor="middle" x="744.5" y="-1180.2" font-family="Helvetica,sans-Serif" font-size="11.00">[authelia]</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:node-red -->
|
||||
<g id="edge20" class="edge">
|
||||
<title>svc:traefik->svc:node-red</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M544.29,-789.1C574.2,-863.38 686,-1141 686,-1141 686,-1141 698.77,-1153 712.28,-1165.68"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="710.1,-1168.43 719.78,-1172.72 714.89,-1163.33 710.1,-1168.43"/>
|
||||
</g>
|
||||
<!-- svc:passbolt-webapp -->
|
||||
<g id="node23" class="node">
|
||||
<title>svc:passbolt-webapp</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M787.5,-36C787.5,-36 701.5,-36 701.5,-36 695.5,-36 689.5,-30 689.5,-24 689.5,-24 689.5,-12 689.5,-12 689.5,-6 695.5,0 701.5,0 701.5,0 787.5,0 787.5,0 793.5,0 799.5,-6 799.5,-12 799.5,-12 799.5,-24 799.5,-24 799.5,-30 793.5,-36 787.5,-36"/>
|
||||
<text text-anchor="middle" x="744.5" y="-15.2" font-family="Helvetica,sans-Serif" font-size="11.00">passbolt-webapp</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:passbolt-webapp -->
|
||||
<g id="edge22" class="edge">
|
||||
<title>svc:traefik->svc:passbolt-webapp</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M540.83,-752.92C564.15,-642.88 686,-68 686,-68 686,-68 700.43,-55.45 714.83,-42.93"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="717.49,-45.25 722.74,-36.05 712.9,-39.97 717.49,-45.25"/>
|
||||
</g>
|
||||
<!-- svc:portainer -->
|
||||
<g id="node25" class="node">
|
||||
<title>svc:portainer</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M766,-478C766,-478 723,-478 723,-478 717,-478 711,-472 711,-466 711,-466 711,-446 711,-446 711,-440 717,-434 723,-434 723,-434 766,-434 766,-434 772,-434 778,-440 778,-446 778,-446 778,-466 778,-466 778,-472 772,-478 766,-478"/>
|
||||
<text text-anchor="middle" x="744.5" y="-465.2" font-family="Helvetica,sans-Serif" font-size="11.00">portainer</text>
|
||||
<text text-anchor="middle" x="744.5" y="-453.2" font-family="Helvetica,sans-Serif" font-size="11.00">:9000</text>
|
||||
<text text-anchor="middle" x="744.5" y="-441.2" font-family="Helvetica,sans-Serif" font-size="11.00">[TLS]</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:portainer -->
|
||||
<g id="edge24" class="edge">
|
||||
<title>svc:traefik->svc:portainer</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M547.48,-752.65C581.39,-693.24 686,-510 686,-510 686,-510 698.77,-498 712.28,-485.32"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="714.89,-487.67 719.78,-478.28 710.1,-482.57 714.89,-487.67"/>
|
||||
</g>
|
||||
<!-- svc:prometheus -->
|
||||
<g id="node27" class="node">
|
||||
<title>svc:prometheus</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M774,-1108C774,-1108 715,-1108 715,-1108 709,-1108 703,-1102 703,-1096 703,-1096 703,-1076 703,-1076 703,-1070 709,-1064 715,-1064 715,-1064 774,-1064 774,-1064 780,-1064 786,-1070 786,-1076 786,-1076 786,-1096 786,-1096 786,-1102 780,-1108 774,-1108"/>
|
||||
<text text-anchor="middle" x="744.5" y="-1095.2" font-family="Helvetica,sans-Serif" font-size="11.00">prometheus</text>
|
||||
<text text-anchor="middle" x="744.5" y="-1083.2" font-family="Helvetica,sans-Serif" font-size="11.00">:9090</text>
|
||||
<text text-anchor="middle" x="744.5" y="-1071.2" font-family="Helvetica,sans-Serif" font-size="11.00">[authelia]</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:prometheus -->
|
||||
<g id="edge26" class="edge">
|
||||
<title>svc:traefik->svc:prometheus</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M547.48,-789.35C581.39,-848.76 686,-1032 686,-1032 686,-1032 698.77,-1044 712.28,-1056.68"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="710.1,-1059.43 719.78,-1063.72 714.89,-1054.33 710.1,-1059.43"/>
|
||||
</g>
|
||||
<!-- svc:searxng-webapp -->
|
||||
<g id="node29" class="node">
|
||||
<title>svc:searxng-webapp</title>
|
||||
<path fill="#dcfce7" stroke="black" d="M786,-369C786,-369 703,-369 703,-369 697,-369 691,-363 691,-357 691,-357 691,-345 691,-345 691,-339 697,-333 703,-333 703,-333 786,-333 786,-333 792,-333 798,-339 798,-345 798,-345 798,-357 798,-357 798,-363 792,-369 786,-369"/>
|
||||
<text text-anchor="middle" x="744.5" y="-348.2" font-family="Helvetica,sans-Serif" font-size="11.00">searxng-webapp</text>
|
||||
</g>
|
||||
<!-- svc:traefik->svc:searxng-webapp -->
|
||||
<g id="edge28" class="edge">
|
||||
<title>svc:traefik->svc:searxng-webapp</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="1.4" d="M544.29,-752.9C574.2,-678.62 686,-401 686,-401 686,-401 700.43,-388.45 714.83,-375.93"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="717.49,-378.25 722.74,-369.05 712.9,-372.97 717.49,-378.25"/>
|
||||
</g>
|
||||
<!-- net:traefik -->
|
||||
<g id="node36" class="node">
|
||||
<title>net:traefik</title>
|
||||
<ellipse fill="#f8fafc" stroke="black" cx="1007.69" cy="-878" rx="31.04" ry="18"/>
|
||||
<text text-anchor="middle" x="1007.69" y="-875.2" font-family="Helvetica,sans-Serif" font-size="11.00">traefik</text>
|
||||
</g>
|
||||
<!-- svc:traefik->net:traefik -->
|
||||
<g id="edge55" class="edge">
|
||||
<title>svc:traefik->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M542.75,-752.82C570.13,-666.27 686,-300 686,-300 686,-300 803,-300 803,-300 803,-300 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
|
||||
</g>
|
||||
<!-- svc:authelia->net:traefik -->
|
||||
<g id="edge32" class="edge">
|
||||
<title>svc:authelia->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M775.21,-783.17C824.86,-803.51 924.25,-844.23 975.12,-865.06"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="974.26,-867.36 981.67,-867.75 976.12,-862.83 974.26,-867.36"/>
|
||||
</g>
|
||||
<!-- dns:auth.<domain> -->
|
||||
<g id="node4" class="node">
|
||||
<title>dns:auth.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="123.5,-1496 25.5,-1496 25.5,-1460 129.5,-1460 129.5,-1490 123.5,-1496"/>
|
||||
<polyline fill="none" stroke="black" points="123.5,-1496 123.5,-1490 "/>
|
||||
<polyline fill="none" stroke="black" points="129.5,-1490 123.5,-1490 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-1475.2" font-family="Helvetica,sans-Serif" font-size="11.00">auth.<domain></text>
|
||||
</g>
|
||||
<!-- dns:auth.<domain>->dynu -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>dns:auth.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M106.12,-1459.95C128.03,-1445.63 155,-1428 155,-1428 155,-1428 286.65,-925.11 319.6,-799.28"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="323.07,-799.82 322.22,-789.26 316.3,-798.05 323.07,-799.82"/>
|
||||
</g>
|
||||
<!-- svc:gitea->net:traefik -->
|
||||
<g id="edge33" class="edge">
|
||||
<title>svc:gitea->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M768.56,-688.05C784.55,-703.36 803,-721 803,-721 803,-721 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
|
||||
</g>
|
||||
<!-- dns:gitea.<domain> -->
|
||||
<g id="node6" class="node">
|
||||
<title>dns:gitea.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="125,-1395 24,-1395 24,-1359 131,-1359 131,-1389 125,-1395"/>
|
||||
<polyline fill="none" stroke="black" points="125,-1395 125,-1389 "/>
|
||||
<polyline fill="none" stroke="black" points="131,-1389 125,-1389 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-1374.2" font-family="Helvetica,sans-Serif" font-size="11.00">gitea.<domain></text>
|
||||
</g>
|
||||
<!-- dns:gitea.<domain>->dynu -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>dns:gitea.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M106.12,-1358.95C128.03,-1344.63 155,-1327 155,-1327 155,-1327 283.58,-911.36 318.4,-798.82"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="321.76,-799.77 321.38,-789.18 315.08,-797.7 321.76,-799.77"/>
|
||||
</g>
|
||||
<!-- svc:gotify->net:traefik -->
|
||||
<g id="edge34" class="edge">
|
||||
<title>svc:gotify->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-579.05C782.73,-593.37 803,-611 803,-611 803,-611 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
|
||||
</g>
|
||||
<!-- dns:gotify.<domain> -->
|
||||
<g id="node8" class="node">
|
||||
<title>dns:gotify.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="126,-1294 23,-1294 23,-1258 132,-1258 132,-1288 126,-1294"/>
|
||||
<polyline fill="none" stroke="black" points="126,-1294 126,-1288 "/>
|
||||
<polyline fill="none" stroke="black" points="132,-1288 126,-1288 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-1273.2" font-family="Helvetica,sans-Serif" font-size="11.00">gotify.<domain></text>
|
||||
</g>
|
||||
<!-- dns:gotify.<domain>->dynu -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>dns:gotify.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M106.12,-1257.95C128.03,-1243.63 155,-1226 155,-1226 155,-1226 279.21,-897.41 316.53,-798.71"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="319.89,-799.71 320.15,-789.12 313.34,-797.23 319.89,-799.71"/>
|
||||
</g>
|
||||
<!-- net:monitor -->
|
||||
<g id="node33" class="node">
|
||||
<title>net:monitor</title>
|
||||
<ellipse fill="#f8fafc" stroke="black" cx="1007.69" cy="-979" rx="35.46" ry="18"/>
|
||||
<text text-anchor="middle" x="1007.69" y="-976.2" font-family="Helvetica,sans-Serif" font-size="11.00">monitor</text>
|
||||
</g>
|
||||
<!-- svc:grafana->net:monitor -->
|
||||
<g id="edge35" class="edge">
|
||||
<title>svc:grafana->net:monitor</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M774.2,-980.78C820.4,-980.43 911.47,-979.73 964.89,-979.32"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="965.19,-981.77 972.17,-979.26 965.15,-976.87 965.19,-981.77"/>
|
||||
</g>
|
||||
<!-- svc:grafana->net:traefik -->
|
||||
<g id="edge36" class="edge">
|
||||
<title>svc:grafana->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M774.2,-969.68C823.41,-950.28 923.53,-910.8 974.83,-890.57"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="975.82,-892.81 981.44,-887.96 974.03,-888.25 975.82,-892.81"/>
|
||||
</g>
|
||||
<!-- dns:grafana.<domain> -->
|
||||
<g id="node10" class="node">
|
||||
<title>dns:grafana.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="132,-1193 17,-1193 17,-1157 138,-1157 138,-1187 132,-1193"/>
|
||||
<polyline fill="none" stroke="black" points="132,-1193 132,-1187 "/>
|
||||
<polyline fill="none" stroke="black" points="138,-1187 132,-1187 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-1172.2" font-family="Helvetica,sans-Serif" font-size="11.00">grafana.<domain></text>
|
||||
</g>
|
||||
<!-- dns:grafana.<domain>->dynu -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>dns:grafana.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M106.12,-1156.95C128.03,-1142.63 155,-1125 155,-1125 155,-1125 273.61,-880.89 313.84,-798.09"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="317.01,-799.57 318.23,-789.04 310.72,-796.51 317.01,-799.57"/>
|
||||
</g>
|
||||
<!-- net:gramps -->
|
||||
<g id="node32" class="node">
|
||||
<title>net:gramps</title>
|
||||
<ellipse fill="#f8fafc" stroke="black" cx="1007.69" cy="-1080" rx="34.76" ry="18"/>
|
||||
<text text-anchor="middle" x="1007.69" y="-1077.2" font-family="Helvetica,sans-Serif" font-size="11.00">gramps</text>
|
||||
</g>
|
||||
<!-- svc:grampsweb->net:gramps -->
|
||||
<g id="edge37" class="edge">
|
||||
<title>svc:grampsweb->net:gramps</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-1491.95C782.73,-1477.63 803,-1460 803,-1460 803,-1460 949.23,-1187.21 993.89,-1103.88"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="996.27,-1104.64 997.41,-1097.32 991.95,-1102.33 996.27,-1104.64"/>
|
||||
</g>
|
||||
<!-- svc:grampsweb->net:traefik -->
|
||||
<g id="edge38" class="edge">
|
||||
<title>svc:grampsweb->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-1491.95C782.73,-1477.63 803,-1460 803,-1460 803,-1460 910,-929 910,-929 910,-929 949.05,-908.4 977.52,-893.39"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="978.73,-895.52 983.78,-890.09 976.45,-891.18 978.73,-895.52"/>
|
||||
</g>
|
||||
<!-- dns:familytree.<domain> -->
|
||||
<g id="node12" class="node">
|
||||
<title>dns:familytree.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="139,-1092 10,-1092 10,-1056 145,-1056 145,-1086 139,-1092"/>
|
||||
<polyline fill="none" stroke="black" points="139,-1092 139,-1086 "/>
|
||||
<polyline fill="none" stroke="black" points="145,-1086 139,-1086 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-1071.2" font-family="Helvetica,sans-Serif" font-size="11.00">familytree.<domain></text>
|
||||
</g>
|
||||
<!-- dns:familytree.<domain>->dynu -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>dns:familytree.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M106.12,-1055.95C128.03,-1041.63 155,-1024 155,-1024 155,-1024 264.64,-862.73 308.84,-797.71"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="311.9,-799.43 314.63,-789.2 306.11,-795.5 311.9,-799.43"/>
|
||||
</g>
|
||||
<!-- svc:influxdb->net:monitor -->
|
||||
<g id="edge39" class="edge">
|
||||
<title>svc:influxdb->net:monitor</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M779.65,-889.47C829.7,-909.2 922.46,-945.78 972.53,-965.53"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="971.89,-967.91 979.3,-968.2 973.69,-963.35 971.89,-967.91"/>
|
||||
</g>
|
||||
<!-- svc:influxdb->net:traefik -->
|
||||
<g id="edge40" class="edge">
|
||||
<title>svc:influxdb->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M779.65,-876.26C828.54,-876.64 918.2,-877.32 968.99,-877.71"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="969.17,-880.16 976.19,-877.77 969.21,-875.26 969.17,-880.16"/>
|
||||
</g>
|
||||
<!-- dns:influxdb.<domain> -->
|
||||
<g id="node14" class="node">
|
||||
<title>dns:influxdb.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="132.5,-991 16.5,-991 16.5,-955 138.5,-955 138.5,-985 132.5,-991"/>
|
||||
<polyline fill="none" stroke="black" points="132.5,-991 132.5,-985 "/>
|
||||
<polyline fill="none" stroke="black" points="138.5,-985 132.5,-985 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-970.2" font-family="Helvetica,sans-Serif" font-size="11.00">influxdb.<domain></text>
|
||||
</g>
|
||||
<!-- dns:influxdb.<domain>->dynu -->
|
||||
<g id="edge13" class="edge">
|
||||
<title>dns:influxdb.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M106.12,-954.95C128.03,-940.63 155,-923 155,-923 155,-923 250.14,-838.92 298.91,-795.83"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="301.42,-798.28 306.59,-789.03 296.78,-793.03 301.42,-798.28"/>
|
||||
</g>
|
||||
<!-- svc:monitor-kuma->net:monitor -->
|
||||
<g id="edge41" class="edge">
|
||||
<title>svc:monitor-kuma->net:monitor</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-1390.95C782.73,-1376.63 803,-1359 803,-1359 803,-1359 910,-1030 910,-1030 910,-1030 947.73,-1010.1 976.05,-995.16"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="977.25,-997.3 982.29,-991.87 974.96,-992.97 977.25,-997.3"/>
|
||||
</g>
|
||||
<!-- svc:monitor-kuma->net:traefik -->
|
||||
<g id="edge42" class="edge">
|
||||
<title>svc:monitor-kuma->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-1390.95C782.73,-1376.63 803,-1359 803,-1359 803,-1359 910,-929 910,-929 910,-929 949.05,-908.4 977.52,-893.39"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="978.73,-895.52 983.78,-890.09 976.45,-891.18 978.73,-895.52"/>
|
||||
</g>
|
||||
<!-- dns:monitor-kuma.<domain> -->
|
||||
<g id="node16" class="node">
|
||||
<title>dns:monitor-kuma.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="149,-890 0,-890 0,-854 155,-854 155,-884 149,-890"/>
|
||||
<polyline fill="none" stroke="black" points="149,-890 149,-884 "/>
|
||||
<polyline fill="none" stroke="black" points="155,-884 149,-884 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-869.2" font-family="Helvetica,sans-Serif" font-size="11.00">monitor-kuma.<domain></text>
|
||||
</g>
|
||||
<!-- dns:monitor-kuma.<domain>->dynu -->
|
||||
<g id="edge15" class="edge">
|
||||
<title>dns:monitor-kuma.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M122.93,-853.94C165.02,-836.83 228.32,-811.11 273.25,-792.85"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="274.59,-796.08 282.54,-789.07 271.96,-789.59 274.59,-796.08"/>
|
||||
</g>
|
||||
<!-- svc:mtls-bridge->net:monitor -->
|
||||
<g id="edge43" class="edge">
|
||||
<title>svc:mtls-bridge->net:monitor</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M769.22,-1281.72C785.06,-1266.85 803,-1250 803,-1250 803,-1250 910,-1030 910,-1030 910,-1030 947.73,-1010.1 976.05,-995.16"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="977.25,-997.3 982.29,-991.87 974.96,-992.97 977.25,-997.3"/>
|
||||
</g>
|
||||
<!-- svc:mtls-bridge->net:traefik -->
|
||||
<g id="edge44" class="edge">
|
||||
<title>svc:mtls-bridge->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M769.22,-1281.72C785.06,-1266.85 803,-1250 803,-1250 803,-1250 910,-929 910,-929 910,-929 949.05,-908.4 977.52,-893.39"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="978.73,-895.52 983.78,-890.09 976.45,-891.18 978.73,-895.52"/>
|
||||
</g>
|
||||
<!-- dns:mtls-bridge.<domain> -->
|
||||
<g id="node18" class="node">
|
||||
<title>dns:mtls-bridge.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="142,-789 7,-789 7,-753 148,-753 148,-783 142,-789"/>
|
||||
<polyline fill="none" stroke="black" points="142,-789 142,-783 "/>
|
||||
<polyline fill="none" stroke="black" points="148,-783 142,-783 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-768.2" font-family="Helvetica,sans-Serif" font-size="11.00">mtls-bridge.<domain></text>
|
||||
</g>
|
||||
<!-- dns:mtls-bridge.<domain>->dynu -->
|
||||
<g id="edge17" class="edge">
|
||||
<title>dns:mtls-bridge.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M148.05,-771C182.94,-771 225.03,-771 259.61,-771"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="259.62,-774.5 269.62,-771 259.62,-767.5 259.62,-774.5"/>
|
||||
</g>
|
||||
<!-- net:nextcloud -->
|
||||
<g id="node34" class="node">
|
||||
<title>net:nextcloud</title>
|
||||
<ellipse fill="#f8fafc" stroke="black" cx="1007.69" cy="-777" rx="42.89" ry="18"/>
|
||||
<text text-anchor="middle" x="1007.69" y="-774.2" font-family="Helvetica,sans-Serif" font-size="11.00">nextcloud</text>
|
||||
</g>
|
||||
<!-- svc:nextcloud-webapp->net:nextcloud -->
|
||||
<g id="edge45" class="edge">
|
||||
<title>svc:nextcloud-webapp->net:nextcloud</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M761.21,-137.31C778.24,-157.15 803,-186 803,-186 803,-186 910,-727 910,-727 910,-727 945.48,-745.35 973.46,-759.81"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="972.67,-762.17 980.02,-763.2 974.92,-757.81 972.67,-762.17"/>
|
||||
</g>
|
||||
<!-- svc:nextcloud-webapp->net:traefik -->
|
||||
<g id="edge46" class="edge">
|
||||
<title>svc:nextcloud-webapp->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M761.21,-137.31C778.24,-157.15 803,-186 803,-186 803,-186 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
|
||||
</g>
|
||||
<!-- dns:nextcloud.<domain> -->
|
||||
<g id="node20" class="node">
|
||||
<title>dns:nextcloud.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="138,-688 11,-688 11,-652 144,-652 144,-682 138,-688"/>
|
||||
<polyline fill="none" stroke="black" points="138,-688 138,-682 "/>
|
||||
<polyline fill="none" stroke="black" points="144,-682 138,-682 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-667.2" font-family="Helvetica,sans-Serif" font-size="11.00">nextcloud.<domain></text>
|
||||
</g>
|
||||
<!-- dns:nextcloud.<domain>->dynu -->
|
||||
<g id="edge19" class="edge">
|
||||
<title>dns:nextcloud.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M122.93,-688.06C165.02,-705.17 228.32,-730.89 273.25,-749.15"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="271.96,-752.41 282.54,-752.93 274.59,-745.92 271.96,-752.41"/>
|
||||
</g>
|
||||
<!-- svc:node-red->net:monitor -->
|
||||
<g id="edge47" class="edge">
|
||||
<title>svc:node-red->net:monitor</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M769.22,-1172.72C785.06,-1157.85 803,-1141 803,-1141 803,-1141 910,-1030 910,-1030 910,-1030 947.73,-1010.1 976.05,-995.16"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="977.25,-997.3 982.29,-991.87 974.96,-992.97 977.25,-997.3"/>
|
||||
</g>
|
||||
<!-- svc:node-red->net:traefik -->
|
||||
<g id="edge48" class="edge">
|
||||
<title>svc:node-red->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M769.22,-1172.72C785.06,-1157.85 803,-1141 803,-1141 803,-1141 910,-929 910,-929 910,-929 949.05,-908.4 977.52,-893.39"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="978.73,-895.52 983.78,-890.09 976.45,-891.18 978.73,-895.52"/>
|
||||
</g>
|
||||
<!-- dns:node-red.<domain> -->
|
||||
<g id="node22" class="node">
|
||||
<title>dns:node-red.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="135.5,-587 13.5,-587 13.5,-551 141.5,-551 141.5,-581 135.5,-587"/>
|
||||
<polyline fill="none" stroke="black" points="135.5,-587 135.5,-581 "/>
|
||||
<polyline fill="none" stroke="black" points="141.5,-581 135.5,-581 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-566.2" font-family="Helvetica,sans-Serif" font-size="11.00">node-red.<domain></text>
|
||||
</g>
|
||||
<!-- dns:node-red.<domain>->dynu -->
|
||||
<g id="edge21" class="edge">
|
||||
<title>dns:node-red.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M106.12,-587.05C128.03,-601.37 155,-619 155,-619 155,-619 250.14,-703.08 298.91,-746.17"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="296.78,-748.97 306.59,-752.97 301.42,-743.72 296.78,-748.97"/>
|
||||
</g>
|
||||
<!-- net:passbolt -->
|
||||
<g id="node35" class="node">
|
||||
<title>net:passbolt</title>
|
||||
<ellipse fill="#f8fafc" stroke="black" cx="1007.69" cy="-676" rx="37.77" ry="18"/>
|
||||
<text text-anchor="middle" x="1007.69" y="-673.2" font-family="Helvetica,sans-Serif" font-size="11.00">passbolt</text>
|
||||
</g>
|
||||
<!-- svc:passbolt-webapp->net:passbolt -->
|
||||
<g id="edge49" class="edge">
|
||||
<title>svc:passbolt-webapp->net:passbolt</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-36.05C782.73,-50.37 803,-68 803,-68 803,-68 960.4,-537.83 998.48,-651.47"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="996.16,-652.27 1000.71,-658.13 1000.81,-650.72 996.16,-652.27"/>
|
||||
</g>
|
||||
<!-- svc:passbolt-webapp->net:traefik -->
|
||||
<g id="edge50" class="edge">
|
||||
<title>svc:passbolt-webapp->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-36.05C782.73,-50.37 803,-68 803,-68 803,-68 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
|
||||
</g>
|
||||
<!-- dns:passbolt.<domain> -->
|
||||
<g id="node24" class="node">
|
||||
<title>dns:passbolt.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="134,-486 15,-486 15,-450 140,-450 140,-480 134,-486"/>
|
||||
<polyline fill="none" stroke="black" points="134,-486 134,-480 "/>
|
||||
<polyline fill="none" stroke="black" points="140,-480 134,-480 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-465.2" font-family="Helvetica,sans-Serif" font-size="11.00">passbolt.<domain></text>
|
||||
</g>
|
||||
<!-- dns:passbolt.<domain>->dynu -->
|
||||
<g id="edge23" class="edge">
|
||||
<title>dns:passbolt.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M106.12,-486.05C128.03,-500.37 155,-518 155,-518 155,-518 264.64,-679.27 308.84,-744.29"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="306.11,-746.5 314.63,-752.8 311.9,-742.57 306.11,-746.5"/>
|
||||
</g>
|
||||
<!-- svc:portainer->net:traefik -->
|
||||
<g id="edge51" class="edge">
|
||||
<title>svc:portainer->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M769.22,-478.28C785.06,-493.15 803,-510 803,-510 803,-510 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
|
||||
</g>
|
||||
<!-- dns:portainer.<domain> -->
|
||||
<g id="node26" class="node">
|
||||
<title>dns:portainer.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="135.5,-385 13.5,-385 13.5,-349 141.5,-349 141.5,-379 135.5,-385"/>
|
||||
<polyline fill="none" stroke="black" points="135.5,-385 135.5,-379 "/>
|
||||
<polyline fill="none" stroke="black" points="141.5,-379 135.5,-379 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-364.2" font-family="Helvetica,sans-Serif" font-size="11.00">portainer.<domain></text>
|
||||
</g>
|
||||
<!-- dns:portainer.<domain>->dynu -->
|
||||
<g id="edge25" class="edge">
|
||||
<title>dns:portainer.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M106.12,-385.05C128.03,-399.37 155,-417 155,-417 155,-417 273.61,-661.11 313.84,-743.91"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="310.72,-745.49 318.23,-752.96 317.01,-742.43 310.72,-745.49"/>
|
||||
</g>
|
||||
<!-- svc:prometheus->net:monitor -->
|
||||
<g id="edge52" class="edge">
|
||||
<title>svc:prometheus->net:monitor</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M786.03,-1069.4C837.48,-1048.32 925.31,-1012.34 973.18,-992.73"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="974.11,-995 979.66,-990.07 972.26,-990.46 974.11,-995"/>
|
||||
</g>
|
||||
<!-- svc:prometheus->net:traefik -->
|
||||
<g id="edge53" class="edge">
|
||||
<title>svc:prometheus->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M769.22,-1063.72C785.06,-1048.85 803,-1032 803,-1032 803,-1032 910,-929 910,-929 910,-929 949.05,-908.4 977.52,-893.39"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="978.73,-895.52 983.78,-890.09 976.45,-891.18 978.73,-895.52"/>
|
||||
</g>
|
||||
<!-- dns:prometheus.<domain> -->
|
||||
<g id="node28" class="node">
|
||||
<title>dns:prometheus.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="144,-284 5,-284 5,-248 150,-248 150,-278 144,-284"/>
|
||||
<polyline fill="none" stroke="black" points="144,-284 144,-278 "/>
|
||||
<polyline fill="none" stroke="black" points="150,-278 144,-278 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-263.2" font-family="Helvetica,sans-Serif" font-size="11.00">prometheus.<domain></text>
|
||||
</g>
|
||||
<!-- dns:prometheus.<domain>->dynu -->
|
||||
<g id="edge27" class="edge">
|
||||
<title>dns:prometheus.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M106.12,-284.05C128.03,-298.37 155,-316 155,-316 155,-316 279.21,-644.59 316.53,-743.29"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="313.34,-744.77 320.15,-752.88 319.89,-742.29 313.34,-744.77"/>
|
||||
</g>
|
||||
<!-- svc:searxng-webapp->net:traefik -->
|
||||
<g id="edge54" class="edge">
|
||||
<title>svc:searxng-webapp->net:traefik</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-369.05C782.73,-383.37 803,-401 803,-401 803,-401 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
|
||||
</g>
|
||||
<!-- dns:searxng.<domain> -->
|
||||
<g id="node30" class="node">
|
||||
<title>dns:searxng.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="133,-183 16,-183 16,-147 139,-147 139,-177 133,-183"/>
|
||||
<polyline fill="none" stroke="black" points="133,-183 133,-177 "/>
|
||||
<polyline fill="none" stroke="black" points="139,-177 133,-177 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-162.2" font-family="Helvetica,sans-Serif" font-size="11.00">searxng.<domain></text>
|
||||
</g>
|
||||
<!-- dns:searxng.<domain>->dynu -->
|
||||
<g id="edge29" class="edge">
|
||||
<title>dns:searxng.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M106.12,-183.05C128.03,-197.37 155,-215 155,-215 155,-215 283.58,-630.64 318.4,-743.18"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="315.08,-744.3 321.38,-752.82 321.76,-742.23 315.08,-744.3"/>
|
||||
</g>
|
||||
<!-- dns:traefik.<domain> -->
|
||||
<g id="node31" class="node">
|
||||
<title>dns:traefik.<domain></title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="128.5,-82 20.5,-82 20.5,-46 134.5,-46 134.5,-76 128.5,-82"/>
|
||||
<polyline fill="none" stroke="black" points="128.5,-82 128.5,-76 "/>
|
||||
<polyline fill="none" stroke="black" points="134.5,-76 128.5,-76 "/>
|
||||
<text text-anchor="middle" x="77.5" y="-61.2" font-family="Helvetica,sans-Serif" font-size="11.00">traefik.<domain></text>
|
||||
</g>
|
||||
<!-- dns:traefik.<domain>->dynu -->
|
||||
<g id="edge31" class="edge">
|
||||
<title>dns:traefik.<domain>->dynu</title>
|
||||
<path fill="none" stroke="#334155" d="M106.12,-82.05C128.03,-96.37 155,-114 155,-114 155,-114 286.65,-616.89 319.6,-742.72"/>
|
||||
<polygon fill="#334155" stroke="#334155" points="316.3,-743.95 322.22,-752.74 323.07,-742.18 316.3,-743.95"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 41 KiB |
@@ -0,0 +1,21 @@
|
||||
# Public Infrastructure Summary
|
||||
|
||||
This documentation is generated from the infrastructure repository. Sensitive values are redacted.
|
||||
|
||||
> Generated docs are sanitised/redacted before publishing to GitHub Pages.
|
||||
|
||||
## Infrastructure diagrams
|
||||
|
||||
### Physical / virtual topology
|
||||
|
||||

|
||||
|
||||
### Docker, Traefik and Dynu routing
|
||||
|
||||

|
||||
|
||||
## Documents
|
||||
|
||||
- [Diagrams](diagrams.md)
|
||||
- [Compose Inventory](compose-inventory.md)
|
||||
- [Traefik Routes](traefik-routes.md)
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 2.43.0 (0)
|
||||
-->
|
||||
<!-- Title: PhysicalTopology Pages: 1 -->
|
||||
<svg width="514pt" height="61pt"
|
||||
viewBox="0.00 0.00 514.00 61.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 57)">
|
||||
<title>PhysicalTopology</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-57 510,-57 510,4 -4,4"/>
|
||||
<!-- placeholder:inventory -->
|
||||
<g id="node1" class="node">
|
||||
<title>placeholder:inventory</title>
|
||||
<polygon fill="#fef3c7" stroke="black" points="500,-53 0,-53 0,0 506,0 506,-47 500,-53"/>
|
||||
<polyline fill="none" stroke="black" points="500,-53 500,-47 "/>
|
||||
<polyline fill="none" stroke="black" points="506,-47 500,-47 "/>
|
||||
<text text-anchor="middle" x="253" y="-37.8" font-family="Times,serif" font-size="14.00">Host inventory JSON not found.</text>
|
||||
<text text-anchor="middle" x="253" y="-22.8" font-family="Times,serif" font-size="14.00">Generate terraform inventory and rerun scripts/docs/generate-all.sh</text>
|
||||
<text text-anchor="middle" x="253" y="-7.8" font-family="Times,serif" font-size="14.00">(--host-inventory <path>).</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,23 @@
|
||||
.md-content img, article img {
|
||||
max-width: 100%;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.diagram-wrap {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
margin: 1rem 0 2rem;
|
||||
}
|
||||
|
||||
.diagram-wrap img {
|
||||
max-width: none;
|
||||
width: 1400px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.diagram-wrap img {
|
||||
width: 1200px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
# Traefik Routes
|
||||
|
||||
| Service | Router | Rule | Entrypoints | TLS | Middlewares | Target Port |
|
||||
|---|---|---|---|---|---|---|
|
||||
| authelia | authelia | Host(`auth.<domain>`) | websecure | true | | |
|
||||
| error-pages | error-pages-router | HostRegexp(`{host:.+}`) | web | | error-pages-middleware | |
|
||||
| gitea | gitea | Host(`gitea.<domain>`) | websecure | true | | 3000 |
|
||||
| gotify | gotify | Host(`gotify.<domain>`) | websecure | | | 80 |
|
||||
| grafana | grafana | Host(`grafana.<domain>`) | websecure | | | 3000 |
|
||||
| grampsweb | gramps | Host(`familytree.<domain>`) | websecure | | | 5000 |
|
||||
| influxdb | influxdb | Host(`influxdb.<domain>`) | websecure | | authelia | 8086 |
|
||||
| monitor-kuma | monitor | Host(`monitor-kuma.<domain>`) | websecure | true | | 3001 |
|
||||
| mtls-bridge | mtls-bridge | Host(`mtls-bridge.<domain>`) | websecure | | mtls-bridge-auth,mtls-bridge-cors | 8080 |
|
||||
| mtls-bridge | mtls-bridge-preflight | Host(`mtls-bridge.<domain>`) && Method(`OPTIONS`) | websecure | | mtls-bridge-cors | |
|
||||
| nextcloud-webapp | nextcloud | Host(`nextcloud.<domain>`) | websecure | | nextcloud-dav, nextcloud-webfinger | |
|
||||
| node-red | node-red | Host(`node-red.<domain>`) | websecure | | authelia | 1880 |
|
||||
| passbolt-webapp | passbolt | Host(`passbolt.<domain>`) | websecure | | | |
|
||||
| portainer | portainer | Host(`portainer.<domain>`) | websecure | true | | 9000 |
|
||||
| prometheus | prometheus | Host(`prometheus.<domain>`) | websecure | | authelia | 9090 |
|
||||
| searxng-webapp | searxng | Host(`searxng.<domain>`) | websecure | | | 8080 |
|
||||
| traefik | traefik | Host(`traefik.<domain>`) | websecure | | authelia | |
|
||||
@@ -8,6 +8,7 @@ This page explains where to find authoritative files quickly.
|
||||
- `apps/` — user/business applications (Nextcloud, Passbolt, Gitea, Gramps, SearXNG).
|
||||
- `monitoring/` — observability and operational tooling (Prometheus, Grafana, InfluxDB, Node-RED, etc.).
|
||||
- `infrastructure/terraform/` — brownfield Terraform inventory/reconciliation layers.
|
||||
- `infrastructure/ansible/` — phase-1 Ansible inventory/configuration scaffold and validation playbooks.
|
||||
- `docs/` — repository-level architecture and workflow documentation.
|
||||
- `archive/` — historical compose/config artifacts not part of active runtime composition.
|
||||
- `secrets/` — local secret material and templates; never commit real values.
|
||||
@@ -17,6 +18,8 @@ This page explains where to find authoritative files quickly.
|
||||
- `services-up.sh` — runtime composition entrypoint for multi-compose environment.
|
||||
- `default-network.yml` — shared docker network definitions used across compose files.
|
||||
- `default-environment.env` — non-secret default env values for compose rendering.
|
||||
- `scripts/codex-setup.sh` — Codex/bootstrap helper to install validation tooling and prepare dummy secret material.
|
||||
- `scripts/codex-maintenance.sh` — Codex maintenance helper to refresh tooling, reconcile dummy secret material, and run safe Ansible validation checks.
|
||||
- `docs/deployment-prerequisites.md` — prerequisite setup before runtime operations.
|
||||
- `docs/security-secrets.md` — secrets documentation and inventory model.
|
||||
|
||||
@@ -30,8 +33,10 @@ This page explains where to find authoritative files quickly.
|
||||
|
||||
## Fast path for future Codex runs
|
||||
|
||||
1. Read [README.md](../README.md).
|
||||
1. Read [README.md](https://github.com/beatz174-bit/docker/blob/main/README.md).
|
||||
2. Read [docs/source-of-truth.md](source-of-truth.md).
|
||||
3. Read [docs/docker-environment.md](docker-environment.md).
|
||||
4. Read [docs/terraform-workflows.md](terraform-workflows.md).
|
||||
5. Only then edit Compose/Terraform files.
|
||||
|
||||
6. For Ansible bootstrap changes, validate inventory and playbook syntax checks only.
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
|
||||
This page explains how secret material is organized in this repository and where to find both human-readable and machine-readable references.
|
||||
|
||||
For machine-readable inventory metadata, use [`../secrets/inventory.json`](../secrets/inventory.json).
|
||||
For machine-readable inventory metadata, use [`secrets/inventory.json`](https://github.com/beatz174-bit/docker/blob/main/secrets/inventory.json).
|
||||
|
||||
## Scope and authority
|
||||
|
||||
- Canonical example template: [`../secrets/.env.secrets.example`](../secrets/.env.secrets.example)
|
||||
- Canonical example template: [`secrets/.env.secrets.example`](https://github.com/beatz174-bit/docker/blob/main/secrets/.env.secrets.example)
|
||||
- Runtime-loaded secret env file (local, non-committed): `../secrets/stack-secrets.env`
|
||||
- Dynu DNS inventory env file (local, non-committed): `../secrets/dynu.env`
|
||||
- Docker secret files (local, non-committed): `../secrets/*.txt`
|
||||
|
||||
Treat the example template as the canonical shape for expected environment variables.
|
||||
@@ -20,14 +21,16 @@ Treat the example template as the canonical shape for expected environment varia
|
||||
- Document expected variable names and usage expectations.
|
||||
2. **Local runtime env file (`stack-secrets.env`)**
|
||||
- Holds local runtime secret values loaded during compose rendering.
|
||||
3. **Local Docker secret files (`*.txt`)**
|
||||
3. **Local Dynu env file (`dynu.env`)**
|
||||
- Holds `DYNU_*` values used by read-only Dynu DNS inventory scripts.
|
||||
4. **Local Docker secret files (`*.txt`)**
|
||||
- Hold password/token material consumed via `*_FILE` style configuration.
|
||||
4. **Externally managed secret inputs**
|
||||
5. **Externally managed secret inputs**
|
||||
- Some values are managed outside shared templates and provided through file mounts or environment substitution.
|
||||
|
||||
## Machine-readable inventory
|
||||
|
||||
- Primary automation source: [`../secrets/inventory.json`](../secrets/inventory.json)
|
||||
- Primary automation source: [`secrets/inventory.json`](https://github.com/beatz174-bit/docker/blob/main/secrets/inventory.json)
|
||||
- Human guidance source: this page
|
||||
|
||||
Automation should parse `secrets/inventory.json` directly rather than scraping Markdown tables.
|
||||
@@ -41,6 +44,7 @@ Before running compose operations, follow [`./deployment-prerequisites.md`](./de
|
||||
Never commit:
|
||||
|
||||
- `secrets/stack-secrets.env`
|
||||
- `secrets/dynu.env`
|
||||
- real `secrets/*.txt` secret files
|
||||
- real Terraform `.tfvars` files containing credentials
|
||||
- Terraform state files with sensitive runtime metadata
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# Public Showcase
|
||||
|
||||
This environment showcases practical infrastructure operations with a documentation-first approach.
|
||||
|
||||
Highlights:
|
||||
|
||||
- Container orchestration with Docker Compose.
|
||||
- Reverse proxy routing and TLS-focused service exposure patterns.
|
||||
- Monitoring and alerting configuration via version-controlled rules.
|
||||
- Documentation automation through CI.
|
||||
- Generated architecture diagrams to aid operational understanding.
|
||||
- Sanitized public documentation for safe sharing.
|
||||
|
||||
This page intentionally avoids private hostnames, internal-only URLs, credentials, and secret values.
|
||||
@@ -10,6 +10,7 @@ This repository has multiple layers. Knowing the authority for each layer preven
|
||||
| Docker shared baseline inputs | `default-network.yml`, `default-environment.env`, `secrets/stack-secrets.env` | Shared network/env material applied during compose rendering. |
|
||||
| Infrastructure inventory and reconciliation | Terraform under `infrastructure/terraform/` | Codified inventory of existing infrastructure and relationships, especially Proxmox VMs and selected Docker mirrors. |
|
||||
| Secret policy and inventory | `docs/security-secrets.md` + `secrets/inventory.json` + local secret files in `secrets/` | What secrets exist, where they are expected, and what automation should parse. |
|
||||
| Host/device configuration bootstrap (emerging) | Ansible under `infrastructure/ansible/` | Gradual inventory/configuration layer for hosts/devices; validation-first at current stage. |
|
||||
|
||||
## Practical meaning
|
||||
|
||||
@@ -29,6 +30,21 @@ Use Terraform when documenting/reconciling existing:
|
||||
|
||||
Do **not** treat Terraform as a full replacement for Compose operations in this repo.
|
||||
|
||||
- Dynu public DNS records remain authoritative at Dynu.
|
||||
- Terraform Dynu configuration mirrors/reconciles Dynu DNS state for documentation and controlled drift management.
|
||||
- Imported Dynu Terraform state reflects actual provider-side DNS state at import time.
|
||||
|
||||
|
||||
### Ansible bootstrap decisions
|
||||
|
||||
Use Ansible under `infrastructure/ansible/` to build inventory and configuration structure incrementally.
|
||||
|
||||
At the current stage:
|
||||
|
||||
- Do **not** treat Ansible as replacement authority for Docker runtime operations.
|
||||
- Do **not** treat Ansible as replacement authority for Terraform inventory/reconciliation.
|
||||
- NixOS remains outside Ansible authority unless explicitly adopted in a later phase.
|
||||
|
||||
## Declared config vs observed/runtime state
|
||||
|
||||
- **Declared config**: files in this repository (Compose, Terraform, docs).
|
||||
|
||||
@@ -43,6 +43,21 @@ Use for existing Proxmox VMs and metadata reconciliation.
|
||||
5. Keep lifecycle ignore rules narrow and explicit.
|
||||
6. Iterate per VM until plan stabilizes.
|
||||
|
||||
|
||||
## Dynu DNS workflow
|
||||
|
||||
Directory: `infrastructure/terraform/dynu/`
|
||||
|
||||
Use for existing Dynu domains and DNS records.
|
||||
|
||||
1. Add or confirm the documentation catalog entry for one hostname.
|
||||
2. Confirm the provider resource type and import ID format.
|
||||
3. Import one existing domain or DNS record at a time.
|
||||
4. Inspect state with `terraform state show`.
|
||||
5. Reconcile only stable, meaningful attributes into hand-maintained `.tf`.
|
||||
6. Keep record IDs, dynamic DNS targets, and provider-computed values out unless intentionally required.
|
||||
7. Re-run plan until intended scope is clean.
|
||||
|
||||
## Physical host metadata workflow
|
||||
|
||||
Physical host metadata currently lives in Proxmox Terraform locals/outputs and is used as documentation inventory context.
|
||||
@@ -75,4 +90,4 @@ Treat generated files as:
|
||||
|
||||
- [docs/source-of-truth.md](source-of-truth.md)
|
||||
- [docs/infrastructure-inventory.md](infrastructure-inventory.md)
|
||||
- [infrastructure/terraform/README.md](../infrastructure/terraform/README.md)
|
||||
- [infrastructure/terraform/README.md](https://github.com/beatz174-bit/docker/blob/main/infrastructure/terraform/README.md)
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# Ansible Foundation (Phase 1)
|
||||
|
||||
This directory provides a minimal Ansible bootstrap for this repository.
|
||||
|
||||
## Purpose
|
||||
|
||||
- Establish a maintainable inventory/configuration foundation for hosts and devices.
|
||||
- Support gradual host onboarding and validation workflows.
|
||||
- Keep boundaries clear with existing Compose and Terraform authorities.
|
||||
|
||||
This is intentionally a **foundation stage**, not full production automation.
|
||||
|
||||
## Boundaries
|
||||
|
||||
- Docker runtime authority remains in Compose files and `services-up.sh`.
|
||||
- Terraform remains the primary structured infrastructure inventory/reconciliation layer.
|
||||
- Ansible here is a complementary configuration/inventory layer.
|
||||
- NixOS and network gear management are not authoritative through Ansible yet.
|
||||
|
||||
## Structure
|
||||
|
||||
- `ansible.cfg` - local defaults for inventory, collections, and output behavior.
|
||||
- `inventory/hosts.yml` - YAML inventory scaffold with starter groups.
|
||||
- `inventory/group_vars/` - shared/group variables.
|
||||
- `inventory/host_vars/` - per-host variables.
|
||||
- `playbooks/ping.yml` - minimal syntax/connection test playbook.
|
||||
- `playbooks/dns-inventory.yml` - local-only Dynu DNS read-only inventory wrapper.
|
||||
- `collections/requirements.yml` - lightweight baseline collections.
|
||||
- `roles/` - reserved for future incremental role adoption.
|
||||
|
||||
## Basic commands
|
||||
|
||||
Run from repository root:
|
||||
|
||||
```bash
|
||||
ansible --version
|
||||
ansible-lint --version
|
||||
ansible-galaxy collection install -r infrastructure/ansible/collections/requirements.yml -p infrastructure/ansible/collections
|
||||
ansible-inventory -i infrastructure/ansible/inventory/hosts.yml --list
|
||||
ansible-playbook -i infrastructure/ansible/inventory/hosts.yml infrastructure/ansible/playbooks/ping.yml --syntax-check
|
||||
ansible-playbook -i infrastructure/ansible/inventory/hosts.yml infrastructure/ansible/playbooks/dns-inventory.yml --syntax-check
|
||||
```
|
||||
|
||||
## Secrets and safety
|
||||
|
||||
- Do not commit real credentials or private keys.
|
||||
- Put sensitive per-host variables in local, untracked files or a future vault approach.
|
||||
- Keep host and device entries factual; avoid speculative production entries.
|
||||
@@ -0,0 +1,9 @@
|
||||
[defaults]
|
||||
inventory = ./inventory/hosts.yml
|
||||
collections_path = ./collections
|
||||
retry_files_enabled = False
|
||||
stdout_callback = yaml
|
||||
host_key_checking = True
|
||||
|
||||
[inventory]
|
||||
enable_plugins = yaml
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
collections:
|
||||
- name: ansible.posix
|
||||
- name: community.general
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
# Bootstrap defaults for the Ansible foundation in this repository.
|
||||
# Keep secrets and environment-specific auth details out of version control.
|
||||
|
||||
# Common interpreter hint for modern Linux hosts. Override per-host if needed.
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
|
||||
# Placeholders for future connection/auth settings:
|
||||
# ansible_user: ""
|
||||
# ansible_port: 22
|
||||
# ansible_ssh_private_key_file: ""
|
||||
|
||||
# Add group-specific settings under inventory/group_vars/<group>.yml
|
||||
# and host-specific settings under inventory/host_vars/<host>.yml.
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
all:
|
||||
children:
|
||||
linux:
|
||||
hosts: {}
|
||||
network:
|
||||
hosts: {}
|
||||
virtualization:
|
||||
hosts: {}
|
||||
nixos:
|
||||
hosts: {}
|
||||
examples:
|
||||
hosts:
|
||||
example-managed-host:
|
||||
ansible_host: example-host.local
|
||||
ansible_connection: ssh
|
||||
# Example only: replace/remove before real operations.
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
# This integration is intentionally read-only.
|
||||
# No Dynu mutations are permitted in this repo at this stage.
|
||||
- name: Build Dynu DNS read-only inventory artifacts
|
||||
hosts: localhost
|
||||
connection: local
|
||||
gather_facts: false
|
||||
vars:
|
||||
repo_root: "{{ playbook_dir }}/../../.."
|
||||
|
||||
tasks:
|
||||
- name: Assert read-only guard variable is set
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- lookup('ansible.builtin.env', 'DYNU_READ_ONLY') == 'true'
|
||||
fail_msg: "Refusing to run: DYNU_READ_ONLY must be exactly 'true'."
|
||||
|
||||
- name: Fetch Dynu DNS (GET-only script)
|
||||
ansible.builtin.command: python3 scripts/dynu/fetch_dynu_dns.py
|
||||
args:
|
||||
chdir: "{{ repo_root }}"
|
||||
|
||||
- name: Correlate Dynu with Traefik and generate docs
|
||||
ansible.builtin.command: python3 scripts/dynu/correlate_dynu_with_traefik.py
|
||||
args:
|
||||
chdir: "{{ repo_root }}"
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
- name: Basic inventory and connectivity check
|
||||
hosts: all
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- name: Ping managed hosts
|
||||
ansible.builtin.ping:
|
||||
@@ -10,17 +10,21 @@ It does **not** replace Docker Compose as runtime deployment authority.
|
||||
- Physical host metadata represented in Terraform locals/outputs.
|
||||
- Select Docker container mirror resources for documentation-oriented tracking.
|
||||
- Outputs that can support documentation and later downstream tooling.
|
||||
- Dynu DNS domain/record import and documentation inventory.
|
||||
|
||||
## What Terraform is not used for (today)
|
||||
|
||||
- Replacing `services-up.sh` / Compose for day-to-day app runtime orchestration.
|
||||
- Broad, immediate greenfield provisioning of the whole stack.
|
||||
- Casual `apply` operations across all infrastructure.
|
||||
- Replacing Dynu as DNS authority.
|
||||
- Blindly recreating production DNS records without import/reconciliation.
|
||||
|
||||
## Directory map
|
||||
|
||||
- `proxmox/` — imported/reconciled VM resources and host metadata outputs.
|
||||
- `docker/` — selective Docker container import/mirror resources.
|
||||
- `dynu/` — Dynu DNS brownfield import/reconciliation and DNS documentation outputs.
|
||||
- `bootstrap/` — backend/provider bootstrap scaffolding.
|
||||
- `modules/` — placeholder module directories for future stable abstractions.
|
||||
- `scripts/reconcile_from_plan.sh` — helper to convert generated plan config into reviewable draft files.
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.terraform.io/beatz174-bit/dynu" {
|
||||
version = "0.3.0"
|
||||
hashes = [
|
||||
"h1:yftAEp/lcPmbVRV8YenFaMJElUkt3j79TSt3OcQnwk4=",
|
||||
"zh:1f7344737dff5b12155e8308b1cb55b0cc6f83a4a5776eb1cc2273bc84bb8fa2",
|
||||
"zh:212662c4f5b979401f282f7f9856480bad86d9e488110bebc589a4eeb892ff02",
|
||||
"zh:2ed5294fc7db7639c41f99a9d7bcfff6585f1f372eb23cd8229adfe219cba63a",
|
||||
"zh:30fc9df00120f309ae969ae107e4dc4a0b04517f2c07a78934a2c479995f77b8",
|
||||
"zh:3126369f6dc86e8083ec12a2643ea87a13543ed12631b40375b7b9563da41474",
|
||||
"zh:3c5775a6763608253e2698b85dcee42eafd6ca8e08a8851866123de403a331b0",
|
||||
"zh:50bedffbee48505604d05181172018143e54c68249761a749fb7c115eec4ce04",
|
||||
"zh:528549a2763dd2fbf3ffe047fa19c0524eb08addd777c9c0350800394ff16235",
|
||||
"zh:99049e25d7d3fb26e2a94d6e609c8989efcee1af1568a77110a70bce2c01f1ef",
|
||||
"zh:9a6490b67aca08b135e5ba7092fc315e67177be1f280f6c0d06b9c64a0892d3a",
|
||||
"zh:bfaae08fb5a0b10184a7b6e8382038d0a0b9936ea13efa1462c35cee902b0c14",
|
||||
"zh:c0dbe59b9bfcbd42f3da1732615669a579275acc654c75127612d86f319b38b3",
|
||||
"zh:f809ab383cca0a5f83072981c64208cbd7fa67e986a86ee02dd2c82333221e32",
|
||||
"zh:f815fa2f8681477f159eb9a32b78f8988065e5040b7feea42604bac469c2c4eb",
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
# Dynu Terraform Layer (Brownfield DNS Reconciliation)
|
||||
|
||||
This Terraform root is for **Dynu DNS brownfield reconciliation**. The intended pattern is:
|
||||
|
||||
1. Import the existing root domain object.
|
||||
2. Read inventory through `data.dynu_dns_records.root`.
|
||||
3. Generate reviewable `dynu_dns_record` resources and import commands.
|
||||
4. Import every existing DNS record into matching Terraform resources.
|
||||
5. Use `terraform plan` as the reconciliation check before any apply.
|
||||
|
||||
## Provider behavior to keep in mind
|
||||
|
||||
- Source: `beatz174-bit/dynu`
|
||||
- `dynu_domain` import requires a **numeric Dynu domain ID**.
|
||||
- Importing `dynu_domain` imports only the root domain object.
|
||||
- It **does not** import DNS records/subdomains.
|
||||
- `dynu_dns_record` imports require `<domain_id>/<record_id>`.
|
||||
|
||||
## Variables
|
||||
|
||||
- `dynu_root_domain` (default: `lan.ddnsgeek.com`)
|
||||
- `dynu_api_key` (sensitive)
|
||||
- `dynu_username` / `dynu_password` (optional)
|
||||
|
||||
## Safe validation commands
|
||||
|
||||
```bash
|
||||
cd infrastructure/terraform/dynu
|
||||
terraform fmt -check -recursive
|
||||
terraform init -backend=false -input=false
|
||||
terraform validate
|
||||
python3 -m py_compile scripts/generate-brownfield-records.py
|
||||
```
|
||||
|
||||
## Brownfield workflow
|
||||
|
||||
```bash
|
||||
cd infrastructure/terraform/dynu
|
||||
|
||||
terraform init
|
||||
terraform import dynu_domain.lan_ddnsgeek_com '<numeric-dynu-domain-id>'
|
||||
|
||||
terraform apply -refresh-only
|
||||
terraform output -json dynu_dns_records > /tmp/dynu-records.json
|
||||
|
||||
python3 scripts/generate-brownfield-records.py --dry-run
|
||||
python3 scripts/generate-brownfield-records.py --overwrite
|
||||
|
||||
# Review generated/dynu_dns_records.generated.tf
|
||||
# Review generated/import-dynu-dns-records.sh
|
||||
|
||||
bash generated/import-dynu-dns-records.sh
|
||||
|
||||
terraform plan
|
||||
```
|
||||
|
||||
## What each component means
|
||||
|
||||
- `data.dynu_dns_records.root`: read-only live inventory from Dynu.
|
||||
- `generated/dynu_dns_records.generated.tf`: generated management-intent resources; includes `prevent_destroy = true` on each record.
|
||||
- `generated/import-dynu-dns-records.sh`: imports each discovered record to its generated `dynu_dns_record` address using `<domain_id>/<record_id>`.
|
||||
- `terraform plan` after imports: reconciliation checkpoint. Any create/update/delete must be reviewed manually before apply.
|
||||
|
||||
## Generated artifacts
|
||||
|
||||
The helper script writes these files under `generated/`:
|
||||
|
||||
- `generated/dynu_dns_records_inventory.json`
|
||||
- `generated/dynu_dns_records.generated.tf`
|
||||
- `generated/import-dynu-dns-records.sh`
|
||||
|
||||
These are generated outputs meant for operator review before use in production.
|
||||
|
||||
|
||||
### Generator output selection (interactive + automation)
|
||||
|
||||
The brownfield generator defaults to Terraform output `dynu_dns_records`:
|
||||
|
||||
```bash
|
||||
python3 scripts/generate-brownfield-records.py --dry-run
|
||||
```
|
||||
|
||||
If the default output is missing/unusable and stdin is interactive, the script shows a picker of available Terraform outputs and indicates which ones are usable for DNS imports.
|
||||
|
||||
```bash
|
||||
# Interactive mode: choose from available Terraform outputs
|
||||
python3 scripts/generate-brownfield-records.py --dry-run
|
||||
|
||||
# Non-interactive mode: specify output explicitly
|
||||
python3 scripts/generate-brownfield-records.py \
|
||||
--records-output dynu_dns_inventory \
|
||||
--dry-run
|
||||
|
||||
# Disable menu and fail fast
|
||||
python3 scripts/generate-brownfield-records.py \
|
||||
--no-interactive \
|
||||
--dry-run
|
||||
|
||||
# Use saved terraform output JSON and choose interactively
|
||||
terraform output -json > generated/terraform-output.json
|
||||
python3 scripts/generate-brownfield-records.py \
|
||||
--from-file generated/terraform-output.json \
|
||||
--dry-run
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- The menu shows Terraform outputs currently stored in state.
|
||||
- If newly added outputs do not appear, run:
|
||||
|
||||
```bash
|
||||
terraform apply -refresh-only
|
||||
```
|
||||
|
||||
- The selected output must contain real Dynu provider record fields:
|
||||
- `id`
|
||||
- `domain_id`
|
||||
- `hostname`
|
||||
- `record_type`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Plan shows a large wall of `+` values under outputs
|
||||
|
||||
Cause:
|
||||
|
||||
Terraform is planning to save **new output values** to state (for example, live records from `data.dynu_dns_records.root`). This is not creating DNS records by itself.
|
||||
|
||||
How to verify:
|
||||
|
||||
- Output-only changes appear under `Changes to Outputs`.
|
||||
- Real DNS changes appear as `dynu_dns_record` resource create/update/delete actions.
|
||||
|
||||
Use:
|
||||
|
||||
```bash
|
||||
terraform apply -refresh-only
|
||||
```
|
||||
|
||||
to persist refreshed data source and output values only.
|
||||
|
||||
### Error: `There is no function named "regexreplace"`
|
||||
|
||||
Cause:
|
||||
|
||||
`regexreplace` is not a Terraform function. Resource-name slugification should not be implemented in Terraform HCL for this workflow.
|
||||
|
||||
Fix:
|
||||
|
||||
- Keep `inventory.tf` focused on reading live records via `data.dynu_dns_records.root`.
|
||||
- Keep Terraform outputs simple (for example, `<domain_id>/<record_id>` mappings).
|
||||
- Let `scripts/generate-brownfield-records.py` generate Terraform-safe resource names with Python `tf_name(record)`.
|
||||
|
||||
### Error: `'"'"'dynu_dns_records'"'"'`
|
||||
|
||||
Cause:
|
||||
|
||||
The helper script reads `terraform output -json` and expects an output named `dynu_dns_records`.
|
||||
|
||||
Fix:
|
||||
|
||||
```bash
|
||||
cd infrastructure/terraform/dynu
|
||||
terraform init
|
||||
terraform apply -refresh-only
|
||||
terraform output -json | jq 'keys'
|
||||
```
|
||||
|
||||
Confirm `dynu_dns_records` appears in the key list.
|
||||
|
||||
If it does not, check that the Terraform config contains:
|
||||
|
||||
```hcl
|
||||
data "dynu_dns_records" "root" {
|
||||
hostname = var.dynu_root_domain
|
||||
}
|
||||
|
||||
output "dynu_dns_records" {
|
||||
value = data.dynu_dns_records.root.records
|
||||
}
|
||||
```
|
||||
|
||||
Then rerun:
|
||||
|
||||
```bash
|
||||
python3 scripts/generate-brownfield-records.py --dry-run
|
||||
```
|
||||
@@ -0,0 +1,7 @@
|
||||
data "dynu_domain" "lan" {
|
||||
hostname = "lan.ddnsgeek.com"
|
||||
}
|
||||
|
||||
output "dynu_domain_id" {
|
||||
value = data.dynu_domain.lan.domain.id
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
locals {
|
||||
dynu_domain = var.dynu_root_domain
|
||||
}
|
||||
|
||||
# Import-first resource skeleton for the production Dynu zone.
|
||||
# `name` is required by provider schema and can be reconciled after import.
|
||||
resource "dynu_domain" "lan_ddnsgeek_com" {
|
||||
name = local.dynu_domain
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,518 @@
|
||||
# ---------------------------------------------------------------------------
|
||||
# GENERATED FILE - REVIEW BEFORE USE
|
||||
#
|
||||
# Generated from Dynu brownfield DNS inventory.
|
||||
# Do not blindly apply this file to production DNS.
|
||||
# Import records into Terraform state before allowing Terraform to manage them.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
resource "dynu_dns_record" "auth_lan_ddnsgeek_com_a_18483099" {
|
||||
hostname = "auth.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "auth"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "auth_lan_ddnsgeek_com_a_19646048" {
|
||||
hostname = "auth.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "auth"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "edge_lan_ddnsgeek_com_a_10453241" {
|
||||
hostname = "edge.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "edge"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "edge_lan_ddnsgeek_com_a_19646062" {
|
||||
hostname = "edge.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "edge"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "familytree_lan_ddnsgeek_com_a_17017685" {
|
||||
hostname = "familytree.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "familytree"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "familytree_lan_ddnsgeek_com_a_19646056" {
|
||||
hostname = "familytree.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "familytree"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "gitea_lan_ddnsgeek_com_a_14682463" {
|
||||
hostname = "gitea.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "gitea"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "gitea_lan_ddnsgeek_com_a_19646063" {
|
||||
hostname = "gitea.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "gitea"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "gotify_lan_ddnsgeek_com_a_17439061" {
|
||||
hostname = "gotify.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "gotify"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "gotify_lan_ddnsgeek_com_a_19646047" {
|
||||
hostname = "gotify.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "gotify"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "grafana_lan_ddnsgeek_com_a_18113762" {
|
||||
hostname = "grafana.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "grafana"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "grafana_lan_ddnsgeek_com_a_19646050" {
|
||||
hostname = "grafana.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "grafana"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "influxdb_lan_ddnsgeek_com_a_18562198" {
|
||||
hostname = "influxdb.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "influxdb"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "influxdb_lan_ddnsgeek_com_a_19646059" {
|
||||
hostname = "influxdb.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "influxdb"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "kuma_lan_ddnsgeek_com_a_17454978" {
|
||||
hostname = "kuma.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 90
|
||||
enabled = true
|
||||
content = "120.155.99.146"
|
||||
node_name = "kuma"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "lan_ddnsgeek_com_soa_8299670" {
|
||||
hostname = "lan.ddnsgeek.com"
|
||||
record_type = "SOA"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "ns1.dynu.com. administrator.dynu.com. 0 3600 900 604800 300"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "monitor_kuma_lan_ddnsgeek_com_a_17462342" {
|
||||
hostname = "monitor-kuma.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "monitor-kuma"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "monitor_kuma_lan_ddnsgeek_com_a_19646051" {
|
||||
hostname = "monitor-kuma.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "monitor-kuma"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "mtls_bridge_lan_ddnsgeek_com_a_19232643" {
|
||||
hostname = "mtls-bridge.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "mtls-bridge"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "mtls_bridge_lan_ddnsgeek_com_a_19646058" {
|
||||
hostname = "mtls-bridge.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "mtls-bridge"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "nextcloud_lan_ddnsgeek_com_a_10453260" {
|
||||
hostname = "nextcloud.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "nextcloud"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "nextcloud_lan_ddnsgeek_com_a_19646057" {
|
||||
hostname = "nextcloud.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "nextcloud"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "node_red_lan_ddnsgeek_com_a_19041230" {
|
||||
hostname = "node-red.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "node-red"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "node_red_lan_ddnsgeek_com_a_19646053" {
|
||||
hostname = "node-red.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "node-red"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "passbolt_lan_ddnsgeek_com_a_10453262" {
|
||||
hostname = "passbolt.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "passbolt"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "passbolt_lan_ddnsgeek_com_a_19646049" {
|
||||
hostname = "passbolt.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "passbolt"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "portainer_lan_ddnsgeek_com_a_17458810" {
|
||||
hostname = "portainer.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "portainer"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "portainer_lan_ddnsgeek_com_a_19646046" {
|
||||
hostname = "portainer.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "portainer"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "prometheus_lan_ddnsgeek_com_a_18483311" {
|
||||
hostname = "prometheus.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "prometheus"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "prometheus_lan_ddnsgeek_com_a_19646061" {
|
||||
hostname = "prometheus.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "prometheus"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "searxng_lan_ddnsgeek_com_a_10453263" {
|
||||
hostname = "searxng.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "searxng"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "searxng_lan_ddnsgeek_com_a_19646055" {
|
||||
hostname = "searxng.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "searxng"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "shifts_lan_ddnsgeek_com_a_15901565" {
|
||||
hostname = "shifts.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "shifts"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "shifts_lan_ddnsgeek_com_a_19646052" {
|
||||
hostname = "shifts.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "shifts"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "stockfill_lan_ddnsgeek_com_a_17081867" {
|
||||
hostname = "stockfill.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "stockfill"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "stockfill_lan_ddnsgeek_com_a_19646060" {
|
||||
hostname = "stockfill.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "stockfill"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "traefik_lan_ddnsgeek_com_a_10453240" {
|
||||
hostname = "traefik.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "traefik"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "traefik_lan_ddnsgeek_com_a_19646054" {
|
||||
hostname = "traefik.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "traefik"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,518 @@
|
||||
# ---------------------------------------------------------------------------
|
||||
# GENERATED FILE - REVIEW BEFORE USE
|
||||
#
|
||||
# Generated from Dynu brownfield DNS inventory.
|
||||
# Do not blindly apply this file to production DNS.
|
||||
# Import records into Terraform state before allowing Terraform to manage them.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
resource "dynu_dns_record" "auth_lan_ddnsgeek_com_a_18483099" {
|
||||
hostname = "auth.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "auth"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "auth_lan_ddnsgeek_com_a_19646048" {
|
||||
hostname = "auth.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "auth"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "edge_lan_ddnsgeek_com_a_10453241" {
|
||||
hostname = "edge.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "edge"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "edge_lan_ddnsgeek_com_a_19646062" {
|
||||
hostname = "edge.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "edge"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "familytree_lan_ddnsgeek_com_a_17017685" {
|
||||
hostname = "familytree.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "familytree"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "familytree_lan_ddnsgeek_com_a_19646056" {
|
||||
hostname = "familytree.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "familytree"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "gitea_lan_ddnsgeek_com_a_14682463" {
|
||||
hostname = "gitea.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "gitea"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "gitea_lan_ddnsgeek_com_a_19646063" {
|
||||
hostname = "gitea.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "gitea"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "gotify_lan_ddnsgeek_com_a_17439061" {
|
||||
hostname = "gotify.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "gotify"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "gotify_lan_ddnsgeek_com_a_19646047" {
|
||||
hostname = "gotify.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "gotify"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "grafana_lan_ddnsgeek_com_a_18113762" {
|
||||
hostname = "grafana.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "grafana"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "grafana_lan_ddnsgeek_com_a_19646050" {
|
||||
hostname = "grafana.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "grafana"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "influxdb_lan_ddnsgeek_com_a_18562198" {
|
||||
hostname = "influxdb.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "influxdb"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "influxdb_lan_ddnsgeek_com_a_19646059" {
|
||||
hostname = "influxdb.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "influxdb"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "kuma_lan_ddnsgeek_com_a_17454978" {
|
||||
hostname = "kuma.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 60
|
||||
enabled = true
|
||||
content = "120.155.99.146"
|
||||
node_name = "kuma"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "lan_ddnsgeek_com_soa_8299670" {
|
||||
hostname = "lan.ddnsgeek.com"
|
||||
record_type = "SOA"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "ns1.dynu.com. administrator.dynu.com. 0 3600 900 604800 300"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "monitor_kuma_lan_ddnsgeek_com_a_17462342" {
|
||||
hostname = "monitor-kuma.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "monitor-kuma"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "monitor_kuma_lan_ddnsgeek_com_a_19646051" {
|
||||
hostname = "monitor-kuma.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "monitor-kuma"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "mtls_bridge_lan_ddnsgeek_com_a_19232643" {
|
||||
hostname = "mtls-bridge.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "mtls-bridge"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "mtls_bridge_lan_ddnsgeek_com_a_19646058" {
|
||||
hostname = "mtls-bridge.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "mtls-bridge"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "nextcloud_lan_ddnsgeek_com_a_10453260" {
|
||||
hostname = "nextcloud.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "nextcloud"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "nextcloud_lan_ddnsgeek_com_a_19646057" {
|
||||
hostname = "nextcloud.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "nextcloud"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "node_red_lan_ddnsgeek_com_a_19041230" {
|
||||
hostname = "node-red.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "node-red"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "node_red_lan_ddnsgeek_com_a_19646053" {
|
||||
hostname = "node-red.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "node-red"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "passbolt_lan_ddnsgeek_com_a_10453262" {
|
||||
hostname = "passbolt.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "passbolt"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "passbolt_lan_ddnsgeek_com_a_19646049" {
|
||||
hostname = "passbolt.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "passbolt"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "portainer_lan_ddnsgeek_com_a_17458810" {
|
||||
hostname = "portainer.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "portainer"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "portainer_lan_ddnsgeek_com_a_19646046" {
|
||||
hostname = "portainer.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "portainer"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "prometheus_lan_ddnsgeek_com_a_18483311" {
|
||||
hostname = "prometheus.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "prometheus"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "prometheus_lan_ddnsgeek_com_a_19646061" {
|
||||
hostname = "prometheus.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "prometheus"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "searxng_lan_ddnsgeek_com_a_10453263" {
|
||||
hostname = "searxng.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "searxng"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "searxng_lan_ddnsgeek_com_a_19646055" {
|
||||
hostname = "searxng.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "searxng"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "shifts_lan_ddnsgeek_com_a_15901565" {
|
||||
hostname = "shifts.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "shifts"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "shifts_lan_ddnsgeek_com_a_19646052" {
|
||||
hostname = "shifts.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "shifts"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "stockfill_lan_ddnsgeek_com_a_17081867" {
|
||||
hostname = "stockfill.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "stockfill"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "stockfill_lan_ddnsgeek_com_a_19646060" {
|
||||
hostname = "stockfill.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "stockfill"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "traefik_lan_ddnsgeek_com_a_10453240" {
|
||||
hostname = "traefik.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
dynamic = true
|
||||
node_name = "traefik"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "dynu_dns_record" "traefik_lan_ddnsgeek_com_a_19646054" {
|
||||
hostname = "traefik.lan.ddnsgeek.com"
|
||||
record_type = "A"
|
||||
ttl = 120
|
||||
enabled = true
|
||||
content = "167.179.167.166"
|
||||
group = "home"
|
||||
node_name = "traefik"
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,534 @@
|
||||
[
|
||||
{
|
||||
"content": null,
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": null,
|
||||
"host": null,
|
||||
"hostname": "auth.lan.ddnsgeek.com",
|
||||
"id": 18483099,
|
||||
"node_name": "auth",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2026-02-17T12:59:58.803"
|
||||
},
|
||||
{
|
||||
"content": "167.179.167.166",
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": "home",
|
||||
"host": null,
|
||||
"hostname": "auth.lan.ddnsgeek.com",
|
||||
"id": 19646048,
|
||||
"node_name": "auth",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2026-05-12T21:11:37.693"
|
||||
},
|
||||
{
|
||||
"content": null,
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": null,
|
||||
"host": null,
|
||||
"hostname": "edge.lan.ddnsgeek.com",
|
||||
"id": 10453241,
|
||||
"node_name": "edge",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2024-03-18T01:27:26"
|
||||
},
|
||||
{
|
||||
"content": "167.179.167.166",
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": "home",
|
||||
"host": null,
|
||||
"hostname": "edge.lan.ddnsgeek.com",
|
||||
"id": 19646062,
|
||||
"node_name": "edge",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2026-05-12T21:11:45.237"
|
||||
},
|
||||
{
|
||||
"content": null,
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": null,
|
||||
"host": null,
|
||||
"hostname": "familytree.lan.ddnsgeek.com",
|
||||
"id": 17017685,
|
||||
"node_name": "familytree",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2025-11-17T04:43:07.953"
|
||||
},
|
||||
{
|
||||
"content": "167.179.167.166",
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": "home",
|
||||
"host": null,
|
||||
"hostname": "familytree.lan.ddnsgeek.com",
|
||||
"id": 19646056,
|
||||
"node_name": "familytree",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2026-05-12T21:11:44.25"
|
||||
},
|
||||
{
|
||||
"content": null,
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": null,
|
||||
"host": null,
|
||||
"hostname": "gitea.lan.ddnsgeek.com",
|
||||
"id": 14682463,
|
||||
"node_name": "gitea",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2025-07-16T03:15:50.38"
|
||||
},
|
||||
{
|
||||
"content": "167.179.167.166",
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": "home",
|
||||
"host": null,
|
||||
"hostname": "gitea.lan.ddnsgeek.com",
|
||||
"id": 19646063,
|
||||
"node_name": "gitea",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2026-05-12T21:11:45.343"
|
||||
},
|
||||
{
|
||||
"content": null,
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": null,
|
||||
"host": null,
|
||||
"hostname": "gotify.lan.ddnsgeek.com",
|
||||
"id": 17439061,
|
||||
"node_name": "gotify",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2025-12-16T09:35:47.307"
|
||||
},
|
||||
{
|
||||
"content": "167.179.167.166",
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": "home",
|
||||
"host": null,
|
||||
"hostname": "gotify.lan.ddnsgeek.com",
|
||||
"id": 19646047,
|
||||
"node_name": "gotify",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2026-05-12T21:11:37.693"
|
||||
},
|
||||
{
|
||||
"content": null,
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": null,
|
||||
"host": null,
|
||||
"hostname": "grafana.lan.ddnsgeek.com",
|
||||
"id": 18113762,
|
||||
"node_name": "grafana",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2026-01-28T04:38:25.92"
|
||||
},
|
||||
{
|
||||
"content": "167.179.167.166",
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": "home",
|
||||
"host": null,
|
||||
"hostname": "grafana.lan.ddnsgeek.com",
|
||||
"id": 19646050,
|
||||
"node_name": "grafana",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2026-05-12T21:11:37.72"
|
||||
},
|
||||
{
|
||||
"content": null,
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": null,
|
||||
"host": null,
|
||||
"hostname": "influxdb.lan.ddnsgeek.com",
|
||||
"id": 18562198,
|
||||
"node_name": "influxdb",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2026-02-22T16:46:26.85"
|
||||
},
|
||||
{
|
||||
"content": "167.179.167.166",
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": "home",
|
||||
"host": null,
|
||||
"hostname": "influxdb.lan.ddnsgeek.com",
|
||||
"id": 19646059,
|
||||
"node_name": "influxdb",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2026-05-12T21:11:44.85"
|
||||
},
|
||||
{
|
||||
"content": "120.155.99.146",
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": null,
|
||||
"host": null,
|
||||
"hostname": "kuma.lan.ddnsgeek.com",
|
||||
"id": 17454978,
|
||||
"node_name": "kuma",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 60,
|
||||
"updated_on": "2026-04-21T04:50:04.81"
|
||||
},
|
||||
{
|
||||
"content": "ns1.dynu.com. administrator.dynu.com. 0 3600 900 604800 300",
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": null,
|
||||
"host": null,
|
||||
"hostname": "lan.ddnsgeek.com",
|
||||
"id": 8299670,
|
||||
"node_name": null,
|
||||
"record_type": "SOA",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2022-03-15T10:08:15"
|
||||
},
|
||||
{
|
||||
"content": null,
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": null,
|
||||
"host": null,
|
||||
"hostname": "monitor-kuma.lan.ddnsgeek.com",
|
||||
"id": 17462342,
|
||||
"node_name": "monitor-kuma",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2025-12-17T14:47:25"
|
||||
},
|
||||
{
|
||||
"content": "167.179.167.166",
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": "home",
|
||||
"host": null,
|
||||
"hostname": "monitor-kuma.lan.ddnsgeek.com",
|
||||
"id": 19646051,
|
||||
"node_name": "monitor-kuma",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2026-05-12T21:11:37.727"
|
||||
},
|
||||
{
|
||||
"content": null,
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": null,
|
||||
"host": null,
|
||||
"hostname": "mtls-bridge.lan.ddnsgeek.com",
|
||||
"id": 19232643,
|
||||
"node_name": "mtls-bridge",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2026-04-13T04:20:00.11"
|
||||
},
|
||||
{
|
||||
"content": "167.179.167.166",
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": "home",
|
||||
"host": null,
|
||||
"hostname": "mtls-bridge.lan.ddnsgeek.com",
|
||||
"id": 19646058,
|
||||
"node_name": "mtls-bridge",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2026-05-12T21:11:44.737"
|
||||
},
|
||||
{
|
||||
"content": null,
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": null,
|
||||
"host": null,
|
||||
"hostname": "nextcloud.lan.ddnsgeek.com",
|
||||
"id": 10453260,
|
||||
"node_name": "nextcloud",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2024-03-18T01:40:00"
|
||||
},
|
||||
{
|
||||
"content": "167.179.167.166",
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": "home",
|
||||
"host": null,
|
||||
"hostname": "nextcloud.lan.ddnsgeek.com",
|
||||
"id": 19646057,
|
||||
"node_name": "nextcloud",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2026-05-12T21:11:44.53"
|
||||
},
|
||||
{
|
||||
"content": null,
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": null,
|
||||
"host": null,
|
||||
"hostname": "node-red.lan.ddnsgeek.com",
|
||||
"id": 19041230,
|
||||
"node_name": "node-red",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2026-03-30T04:51:39.68"
|
||||
},
|
||||
{
|
||||
"content": "167.179.167.166",
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": "home",
|
||||
"host": null,
|
||||
"hostname": "node-red.lan.ddnsgeek.com",
|
||||
"id": 19646053,
|
||||
"node_name": "node-red",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2026-05-12T21:11:37.737"
|
||||
},
|
||||
{
|
||||
"content": null,
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": null,
|
||||
"host": null,
|
||||
"hostname": "passbolt.lan.ddnsgeek.com",
|
||||
"id": 10453262,
|
||||
"node_name": "passbolt",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2024-03-18T01:40:18"
|
||||
},
|
||||
{
|
||||
"content": "167.179.167.166",
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": "home",
|
||||
"host": null,
|
||||
"hostname": "passbolt.lan.ddnsgeek.com",
|
||||
"id": 19646049,
|
||||
"node_name": "passbolt",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2026-05-12T21:11:37.693"
|
||||
},
|
||||
{
|
||||
"content": null,
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": null,
|
||||
"host": null,
|
||||
"hostname": "portainer.lan.ddnsgeek.com",
|
||||
"id": 17458810,
|
||||
"node_name": "portainer",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2025-12-17T10:23:40.077"
|
||||
},
|
||||
{
|
||||
"content": "167.179.167.166",
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": "home",
|
||||
"host": null,
|
||||
"hostname": "portainer.lan.ddnsgeek.com",
|
||||
"id": 19646046,
|
||||
"node_name": "portainer",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2026-05-12T21:11:37.693"
|
||||
},
|
||||
{
|
||||
"content": null,
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": null,
|
||||
"host": null,
|
||||
"hostname": "prometheus.lan.ddnsgeek.com",
|
||||
"id": 18483311,
|
||||
"node_name": "prometheus",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2026-02-17T13:17:00.55"
|
||||
},
|
||||
{
|
||||
"content": "167.179.167.166",
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": "home",
|
||||
"host": null,
|
||||
"hostname": "prometheus.lan.ddnsgeek.com",
|
||||
"id": 19646061,
|
||||
"node_name": "prometheus",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2026-05-12T21:11:45.033"
|
||||
},
|
||||
{
|
||||
"content": null,
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": null,
|
||||
"host": null,
|
||||
"hostname": "searxng.lan.ddnsgeek.com",
|
||||
"id": 10453263,
|
||||
"node_name": "searxng",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2024-03-18T01:40:34"
|
||||
},
|
||||
{
|
||||
"content": "167.179.167.166",
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": "home",
|
||||
"host": null,
|
||||
"hostname": "searxng.lan.ddnsgeek.com",
|
||||
"id": 19646055,
|
||||
"node_name": "searxng",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2026-05-12T21:11:38.797"
|
||||
},
|
||||
{
|
||||
"content": null,
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": null,
|
||||
"host": null,
|
||||
"hostname": "shifts.lan.ddnsgeek.com",
|
||||
"id": 15901565,
|
||||
"node_name": "shifts",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2025-09-30T04:25:20.65"
|
||||
},
|
||||
{
|
||||
"content": "167.179.167.166",
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": "home",
|
||||
"host": null,
|
||||
"hostname": "shifts.lan.ddnsgeek.com",
|
||||
"id": 19646052,
|
||||
"node_name": "shifts",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2026-05-12T21:11:37.727"
|
||||
},
|
||||
{
|
||||
"content": null,
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": null,
|
||||
"host": null,
|
||||
"hostname": "stockfill.lan.ddnsgeek.com",
|
||||
"id": 17081867,
|
||||
"node_name": "stockfill",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2025-11-21T06:49:33.47"
|
||||
},
|
||||
{
|
||||
"content": "167.179.167.166",
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": "home",
|
||||
"host": null,
|
||||
"hostname": "stockfill.lan.ddnsgeek.com",
|
||||
"id": 19646060,
|
||||
"node_name": "stockfill",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2026-05-12T21:11:44.987"
|
||||
},
|
||||
{
|
||||
"content": null,
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": null,
|
||||
"host": null,
|
||||
"hostname": "traefik.lan.ddnsgeek.com",
|
||||
"id": 10453240,
|
||||
"node_name": "traefik",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2024-03-18T01:27:13"
|
||||
},
|
||||
{
|
||||
"content": "167.179.167.166",
|
||||
"domain_id": 9695470,
|
||||
"domain_name": "lan.ddnsgeek.com",
|
||||
"group": "home",
|
||||
"host": null,
|
||||
"hostname": "traefik.lan.ddnsgeek.com",
|
||||
"id": 19646054,
|
||||
"node_name": "traefik",
|
||||
"record_type": "A",
|
||||
"state": true,
|
||||
"ttl": 120,
|
||||
"updated_on": "2026-05-12T21:11:37.737"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,243 @@
|
||||
#!/usr/bin/env bash
|
||||
# ---------------------------------------------------------------------------
|
||||
# GENERATED FILE - REVIEW BEFORE USE
|
||||
#
|
||||
# Imports existing Dynu DNS records into Terraform state.
|
||||
# Does not apply changes.
|
||||
# ---------------------------------------------------------------------------
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TF_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
cd "${TF_ROOT}"
|
||||
|
||||
# Re-running imports will fail for resources already in state.
|
||||
# This script skips imports when state already contains the resource address.
|
||||
|
||||
if terraform state show 'dynu_dns_record.auth_lan_ddnsgeek_com_a_18483099' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.auth_lan_ddnsgeek_com_a_18483099'
|
||||
else
|
||||
terraform import 'dynu_dns_record.auth_lan_ddnsgeek_com_a_18483099' '9695470/18483099'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.auth_lan_ddnsgeek_com_a_19646048' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.auth_lan_ddnsgeek_com_a_19646048'
|
||||
else
|
||||
terraform import 'dynu_dns_record.auth_lan_ddnsgeek_com_a_19646048' '9695470/19646048'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.edge_lan_ddnsgeek_com_a_10453241' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.edge_lan_ddnsgeek_com_a_10453241'
|
||||
else
|
||||
terraform import 'dynu_dns_record.edge_lan_ddnsgeek_com_a_10453241' '9695470/10453241'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.edge_lan_ddnsgeek_com_a_19646062' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.edge_lan_ddnsgeek_com_a_19646062'
|
||||
else
|
||||
terraform import 'dynu_dns_record.edge_lan_ddnsgeek_com_a_19646062' '9695470/19646062'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.familytree_lan_ddnsgeek_com_a_17017685' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.familytree_lan_ddnsgeek_com_a_17017685'
|
||||
else
|
||||
terraform import 'dynu_dns_record.familytree_lan_ddnsgeek_com_a_17017685' '9695470/17017685'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.familytree_lan_ddnsgeek_com_a_19646056' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.familytree_lan_ddnsgeek_com_a_19646056'
|
||||
else
|
||||
terraform import 'dynu_dns_record.familytree_lan_ddnsgeek_com_a_19646056' '9695470/19646056'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.gitea_lan_ddnsgeek_com_a_14682463' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.gitea_lan_ddnsgeek_com_a_14682463'
|
||||
else
|
||||
terraform import 'dynu_dns_record.gitea_lan_ddnsgeek_com_a_14682463' '9695470/14682463'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.gitea_lan_ddnsgeek_com_a_19646063' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.gitea_lan_ddnsgeek_com_a_19646063'
|
||||
else
|
||||
terraform import 'dynu_dns_record.gitea_lan_ddnsgeek_com_a_19646063' '9695470/19646063'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.gotify_lan_ddnsgeek_com_a_17439061' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.gotify_lan_ddnsgeek_com_a_17439061'
|
||||
else
|
||||
terraform import 'dynu_dns_record.gotify_lan_ddnsgeek_com_a_17439061' '9695470/17439061'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.gotify_lan_ddnsgeek_com_a_19646047' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.gotify_lan_ddnsgeek_com_a_19646047'
|
||||
else
|
||||
terraform import 'dynu_dns_record.gotify_lan_ddnsgeek_com_a_19646047' '9695470/19646047'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.grafana_lan_ddnsgeek_com_a_18113762' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.grafana_lan_ddnsgeek_com_a_18113762'
|
||||
else
|
||||
terraform import 'dynu_dns_record.grafana_lan_ddnsgeek_com_a_18113762' '9695470/18113762'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.grafana_lan_ddnsgeek_com_a_19646050' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.grafana_lan_ddnsgeek_com_a_19646050'
|
||||
else
|
||||
terraform import 'dynu_dns_record.grafana_lan_ddnsgeek_com_a_19646050' '9695470/19646050'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.influxdb_lan_ddnsgeek_com_a_18562198' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.influxdb_lan_ddnsgeek_com_a_18562198'
|
||||
else
|
||||
terraform import 'dynu_dns_record.influxdb_lan_ddnsgeek_com_a_18562198' '9695470/18562198'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.influxdb_lan_ddnsgeek_com_a_19646059' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.influxdb_lan_ddnsgeek_com_a_19646059'
|
||||
else
|
||||
terraform import 'dynu_dns_record.influxdb_lan_ddnsgeek_com_a_19646059' '9695470/19646059'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.kuma_lan_ddnsgeek_com_a_17454978' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.kuma_lan_ddnsgeek_com_a_17454978'
|
||||
else
|
||||
terraform import 'dynu_dns_record.kuma_lan_ddnsgeek_com_a_17454978' '9695470/17454978'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.lan_ddnsgeek_com_soa_8299670' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.lan_ddnsgeek_com_soa_8299670'
|
||||
else
|
||||
terraform import 'dynu_dns_record.lan_ddnsgeek_com_soa_8299670' '9695470/8299670'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.monitor_kuma_lan_ddnsgeek_com_a_17462342' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.monitor_kuma_lan_ddnsgeek_com_a_17462342'
|
||||
else
|
||||
terraform import 'dynu_dns_record.monitor_kuma_lan_ddnsgeek_com_a_17462342' '9695470/17462342'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.monitor_kuma_lan_ddnsgeek_com_a_19646051' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.monitor_kuma_lan_ddnsgeek_com_a_19646051'
|
||||
else
|
||||
terraform import 'dynu_dns_record.monitor_kuma_lan_ddnsgeek_com_a_19646051' '9695470/19646051'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.mtls_bridge_lan_ddnsgeek_com_a_19232643' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.mtls_bridge_lan_ddnsgeek_com_a_19232643'
|
||||
else
|
||||
terraform import 'dynu_dns_record.mtls_bridge_lan_ddnsgeek_com_a_19232643' '9695470/19232643'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.mtls_bridge_lan_ddnsgeek_com_a_19646058' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.mtls_bridge_lan_ddnsgeek_com_a_19646058'
|
||||
else
|
||||
terraform import 'dynu_dns_record.mtls_bridge_lan_ddnsgeek_com_a_19646058' '9695470/19646058'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.nextcloud_lan_ddnsgeek_com_a_10453260' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.nextcloud_lan_ddnsgeek_com_a_10453260'
|
||||
else
|
||||
terraform import 'dynu_dns_record.nextcloud_lan_ddnsgeek_com_a_10453260' '9695470/10453260'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.nextcloud_lan_ddnsgeek_com_a_19646057' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.nextcloud_lan_ddnsgeek_com_a_19646057'
|
||||
else
|
||||
terraform import 'dynu_dns_record.nextcloud_lan_ddnsgeek_com_a_19646057' '9695470/19646057'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.node_red_lan_ddnsgeek_com_a_19041230' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.node_red_lan_ddnsgeek_com_a_19041230'
|
||||
else
|
||||
terraform import 'dynu_dns_record.node_red_lan_ddnsgeek_com_a_19041230' '9695470/19041230'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.node_red_lan_ddnsgeek_com_a_19646053' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.node_red_lan_ddnsgeek_com_a_19646053'
|
||||
else
|
||||
terraform import 'dynu_dns_record.node_red_lan_ddnsgeek_com_a_19646053' '9695470/19646053'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.passbolt_lan_ddnsgeek_com_a_10453262' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.passbolt_lan_ddnsgeek_com_a_10453262'
|
||||
else
|
||||
terraform import 'dynu_dns_record.passbolt_lan_ddnsgeek_com_a_10453262' '9695470/10453262'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.passbolt_lan_ddnsgeek_com_a_19646049' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.passbolt_lan_ddnsgeek_com_a_19646049'
|
||||
else
|
||||
terraform import 'dynu_dns_record.passbolt_lan_ddnsgeek_com_a_19646049' '9695470/19646049'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.portainer_lan_ddnsgeek_com_a_17458810' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.portainer_lan_ddnsgeek_com_a_17458810'
|
||||
else
|
||||
terraform import 'dynu_dns_record.portainer_lan_ddnsgeek_com_a_17458810' '9695470/17458810'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.portainer_lan_ddnsgeek_com_a_19646046' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.portainer_lan_ddnsgeek_com_a_19646046'
|
||||
else
|
||||
terraform import 'dynu_dns_record.portainer_lan_ddnsgeek_com_a_19646046' '9695470/19646046'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.prometheus_lan_ddnsgeek_com_a_18483311' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.prometheus_lan_ddnsgeek_com_a_18483311'
|
||||
else
|
||||
terraform import 'dynu_dns_record.prometheus_lan_ddnsgeek_com_a_18483311' '9695470/18483311'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.prometheus_lan_ddnsgeek_com_a_19646061' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.prometheus_lan_ddnsgeek_com_a_19646061'
|
||||
else
|
||||
terraform import 'dynu_dns_record.prometheus_lan_ddnsgeek_com_a_19646061' '9695470/19646061'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.searxng_lan_ddnsgeek_com_a_10453263' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.searxng_lan_ddnsgeek_com_a_10453263'
|
||||
else
|
||||
terraform import 'dynu_dns_record.searxng_lan_ddnsgeek_com_a_10453263' '9695470/10453263'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.searxng_lan_ddnsgeek_com_a_19646055' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.searxng_lan_ddnsgeek_com_a_19646055'
|
||||
else
|
||||
terraform import 'dynu_dns_record.searxng_lan_ddnsgeek_com_a_19646055' '9695470/19646055'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.shifts_lan_ddnsgeek_com_a_15901565' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.shifts_lan_ddnsgeek_com_a_15901565'
|
||||
else
|
||||
terraform import 'dynu_dns_record.shifts_lan_ddnsgeek_com_a_15901565' '9695470/15901565'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.shifts_lan_ddnsgeek_com_a_19646052' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.shifts_lan_ddnsgeek_com_a_19646052'
|
||||
else
|
||||
terraform import 'dynu_dns_record.shifts_lan_ddnsgeek_com_a_19646052' '9695470/19646052'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.stockfill_lan_ddnsgeek_com_a_17081867' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.stockfill_lan_ddnsgeek_com_a_17081867'
|
||||
else
|
||||
terraform import 'dynu_dns_record.stockfill_lan_ddnsgeek_com_a_17081867' '9695470/17081867'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.stockfill_lan_ddnsgeek_com_a_19646060' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.stockfill_lan_ddnsgeek_com_a_19646060'
|
||||
else
|
||||
terraform import 'dynu_dns_record.stockfill_lan_ddnsgeek_com_a_19646060' '9695470/19646060'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.traefik_lan_ddnsgeek_com_a_10453240' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.traefik_lan_ddnsgeek_com_a_10453240'
|
||||
else
|
||||
terraform import 'dynu_dns_record.traefik_lan_ddnsgeek_com_a_10453240' '9695470/10453240'
|
||||
fi
|
||||
|
||||
if terraform state show 'dynu_dns_record.traefik_lan_ddnsgeek_com_a_19646054' >/dev/null 2>&1; then
|
||||
echo 'Skipping already imported: dynu_dns_record.traefik_lan_ddnsgeek_com_a_19646054'
|
||||
else
|
||||
terraform import 'dynu_dns_record.traefik_lan_ddnsgeek_com_a_19646054' '9695470/19646054'
|
||||
fi
|
||||
@@ -0,0 +1,16 @@
|
||||
# Copy this file to imports.tf and adjust IDs after confirming the
|
||||
# published provider docs for import ID formats.
|
||||
# For dynu_domain, import ID is commonly the root domain name.
|
||||
|
||||
import {
|
||||
to = dynu_domain.lan_ddnsgeek_com
|
||||
id = var.dynu_root_domain
|
||||
}
|
||||
|
||||
# DNS record imports are intentionally examples only because the provider
|
||||
# requires explicit record_type/hostname in config before import.
|
||||
#
|
||||
# import {
|
||||
# to = dynu_dns_record.grafana_lan_ddnsgeek_com
|
||||
# id = var.dynu_record_import_id
|
||||
# }
|
||||
@@ -0,0 +1,3 @@
|
||||
data "dynu_dns_records" "root" {
|
||||
hostname = var.dynu_root_domain
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
output "dynu_domain" {
|
||||
description = "Primary Dynu domain represented by this Terraform root."
|
||||
value = local.dynu_domain
|
||||
}
|
||||
|
||||
output "dynu_dns_records_catalog" {
|
||||
description = "Documentation catalog of expected Dynu DNS records discovered from repo service exposure."
|
||||
value = local.dynu_dns_records_catalog
|
||||
}
|
||||
|
||||
output "dynu_dns_inventory" {
|
||||
description = "Documentation-friendly Dynu DNS inventory for export and merge into broader infrastructure docs."
|
||||
value = {
|
||||
provider = "dynu"
|
||||
domain = local.dynu_domain
|
||||
record_count = length(local.dynu_dns_records_catalog)
|
||||
records = local.dynu_dns_records_catalog
|
||||
}
|
||||
}
|
||||
|
||||
output "dynu_root_domain_id" {
|
||||
description = "Dynu numeric domain ID resolved from dynu_root_domain."
|
||||
value = data.dynu_dns_records.root.domain_id
|
||||
}
|
||||
|
||||
output "dynu_root_domain_name" {
|
||||
description = "Dynu root domain name resolved from dynu_root_domain."
|
||||
value = data.dynu_dns_records.root.domain_name
|
||||
}
|
||||
|
||||
output "dynu_dns_records" {
|
||||
description = "Full read-only DNS record inventory returned by Dynu."
|
||||
value = data.dynu_dns_records.root.records
|
||||
}
|
||||
|
||||
output "dynu_dns_hostnames" {
|
||||
description = "Sorted hostname list discovered for dynu_root_domain."
|
||||
value = sort(distinct([for record in data.dynu_dns_records.root.records : record.hostname]))
|
||||
}
|
||||
|
||||
output "dynu_dns_record_import_ids" {
|
||||
description = "Map of Dynu DNS record identity to provider import IDs in domain_id/record_id format."
|
||||
value = {
|
||||
for record in data.dynu_dns_records.root.records :
|
||||
format("%s/%s/%s", record.hostname, record.record_type, record.id) => format("%s/%s", record.domain_id, record.id)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
provider "dynu" {
|
||||
# Keep auth local-only; do not commit credentials.
|
||||
api_key = var.dynu_api_key
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
locals {
|
||||
dynu_dns_records_catalog_base = {
|
||||
auth = {
|
||||
hostname = "auth"
|
||||
service = "authelia"
|
||||
source = "core/authelia/docker-compose.yml"
|
||||
purpose = "Authentication portal"
|
||||
record_type = null
|
||||
ttl = null
|
||||
target = null
|
||||
proxied = null
|
||||
}
|
||||
gitea = {
|
||||
hostname = "gitea"
|
||||
service = "gitea"
|
||||
source = "apps/gitea/docker-compose.yml"
|
||||
purpose = "Gitea service endpoint"
|
||||
record_type = null
|
||||
ttl = null
|
||||
target = null
|
||||
proxied = null
|
||||
}
|
||||
gotify = {
|
||||
hostname = "gotify"
|
||||
service = "gotify"
|
||||
source = "monitoring/gotify/docker-compose.yml"
|
||||
purpose = "Gotify notifications"
|
||||
record_type = null
|
||||
ttl = null
|
||||
target = null
|
||||
proxied = null
|
||||
}
|
||||
grafana = {
|
||||
hostname = "grafana"
|
||||
service = "grafana"
|
||||
source = "monitoring/grafana/docker-compose.yml"
|
||||
purpose = "Grafana monitoring UI"
|
||||
record_type = null
|
||||
ttl = null
|
||||
target = null
|
||||
proxied = null
|
||||
}
|
||||
familytree = {
|
||||
hostname = "familytree"
|
||||
service = "gramps"
|
||||
source = "apps/gramps/docker-compose.yml"
|
||||
purpose = "Family tree application"
|
||||
record_type = null
|
||||
ttl = null
|
||||
target = null
|
||||
proxied = null
|
||||
}
|
||||
influxdb = {
|
||||
hostname = "influxdb"
|
||||
service = "influxdb"
|
||||
source = "monitoring/influxdb/docker-compose.yml"
|
||||
purpose = "InfluxDB metrics endpoint"
|
||||
record_type = null
|
||||
ttl = null
|
||||
target = null
|
||||
proxied = null
|
||||
}
|
||||
monitor_kuma = {
|
||||
hostname = "monitor-kuma"
|
||||
service = "uptime-kuma"
|
||||
source = "monitoring/uptime-kuma/docker-compose.yml"
|
||||
purpose = "Uptime Kuma monitoring UI"
|
||||
record_type = null
|
||||
ttl = null
|
||||
target = null
|
||||
proxied = null
|
||||
}
|
||||
mtls_bridge = {
|
||||
hostname = "mtls-bridge"
|
||||
service = "mtls-bridge"
|
||||
source = "monitoring/mtls-bridge/docker-compose.yml"
|
||||
purpose = "mTLS bridge API"
|
||||
record_type = null
|
||||
ttl = null
|
||||
target = null
|
||||
proxied = null
|
||||
}
|
||||
nextcloud = {
|
||||
hostname = "nextcloud"
|
||||
service = "nextcloud-webapp"
|
||||
source = "apps/nextcloud/docker-compose.yml"
|
||||
purpose = "Nextcloud service endpoint"
|
||||
record_type = null
|
||||
ttl = null
|
||||
target = null
|
||||
proxied = null
|
||||
}
|
||||
node_red = {
|
||||
hostname = "node-red"
|
||||
service = "node-red"
|
||||
source = "monitoring/node-red/docker-compose.yml"
|
||||
purpose = "Node-RED automation UI/API"
|
||||
record_type = null
|
||||
ttl = null
|
||||
target = null
|
||||
proxied = null
|
||||
}
|
||||
passbolt = {
|
||||
hostname = "passbolt"
|
||||
service = "passbolt-webapp"
|
||||
source = "apps/passbolt/docker-compose.yml"
|
||||
purpose = "Passbolt password management"
|
||||
record_type = null
|
||||
ttl = null
|
||||
target = null
|
||||
proxied = null
|
||||
}
|
||||
portainer = {
|
||||
hostname = "portainer"
|
||||
service = "portainer"
|
||||
source = "monitoring/portainer/docker-compose.yml"
|
||||
purpose = "Portainer admin endpoint"
|
||||
record_type = null
|
||||
ttl = null
|
||||
target = null
|
||||
proxied = null
|
||||
}
|
||||
prometheus = {
|
||||
hostname = "prometheus"
|
||||
service = "prometheus"
|
||||
source = "monitoring/prometheus/docker-compose.yml"
|
||||
purpose = "Prometheus metrics endpoint"
|
||||
record_type = null
|
||||
ttl = null
|
||||
target = null
|
||||
proxied = null
|
||||
}
|
||||
searxng = {
|
||||
hostname = "searxng"
|
||||
service = "searxng"
|
||||
source = "apps/searxng/docker-compose.yml"
|
||||
purpose = "SearXNG search endpoint"
|
||||
record_type = null
|
||||
ttl = null
|
||||
target = null
|
||||
proxied = null
|
||||
}
|
||||
traefik = {
|
||||
hostname = "traefik"
|
||||
service = "traefik"
|
||||
source = "core/traefik/docker-compose.yml"
|
||||
purpose = "Traefik dashboard/API endpoint"
|
||||
record_type = null
|
||||
ttl = null
|
||||
target = null
|
||||
proxied = null
|
||||
}
|
||||
}
|
||||
|
||||
dynu_dns_records_catalog = {
|
||||
for key, record in local.dynu_dns_records_catalog_base :
|
||||
key => merge(record, {
|
||||
fqdn = format("%s.%s", record.hostname, local.dynu_domain)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,349 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate Terraform dynu_dns_record resources/import commands from Dynu inventory outputs."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
SCRIPT_PATH = Path(__file__).resolve()
|
||||
TF_ROOT = SCRIPT_PATH.parents[1]
|
||||
GENERATED_DIR = TF_ROOT / "generated"
|
||||
TF_FILE = GENERATED_DIR / "dynu_dns_records.generated.tf"
|
||||
IMPORT_SCRIPT = GENERATED_DIR / "import-dynu-dns-records.sh"
|
||||
INVENTORY_FILE = GENERATED_DIR / "dynu_dns_records_inventory.json"
|
||||
DEFAULT_RECORDS_OUTPUT = "dynu_dns_records"
|
||||
REQUIRED_RECORD_FIELDS = ("id", "domain_id", "hostname", "record_type")
|
||||
|
||||
HEADER_TF = """# ---------------------------------------------------------------------------
|
||||
# GENERATED FILE - REVIEW BEFORE USE
|
||||
#
|
||||
# Generated from Dynu brownfield DNS inventory.
|
||||
# Do not blindly apply this file to production DNS.
|
||||
# Import records into Terraform state before allowing Terraform to manage them.
|
||||
# ---------------------------------------------------------------------------
|
||||
"""
|
||||
|
||||
HEADER_SH = """#!/usr/bin/env bash
|
||||
# ---------------------------------------------------------------------------
|
||||
# GENERATED FILE - REVIEW BEFORE USE
|
||||
#
|
||||
# Imports existing Dynu DNS records into Terraform state.
|
||||
# Does not apply changes.
|
||||
# ---------------------------------------------------------------------------
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TF_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
cd "${TF_ROOT}"
|
||||
|
||||
# Re-running imports will fail for resources already in state.
|
||||
# This script skips imports when state already contains the resource address.
|
||||
"""
|
||||
|
||||
OPTIONAL_FIELDS = ["group", "host", "priority", "weight", "port", "flags", "tag", "value", "node_name"]
|
||||
|
||||
|
||||
def run_terraform_output() -> dict:
|
||||
if not (TF_ROOT / ".terraform").exists():
|
||||
raise RuntimeError("Terraform is not initialized in infrastructure/terraform/dynu. Run: terraform init")
|
||||
|
||||
cmd = ["terraform", "output", "-json"]
|
||||
proc = subprocess.run(cmd, cwd=TF_ROOT, capture_output=True, text=True)
|
||||
if proc.returncode != 0:
|
||||
raise RuntimeError(f"Failed to run {' '.join(cmd)}:\n{proc.stderr.strip()}")
|
||||
return json.loads(proc.stdout)
|
||||
|
||||
|
||||
def type_shape_name(value: object) -> str:
|
||||
if isinstance(value, list):
|
||||
return "list"
|
||||
if isinstance(value, dict):
|
||||
return "object"
|
||||
return type(value).__name__
|
||||
|
||||
|
||||
def extract_records(payload: object, output_name: str) -> list[dict]:
|
||||
source = payload
|
||||
if isinstance(payload, list):
|
||||
source = payload
|
||||
elif isinstance(payload, dict):
|
||||
if isinstance(payload.get("value"), list):
|
||||
source = payload["value"]
|
||||
elif isinstance(payload.get("records"), list):
|
||||
source = payload["records"]
|
||||
elif isinstance(payload.get("value"), dict) and isinstance(payload["value"].get("records"), list):
|
||||
source = payload["value"]["records"]
|
||||
elif output_name in payload and isinstance(payload[output_name], dict):
|
||||
output_wrapper = payload[output_name]
|
||||
if isinstance(output_wrapper.get("value"), list):
|
||||
source = output_wrapper["value"]
|
||||
elif isinstance(output_wrapper.get("value"), dict) and isinstance(output_wrapper["value"].get("records"), list):
|
||||
source = output_wrapper["value"]["records"]
|
||||
elif isinstance(output_wrapper.get("records"), list):
|
||||
source = output_wrapper["records"]
|
||||
else:
|
||||
raise RuntimeError(f"Output '{output_name}' does not contain a records list.")
|
||||
else:
|
||||
raise RuntimeError(f"Output '{output_name}' not found and no records list discovered.")
|
||||
else:
|
||||
raise RuntimeError(f"Unsupported JSON payload type: {type(payload).__name__}")
|
||||
|
||||
if not isinstance(source, list):
|
||||
raise RuntimeError(f"Output '{output_name}' did not resolve to a list of records.")
|
||||
return source
|
||||
|
||||
|
||||
def validate_records(records: list[dict], output_name: str) -> None:
|
||||
for i, record in enumerate(records):
|
||||
if not isinstance(record, dict):
|
||||
raise RuntimeError(f"Selected output '{output_name}' has non-object record at index {i}: {type(record).__name__}.")
|
||||
missing = [field for field in REQUIRED_RECORD_FIELDS if field not in record]
|
||||
if missing:
|
||||
missing_text = ", ".join(missing)
|
||||
raise RuntimeError(
|
||||
f"Selected output '{output_name}' contains records, but they are not importable Dynu provider records. "
|
||||
f"Record #{i} is missing required fields: {missing_text}. "
|
||||
"Choose an output sourced from data.dynu_dns_records.root, such as dynu_dns_records or dynu_dns_inventory."
|
||||
)
|
||||
|
||||
|
||||
def describe_output(output_name: str, output_wrapper: object, full_outputs: dict) -> dict:
|
||||
details = {
|
||||
"name": output_name,
|
||||
"usable": False,
|
||||
"shape": type_shape_name(output_wrapper),
|
||||
"record_count": "none",
|
||||
"error": "no records list found",
|
||||
}
|
||||
if isinstance(output_wrapper, dict) and "value" in output_wrapper:
|
||||
details["shape"] = type_shape_name(output_wrapper.get("value"))
|
||||
|
||||
try:
|
||||
records = extract_records(full_outputs, output_name)
|
||||
except RuntimeError as exc:
|
||||
details["error"] = str(exc)
|
||||
return details
|
||||
|
||||
details["record_count"] = len(records)
|
||||
try:
|
||||
validate_records(records, output_name)
|
||||
except RuntimeError as exc:
|
||||
details["error"] = str(exc)
|
||||
if isinstance(output_wrapper, dict) and isinstance(output_wrapper.get("value"), dict) and isinstance(output_wrapper["value"].get("records"), list):
|
||||
details["shape"] = "object with records list"
|
||||
elif isinstance(output_wrapper, dict) and isinstance(output_wrapper.get("value"), list):
|
||||
details["shape"] = "list"
|
||||
return details
|
||||
|
||||
details["usable"] = True
|
||||
details["error"] = None
|
||||
if isinstance(output_wrapper, dict) and isinstance(output_wrapper.get("value"), dict) and isinstance(output_wrapper["value"].get("records"), list):
|
||||
details["shape"] = "object with records list"
|
||||
elif isinstance(output_wrapper, dict) and isinstance(output_wrapper.get("value"), list):
|
||||
details["shape"] = "list"
|
||||
return details
|
||||
|
||||
|
||||
def choose_output_interactively(outputs: dict, descriptions: list[dict]) -> str | None:
|
||||
print("\nAvailable Terraform outputs:\n")
|
||||
indexed = {str(i): item for i, item in enumerate(descriptions, 1)}
|
||||
by_name = {item["name"]: item for item in descriptions}
|
||||
|
||||
for i, item in enumerate(descriptions, 1):
|
||||
print(f" {i}) {item['name']}")
|
||||
print(f" usable: {'yes' if item['usable'] else 'no'}")
|
||||
print(f" shape: {item['shape']}")
|
||||
print(f" record count: {item['record_count']}")
|
||||
if item["error"]:
|
||||
print(f" reason: {item['error']}")
|
||||
print()
|
||||
|
||||
attempts = 0
|
||||
while attempts < 3:
|
||||
attempts += 1
|
||||
try:
|
||||
selection = input(f"Choose an output to use for DNS records [1-{len(descriptions)}], or press Enter to cancel: ").strip()
|
||||
except KeyboardInterrupt:
|
||||
print("\nSelection cancelled.")
|
||||
return None
|
||||
|
||||
if selection == "":
|
||||
print("Selection cancelled.")
|
||||
return None
|
||||
|
||||
candidate = indexed.get(selection) or by_name.get(selection)
|
||||
if candidate is None:
|
||||
print("Invalid selection. Enter a number from the list or an exact output name.")
|
||||
continue
|
||||
|
||||
if not candidate["usable"]:
|
||||
print(f"Output '{candidate['name']}' is not usable: {candidate['error']}")
|
||||
continue
|
||||
|
||||
return candidate["name"]
|
||||
|
||||
raise RuntimeError("Too many invalid selections. Exiting without writing files.")
|
||||
|
||||
|
||||
def tf_name(record: dict) -> str:
|
||||
base = f"{record.get('hostname', '')}_{record.get('record_type', '')}_{record.get('id', '')}".lower()
|
||||
base = base.replace("*", "wildcard")
|
||||
base = re.sub(r"[^a-z0-9_]+", "_", base)
|
||||
base = re.sub(r"_+", "_", base).strip("_")
|
||||
if not base or not re.match(r"^[a-z]", base):
|
||||
base = f"record_{base}" if base else "record"
|
||||
if not base.endswith(str(record.get("id", ""))):
|
||||
base = f"{base}_{record.get('id', '')}"
|
||||
return base
|
||||
|
||||
|
||||
def hcl_value(value):
|
||||
if isinstance(value, bool):
|
||||
return "true" if value else "false"
|
||||
if isinstance(value, (int, float)):
|
||||
return str(value)
|
||||
return json.dumps(value)
|
||||
|
||||
|
||||
def generate_resources(records: list[dict]) -> str:
|
||||
chunks = [HEADER_TF.rstrip(), ""]
|
||||
for rec in records:
|
||||
name = tf_name(rec)
|
||||
lines = [f'resource "dynu_dns_record" "{name}" {{']
|
||||
lines.append(f" hostname = {hcl_value(rec.get('hostname'))}")
|
||||
lines.append(f" record_type = {hcl_value(rec.get('record_type'))}")
|
||||
if rec.get("ttl") is not None:
|
||||
lines.append(f" ttl = {hcl_value(rec.get('ttl'))}")
|
||||
enabled = rec.get("enabled")
|
||||
if enabled is None:
|
||||
enabled = rec.get("state")
|
||||
if enabled is not None:
|
||||
lines.append(f" enabled = {hcl_value(enabled)}")
|
||||
|
||||
content = rec.get("content")
|
||||
rtype = str(rec.get("record_type", "")).upper()
|
||||
if content in (None, "") and rtype in {"A", "AAAA"}:
|
||||
lines.append(" dynamic = true")
|
||||
elif content not in (None, ""):
|
||||
lines.append(f" content = {hcl_value(content)}")
|
||||
|
||||
for field in OPTIONAL_FIELDS:
|
||||
value = rec.get(field)
|
||||
if value not in (None, ""):
|
||||
lines.append(f" {field.ljust(11)}= {hcl_value(value)}")
|
||||
|
||||
lines.extend([
|
||||
"",
|
||||
" lifecycle {",
|
||||
" prevent_destroy = true",
|
||||
" }",
|
||||
"}",
|
||||
"",
|
||||
])
|
||||
chunks.extend(lines)
|
||||
return "\n".join(chunks).rstrip() + "\n"
|
||||
|
||||
|
||||
def generate_import_script(records: list[dict]) -> str:
|
||||
lines = [HEADER_SH.rstrip(), ""]
|
||||
for rec in records:
|
||||
name = tf_name(rec)
|
||||
import_id = f"{rec['domain_id']}/{rec['id']}"
|
||||
addr = f"dynu_dns_record.{name}"
|
||||
lines.append(f"if terraform state show '{addr}' >/dev/null 2>&1; then")
|
||||
lines.append(f" echo 'Skipping already imported: {addr}'")
|
||||
lines.append("else")
|
||||
lines.append(f" terraform import '{addr}' '{import_id}'")
|
||||
lines.append("fi")
|
||||
lines.append("")
|
||||
return "\n".join(lines).rstrip() + "\n"
|
||||
|
||||
|
||||
def write_file(path: Path, content: str, dry_run: bool, overwrite: bool) -> None:
|
||||
if path.exists() and not overwrite:
|
||||
raise RuntimeError(f"Refusing to overwrite existing file: {path}. Re-run with --overwrite.")
|
||||
if dry_run:
|
||||
print(f"[dry-run] Would write {path}")
|
||||
return
|
||||
path.write_text(content, encoding="utf-8")
|
||||
print(f"Wrote {path}")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--dry-run", action="store_true", help="Print intended output paths without writing files.")
|
||||
parser.add_argument("--overwrite", "--force", action="store_true", dest="overwrite", help="Overwrite existing generated files.")
|
||||
parser.add_argument("--from-file", type=Path, help="Load inventory JSON from a file instead of calling terraform output.")
|
||||
parser.add_argument(
|
||||
"--records-output",
|
||||
default=None,
|
||||
help=(
|
||||
"Terraform output name containing Dynu DNS records. "
|
||||
f"Defaults to {DEFAULT_RECORDS_OUTPUT}; if missing in an interactive terminal, "
|
||||
"the script prompts you to choose from available outputs."
|
||||
),
|
||||
)
|
||||
parser.add_argument("--no-interactive", action="store_true", help="Disable interactive output selection.")
|
||||
args = parser.parse_args()
|
||||
|
||||
records_output_explicit = args.records_output is not None
|
||||
records_output = args.records_output or DEFAULT_RECORDS_OUTPUT
|
||||
|
||||
try:
|
||||
payload = json.loads(args.from_file.read_text(encoding="utf-8")) if args.from_file else run_terraform_output()
|
||||
|
||||
selected_output = records_output
|
||||
descriptions: list[dict] = []
|
||||
if isinstance(payload, dict):
|
||||
descriptions = [describe_output(name, payload[name], payload) for name in sorted(payload)]
|
||||
|
||||
try:
|
||||
records = extract_records(payload, selected_output)
|
||||
validate_records(records, selected_output)
|
||||
except RuntimeError as exc:
|
||||
is_interactive = sys.stdin.isatty() and not args.no_interactive
|
||||
should_prompt = isinstance(payload, dict) and not records_output_explicit and is_interactive
|
||||
if should_prompt:
|
||||
print(f"Terraform output '{selected_output}' was not found or is unusable.\n")
|
||||
chosen = choose_output_interactively(payload, descriptions)
|
||||
if chosen is None:
|
||||
print("Exiting without writing files.")
|
||||
return 1
|
||||
selected_output = chosen
|
||||
records = extract_records(payload, selected_output)
|
||||
validate_records(records, selected_output)
|
||||
else:
|
||||
if isinstance(payload, dict):
|
||||
available = ", ".join(sorted(payload.keys())) or "(none)"
|
||||
if records_output_explicit:
|
||||
raise RuntimeError(
|
||||
f"Missing or unusable Terraform output '{selected_output}'. "
|
||||
f"Available outputs: {available}. Details: {exc}"
|
||||
)
|
||||
raise RuntimeError(
|
||||
f"Missing or unusable Terraform output '{selected_output}'. "
|
||||
f"Available outputs: {available}.\n\n"
|
||||
"Run interactively to choose an output, or pass one explicitly, for example:\n\n"
|
||||
" python3 scripts/generate-brownfield-records.py --records-output dynu_dns_inventory --dry-run"
|
||||
)
|
||||
raise
|
||||
|
||||
GENERATED_DIR.mkdir(parents=True, exist_ok=True)
|
||||
write_file(INVENTORY_FILE, json.dumps(records, indent=2, sort_keys=True) + "\n", args.dry_run, args.overwrite)
|
||||
write_file(TF_FILE, generate_resources(records), args.dry_run, args.overwrite)
|
||||
write_file(IMPORT_SCRIPT, generate_import_script(records), args.dry_run, args.overwrite)
|
||||
if not args.dry_run:
|
||||
IMPORT_SCRIPT.chmod(0o755)
|
||||
return 0
|
||||
except Exception as exc: # noqa: BLE001
|
||||
print(f"Error: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,7 @@
|
||||
# Local-only credentials. Do not commit real values.
|
||||
dynu_api_key = "replace-with-dynu-api-key"
|
||||
dynu_username = null
|
||||
dynu_password = null
|
||||
|
||||
dynu_root_domain = "lan.ddnsgeek.com"
|
||||
dynu_record_import_id = "REPLACE_WITH_DYNU_RECORD_IMPORT_ID"
|
||||
@@ -0,0 +1,33 @@
|
||||
|
||||
variable "dynu_root_domain" {
|
||||
description = "Dynu root domain name to reconcile/import (for example: lan.ddnsgeek.com)."
|
||||
type = string
|
||||
default = "lan.ddnsgeek.com"
|
||||
}
|
||||
|
||||
variable "dynu_api_key" {
|
||||
description = "Dynu API key/token used by the Dynu Terraform provider."
|
||||
type = string
|
||||
sensitive = true
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "dynu_username" {
|
||||
description = "Optional Dynu username, only if required by the provider."
|
||||
type = string
|
||||
sensitive = true
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "dynu_password" {
|
||||
description = "Optional Dynu password, only if required by the provider."
|
||||
type = string
|
||||
sensitive = true
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "dynu_record_import_id" {
|
||||
description = "Placeholder import ID for a single dynu_dns_record during one-at-a-time reconciliation."
|
||||
type = string
|
||||
default = "REPLACE_WITH_DYNU_RECORD_IMPORT_ID"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
terraform {
|
||||
required_version = ">= 1.6.0"
|
||||
|
||||
required_providers {
|
||||
dynu = {
|
||||
source = "beatz174-bit/dynu"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,16 @@ output "physical_hosts" {
|
||||
value = local.physical_hosts
|
||||
}
|
||||
|
||||
output "virtual_hosts" {
|
||||
description = "Virtual host/VM inventory used for documentation"
|
||||
value = local.virtual_hosts
|
||||
}
|
||||
|
||||
|
||||
output "infrastructure_inventory" {
|
||||
description = "Combined infrastructure inventory"
|
||||
value = {
|
||||
physical_hosts = local.physical_hosts
|
||||
virtual_hosts = local.virtual_hosts
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,4 +21,59 @@ locals {
|
||||
notes = "Raspberry Pi host"
|
||||
}
|
||||
}
|
||||
|
||||
# Virtual host inventory for documentation output. This is intentionally
|
||||
# concise and shaped for docs tooling (not a full provider object dump).
|
||||
virtual_hosts = {
|
||||
docker = {
|
||||
name = "docker"
|
||||
type = "virtual"
|
||||
role = "docker-host"
|
||||
proxmox_node = "pve"
|
||||
vm_id = 103
|
||||
management_ip = ""
|
||||
os_family = "linux"
|
||||
notes = "Primary Docker VM"
|
||||
}
|
||||
server_nixos = {
|
||||
name = "server-nixos"
|
||||
type = "virtual"
|
||||
role = "nixos-server"
|
||||
proxmox_node = "pve"
|
||||
vm_id = 104
|
||||
management_ip = ""
|
||||
os_family = "nixos"
|
||||
notes = "General-purpose NixOS VM"
|
||||
}
|
||||
nix_cache = {
|
||||
name = "nix-cache"
|
||||
type = "virtual"
|
||||
role = "cache"
|
||||
proxmox_node = "pve"
|
||||
vm_id = 105
|
||||
management_ip = ""
|
||||
os_family = "linux"
|
||||
notes = "Nix binary cache VM"
|
||||
}
|
||||
pbs = {
|
||||
name = "pbs"
|
||||
type = "virtual"
|
||||
role = "backup"
|
||||
proxmox_node = "pve"
|
||||
vm_id = 106
|
||||
management_ip = ""
|
||||
os_family = "linux"
|
||||
notes = "Proxmox Backup Server VM"
|
||||
}
|
||||
pihole = {
|
||||
name = "pihole"
|
||||
type = "virtual"
|
||||
role = "dns"
|
||||
proxmox_node = "pve"
|
||||
vm_id = 108
|
||||
management_ip = ""
|
||||
os_family = "linux"
|
||||
notes = "DNS filtering VM"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
site_name: Public Infrastructure Documentation
|
||||
site_description: Public-facing infrastructure documentation
|
||||
repo_url: https://github.com/beatz174-bit/docker
|
||||
site_url: https://beatz174-bit.github.io/docker/
|
||||
docs_dir: docs/public
|
||||
site_dir: site-public
|
||||
|
||||
nav:
|
||||
- Home: index.md
|
||||
- Diagrams: diagrams.md
|
||||
- Compose Inventory: compose-inventory.md
|
||||
- Traefik Routes: traefik-routes.md
|
||||
|
||||
theme:
|
||||
name: mkdocs
|
||||
extra_css:
|
||||
- stylesheets/extra.css
|
||||
|
||||
validation:
|
||||
nav:
|
||||
omitted_files: ignore
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
site_name: Infrastructure Documentation
|
||||
site_description: Generated and maintained infrastructure documentation
|
||||
repo_url: https://github.com/beatz174-bit/docker
|
||||
docs_dir: docs
|
||||
|
||||
nav:
|
||||
- Home: index.md
|
||||
- Docker Environment: docker.md
|
||||
- Networking: networking.md
|
||||
- Monitoring: monitoring.md
|
||||
- Automation: automation.md
|
||||
- Operations: operations.md
|
||||
- Public Showcase: showcase.md
|
||||
- Generated:
|
||||
- Compose Inventory: generated/compose-inventory.md
|
||||
- Traefik Routes: generated/traefik-routes.md
|
||||
|
||||
theme:
|
||||
name: mkdocs
|
||||
|
||||
validation:
|
||||
nav:
|
||||
omitted_files: ignore
|
||||
exclude_docs: |
|
||||
README.md
|
||||
@@ -1,3 +1,28 @@
|
||||
{
|
||||
"dockerUpdateAttempts": {}
|
||||
"dockerUpdateAttempts": {
|
||||
"nextcloud-redis|redis:latest|docker": {
|
||||
"time": 1778483142172,
|
||||
"status": "test_failed",
|
||||
"failedAt": 1778483206801,
|
||||
"notified": true
|
||||
},
|
||||
"telegraf|telegraf:latest|raspi": {
|
||||
"time": 1778569512188,
|
||||
"status": "pull_failed",
|
||||
"failedAt": 1778569512904,
|
||||
"notified": true
|
||||
},
|
||||
"traefik|traefik:3|raspi": {
|
||||
"time": 1778569512188,
|
||||
"status": "pull_failed",
|
||||
"failedAt": 1778569512880,
|
||||
"notified": true
|
||||
},
|
||||
"searxng-webapp|searxng/searxng:latest|docker": {
|
||||
"time": 1778613012267,
|
||||
"status": "success",
|
||||
"completedAt": 1778613022513,
|
||||
"notified": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -442,3 +442,79 @@
|
||||
{"ts":"2026-04-17T07:05:12.795Z","flow":"docker-updates","event":"completed","container":"traefik","project":"unknown","host":"raspi","status":"failed","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-04-17T07:05:12.795Z","flow":"docker-updates","event":"completed","container":"traefik","project":"unknown","host":"raspi","status":"locked","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-04-17T19:11:29.490Z","flow":"docker-updates","event":"completed","container":"traefik","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-04-21T07:05:42.645Z","flow":"docker-updates","event":"completed","container":"telegraf","project":"unknown","host":"raspi","status":"failed","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-04-21T07:05:42.646Z","flow":"docker-updates","event":"completed","container":"telegraf","project":"unknown","host":"raspi","status":"locked","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-04-21T07:05:59.089Z","flow":"docker-updates","event":"completed","container":"gitea","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-04-21T07:06:45.971Z","flow":"docker-updates","event":"completed","container":"nextcloud-redis","project":"unknown","host":"docker","status":"failed","success":0,"failed":1,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-04-21T07:06:45.971Z","flow":"docker-updates","event":"completed","container":"nextcloud-redis","project":"unknown","host":"docker","status":"locked","success":0,"failed":1,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-04-21T07:06:56.112Z","flow":"docker-updates","event":"completed","container":"telegraf","project":"unknown","host":"docker","status":"failed","success":0,"failed":1,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-04-21T07:06:56.112Z","flow":"docker-updates","event":"completed","container":"telegraf","project":"unknown","host":"docker","status":"locked","success":0,"failed":1,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-04-22T07:05:43.226Z","flow":"docker-updates","event":"completed","container":"gramps-web","project":"unknown","host":"docker","status":"failed","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-04-22T07:05:43.226Z","flow":"docker-updates","event":"completed","container":"gramps-web","project":"unknown","host":"docker","status":"locked","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-04-22T07:05:43.237Z","flow":"docker-updates","event":"completed","container":"gramps-web-celery","project":"unknown","host":"docker","status":"failed","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-04-22T07:05:43.237Z","flow":"docker-updates","event":"completed","container":"gramps-web-celery","project":"unknown","host":"docker","status":"locked","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-04-22T07:06:55.068Z","flow":"docker-updates","event":"completed","container":"influxdb","project":"unknown","host":"docker","status":"failed","success":0,"failed":1,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-04-22T07:06:55.069Z","flow":"docker-updates","event":"completed","container":"influxdb","project":"unknown","host":"docker","status":"locked","success":0,"failed":1,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-04-22T11:50:51.776Z","flow":"docker-updates","event":"completed","container":"gitea","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-04-24T07:06:46.595Z","flow":"docker-updates","event":"completed","container":"nextcloud-redis","project":"unknown","host":"docker","status":"failed","success":0,"failed":1,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-04-24T07:06:46.596Z","flow":"docker-updates","event":"completed","container":"nextcloud-redis","project":"unknown","host":"docker","status":"locked","success":0,"failed":1,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-04-25T07:06:01.141Z","flow":"docker-updates","event":"completed","container":"gitea","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":1,"code":0,"error":""}
|
||||
{"ts":"2026-04-25T07:06:05.952Z","flow":"docker-updates","event":"completed","container":"searxng-webapp","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-04-25T08:21:45.451Z","flow":"docker-updates","event":"completed","container":"nextcloud-redis","project":"unknown","host":"docker","status":"failed","success":0,"failed":1,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-04-25T08:21:45.451Z","flow":"docker-updates","event":"completed","container":"nextcloud-redis","project":"unknown","host":"docker","status":"locked","success":0,"failed":1,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-04-26T07:05:42.647Z","flow":"docker-updates","event":"completed","container":"traefik","project":"unknown","host":"raspi","status":"failed","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-04-26T07:05:42.647Z","flow":"docker-updates","event":"completed","container":"traefik","project":"unknown","host":"raspi","status":"locked","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-04-26T07:06:59.195Z","flow":"docker-updates","event":"completed","container":"traefik","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-04-26T19:10:52.147Z","flow":"docker-updates","event":"completed","container":"searxng-webapp","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-04-26T19:10:52.183Z","flow":"docker-updates","event":"completed","container":"gitea","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-04-27T07:05:42.810Z","flow":"docker-updates","event":"completed","container":"gramps-web","project":"unknown","host":"docker","status":"failed","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-04-27T07:05:42.811Z","flow":"docker-updates","event":"completed","container":"gramps-web","project":"unknown","host":"docker","status":"locked","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-04-27T07:05:42.820Z","flow":"docker-updates","event":"completed","container":"gramps-web-celery","project":"unknown","host":"docker","status":"failed","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-04-27T07:05:42.820Z","flow":"docker-updates","event":"completed","container":"gramps-web-celery","project":"unknown","host":"docker","status":"locked","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-04-27T19:11:52.376Z","flow":"docker-updates","event":"completed","container":"traefik","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-04-29T07:06:01.058Z","flow":"docker-updates","event":"completed","container":"searxng-webapp","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-04-30T07:05:42.792Z","flow":"docker-updates","event":"completed","container":"traefik","project":"unknown","host":"raspi","status":"failed","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-04-30T07:05:42.793Z","flow":"docker-updates","event":"completed","container":"traefik","project":"unknown","host":"raspi","status":"locked","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-04-30T07:07:03.429Z","flow":"docker-updates","event":"completed","container":"traefik","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-04-30T07:55:52.210Z","flow":"docker-updates","event":"completed","container":"searxng-webapp","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-05-02T07:06:02.030Z","flow":"docker-updates","event":"completed","container":"searxng-webapp","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-05-03T19:11:00.985Z","flow":"docker-updates","event":"completed","container":"searxng-webapp","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-05-04T07:05:43.437Z","flow":"docker-updates","event":"completed","container":"gramps-web-celery","project":"unknown","host":"docker","status":"failed","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-05-04T07:05:43.437Z","flow":"docker-updates","event":"completed","container":"gramps-web-celery","project":"unknown","host":"docker","status":"locked","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-05-04T07:05:43.478Z","flow":"docker-updates","event":"completed","container":"gramps-web","project":"unknown","host":"docker","status":"failed","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-05-04T07:05:43.478Z","flow":"docker-updates","event":"completed","container":"gramps-web","project":"unknown","host":"docker","status":"locked","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-05-04T07:06:49.671Z","flow":"docker-updates","event":"completed","container":"pihole-exporter","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-05-05T07:06:53.883Z","flow":"docker-updates","event":"completed","container":"influxdb","project":"unknown","host":"docker","status":"failed","success":0,"failed":1,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-05-05T07:06:53.883Z","flow":"docker-updates","event":"completed","container":"influxdb","project":"unknown","host":"docker","status":"locked","success":0,"failed":1,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-05-05T07:15:53.751Z","flow":"docker-updates","event":"completed","container":"searxng-webapp","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-05-05T19:11:46.870Z","flow":"docker-updates","event":"completed","container":"pihole-exporter","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-05-06T07:05:43.370Z","flow":"docker-updates","event":"completed","container":"traefik","project":"unknown","host":"raspi","status":"failed","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-05-06T07:05:43.371Z","flow":"docker-updates","event":"completed","container":"traefik","project":"unknown","host":"raspi","status":"locked","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-05-06T07:06:52.049Z","flow":"docker-updates","event":"completed","container":"nextcloud-redis","project":"unknown","host":"docker","status":"failed","success":0,"failed":1,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-05-06T07:06:52.050Z","flow":"docker-updates","event":"completed","container":"nextcloud-redis","project":"unknown","host":"docker","status":"locked","success":0,"failed":1,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-05-06T07:07:04.711Z","flow":"docker-updates","event":"completed","container":"traefik","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-05-06T19:21:00.548Z","flow":"docker-updates","event":"completed","container":"searxng-webapp","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-05-07T07:05:43.486Z","flow":"docker-updates","event":"completed","container":"portainer-agent","project":"unknown","host":"raspi","status":"failed","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-05-07T07:05:43.486Z","flow":"docker-updates","event":"completed","container":"portainer-agent","project":"unknown","host":"raspi","status":"locked","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-05-07T07:06:00.453Z","flow":"docker-updates","event":"completed","container":"gramps-redis","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-05-07T07:06:54.399Z","flow":"docker-updates","event":"completed","container":"portainer","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-05-07T07:16:46.710Z","flow":"docker-updates","event":"completed","container":"pihole-exporter","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-05-07T19:11:59.538Z","flow":"docker-updates","event":"completed","container":"traefik","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-05-08T07:25:53.059Z","flow":"docker-updates","event":"completed","container":"searxng-webapp","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-05-08T19:10:53.346Z","flow":"docker-updates","event":"completed","container":"gramps-redis","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-05-08T19:11:48.007Z","flow":"docker-updates","event":"completed","container":"portainer","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":1,"code":0,"error":""}
|
||||
{"ts":"2026-05-08T19:21:46.912Z","flow":"docker-updates","event":"completed","container":"pihole-exporter","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-05-09T07:05:43.856Z","flow":"docker-updates","event":"completed","container":"telegraf","project":"unknown","host":"raspi","status":"failed","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-05-09T07:05:43.856Z","flow":"docker-updates","event":"completed","container":"telegraf","project":"unknown","host":"raspi","status":"locked","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-05-09T07:06:56.795Z","flow":"docker-updates","event":"completed","container":"telegraf","project":"unknown","host":"docker","status":"failed","success":0,"failed":1,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-05-09T07:06:56.795Z","flow":"docker-updates","event":"completed","container":"telegraf","project":"unknown","host":"docker","status":"locked","success":0,"failed":1,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-05-09T07:16:59.180Z","flow":"docker-updates","event":"completed","container":"traefik","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-05-09T19:30:53.415Z","flow":"docker-updates","event":"completed","container":"searxng-webapp","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-05-11T07:06:02.234Z","flow":"docker-updates","event":"completed","container":"searxng-webapp","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-05-11T07:06:46.802Z","flow":"docker-updates","event":"completed","container":"nextcloud-redis","project":"unknown","host":"docker","status":"failed","success":0,"failed":1,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-05-11T07:06:46.802Z","flow":"docker-updates","event":"completed","container":"nextcloud-redis","project":"unknown","host":"docker","status":"locked","success":0,"failed":1,"duration_ms":0,"code":0,"error":""}
|
||||
{"ts":"2026-05-12T07:05:12.881Z","flow":"docker-updates","event":"completed","container":"traefik","project":"unknown","host":"raspi","status":"failed","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-05-12T07:05:12.881Z","flow":"docker-updates","event":"completed","container":"traefik","project":"unknown","host":"raspi","status":"locked","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-05-12T07:05:12.904Z","flow":"docker-updates","event":"completed","container":"telegraf","project":"unknown","host":"raspi","status":"failed","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-05-12T07:05:12.904Z","flow":"docker-updates","event":"completed","container":"telegraf","project":"unknown","host":"raspi","status":"locked","success":0,"failed":1,"duration_ms":0,"code":1,"error":""}
|
||||
{"ts":"2026-05-12T19:10:22.519Z","flow":"docker-updates","event":"completed","container":"searxng-webapp","project":"unknown","host":"docker","status":"success","success":1,"failed":0,"duration_ms":0,"code":0,"error":""}
|
||||
|
||||
@@ -41,7 +41,8 @@ dummy_value_for_key() {
|
||||
local key="$1"
|
||||
case "$key" in
|
||||
*EMAIL* ) echo "dummy@example.com" ;;
|
||||
*USER*|*USERNAME* ) echo "dummy-user" ;;
|
||||
*DB_USER* ) echo "dummyuser" ;;
|
||||
*USERNAME*|*USER* ) echo "dummy-user" ;;
|
||||
*DOMAIN* ) echo "example.lan.ddnsgeek.com" ;;
|
||||
*TZ ) echo "Australia/Brisbane" ;;
|
||||
*URL* ) echo "https://example.lan.ddnsgeek.com" ;;
|
||||
@@ -49,7 +50,6 @@ dummy_value_for_key() {
|
||||
*PASSWORD*|*PASS*|*TOKEN*|*SECRET*|*KEY*|*JWT* ) echo "dummy-${key,,}" ;;
|
||||
*FINGERPRINT* ) echo "0000000000000000000000000000000000000000" ;;
|
||||
*DB_NAME* ) echo "dummydb" ;;
|
||||
*DB_USER* ) echo "dummyuser" ;;
|
||||
*NAME* ) echo "dummy-name" ;;
|
||||
*ADDRESS* ) echo "dummy" ;;
|
||||
* ) echo "dummy-value" ;;
|
||||
@@ -120,3 +120,35 @@ reconcile_file_based_secrets
|
||||
echo "== Dummy secret reconciliation complete =="
|
||||
echo "stack env: $STACK_ENV"
|
||||
jq -r '.file_based_secrets[].path' "$INVENTORY_JSON" | sed 's/^/file secret: /'
|
||||
|
||||
|
||||
REPO_ROOT="${CODEX_REPO_DIR:-$PWD}"
|
||||
ANSIBLE_DIR="$REPO_ROOT/infrastructure/ansible"
|
||||
ANSIBLE_CONFIG="$ANSIBLE_DIR/ansible.cfg"
|
||||
ANSIBLE_COLLECTIONS_REQ="$ANSIBLE_DIR/collections/requirements.yml"
|
||||
ANSIBLE_INVENTORY="$ANSIBLE_DIR/inventory/hosts.yml"
|
||||
ANSIBLE_PING_PLAYBOOK="$ANSIBLE_DIR/playbooks/ping.yml"
|
||||
|
||||
if [[ -f "$ANSIBLE_COLLECTIONS_REQ" ]]; then
|
||||
echo "== Refresh Ansible collections (bootstrap) =="
|
||||
ansible-galaxy collection install -r "$ANSIBLE_COLLECTIONS_REQ" -p "$ANSIBLE_DIR/collections" || true
|
||||
fi
|
||||
|
||||
if command -v ansible >/dev/null 2>&1; then
|
||||
echo "== Ansible bootstrap validation =="
|
||||
ANSIBLE_CONFIG="$ANSIBLE_CONFIG" ansible --version | head -n 1 || true
|
||||
|
||||
if command -v ansible-lint >/dev/null 2>&1; then
|
||||
ansible-lint --version || true
|
||||
fi
|
||||
|
||||
if [[ -f "$ANSIBLE_INVENTORY" ]]; then
|
||||
ANSIBLE_CONFIG="$ANSIBLE_CONFIG" \
|
||||
ansible-inventory -i "$ANSIBLE_INVENTORY" --list > /dev/null || true
|
||||
fi
|
||||
|
||||
if [[ -f "$ANSIBLE_PING_PLAYBOOK" && -f "$ANSIBLE_INVENTORY" ]]; then
|
||||
ANSIBLE_CONFIG="$ANSIBLE_CONFIG" \
|
||||
ansible-playbook -i "$ANSIBLE_INVENTORY" "$ANSIBLE_PING_PLAYBOOK" --syntax-check || true
|
||||
fi
|
||||
fi
|
||||
|
||||
+34
-2
@@ -104,7 +104,8 @@ dummy_value_for_key() {
|
||||
local key="$1"
|
||||
case "$key" in
|
||||
*EMAIL* ) echo "dummy@example.com" ;;
|
||||
*USER*|*USERNAME* ) echo "dummy-user" ;;
|
||||
*DB_USER* ) echo "dummyuser" ;;
|
||||
*USERNAME*|*USER* ) echo "dummy-user" ;;
|
||||
*DOMAIN* ) echo "example.lan.ddnsgeek.com" ;;
|
||||
*TZ ) echo "Australia/Brisbane" ;;
|
||||
*URL* ) echo "https://example.lan.ddnsgeek.com" ;;
|
||||
@@ -112,7 +113,6 @@ dummy_value_for_key() {
|
||||
*PASSWORD*|*PASS*|*TOKEN*|*SECRET*|*KEY*|*JWT* ) echo "dummy-${key,,}" ;;
|
||||
*FINGERPRINT* ) echo "0000000000000000000000000000000000000000" ;;
|
||||
*DB_NAME* ) echo "dummydb" ;;
|
||||
*DB_USER* ) echo "dummyuser" ;;
|
||||
*NAME* ) echo "dummy-name" ;;
|
||||
*ADDRESS* ) echo "dummy" ;;
|
||||
* ) echo "dummy-value" ;;
|
||||
@@ -152,6 +152,38 @@ ensure_dummy_secret_files() {
|
||||
render_dummy_stack_env
|
||||
ensure_dummy_secret_files
|
||||
|
||||
|
||||
ANSIBLE_DIR="$REPO_ROOT/infrastructure/ansible"
|
||||
ANSIBLE_CONFIG="$ANSIBLE_DIR/ansible.cfg"
|
||||
ANSIBLE_COLLECTIONS_REQ="$ANSIBLE_DIR/collections/requirements.yml"
|
||||
ANSIBLE_INVENTORY="$ANSIBLE_DIR/inventory/hosts.yml"
|
||||
ANSIBLE_PING_PLAYBOOK="$ANSIBLE_DIR/playbooks/ping.yml"
|
||||
|
||||
if [[ -f "$ANSIBLE_COLLECTIONS_REQ" ]]; then
|
||||
echo "== Ansible collections (bootstrap) =="
|
||||
ansible-galaxy collection install -r "$ANSIBLE_COLLECTIONS_REQ" -p "$ANSIBLE_DIR/collections" || true
|
||||
fi
|
||||
|
||||
if command -v ansible >/dev/null 2>&1; then
|
||||
echo "== Ansible bootstrap validation =="
|
||||
ANSIBLE_CONFIG="$ANSIBLE_CONFIG" ansible --version | head -n 1 || true
|
||||
|
||||
if command -v ansible-lint >/dev/null 2>&1; then
|
||||
ansible-lint --version || true
|
||||
else
|
||||
echo "ansible-lint not available; skipping version check"
|
||||
fi
|
||||
|
||||
if [[ -f "$ANSIBLE_INVENTORY" ]]; then
|
||||
ANSIBLE_CONFIG="$ANSIBLE_CONFIG" \
|
||||
ansible-inventory -i "$ANSIBLE_INVENTORY" --list > /dev/null || true
|
||||
fi
|
||||
|
||||
if [[ -f "$ANSIBLE_PING_PLAYBOOK" && -f "$ANSIBLE_INVENTORY" ]]; then
|
||||
ANSIBLE_CONFIG="$ANSIBLE_CONFIG" \
|
||||
ansible-playbook -i "$ANSIBLE_INVENTORY" "$ANSIBLE_PING_PLAYBOOK" --syntax-check || true
|
||||
fi
|
||||
fi
|
||||
echo
|
||||
echo "== Installed versions =="
|
||||
bash --version | head -n 1 || true
|
||||
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
inv_dir="${repo_root}/infrastructure/terraform/proxmox"
|
||||
json_out="${repo_root}/data/terraform/proxmox-inventory.json"
|
||||
md_out="${repo_root}/docs/generated/host-topology.md"
|
||||
|
||||
mkdir -p "$(dirname "${json_out}")" "$(dirname "${md_out}")"
|
||||
|
||||
(
|
||||
cd "${inv_dir}"
|
||||
terraform output -json infrastructure_inventory > "${json_out}"
|
||||
)
|
||||
|
||||
python3 "${repo_root}/scripts/docs/generate_host_topology.py" \
|
||||
--input "${json_out}" \
|
||||
--output "${md_out}"
|
||||
|
||||
echo "Generated: ${md_out}"
|
||||
@@ -0,0 +1,4 @@
|
||||
PROJECT_ROOT=.
|
||||
TZ=UTC
|
||||
DOMAIN=example.internal
|
||||
SECRETS_ENV=scripts/docs/ci-secrets-placeholder.env
|
||||
@@ -0,0 +1,2 @@
|
||||
EXAMPLE_PASSWORD=placeholder
|
||||
EXAMPLE_TOKEN=placeholder
|
||||
Executable
+115
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
ALLOW_STALE_TERRAFORM=0
|
||||
SKIP_TERRAFORM=0
|
||||
SKIP_DNS=0
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Usage: scripts/docs/generate-all.sh [--allow-stale-terraform] [--skip-terraform] [--skip-dns]
|
||||
|
||||
Options:
|
||||
--allow-stale-terraform Use existing Terraform JSON when terraform is unavailable.
|
||||
--skip-terraform Skip Terraform refresh and require existing JSON artifacts.
|
||||
--skip-dns Skip Dynu DNS Terraform refresh.
|
||||
USAGE
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--allow-stale-terraform) ALLOW_STALE_TERRAFORM=1 ;;
|
||||
--skip-terraform) SKIP_TERRAFORM=1 ;;
|
||||
--skip-dns) SKIP_DNS=1 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "ERROR: Unknown option: $1" >&2; usage; exit 1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
mkdir -p docs/generated docs/diagrams docs/public data/terraform
|
||||
[[ -d infrastructure/terraform/proxmox ]] && mkdir -p infrastructure/terraform/proxmox/generated
|
||||
[[ -d infrastructure/terraform/dynu ]] && mkdir -p infrastructure/terraform/dynu/generated
|
||||
|
||||
if ! scripts/docs/render-compose-config.sh; then
|
||||
echo "ERROR: Docker Compose config render failed." >&2
|
||||
echo "Check services-up.sh, compose files, and default-environment.env." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -s docs/generated/docker-compose.resolved.yml ]]; then
|
||||
echo "ERROR: Expected non-empty docs/generated/docker-compose.resolved.yml after compose render." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PROXMOX_DIR="infrastructure/terraform/proxmox"
|
||||
PRIMARY_HOST_INV="data/terraform/proxmox-inventory.json"
|
||||
FALLBACK_HOST_INV="infrastructure/terraform/proxmox/generated/infrastructure_inventory.json"
|
||||
HOST_INVENTORY=""
|
||||
|
||||
if [[ -d "$PROXMOX_DIR" ]]; then
|
||||
if [[ "$SKIP_TERRAFORM" -eq 1 ]]; then
|
||||
echo "INFO: --skip-terraform set; using pre-generated Terraform JSON artifacts." >&2
|
||||
elif command -v terraform >/dev/null 2>&1; then
|
||||
scripts/docs/build_host_topology.sh
|
||||
elif [[ "$ALLOW_STALE_TERRAFORM" -eq 1 ]]; then
|
||||
echo "WARNING: terraform unavailable; using stale Terraform JSON due to --allow-stale-terraform." >&2
|
||||
else
|
||||
echo "ERROR: terraform is not available, but $PROXMOX_DIR exists and must be refreshed." >&2
|
||||
echo "Install terraform and rerun scripts/docs/generate-all.sh, or use --allow-stale-terraform/--skip-terraform intentionally." >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "ERROR: Required Terraform directory missing: $PROXMOX_DIR" >&2
|
||||
echo "Physical topology diagrams require host inventory from Terraform." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for p in "$PRIMARY_HOST_INV" "$FALLBACK_HOST_INV"; do
|
||||
if [[ -s "$p" ]]; then
|
||||
HOST_INVENTORY="$p"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ -z "$HOST_INVENTORY" ]]; then
|
||||
echo "ERROR: Host topology inventory is missing." >&2
|
||||
echo "Expected one of: $PRIMARY_HOST_INV or $FALLBACK_HOST_INV" >&2
|
||||
echo "Run scripts/docs/build_host_topology.sh from a machine with Terraform state access, then rerun scripts/docs/generate-all.sh." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DNS_INVENTORY="infrastructure/terraform/dynu/generated/dynu_dns_records_inventory.json"
|
||||
if [[ "$SKIP_DNS" -eq 0 && -d infrastructure/terraform/dynu ]]; then
|
||||
if [[ "$SKIP_TERRAFORM" -eq 1 ]]; then
|
||||
echo "INFO: Skipping Dynu Terraform refresh due to --skip-terraform." >&2
|
||||
elif command -v terraform >/dev/null 2>&1; then
|
||||
scripts/docs/generate_dynu_dns_inventory.sh
|
||||
elif [[ "$ALLOW_STALE_TERRAFORM" -eq 1 ]]; then
|
||||
echo "WARNING: terraform unavailable; using stale Dynu DNS inventory due to --allow-stale-terraform." >&2
|
||||
else
|
||||
echo "ERROR: terraform unavailable; cannot refresh Dynu DNS inventory while infrastructure/terraform/dynu exists." >&2
|
||||
echo "Install terraform and rerun, or use --allow-stale-terraform/--skip-terraform intentionally." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
python3 scripts/docs/generate-compose-inventory.py docs/generated/docker-compose.resolved.yml docs/generated/compose-inventory.md
|
||||
python3 scripts/docs/generate-traefik-routes.py docs/generated/docker-compose.resolved.yml docs/generated/traefik-routes.md
|
||||
python3 scripts/docs/generate-docs-index.py docs/generated/index.md
|
||||
|
||||
GEN_ARGS=(--compose docs/generated/docker-compose.resolved.yml --out-dir docs/diagrams --domain-display redacted-label --host-inventory "$HOST_INVENTORY")
|
||||
[[ -s "$DNS_INVENTORY" && "$SKIP_DNS" -eq 0 ]] && GEN_ARGS+=(--dns-inventory "$DNS_INVENTORY")
|
||||
python3 scripts/docs/generate-diagrams.py "${GEN_ARGS[@]}"
|
||||
python3 scripts/docs/sanitize-public-docs.py docs/generated docs/diagrams docs/public
|
||||
|
||||
[[ -s docs/public/physical-topology.svg ]] || { echo "ERROR: docs/public/physical-topology.svg missing or empty." >&2; exit 1; }
|
||||
if grep -Fq "Host inventory JSON not found" docs/public/physical-topology.svg || grep -Fq "Generate terraform inventory" docs/public/physical-topology.svg; then
|
||||
echo "ERROR: docs/public/physical-topology.svg contains placeholder/error text; host inventory refresh failed." >&2
|
||||
exit 1
|
||||
fi
|
||||
[[ -s docs/public/docker-traefik-dynu.svg ]] || { echo "ERROR: docs/public/docker-traefik-dynu.svg missing or empty." >&2; exit 1; }
|
||||
[[ -s docs/generated/docker-compose.resolved.yml ]] || { echo "ERROR: docs/generated/docker-compose.resolved.yml missing or empty." >&2; exit 1; }
|
||||
[[ -s docs/generated/host-topology.md ]] || { echo "ERROR: docs/generated/host-topology.md missing or empty." >&2; exit 1; }
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env python3
|
||||
import hashlib
|
||||
import sys, yaml
|
||||
|
||||
def md(v): return str(v).replace('|','\\|') if v is not None else ''
|
||||
|
||||
inp,out=sys.argv[1],sys.argv[2]
|
||||
with open(inp) as f: c=yaml.safe_load(f) or {}
|
||||
svcs=c.get('services',{}) or {}
|
||||
nets=c.get('networks',{}) or {}
|
||||
vols=c.get('volumes',{}) or {}
|
||||
raw=open(inp,'rb').read()
|
||||
fingerprint=hashlib.sha256(raw).hexdigest()[:12]
|
||||
lines=["# Docker Compose Inventory","",f"Source fingerprint: `{fingerprint}`","","## Summary","","| Item | Count |","|---|---:|",f"| Services | {len(svcs)} |",f"| Networks | {len(nets)} |",f"| Volumes | {len(vols)} |","","## Services","","| Service | Container | Image | Build | Profiles | Networks | Ports | Restart |","|---|---|---|---|---|---|---|---|"]
|
||||
for n,s in sorted(svcs.items()):
|
||||
build=s.get('build','')
|
||||
if isinstance(build,dict): build=build.get('context','')
|
||||
ports=', '.join(str(p) for p in s.get('ports',[]) )
|
||||
networks=', '.join((s.get('networks') or {}).keys() if isinstance(s.get('networks'),dict) else (s.get('networks') or []))
|
||||
profiles=', '.join(s.get('profiles',[]) or [])
|
||||
lines.append(f"| {md(n)} | {md(s.get('container_name',''))} | {md(s.get('image',''))} | {md(build)} | {md(profiles)} | {md(networks)} | {md(ports)} | {md(s.get('restart',''))} |")
|
||||
lines += ["","## Networks","","| Network | Driver | External |","|---|---|---|"]
|
||||
for n,v in sorted(nets.items()): lines.append(f"| {md(n)} | {md((v or {}).get('driver',''))} | {md((v or {}).get('external',False))} |")
|
||||
lines += ["","## Volumes","","| Volume | External |","|---|---|"]
|
||||
for n,v in sorted(vols.items()): lines.append(f"| {md(n)} | {md((v or {}).get('external',False))} |")
|
||||
open(out,'w').write('\n'.join(lines)+'\n')
|
||||
@@ -0,0 +1,360 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import json
|
||||
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 {}
|
||||
|
||||
|
||||
PARENT_KEYS = ("node", "node_name", "host", "physical_host", "hypervisor_host", "proxmox_node")
|
||||
|
||||
|
||||
def display_domain(value: str, mode: str) -> str:
|
||||
if mode == "full":
|
||||
return value
|
||||
if mode == "placeholder":
|
||||
return "<internal-domain>" if INTERNAL_DOMAIN_RE.search(value) else value
|
||||
if INTERNAL_DOMAIN_RE.search(value):
|
||||
label = re.sub(r"\.lan\.ddnsgeek\.com$", "", value)
|
||||
return f"{label}.<domain>"
|
||||
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 categorize_service(service_name: str) -> str:
|
||||
s = service_name.lower()
|
||||
if any(k in s for k in ["traefik", "authelia", "oauth", "auth", "proxy", "nginx", "caddy"]):
|
||||
return "edge/proxy/auth"
|
||||
if any(k in s for k in ["prometheus", "grafana", "loki", "promtail", "alert", "node-exporter", "cadvisor"]):
|
||||
return "monitoring"
|
||||
if any(k in s for k in ["watchtower", "diun", "ansible", "cron", "runner", "backup"]):
|
||||
return "automation"
|
||||
if any(k in s for k in ["postgres", "mariadb", "mysql", "redis", "minio", "nfs", "storage", "db", "queue"]):
|
||||
return "storage/database/support"
|
||||
return "apps"
|
||||
|
||||
|
||||
def load_inventory(path: Path | None) -> dict:
|
||||
if not path or not path.exists():
|
||||
return {}
|
||||
payload = json.loads(path.read_text())
|
||||
return payload.get("value", payload) if isinstance(payload, dict) else {}
|
||||
|
||||
|
||||
def to_records(data) -> list[dict]:
|
||||
if not isinstance(data, dict):
|
||||
return []
|
||||
out = []
|
||||
for key, value in data.items():
|
||||
if isinstance(value, dict):
|
||||
rec = dict(value)
|
||||
rec.setdefault("_key", str(key))
|
||||
rec.setdefault("name", rec.get("hostname") or rec.get("vm_name") or str(key))
|
||||
out.append(rec)
|
||||
return out
|
||||
|
||||
|
||||
def parent_name(item: dict) -> str:
|
||||
for k in PARENT_KEYS:
|
||||
v = item.get(k)
|
||||
if v:
|
||||
return str(v)
|
||||
return ""
|
||||
|
||||
|
||||
def generate_physical_topology(compose: dict, inventory: dict, out_dot: Path, out_svg: Path) -> None:
|
||||
physical = to_records(inventory.get("physical_hosts", {}))
|
||||
virtual = to_records(inventory.get("virtual_hosts", {})) + to_records(inventory.get("vms", {}))
|
||||
if not physical and not virtual:
|
||||
lines = [
|
||||
"digraph PhysicalTopology {",
|
||||
" graph [rankdir=LR, fontname=\"Helvetica\", nodesep=1.0, ranksep=1.5];",
|
||||
' "placeholder:inventory" [shape=note, style="filled", fillcolor="#fef3c7", label="Host inventory JSON not found.\\nGenerate terraform inventory and rerun scripts/docs/generate-all.sh\\n(--host-inventory <path>)."];',
|
||||
]
|
||||
lines.append("}")
|
||||
write_dot(out_dot, lines)
|
||||
render_svg(out_dot, out_svg)
|
||||
return
|
||||
|
||||
lines = [
|
||||
"digraph PhysicalTopology {",
|
||||
" graph [rankdir=LR, compound=true, splines=polyline, nodesep=0.95, ranksep=1.7, ratio=compress, fontname=\"Helvetica\", fontsize=13, concentrate=true, newrank=true];",
|
||||
" node [fontname=\"Helvetica\", fontsize=12, style=\"rounded,filled\", fillcolor=\"#ffffff\"];",
|
||||
" edge [fontname=\"Helvetica\", fontsize=10, color=\"#64748b\"];",
|
||||
]
|
||||
phys_names = {str(p.get("name")): p for p in physical}
|
||||
children: dict[str, list[dict]] = {k: [] for k in phys_names}
|
||||
orphans: list[dict] = []
|
||||
for vm in virtual:
|
||||
parent = parent_name(vm)
|
||||
if parent in children:
|
||||
children[parent].append(vm)
|
||||
else:
|
||||
orphans.append(vm)
|
||||
for host, record in sorted(phys_names.items()):
|
||||
host_role = str(record.get("role", "") or "")
|
||||
cluster_label = f"{host}\\n{host_role}" if host_role else host
|
||||
lines.extend([
|
||||
f' subgraph "cluster_{host}" {{',
|
||||
f' label="{cluster_label}";',
|
||||
' style="rounded,filled";',
|
||||
' color="#60a5fa";',
|
||||
' fillcolor="#eff6ff";',
|
||||
f' "phys:{host}" [label="{host}", shape=box3d, fillcolor="#bfdbfe"];',
|
||||
])
|
||||
for vm in sorted(children.get(host, []), key=lambda x: str(x.get("name", "")).lower()):
|
||||
vm_name = str(vm.get("name"))
|
||||
vm_role = str(vm.get("role", "") or "virtual host")
|
||||
cluster_id = f"cluster_{host}_{re.sub(r'[^a-zA-Z0-9]+', '_', vm_name)}"
|
||||
lines.extend([
|
||||
f' subgraph "{cluster_id}" {{',
|
||||
f' label="{vm_name}";',
|
||||
' style="rounded,dashed";',
|
||||
' color="#bfdbfe";',
|
||||
' fillcolor="#f8fbff";',
|
||||
f' "vm:{vm_name}" [label="{vm_name}\\n{vm_role}", shape=component, fillcolor="#dcfce7"];',
|
||||
])
|
||||
if "docker" in vm_role.lower() or "docker" in vm_name.lower():
|
||||
lines.append(f' "role:{vm_name}" [label="Docker host", shape=box, fillcolor="#fef3c7"];')
|
||||
lines.append(f' "vm:{vm_name}" -> "role:{vm_name}" [style=dashed, label="runs"];')
|
||||
lines.append(' }')
|
||||
lines.append(f' "phys:{host}" -> "vm:{vm_name}" [label="hosts"];')
|
||||
lines.append(' }')
|
||||
if orphans:
|
||||
lines.extend([
|
||||
' subgraph "cluster_orphans" {',
|
||||
' label="Unmapped virtual hosts"; style="rounded,dashed"; color="#d1d5db";',
|
||||
])
|
||||
for vm in sorted(orphans, key=lambda x: str(x.get("name", "")).lower()):
|
||||
vm_name = str(vm.get("name"))
|
||||
lines.append(f' "vm:{vm_name}" [label="{vm_name}", shape=component, fillcolor="#fee2e2"];')
|
||||
lines.append(" }")
|
||||
|
||||
lines.extend([
|
||||
' subgraph "cluster_legend" {',
|
||||
' label="Legend"; style="rounded"; color="#d1d5db";',
|
||||
' "leg_host" [label="Physical host", shape=box3d, fillcolor="#eff6ff"];',
|
||||
' "leg_vm" [label="Virtual machine", shape=component, fillcolor="#dcfce7"];',
|
||||
' "leg_role" [label="Hosted role", shape=box, fillcolor="#fef3c7"];',
|
||||
' "leg_host" -> "leg_vm" [label="hosts"];',
|
||||
' "leg_vm" -> "leg_role" [style=dashed, label="runs"];',
|
||||
' }',
|
||||
"}",
|
||||
])
|
||||
write_dot(out_dot, lines)
|
||||
render_svg(out_dot, out_svg)
|
||||
|
||||
|
||||
def generate_docker_traefik_dynu(compose: dict, dns_inventory: dict, domain_mode: str, out_dot: Path, out_svg: Path) -> None:
|
||||
services = compose.get("services") or {}
|
||||
networks = compose.get("networks") or {}
|
||||
lines = [
|
||||
"digraph DockerTraefikDynu {",
|
||||
" graph [rankdir=LR, compound=true, splines=polyline, nodesep=0.9, ranksep=1.6, fontname=\"Helvetica\", concentrate=true, newrank=true];",
|
||||
" node [fontname=\"Helvetica\", fontsize=11, style=\"rounded,filled\"];",
|
||||
" edge [fontname=\"Helvetica\", fontsize=9, color=\"#334155\"];",
|
||||
' "dynu" [label="Dynu / Public DNS", shape=box, fillcolor="#fde68a"];',
|
||||
' "svc:traefik" [label="Traefik", shape=box, fillcolor="#bfdbfe"];',
|
||||
' "dynu" -> "svc:traefik" [penwidth=1.6];',
|
||||
]
|
||||
|
||||
routes: dict[str, dict] = {}
|
||||
dns_nodes: set[str] = set()
|
||||
|
||||
for svc_name, svc in sorted(services.items()):
|
||||
labels = parse_labels(svc)
|
||||
router_prefix = "traefik.http.routers."
|
||||
service_prefix = "traefik.http.services."
|
||||
lb_ports = {}
|
||||
for k, v in labels.items():
|
||||
if k.startswith(service_prefix) and k.endswith(".loadbalancer.server.port"):
|
||||
lb_ports[k[len(service_prefix):].split(".", 1)[0]] = v
|
||||
|
||||
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", "")
|
||||
target = labels.get(f"{router_prefix}{router}.service", svc_name)
|
||||
middlewares = labels.get(f"{router_prefix}{router}.middlewares", "")
|
||||
tls = labels.get(f"{router_prefix}{router}.tls", "false")
|
||||
port = lb_ports.get(target, "")
|
||||
badges = []
|
||||
if str(tls).lower() in ("true", "1"):
|
||||
badges.append("TLS")
|
||||
mw_low = middlewares.lower()
|
||||
if "authelia" in mw_low:
|
||||
badges.append("authelia")
|
||||
if "mtls" in mw_low:
|
||||
badges.append("mTLS")
|
||||
hosts = [display_domain(h, domain_mode) for h in extract_hosts(rule)]
|
||||
if not hosts:
|
||||
continue
|
||||
info = routes.setdefault(svc_name, {"hosts": set(), "port": port, "badges": set()})
|
||||
info["hosts"].update(hosts)
|
||||
if port:
|
||||
info["port"] = port
|
||||
info["badges"].update(badges)
|
||||
|
||||
for svc_name, info in sorted(routes.items()):
|
||||
label = svc_name
|
||||
if info.get("port"):
|
||||
label += f"\n:{info['port']}"
|
||||
if info.get("badges"):
|
||||
label += "\n[" + ", ".join(sorted(info["badges"])) + "]"
|
||||
lines.append(f' "svc:{svc_name}" [label="{label}", shape=box, fillcolor="#dcfce7"];')
|
||||
lines.append(f' "svc:traefik" -> "svc:{svc_name}" [penwidth=1.4];')
|
||||
for host in sorted(info["hosts"]):
|
||||
dns_nodes.add(host)
|
||||
lines.append(f' "dns:{host}" [label="{host}", shape=note, fillcolor="#fef3c7"];')
|
||||
lines.append(f' "dns:{host}" -> "dynu";')
|
||||
for record in (dns_inventory.get("records", []) if isinstance(dns_inventory, dict) else []):
|
||||
host = record.get("hostname") or record.get("name")
|
||||
if not host:
|
||||
continue
|
||||
host_disp = display_domain(str(host), domain_mode)
|
||||
if host_disp in dns_nodes:
|
||||
continue
|
||||
dns_nodes.add(host_disp)
|
||||
lines.append(f' "dns:{host_disp}" [label="{host_disp}", shape=note, fillcolor="#fef3c7"];')
|
||||
lines.append(f' "dns:{host_disp}" -> "dynu" [style=dashed, color=\"#94a3b8\"];')
|
||||
|
||||
lines.append(' { rank=same; ' + '; '.join([f'"dns:{d}"' for d in sorted(dns_nodes)]) + '; }' if dns_nodes else '')
|
||||
|
||||
lines.append(' subgraph "cluster_networks" {')
|
||||
lines.append(' label="Docker backend networks"; style="rounded,dashed"; color="#d1d5db";')
|
||||
for net in sorted(networks.keys()):
|
||||
lines.append(f' "net:{net}" [label="{net}", shape=ellipse, fillcolor="#f8fafc"];')
|
||||
lines.append(' }')
|
||||
|
||||
for svc_name in sorted(routes.keys()):
|
||||
svc = services.get(svc_name, {})
|
||||
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}" [style=dashed, color="#94a3b8", arrowsize=0.7];')
|
||||
|
||||
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")
|
||||
parser.add_argument("--host-inventory")
|
||||
parser.add_argument("--dns-inventory")
|
||||
parser.add_argument("--domain-display", choices=["full", "redacted-label", "placeholder"], default="redacted-label")
|
||||
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 <compose.yml> --out-dir <dir>")
|
||||
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
compose = load_compose(compose_path)
|
||||
host_inventory = load_inventory(Path(args.host_inventory)) if args.host_inventory else {}
|
||||
dns_inventory = load_inventory(Path(args.dns_inventory)) if args.dns_inventory else {}
|
||||
|
||||
generate_docker_traefik_dynu(compose, dns_inventory, args.domain_display, out_dir / "docker-traefik-dynu.dot", out_dir / "docker-traefik-dynu.svg")
|
||||
generate_physical_topology(compose, host_inventory, 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()
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python3
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
out = Path(sys.argv[1])
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
out.write_text(
|
||||
"""# Generated Documentation
|
||||
|
||||
This directory contains documentation generated automatically from repository configuration.
|
||||
|
||||
## Files
|
||||
|
||||
- [Compose file list](compose-files.txt)
|
||||
- [Resolved Docker Compose config](docker-compose.resolved.yml)
|
||||
- [Compose inventory](compose-inventory.md)
|
||||
- [Traefik routes](traefik-routes.md)
|
||||
- [Docker Compose diagram](../diagrams/docker-compose.svg)
|
||||
"""
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys,yaml,glob
|
||||
out=sys.argv[1]
|
||||
patterns=["monitoring/prometheus/rules/**/*.yml","monitoring/prometheus/rules/**/*.yaml","**/prometheus/rules/**/*.yml","**/prometheus/rules/**/*.yaml"]
|
||||
files=sorted({f for p in patterns for f in glob.glob(p,recursive=True)})
|
||||
lines=["# Prometheus Rules","", "| File | Group | Alert | Expr | For | Labels | Annotations |","|---|---|---|---|---|---|---|"]
|
||||
if not files:
|
||||
open(out,'w').write("# Prometheus Rules\n\nNo Prometheus rule files were found.\n"); sys.exit(0)
|
||||
for fp in files:
|
||||
try:data=yaml.safe_load(open(fp)) or {}
|
||||
except Exception as e: raise SystemExit(f"Malformed YAML in {fp}: {e}")
|
||||
for g in data.get('groups',[]) or []:
|
||||
for r in g.get('rules',[]) or []:
|
||||
lines.append(f"| {fp} | {g.get('name','')} | {r.get('alert','')} | {str(r.get('expr','')).replace('|','\\|')} | {r.get('for','')} | {r.get('labels',{})} | {r.get('annotations',{})} |")
|
||||
open(out,'w').write('\n'.join(lines)+'\n')
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys,yaml,re
|
||||
inp,out=sys.argv[1],sys.argv[2]
|
||||
with open(inp) as f: c=yaml.safe_load(f) or {}
|
||||
rows=[]
|
||||
for sname,svc in (c.get('services') or {}).items():
|
||||
labels=svc.get('labels') or {}
|
||||
if isinstance(labels,list):
|
||||
d={}
|
||||
for l in labels:
|
||||
if '=' in str(l):k,v=str(l).split('=',1);d[k]=v
|
||||
labels=d
|
||||
routers={}
|
||||
for k,v in labels.items():
|
||||
m=re.match(r'traefik\.http\.routers\.([^.]+)\.(rule|entrypoints|tls|middlewares)$',k)
|
||||
if m: routers.setdefault(m.group(1),{})[m.group(2)]=v
|
||||
ports={}
|
||||
for k,v in labels.items():
|
||||
m=re.match(r'traefik\.http\.services\.([^.]+)\.loadbalancer\.server\.port$',k)
|
||||
if m: ports[m.group(1)]=v
|
||||
for r,rv in routers.items():
|
||||
rows.append((sname,r,rv.get('rule',''),rv.get('entrypoints',''),rv.get('tls',''),rv.get('middlewares',''),ports.get(r,'')))
|
||||
lines=["# Traefik Routes","", "| Service | Router | Rule | Entrypoints | TLS | Middlewares | Target Port |","|---|---|---|---|---|---|---|"]
|
||||
if not rows:
|
||||
lines=["# Traefik Routes","","No Traefik routes were detected."]
|
||||
else:
|
||||
for r in sorted(rows): lines.append('| '+' | '.join(str(x).replace('|','\\|') for x in r)+' |')
|
||||
open(out,'w').write('\n'.join(lines)+'\n')
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
dynu_dir="${repo_root}/infrastructure/terraform/dynu"
|
||||
out_json="${dynu_dir}/generated/dynu_dns_records_inventory.json"
|
||||
|
||||
if [[ ! -d "${dynu_dir}" ]]; then
|
||||
echo "ERROR: Dynu Terraform directory not found at ${dynu_dir}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v terraform >/dev/null 2>&1; then
|
||||
echo "ERROR: terraform is required to refresh Dynu DNS inventory." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "${out_json}")"
|
||||
|
||||
(
|
||||
cd "${dynu_dir}"
|
||||
if terraform output -json dynu_dns_inventory > "${out_json}"; then
|
||||
:
|
||||
elif terraform output -json dynu_dns_records_catalog > "${out_json}"; then
|
||||
:
|
||||
else
|
||||
echo "ERROR: Failed to export Dynu DNS inventory from Terraform outputs. Tried: dynu_dns_inventory, dynu_dns_records_catalog." >&2
|
||||
echo "Run 'cd infrastructure/terraform/dynu && terraform output' to inspect available outputs." >&2
|
||||
exit 1
|
||||
fi
|
||||
)
|
||||
|
||||
if [[ ! -s "${out_json}" ]]; then
|
||||
echo "ERROR: Dynu DNS inventory output file is empty: ${out_json}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Generated: ${out_json}"
|
||||
Executable
+189
@@ -0,0 +1,189 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate host topology Markdown/Mermaid from Terraform inventory JSON."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
PARENT_KEYS = [
|
||||
"node",
|
||||
"node_name",
|
||||
"host",
|
||||
"physical_host",
|
||||
"hypervisor_host",
|
||||
"proxmox_node",
|
||||
]
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate Markdown topology from terraform output JSON"
|
||||
)
|
||||
parser.add_argument("--input", required=True, help="Input JSON path")
|
||||
parser.add_argument("--output", required=True, help="Output Markdown path")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def load_inventory(raw: dict[str, Any]) -> dict[str, Any]:
|
||||
payload = raw.get("value", raw)
|
||||
if not isinstance(payload, dict):
|
||||
raise ValueError("Inventory payload must be an object")
|
||||
return payload
|
||||
|
||||
|
||||
def infer_name(key: str, item: dict[str, Any]) -> str:
|
||||
for candidate in ("name", "hostname", "vm_name", "id"):
|
||||
value = item.get(candidate)
|
||||
if value:
|
||||
return str(value)
|
||||
return key
|
||||
|
||||
|
||||
def to_records(host_map: Any) -> list[dict[str, Any]]:
|
||||
if isinstance(host_map, dict):
|
||||
records: list[dict[str, Any]] = []
|
||||
for key, val in host_map.items():
|
||||
if isinstance(val, dict):
|
||||
rec = dict(val)
|
||||
rec.setdefault("_key", str(key))
|
||||
rec.setdefault("name", infer_name(str(key), rec))
|
||||
records.append(rec)
|
||||
return sorted(records, key=lambda x: str(x.get("name", "")).lower())
|
||||
return []
|
||||
|
||||
|
||||
def escape_cell(value: Any) -> str:
|
||||
if value is None:
|
||||
return ""
|
||||
text = str(value).replace("|", "\\|").replace("\n", "<br>")
|
||||
return text
|
||||
|
||||
|
||||
def get_first(item: dict[str, Any], *keys: str) -> str:
|
||||
for key in keys:
|
||||
value = item.get(key)
|
||||
if value is not None and value != "":
|
||||
return str(value)
|
||||
return ""
|
||||
|
||||
|
||||
def detect_virtual_hosts(inv: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
virtual: list[dict[str, Any]] = []
|
||||
for key in ("virtual_hosts", "vms"):
|
||||
virtual.extend(to_records(inv.get(key, {})))
|
||||
return sorted(virtual, key=lambda x: str(x.get("name", "")).lower())
|
||||
|
||||
|
||||
def build_mermaid(physical: list[dict[str, Any]], virtual: list[dict[str, Any]]) -> list[str]:
|
||||
lines = ["```mermaid", "flowchart TD"]
|
||||
for p in physical:
|
||||
pname = get_first(p, "name", "hostname", "_key")
|
||||
pid = f"phys_{pname.lower().replace('-', '_').replace(' ', '_')}"
|
||||
lines.append(f' {pid}["{pname}\\nphysical"]')
|
||||
|
||||
for v in virtual:
|
||||
vname = get_first(v, "name", "hostname", "_key")
|
||||
vid = f"virt_{vname.lower().replace('-', '_').replace(' ', '_')}"
|
||||
parent = ""
|
||||
for k in PARENT_KEYS:
|
||||
parent = get_first(v, k)
|
||||
if parent:
|
||||
break
|
||||
|
||||
lines.append(f' {vid}["{vname}\\nvirtual"]')
|
||||
if parent:
|
||||
pid = f"phys_{parent.lower().replace('-', '_').replace(' ', '_')}"
|
||||
lines.append(f" {pid} --> {vid}")
|
||||
|
||||
lines.append("```")
|
||||
return lines
|
||||
|
||||
|
||||
def build_table(headers: list[str], rows: list[list[str]]) -> list[str]:
|
||||
out = ["| " + " | ".join(headers) + " |", "| " + " | ".join(["---"] * len(headers)) + " |"]
|
||||
for row in rows:
|
||||
out.append("| " + " | ".join(escape_cell(cell) for cell in row) + " |")
|
||||
return out
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
raw = json.loads(Path(args.input).read_text(encoding="utf-8"))
|
||||
inv = load_inventory(raw)
|
||||
|
||||
physical = to_records(inv.get("physical_hosts", {}))
|
||||
virtual = detect_virtual_hosts(inv)
|
||||
|
||||
now = datetime.now(timezone.utc).replace(microsecond=0).isoformat()
|
||||
lines: list[str] = [
|
||||
"# Host Topology",
|
||||
"",
|
||||
f"> Generated by `scripts/docs/generate_host_topology.py` on {now}.",
|
||||
"",
|
||||
"## Topology Diagram",
|
||||
"",
|
||||
]
|
||||
lines.extend(build_mermaid(physical, virtual))
|
||||
lines.extend(["", "## Physical Hosts", ""])
|
||||
|
||||
physical_rows = [
|
||||
[
|
||||
get_first(p, "name", "hostname", "_key"),
|
||||
get_first(p, "type"),
|
||||
get_first(p, "role"),
|
||||
get_first(p, "management", "management_ip"),
|
||||
get_first(p, "os", "os_family"),
|
||||
get_first(p, "hypervisor"),
|
||||
get_first(p, "location"),
|
||||
get_first(p, "notes"),
|
||||
]
|
||||
for p in physical
|
||||
]
|
||||
lines.extend(
|
||||
build_table(
|
||||
["Name", "Type", "Role", "Management", "OS", "Hypervisor", "Location", "Notes"],
|
||||
physical_rows,
|
||||
)
|
||||
)
|
||||
|
||||
lines.extend(["", "## Virtual Hosts", ""])
|
||||
if virtual:
|
||||
virtual_rows = []
|
||||
for v in virtual:
|
||||
parent = ""
|
||||
for k in PARENT_KEYS:
|
||||
parent = get_first(v, k)
|
||||
if parent:
|
||||
break
|
||||
virtual_rows.append(
|
||||
[
|
||||
get_first(v, "name", "hostname", "_key"),
|
||||
get_first(v, "type"),
|
||||
get_first(v, "role"),
|
||||
parent,
|
||||
get_first(v, "management", "management_ip", "ip"),
|
||||
get_first(v, "os", "os_family"),
|
||||
get_first(v, "notes"),
|
||||
]
|
||||
)
|
||||
lines.extend(
|
||||
build_table(
|
||||
["Name", "Type", "Role", "Parent/Node", "Management", "OS", "Notes"],
|
||||
virtual_rows,
|
||||
)
|
||||
)
|
||||
else:
|
||||
lines.append("No VM/virtual host data found in the current Terraform inventory output.")
|
||||
|
||||
out_path = Path(args.output)
|
||||
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
out_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Discover compose files for docs tooling and CI without running containers.
|
||||
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
declare -a files=()
|
||||
|
||||
if [ -f services-up.sh ]; then
|
||||
# Parse literal FILES array entries in services-up.sh (e.g. default-network.yml).
|
||||
while IFS= read -r line; do
|
||||
path=$(sed -E 's#.*\$PROJECT_ROOT/([^" ]+).*#\1#' <<<"$line")
|
||||
[ -f "$path" ] && files+=("$path")
|
||||
done < <(awk '/^FILES=\(/,/^\)/ {print}' services-up.sh | grep -E '\-f[[:space:]]+"\$PROJECT_ROOT/')
|
||||
|
||||
# Reuse the same compose roots used by services-up.sh to avoid archived compose files.
|
||||
if grep -q 'find "\$PROJECT_ROOT/apps" "\$PROJECT_ROOT/monitoring" "\$PROJECT_ROOT/core"' services-up.sh; then
|
||||
while IFS= read -r f; do files+=("$f"); done < <(
|
||||
find apps monitoring core -maxdepth 2 -type f \
|
||||
\( -name 'docker-compose.yml' -o -name 'docker-compose.yaml' -o -name 'compose.yml' -o -name 'compose.yaml' \) \
|
||||
| sed 's#^\./##' | sort
|
||||
)
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${#files[@]}" -eq 0 ]; then
|
||||
while IFS= read -r f; do files+=("$f"); done < <(
|
||||
find . -type f \
|
||||
\( -name 'docker-compose.yml' -o -name 'docker-compose.yaml' -o -name 'compose.yml' -o -name 'compose.yaml' \) \
|
||||
| sed 's#^\./##' | grep -v '^archive/' | sort
|
||||
)
|
||||
fi
|
||||
|
||||
printf '%s\n' "${files[@]}" | awk 'NF' | awk '!seen[$0]++'
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
||||
cd "$ROOT"
|
||||
mkdir -p docs/generated
|
||||
mapfile -t COMPOSE_FILES < <(scripts/docs/list-compose-files.sh)
|
||||
if [ "${#COMPOSE_FILES[@]}" -eq 0 ]; then
|
||||
echo "No compose files found" >&2
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\n' "${COMPOSE_FILES[@]}" > docs/generated/compose-files.txt
|
||||
|
||||
if [ ! -x ./services-up.sh ]; then
|
||||
echo "services-up.sh is missing or not executable" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
./services-up.sh --profile all config > docs/generated/docker-compose.resolved.yml
|
||||
|
||||
service_count="$(
|
||||
python3 - <<'PY'
|
||||
import yaml
|
||||
from pathlib import Path
|
||||
|
||||
data = yaml.safe_load(Path("docs/generated/docker-compose.resolved.yml").read_text()) or {}
|
||||
print(len(data.get("services") or {}))
|
||||
PY
|
||||
)"
|
||||
|
||||
if [ "$service_count" -eq 0 ]; then
|
||||
echo "ERROR: rendered compose config contains zero services; check --profile all / COMPOSE_PROFILES." >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env python3
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
src_generated = Path(sys.argv[1])
|
||||
src_diagrams = Path(sys.argv[2])
|
||||
out_dir = Path(sys.argv[3])
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def sanitize_text(content: str) -> str:
|
||||
content = re.sub(r'\b([a-zA-Z0-9-]+)\.lan\.ddnsgeek\.com\b', r'\1.<domain>', content)
|
||||
content = re.sub(
|
||||
r'\b(?:10\.\d{1,3}\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3})\b',
|
||||
'<private-ip>',
|
||||
content,
|
||||
)
|
||||
content = re.sub(r'(?i)\b(password|token|api[_-]?key|secret)\s*[:=]\s*[^\s\n]+', r'\1=<redacted>', content)
|
||||
content = re.sub(r'(?m)^([A-Z0-9_]*(?:PASSWORD|TOKEN|API_KEY|SECRET)[A-Z0-9_]*)\s*[:=]\s*.*$', r'\1=<redacted>', content)
|
||||
return content
|
||||
|
||||
|
||||
for name in ['compose-inventory.md', 'traefik-routes.md']:
|
||||
src = src_generated / name
|
||||
if src.exists():
|
||||
(out_dir / name).write_text(sanitize_text(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 documentation is generated from the infrastructure repository. Sensitive values are redacted.
|
||||
|
||||
> Generated docs are sanitised/redacted before publishing to GitHub Pages.
|
||||
|
||||
## Infrastructure diagrams
|
||||
|
||||
### Physical / virtual topology
|
||||
|
||||

|
||||
|
||||
### Docker, Traefik and Dynu routing
|
||||
|
||||

|
||||
|
||||
## Documents
|
||||
|
||||
- [Diagrams](diagrams.md)
|
||||
- [Compose Inventory](compose-inventory.md)
|
||||
- [Traefik Routes](traefik-routes.md)
|
||||
"""
|
||||
)
|
||||
|
||||
(out_dir / 'diagrams.md').write_text(
|
||||
"""# Infrastructure diagrams
|
||||
|
||||
## Physical / virtual topology
|
||||
|
||||
This view groups containers by inferred host and service role (edge/proxy/auth, monitoring, automation, apps, and supporting storage/services).
|
||||
|
||||
<div class="diagram-wrap">
|
||||
<img src="physical-topology.svg" alt="Physical topology">
|
||||
</div>
|
||||
|
||||
## Docker, Traefik and Dynu routing
|
||||
|
||||
This view shows sanitised public DNS names flowing to Traefik, then to exposed Docker services, with backend Docker network membership shown as secondary context.
|
||||
|
||||
_Diagrams are generated from Compose data and Traefik labels._
|
||||
|
||||
<div class="diagram-wrap">
|
||||
<img src="docker-traefik-dynu.svg" alt="Docker Traefik Dynu">
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# This integration is intentionally read-only.
|
||||
# No Dynu mutations are permitted in this repo at this stage.
|
||||
|
||||
# Optional convenience: auto-load local Dynu env file when variables are unset.
|
||||
if [[ -f "secrets/dynu.env" ]]; then
|
||||
set -a
|
||||
# shellcheck source=/dev/null
|
||||
source "secrets/dynu.env"
|
||||
set +a
|
||||
fi
|
||||
|
||||
if [[ "${DYNU_READ_ONLY:-}" != "true" ]]; then
|
||||
echo "Refusing to run: DYNU_READ_ONLY must be exactly 'true'." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [[ -z "${DYNU_API_KEY:-}" ]]; then
|
||||
echo "Missing DYNU_API_KEY. Set it in env or secrets/dynu.env." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
python3 scripts/dynu/fetch_dynu_dns.py
|
||||
python3 scripts/dynu/correlate_dynu_with_traefik.py
|
||||
Executable
+465
@@ -0,0 +1,465 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Correlate Dynu DNS data with Traefik host rules in compose sources.
|
||||
|
||||
This integration is intentionally read-only.
|
||||
No Dynu mutations are permitted in this repo at this stage.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Iterable, List, Set
|
||||
|
||||
import yaml
|
||||
|
||||
BASE_DOMAIN = "lan.ddnsgeek.com"
|
||||
ALLOWED_UNMAPPED_HOSTNAMES = ["edge.lan.ddnsgeek.com"]
|
||||
DYN_DATA = Path("data/dns/dynu_live.json")
|
||||
OUT_JSON = Path("data/dns/dynu_traefik_inventory.json")
|
||||
OUT_MD = Path("docs/generated/dns-inventory.md")
|
||||
|
||||
HOST_CALL_RE = re.compile(r"Host\s*\(([^)]*)\)", re.IGNORECASE)
|
||||
QUOTED_HOST_RE = re.compile(r"[`\"']([^`\"']+)[`\"']")
|
||||
ROUTER_LABEL_RE = re.compile(r"^traefik\.http\.routers\.([^.]+)\.(.+)$")
|
||||
|
||||
|
||||
class ReadOnlyError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def require_read_only() -> None:
|
||||
if os.environ.get("DYNU_READ_ONLY") != "true":
|
||||
raise ReadOnlyError(
|
||||
"Refusing to run: DYNU_READ_ONLY must be exactly 'true'. "
|
||||
"This integration is intentionally read-only."
|
||||
)
|
||||
|
||||
|
||||
def compose_files(root: Path) -> List[Path]:
|
||||
files: Set[Path] = set()
|
||||
if (root / "default-network.yml").exists():
|
||||
files.add(root / "default-network.yml")
|
||||
|
||||
for area in ("apps", "monitoring", "core"):
|
||||
base = root / area
|
||||
if not base.exists():
|
||||
continue
|
||||
for pattern in ("**/docker-compose.yml", "**/docker-compose.yaml"):
|
||||
files.update(p for p in base.glob(pattern) if p.is_file())
|
||||
|
||||
return sorted(files)
|
||||
|
||||
|
||||
def parse_hosts_from_rule(rule: str) -> List[str]:
|
||||
hosts: Set[str] = set()
|
||||
for call_fragment in HOST_CALL_RE.findall(rule):
|
||||
quoted_hosts = QUOTED_HOST_RE.findall(call_fragment)
|
||||
for host in quoted_hosts:
|
||||
clean = host.strip().strip(".").lower()
|
||||
if clean:
|
||||
hosts.add(clean)
|
||||
|
||||
if not quoted_hosts:
|
||||
for token in call_fragment.split(","):
|
||||
clean = token.strip().strip(".`\"'").lower()
|
||||
if clean:
|
||||
hosts.add(clean)
|
||||
|
||||
return sorted(hosts)
|
||||
|
||||
|
||||
def load_env_defaults(repo_root: Path) -> Dict[str, str]:
|
||||
env_values: Dict[str, str] = {}
|
||||
for candidate in (repo_root / "default-environment.env", repo_root / ".env"):
|
||||
if not candidate.exists():
|
||||
continue
|
||||
for line in candidate.read_text(encoding="utf-8").splitlines():
|
||||
stripped = line.strip()
|
||||
if not stripped or stripped.startswith("#") or "=" not in stripped:
|
||||
continue
|
||||
key, value = stripped.split("=", 1)
|
||||
env_values[key.strip()] = value.strip().strip("'\"")
|
||||
return env_values
|
||||
|
||||
|
||||
def resolve_rule_variables(rule: str, env_values: Dict[str, str]) -> str:
|
||||
var_re = re.compile(r"\$\{([A-Za-z_][A-Za-z0-9_]*)\}")
|
||||
|
||||
def replacer(match: re.Match[str]) -> str:
|
||||
key = match.group(1)
|
||||
if key in os.environ:
|
||||
return os.environ[key]
|
||||
return env_values.get(key, match.group(0))
|
||||
|
||||
return var_re.sub(replacer, rule)
|
||||
|
||||
|
||||
def normalize_labels(raw_labels: Any) -> Dict[str, str]:
|
||||
labels: Dict[str, str] = {}
|
||||
if isinstance(raw_labels, dict):
|
||||
for key, value in raw_labels.items():
|
||||
labels[str(key)] = "" if value is None else str(value)
|
||||
return labels
|
||||
|
||||
if isinstance(raw_labels, list):
|
||||
for item in raw_labels:
|
||||
if isinstance(item, str) and "=" in item:
|
||||
key, value = item.split("=", 1)
|
||||
labels[key.strip()] = value.strip()
|
||||
elif isinstance(item, str):
|
||||
labels[item.strip()] = ""
|
||||
return labels
|
||||
|
||||
return labels
|
||||
|
||||
|
||||
def infer_stack(compose_file: Path) -> str:
|
||||
parts = compose_file.parts
|
||||
return parts[0] if parts else "unknown"
|
||||
|
||||
|
||||
def boolish(value: str) -> bool:
|
||||
return value.strip().lower() in {"1", "true", "yes", "on"}
|
||||
|
||||
|
||||
def parse_middlewares(raw_value: str) -> List[str]:
|
||||
return [item.strip() for item in raw_value.split(",") if item.strip()]
|
||||
|
||||
|
||||
def extract_traefik_hosts(path: Path, env_values: Dict[str, str]) -> List[Dict[str, Any]]:
|
||||
try:
|
||||
payload = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
||||
except yaml.YAMLError as exc:
|
||||
raise RuntimeError(f"Failed to parse compose YAML in {path}: {exc}") from exc
|
||||
|
||||
services = payload.get("services")
|
||||
if not isinstance(services, dict):
|
||||
return []
|
||||
|
||||
entries: List[Dict[str, Any]] = []
|
||||
stack = infer_stack(path)
|
||||
|
||||
for service_name, service_payload in services.items():
|
||||
if not isinstance(service_payload, dict):
|
||||
continue
|
||||
|
||||
labels = normalize_labels(service_payload.get("labels"))
|
||||
router_fields: Dict[str, Dict[str, str]] = defaultdict(dict)
|
||||
|
||||
for label_key, label_value in labels.items():
|
||||
match = ROUTER_LABEL_RE.match(label_key)
|
||||
if not match:
|
||||
continue
|
||||
router_name, field_name = match.groups()
|
||||
router_fields[router_name][field_name] = label_value
|
||||
|
||||
for router_name, fields in router_fields.items():
|
||||
rule = fields.get("rule", "")
|
||||
if not rule:
|
||||
continue
|
||||
|
||||
router_label_key = f"traefik.http.routers.{router_name}.rule"
|
||||
middlewares = parse_middlewares(fields.get("middlewares", ""))
|
||||
tls_options = fields.get("tls.options", "")
|
||||
tls_enabled = boolish(fields.get("tls", "")) or bool(tls_options) or bool(fields.get("tls.certresolver", ""))
|
||||
|
||||
lowered_metadata = " ".join([tls_options, ",".join(middlewares)]).lower()
|
||||
uses_mtls = "mtls" in lowered_metadata
|
||||
uses_authelia = "authelia" in lowered_metadata
|
||||
|
||||
resolved_rule = resolve_rule_variables(rule, env_values)
|
||||
for fqdn in parse_hosts_from_rule(resolved_rule):
|
||||
entries.append(
|
||||
{
|
||||
"fqdn": fqdn,
|
||||
"service": str(service_name),
|
||||
"stack": stack,
|
||||
"source_compose_file": str(path),
|
||||
"router": router_name,
|
||||
"router_label_keys": [router_label_key],
|
||||
"raw_rule": rule,
|
||||
"resolved_rule": resolved_rule,
|
||||
"uses_tls": tls_enabled,
|
||||
"tls_options": tls_options,
|
||||
"middlewares": middlewares,
|
||||
"uses_mtls": uses_mtls,
|
||||
"uses_authelia": uses_authelia,
|
||||
}
|
||||
)
|
||||
|
||||
return entries
|
||||
|
||||
|
||||
def load_dynu(path: Path) -> Dict[str, List[Dict[str, str]]]:
|
||||
payload = json.loads(path.read_text(encoding="utf-8"))
|
||||
if payload.get("base_domain") != BASE_DOMAIN:
|
||||
raise RuntimeError(
|
||||
f"Dynu JSON base_domain mismatch. Expected {BASE_DOMAIN}, got {payload.get('base_domain')}"
|
||||
)
|
||||
|
||||
index: Dict[str, List[Dict[str, str]]] = defaultdict(list)
|
||||
for domain in payload.get("domains", []):
|
||||
for record in domain.get("records", []):
|
||||
host = str(record.get("hostname", "")).strip(".").lower()
|
||||
if host:
|
||||
index[host].append(
|
||||
{
|
||||
"type": str(record.get("type", "")),
|
||||
"value": str(record.get("value", "")),
|
||||
"target": str(record.get("target") or ""),
|
||||
"ttl": str(record.get("ttl") if record.get("ttl") is not None else ""),
|
||||
}
|
||||
)
|
||||
|
||||
for host in index:
|
||||
index[host] = sorted(index[host], key=lambda x: (x["type"], x["value"], x["target"], x["ttl"]))
|
||||
return index
|
||||
|
||||
|
||||
def is_subdomain_of_base(fqdn: str) -> bool:
|
||||
return fqdn.endswith(f".{BASE_DOMAIN}")
|
||||
|
||||
|
||||
def summarize_reasons(
|
||||
has_traefik: bool,
|
||||
has_dns: bool,
|
||||
is_allowed_unmapped: bool,
|
||||
is_ambiguous: bool,
|
||||
is_enforced_dns_subdomain: bool,
|
||||
) -> List[str]:
|
||||
reasons: List[str] = []
|
||||
if has_traefik and has_dns:
|
||||
reasons.append("mapped")
|
||||
if has_dns and not has_traefik and is_allowed_unmapped:
|
||||
reasons.append("allowed_unmapped")
|
||||
if has_dns and not has_traefik and is_enforced_dns_subdomain and not is_allowed_unmapped:
|
||||
reasons.append("unexpected_unmapped")
|
||||
if has_dns and not has_traefik:
|
||||
reasons.append("dns_only")
|
||||
if has_traefik and not has_dns:
|
||||
reasons.append("traefik_only")
|
||||
if is_ambiguous:
|
||||
reasons.append("duplicate_mapping")
|
||||
reasons.append("ambiguous_mapping")
|
||||
return reasons
|
||||
|
||||
|
||||
def write_markdown(data: Dict[str, Any]) -> None:
|
||||
inventory = data["inventory"]
|
||||
|
||||
lines = [
|
||||
"# DNS Inventory (Dynu + Traefik)",
|
||||
"",
|
||||
"> This integration is intentionally read-only. No Dynu mutations are permitted in this repo at this stage.",
|
||||
"",
|
||||
f"- Base domain: `{data['base_domain']}`",
|
||||
f"- Dynu fetched at: `{data['dynu_fetched_at']}`",
|
||||
f"- Inventory generated at: `{data['generated_at']}`",
|
||||
"",
|
||||
"## Summary",
|
||||
"",
|
||||
f"- Traefik hostnames discovered: **{data['summary']['traefik_hostnames']}**",
|
||||
f"- Dynu hostnames discovered: **{data['summary']['dynu_hostnames']}**",
|
||||
f"- Mapped hostnames: **{data['summary']['mapped_hostnames']}**",
|
||||
f"- DNS-only hostnames: **{data['summary']['dns_only_hostnames']}**",
|
||||
f"- Traefik-only hostnames: **{data['summary']['traefik_only_hostnames']}**",
|
||||
f"- Ambiguous hostnames: **{len(data['validation']['ambiguous_hostnames'])}**",
|
||||
"",
|
||||
"## Validation",
|
||||
"",
|
||||
f"- Validation ok: **{str(data['validation']['validation_ok']).lower()}**",
|
||||
f"- Allowed unmapped hostnames: `{', '.join(data['validation']['allowed_unmapped_hostnames'])}`",
|
||||
f"- Unexpected unmapped hostnames: **{len(data['validation']['unexpected_unmapped_hostnames'])}**",
|
||||
f"- Duplicate hostnames: **{len(data['validation']['duplicate_hostnames'])}**",
|
||||
f"- Ambiguous hostnames: **{len(data['validation']['ambiguous_hostnames'])}**",
|
||||
"",
|
||||
]
|
||||
|
||||
def bullet_list(title: str, values: Iterable[str]) -> None:
|
||||
rows = list(values)
|
||||
lines.extend([f"### {title}", ""])
|
||||
if not rows:
|
||||
lines.append("_None._")
|
||||
else:
|
||||
for value in rows:
|
||||
lines.append(f"- `{value}`")
|
||||
lines.append("")
|
||||
|
||||
bullet_list("Allowed unmapped hostnames", data["validation"]["allowed_unmapped_hostnames"])
|
||||
bullet_list("Unexpected unmapped hostnames", data["validation"]["unexpected_unmapped_hostnames"])
|
||||
bullet_list("Duplicate hostnames", data["validation"]["duplicate_hostnames"])
|
||||
bullet_list("Ambiguous hostnames", data["validation"]["ambiguous_hostnames"])
|
||||
|
||||
lines.extend(
|
||||
[
|
||||
"## Correlation",
|
||||
"",
|
||||
"| Hostname | Status | Reasons | Service(s) | Route metadata | DNS records |",
|
||||
"|---|---|---|---|---|---|",
|
||||
]
|
||||
)
|
||||
|
||||
for row in inventory:
|
||||
services = sorted({f"{entry['stack']}/{entry['service']}" for entry in row["traefik_entries"]})
|
||||
service_cell = ", ".join(services) if services else "-"
|
||||
reason_cell = ", ".join(row["reasons"]) if row["reasons"] else "-"
|
||||
|
||||
route_chunks = []
|
||||
for entry in row["traefik_entries"]:
|
||||
middlewares = ",".join(entry.get("middlewares", [])) or "-"
|
||||
route_chunks.append(
|
||||
f"{entry['router']} [tls={str(entry['uses_tls']).lower()}, mtls={str(entry['uses_mtls']).lower()}, authelia={str(entry['uses_authelia']).lower()}, tls_options={entry.get('tls_options') or '-'}, middlewares={middlewares}]"
|
||||
)
|
||||
route_cell = "<br>".join(route_chunks) if route_chunks else "-"
|
||||
|
||||
dns_cell = ", ".join(f"{item['type']}:{item['value']}" for item in row["dynu_records"]) if row["dynu_records"] else "-"
|
||||
lines.append(f"| `{row['fqdn']}` | `{row['status']}` | `{reason_cell}` | {service_cell} | {route_cell} | {dns_cell} |")
|
||||
|
||||
OUT_MD.parent.mkdir(parents=True, exist_ok=True)
|
||||
OUT_MD.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
try:
|
||||
require_read_only()
|
||||
except ReadOnlyError as exc:
|
||||
print(str(exc), file=sys.stderr)
|
||||
return 2
|
||||
|
||||
if not DYN_DATA.exists():
|
||||
print(f"Missing {DYN_DATA}. Run fetch_dynu_dns.py first.", file=sys.stderr)
|
||||
return 3
|
||||
|
||||
dyn_payload = json.loads(DYN_DATA.read_text(encoding="utf-8"))
|
||||
dynu_index = load_dynu(DYN_DATA)
|
||||
|
||||
repo_root = Path(__file__).resolve().parents[2]
|
||||
env_values = load_env_defaults(repo_root)
|
||||
hosts: List[Dict[str, Any]] = []
|
||||
for cf in compose_files(repo_root):
|
||||
hosts.extend(extract_traefik_hosts(cf.relative_to(repo_root), env_values))
|
||||
|
||||
by_fqdn: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
|
||||
for entry in hosts:
|
||||
if entry["fqdn"] == BASE_DOMAIN or is_subdomain_of_base(entry["fqdn"]):
|
||||
by_fqdn[entry["fqdn"]].append(entry)
|
||||
|
||||
duplicate_hostnames = sorted(k for k, v in by_fqdn.items() if len(v) > 1)
|
||||
|
||||
combined_fqdns = sorted(set(by_fqdn.keys()) | set(dynu_index.keys()))
|
||||
inventory = []
|
||||
ambiguous_hostnames: List[str] = []
|
||||
|
||||
for fqdn in combined_fqdns:
|
||||
traefik_entries = sorted(
|
||||
by_fqdn.get(fqdn, []),
|
||||
key=lambda x: (x["stack"], x["service"], x["source_compose_file"], x["router"]),
|
||||
)
|
||||
dns_records = dynu_index.get(fqdn, [])
|
||||
|
||||
is_allowed_unmapped = fqdn in ALLOWED_UNMAPPED_HOSTNAMES
|
||||
has_traefik = bool(traefik_entries)
|
||||
has_dns = bool(dns_records)
|
||||
|
||||
service_keys = {f"{item['stack']}/{item['service']}" for item in traefik_entries}
|
||||
is_ambiguous = len(service_keys) > 1
|
||||
if is_ambiguous:
|
||||
ambiguous_hostnames.append(fqdn)
|
||||
|
||||
is_enforced_dns_subdomain = is_subdomain_of_base(fqdn)
|
||||
|
||||
if has_traefik and has_dns:
|
||||
status = "mapped"
|
||||
elif has_dns and is_allowed_unmapped:
|
||||
status = "allowed_unmapped"
|
||||
elif has_dns and not has_traefik and is_enforced_dns_subdomain:
|
||||
status = "unexpected_unmapped"
|
||||
elif has_dns and not has_traefik:
|
||||
status = "dns_only"
|
||||
else:
|
||||
status = "traefik_only"
|
||||
|
||||
reasons = summarize_reasons(
|
||||
has_traefik, has_dns, is_allowed_unmapped, is_ambiguous, is_enforced_dns_subdomain
|
||||
)
|
||||
|
||||
inventory.append(
|
||||
{
|
||||
"fqdn": fqdn,
|
||||
"status": status,
|
||||
"reasons": reasons,
|
||||
"duplicate": fqdn in duplicate_hostnames,
|
||||
"traefik_entries": traefik_entries,
|
||||
"dynu_records": dns_records,
|
||||
}
|
||||
)
|
||||
|
||||
subdomain_dns_hosts = sorted(host for host in dynu_index if is_subdomain_of_base(host))
|
||||
unexpected_unmapped_hostnames = sorted(
|
||||
host for host in subdomain_dns_hosts if host not in by_fqdn and host not in ALLOWED_UNMAPPED_HOSTNAMES
|
||||
)
|
||||
|
||||
validation = {
|
||||
"allowed_unmapped_hostnames": sorted(ALLOWED_UNMAPPED_HOSTNAMES),
|
||||
"unexpected_unmapped_hostnames": unexpected_unmapped_hostnames,
|
||||
"duplicate_hostnames": duplicate_hostnames,
|
||||
"ambiguous_hostnames": sorted(set(ambiguous_hostnames)),
|
||||
"validation_ok": len(unexpected_unmapped_hostnames) == 0,
|
||||
}
|
||||
|
||||
dynu_rows = []
|
||||
for fqdn in sorted(dynu_index.keys()):
|
||||
for rec in dynu_index[fqdn]:
|
||||
dynu_rows.append(
|
||||
{
|
||||
"hostname": fqdn,
|
||||
"type": rec["type"],
|
||||
"value": rec["value"],
|
||||
"ttl": rec["ttl"],
|
||||
}
|
||||
)
|
||||
|
||||
output = {
|
||||
"source": "dynu+traefik",
|
||||
"read_only": True,
|
||||
"base_domain": BASE_DOMAIN,
|
||||
"dynu_fetched_at": dyn_payload.get("fetched_at"),
|
||||
"generated_at": datetime.now(timezone.utc).replace(microsecond=0).isoformat(),
|
||||
"summary": {
|
||||
"traefik_hostnames": len(by_fqdn),
|
||||
"dynu_hostnames": len(dynu_index),
|
||||
"mapped_hostnames": sum(1 for x in inventory if x["status"] == "mapped"),
|
||||
"dns_only_hostnames": sum(1 for x in inventory if "dns_only" in x["reasons"]),
|
||||
"traefik_only_hostnames": sum(1 for x in inventory if x["status"] == "traefik_only"),
|
||||
},
|
||||
"validation": validation,
|
||||
"inventory": inventory,
|
||||
"dynu_records_table": dynu_rows,
|
||||
}
|
||||
|
||||
OUT_JSON.parent.mkdir(parents=True, exist_ok=True)
|
||||
OUT_JSON.write_text(json.dumps(output, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||
write_markdown(output)
|
||||
|
||||
print(f"Wrote {OUT_JSON}")
|
||||
print(f"Wrote {OUT_MD}")
|
||||
|
||||
if os.environ.get("DYNU_ENFORCE_VALIDATION") == "true" and not validation["validation_ok"]:
|
||||
print(
|
||||
"Validation failed: unexpected unmapped hostnames were found: "
|
||||
+ ", ".join(validation["unexpected_unmapped_hostnames"]),
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 4
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user