This commit is contained in:
2025-12-02 06:14:41 +01:00
commit 3d89a5245f
11 changed files with 1126 additions and 0 deletions

33
.gitignore vendored Normal file
View File

@@ -0,0 +1,33 @@
# Virtual Environment
.venv/
venv/
env/
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
# Generated diagram files
*.png
*.pdf
*.svg
*.gv
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Distribution / packaging
dist/
build/
*.egg-info/

192
DEPLOYMENT_GUIDE.md Normal file
View File

@@ -0,0 +1,192 @@
# Deployment Guide - App Pipeline
Complete guide for deploying applications using the custom deployment pipeline with Gitea, Docker, and Traefik.
## 1. Create New Application
### 1.1 Gitea Repository
1. Log in to Gitea: https://git.appmodel.nl
2. Create a new repository:
- **Owner**: Tour
- **Name**: `viewer` (or your app name)
### 1.2 Generate Skeleton on Server
On the server, run:
```bash
apps-create viewer static-fe
```
This command:
- Sets up `/opt/apps/viewer` (attempts to clone from `git@git.appmodel.nl:Tour/viewer.git`)
- Generates a multi-stage Dockerfile for a Node-based static frontend
- Creates `~/infra/viewer/docker-compose.yml` with:
- Service `viewer`
- Connection to `traefik_net`
- Traefik route: `https://viewer.appmodel.nl`
## 2. Development & Build
On your development machine:
```bash
# Clone the repository
git clone git@git.appmodel.nl:Tour/viewer.git
cd viewer
# Build your app as usual
npm install
npm run build # output goes to dist/
# Commit and push
git add .
git commit -m "First version"
git push
```
**Note**: The Dockerfile expects a `dist/` directory containing static files.
## 3. Deployment Pipeline
### 3.1 Manual Deploy with `app-deploy`
On the server, use the generic deploy command:
```bash
app-deploy viewer
```
This command performs:
1. `cd /opt/apps/viewer`
2. `git pull --ff-only`
3. `cd /home/tour/infra/viewer`
4. `docker compose up -d --build viewer`
Logs are saved to: `/var/log/app-deploy-viewer.log`
### 3.2 Automatic Deployment via Gitea Hook
Set up automatic deployment on every push:
1. In Gitea, go to repository `Tour/viewer`
2. Navigate to **Settings → Git Hooks**
3. Select **post-receive**
4. Add the following script:
```bash
#!/usr/bin/env bash
/usr/local/bin/app-deploy viewer
```
**Result**: Every `git push` to `Tour/viewer` automatically triggers a deployment.
## 4. Traefik & DNS Configuration
### Traefik Setup
Traefik runs in the traefik stack on the same server and manages:
- `git.appmodel.nl`
- `auction.appmodel.nl`
- `aupi.appmodel.nl`
- ... (new apps via labels)
### Auto-Generated Labels
The `apps-create` command automatically adds the correct Traefik labels:
```yaml
labels:
- "traefik.enable=true"
- "traefik.http.routers.viewer.rule=Host(`viewer.appmodel.nl`)"
- "traefik.http.routers.viewer.entrypoints=websecure"
- "traefik.http.routers.viewer.tls=true"
- "traefik.http.routers.viewer.tls.certresolver=letsencrypt"
- "traefik.http.services.viewer.loadbalancer.server.port=80"
```
### DNS Configuration
Add a DNS record pointing to your server's public IP:
```
viewer.appmodel.nl → <server-public-ip>
```
Traefik + Let's Encrypt will automatically handle SSL certificate generation.
## 5. Future Application Types
The `apps-create` script currently supports:
- **`static-fe`** Node build → Nginx → static frontend
### Planned Types
Future app types can be added:
```bash
# Python API
apps-create stats api-py
# Background worker/crawler
apps-create crawler worker-py
```
Each type will have:
- Custom Dockerfile template
- Standard `docker-compose.yml` configuration
- Same deployment pipeline: `app-deploy <name>`
## Quick Reference
### Create and Deploy New App
```bash
# On server: create skeleton
apps-create viewer static-fe
# On dev machine: develop and push
git clone git@git.appmodel.nl:Tour/viewer.git
cd viewer
npm install
npm run build
git add .
git commit -m "Initial commit"
git push
# On server: deploy (or auto-deploys via hook)
app-deploy viewer
```
### Existing Infrastructure
| Service | URL | Description |
|---------|-----|-------------|
| Gitea | https://git.appmodel.nl | Git repository hosting |
| Auction | https://auction.appmodel.nl | Auction frontend |
| Aupi API | https://aupi.appmodel.nl | Auction backend API |
| Traefik | - | Reverse proxy & SSL |
### Log Locations
- Deployment logs: `/var/log/app-deploy-<app-name>.log`
- Docker logs: `docker compose logs -f <service-name>`
### Useful Commands
```bash
# View deployment logs
tail -f /var/log/app-deploy-viewer.log
# View container logs
cd ~/infra/viewer
docker compose logs -f viewer
# Restart service
docker compose restart viewer
# Rebuild without cache
docker compose up -d --build --no-cache viewer
```

82
README.md Normal file
View File

@@ -0,0 +1,82 @@
# Network Architecture Diagrams
Python scripts to generate network architecture diagrams using the [Diagrams](https://diagrams.mingrammer.com/) library.
## Prerequisites
- Python 3.8+
- Graphviz ([installation guide](https://graphviz.org/download/))
### Installing Graphviz
**Windows:**
```bash
choco install graphviz
# or download from https://graphviz.org/download/
```
**macOS:**
```bash
brew install graphviz
```
**Linux:**
```bash
sudo apt-get install graphviz # Debian/Ubuntu
sudo yum install graphviz # RHEL/CentOS
```
## Setup
1. Create a virtual environment:
```bash
python -m venv .venv
```
2. Activate the virtual environment:
```bash
# Windows
.venv\Scripts\activate
# macOS/Linux
source .venv/bin/activate
```
3. Install dependencies:
```bash
pip install -r requirements.txt
```
## Usage
Run the diagram generation scripts:
```bash
# Generate LAN architecture diagram
python lan_architecture.py
# Generate main diagram
python main.py
```
Output files will be generated in the current directory as PNG images.
## Available Diagrams
- `lan_architecture.py` - Home Lab / Auction Stack Architecture diagram
- `main.py` - Network architecture diagram
## Deployment
To deploy or share this project:
1. Ensure Graphviz is installed on the target system
2. Clone the repository
3. Follow the setup instructions above
4. Run the desired diagram script
## Notes
- Generated diagram files (`.png`, `.gv`) are excluded from version control
- The diagrams use a left-to-right (`LR`) layout direction
- Output files are saved with `show=False` to prevent automatic opening

31
build-pipeline.md Normal file
View File

@@ -0,0 +1,31 @@
# Appmodel Home Lab Build & Deploy Pipeline
Dit document beschrijft hoe een nieuwe applicatie in het home lab wordt aangemaakt en hoe de build- & deploy-pipeline werkt.
## Overzicht
- **Broncode**: Gitea (`https://git.appmodel.nl`)
- **Build & runtime**: Docker containers op netwerk `traefik_net`
- **Routing & TLS**: Traefik (`https://traefik.appmodel.nl`)
- **Automatische deploy**: Gitea `post-receive` hooks → `app-deploy <app>`
## Pipeline in één diagram
```mermaid
flowchart LR
Dev[💻 Dev machine\nVS Code / Git] -->|git push| Gitea[📚 Gitea\nTour/<app>]
subgraph Server[🏠 Home lab server\n192.168.1.159]
Gitea --> Hook[🔔 post-receive hook\n/app-deploy <app>]
Hook --> Deploy[⚙️ app-deploy <app>\n/git pull + docker compose up -d --build <app>]
subgraph Docker[🐳 Docker / traefik_net]
AppC[🧱 App container\n<app>.appmodel.nl]
Traefik[🚦 Traefik\nReverse Proxy]
end
end
Deploy --> AppC
Traefik --> AppC
Client[🌐 Browser / API client] -->|https://<app>.appmodel.nl| Traefik

218
lab/dia-kimi.html Normal file
View File

@@ -0,0 +1,218 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Home-Lab diagram Mermaid + Kroki (Graphviz)</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
:root{--bg:#f7f9fc;--text:#222;--accent:#0d6efd}
body{margin:0;font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial;font-size:14px;background:var(--bg);color:var(--text)}
header{background:var(--accent);color:#fff;padding:.6rem 1rem;font-size:1.1rem;font-weight:600}
.wrap{display:flex;height:calc(100vh - 40px)}
.panel{flex:1;display:flex;flex-direction:column;overflow:hidden;border-right:1px solid #ddd}
.panel:last-child{border:none}
h3{margin:.4rem .6rem;font-size:1rem}
.btn{margin:.2rem .6rem;padding:.3rem .6rem;border:1px solid #ccc;border-radius:4px;background:#fff;cursor:pointer}
.btn:hover{background:#eee}
#editor{flex:1}
.diagram{flex:2;background:#fff;padding:.5rem;overflow:auto}
svg{max-width:100%;height:auto}
/* mermaid theme tweaks */
.mermaid{height:100%;display:flex;align-items:center;justify-content:center}
</style>
<!-- ========= 1. LIBRARIES ========= -->
<!-- Monaco editor -->
<script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.44.0/min/vs/loader.js"></script>
<!-- Mermaid -->
<script src="https://cdn.jsdelivr.net/npm/mermaid@10.6.1/dist/mermaid.min.js"></script>
<!-- Kroki client (tiny wrapper) -->
<script src="https://cdn.jsdelivr.net/npm/kroki-client@1/dist/kroki-client.min.js"></script>
<!-- html2canvas for PNG export -->
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
</head>
<body>
<header>🏠 Home-Lab diagram editable in browser (localStorage auto-saved)</header>
<div class="wrap">
<!-- ---- LEFT: EDITOR ---- -->
<div class="panel">
<h3>Source (Mermaid syntax)</h3>
<div id="editor"></div>
<div style="padding:.4rem .6rem">
<button class="btn" onclick="render()">▶ Render both</button>
<button class="btn" onclick="savePNG()">💾 Save PNG</button>
<button class="btn" onclick="saveSVG()">💾 Save SVG</button>
<label class="btn">
<input type="checkbox" id="dark"> dark
</label>
</div>
</div>
<!-- ---- MIDDLE: MERMAID ---- -->
<div class="panel">
<h3>Mermaid render</h3>
<div id="mermaid" class="diagram"></div>
</div>
<!-- ---- RIGHT: KROKI / GRAPHVIZ ---- -->
<div class="panel">
<h3>Kroki (Graphviz) render</h3>
<div id="kroki" class="diagram"></div>
</div>
</div>
<script>
/* ========= 2. DEFAULT SOURCE ========= */
const DEFAULT = `flowchart TD
%% ---------- colours ----------
classDef internet fill:#e1f5ff,stroke:#007bff
classDef router fill:#fff3cd,stroke:#ffc107
classDef lan fill:#f8f9ff,stroke:#6c757d,stroke-width:2px
classDef core fill:#ffe6e6,stroke:#dc3545
classDef infra fill:#e6ffe6,stroke:#28a745
classDef worker fill:#f0e6ff,stroke:#6f42c1
classDef iot fill:#fff9e6,stroke:#fd7e14
%% ---------- nodes ----------
internet(🌐 Internet / Cloud):::internet
router{{🛜 Router<br/>hub.lan<br/>192.168.1.1}}:::router
subgraph LAN["🏠 LAN 192.168.1.0/24"]:::lan
subgraph CORE["💻 Core server<br/>192.168.1.159"]:::core
traefik[🚦 Traefik]:::core
gitea[📚 Gitea]:::core
dokku[🐳 Dokku]:::core
auction[🧱 Auction stack]:::core
mi50[🧠 MI50 / Ollama]:::core
end
subgraph INFRA["🧭 Infra & DNS<br/>192.168.1.163"]:::infra
adguard[🛡️ AdGuard]:::infra
artifactory[📦 Artifactory]:::infra
end
ha[🏡 Home Assistant<br/>192.168.1.193]:::infra
atlas[🧱 Atlas<br/>192.168.1.100]:::worker
iot1[📺 IoT-1]:::iot
iot2[📟 IoT-2]:::iot
end
subgraph TETHER["📶 Tether 192.168.137.0/24"]:::lan
hermes[🛰️ Hermes]:::worker
plato[🛰️ Plato]:::worker
end
dev[👨‍💻 Dev laptop]:::internet
%% ---------- edges ----------
internet ==> router
router --> CORE
router --> INFRA
router --> ha
router --> atlas
router --> iot1
router --> iot2
dev ==> gitea
dev ==> dokku
dev ==> mi50
traefik --> gitea
traefik --> auction
traefik --> dokku
CORE -.->|DNS| adguard
ha -.->|DNS| adguard
atlas-.->|DNS| adguard
hermes-.->|DNS| adguard
plato-.->|DNS| adguard
CORE === TETHER
`;
/* ========= 3. EDITOR SETUP ========= */
let editor;
require.config({paths:{vs:'https://cdn.jsdelivr.net/npm/monaco-editor@0.44.0/min/vs'}});
require(['vs/editor/editor.main'], () => {
editor = monaco.editor.create(document.getElementById('editor'), {
value: localStorage.getItem('diagramSrc') || DEFAULT,
language: 'markdown',
theme: 'vs-dark',
minimap: {enabled: false},
wordWrap: 'on'
});
editor.onDidChangeModelContent(() => {
localStorage.setItem('diagramSrc', editor.getValue());
});
render();
});
/* ========= 4. RENDERERS ========= */
mermaid.initialize({startOnLoad: false, theme: 'default'});
function render() {
const src = editor.getValue();
// 4a mermaid
document.getElementById('mermaid').innerHTML = '<div class="mermaid">'+src+'</div>';
mermaid.init();
// 4b kroki graphviz
Kroki.render('graphviz', dotFromMermaid(src)).then(svg => {
document.getElementById('kroki').innerHTML = svg;
}).catch(err => {
document.getElementById('kroki').innerHTML = '<pre>'+err+'</pre>';
});
}
/* crude converter good enough for this topology */
function dotFromMermaid(m) {
let dot = 'digraph G {\nbgcolor=transparent;rankdir=TB;node [shape=rect,style=rounded];\n';
const lines = m.split('\n');
let inSub = false;
lines.forEach(l => {
l = l.trim();
if (l.startsWith('subgraph')) {
const name = (l.match(/subgraph\s+(\w+)/) || [, 'cluster'])[1];
dot += 'subgraph '+name+' {\nlabel="'+l.split('"')[1]+'";\nstyle=filled;fillcolor=lightgrey;\n';
inSub = true;
} else if (l === 'end') {
dot += '}\n';
inSub = false;
} else if (l.includes('[') && l.includes(']')) {
const id = l.split('[')[0].trim();
const label = (l.match(/\[(.*?)\]/) || [, id])[1];
dot += id + ' [label="' + label.replace(/:::.*$/,'') + '"];\n';
} else if (l.includes('-->') || l.includes('==>') || l.includes('-.->')) {
const arr = l.replace(/[~=>\-.]+/g,'->').split('->').map(x=>x.trim()).filter(Boolean);
if (arr.length === 2) dot += arr[0] + ' -> ' + arr[1] + ';\n';
}
});
dot += '}';
return dot;
}
/* ========= 5. EXPORT ========= */
function savePNG() {
html2canvas(document.querySelector('#mermaid svg')).then(canvas => {
download(canvas.toDataURL(), 'home-lab.png');
});
}
function saveSVG() {
const svg = document.querySelector('#mermaid svg');
const url = 'data:image/svg+xml;charset=utf-8,'+encodeURIComponent(svg.outerHTML);
download(url, 'home-lab.svg');
}
function download(href, name) {
const a = Object.assign(document.createElement('a'), {href, download: name});
document.body.appendChild(a); a.click(); a.remove();
}
/* dark toggle */
document.getElementById('dark').onchange = e => {
document.body.style.background = e.target.checked ? '#121212' : '#f7f9fc';
document.body.style.color = e.target.checked ? '#eee' : '#222';
};
</script>
</body>
</html>

121
lan_architecture.py Normal file
View File

@@ -0,0 +1,121 @@
from diagrams import Diagram, Cluster, Edge
from diagrams.onprem.network import Internet
from diagrams.onprem.compute import Server
from diagrams.onprem.iac import Ansible
from diagrams.generic.network import Router, Switch
from diagrams.generic.device import Mobile, Tablet
from diagrams.generic.blank import Blank
from diagrams.onprem.client import Users
from diagrams.onprem.container import Docker
from diagrams.custom import Custom
# Tip: run this in a venv:
# pip install diagrams graphviz
with Diagram("Home Lab / Auction Stack Architecture", show=False, filename="home_lab_architecture", direction="LR"):
internet = Internet("Internet / Cloud")
dev = Users("Dev laptop(s)\nWindows / WSL")
# -------------------- LAN 192.168.1.x --------------------
with Cluster("LAN 192.168.1.0/24"):
router = Router("hub.lan\n192.168.1.1\nRouter / Gateway")
# -------- Core server / desktop (Tour / hephaestus / dokku / ollama) --------
with Cluster("Core server / desktop\nTour / hephaestus / dokku.lan / ollama.lan\n192.168.1.159"):
core_os = Server("Ubuntu host")
traefik = Docker("Traefik\nReverse Proxy")
gitea = Docker("Gitea\n git.appmodel.nl")
dokku = Docker("Dokku\nPaaS / app hosting")
auction_fe = Docker("Auction Frontend\n auction.appmodel.nl")
aupi_be = Docker("Aupi API Backend\n aupi.appmodel.nl")
mi50 = Server("MI50 GPU / Ollama\nAI workloads")
# -------- Infra & DNS (odroid / dns.lan) --------
with Cluster("Infra & DNS\nodroid / dns.lan\n192.168.1.163"):
dns_host = Server("Odroid host")
adguard = Docker("AdGuard Home\nDNS / *.lan / *.appmodel.nl")
artifactory = Docker("Artifactory (future)")
runner = Docker("CI / Build runner (future)")
# -------- Home Assistant --------
with Cluster("Home Automation\nha.lan\n192.168.1.193"):
ha_host = Server("HomeAssistant host")
hass = Docker("Home Assistant")
# -------- Extra node / worker --------
atlas = Server("atlas.lan\n192.168.1.100\n(extra node / worker)")
# -------- IoT / devices --------
with Cluster("IoT / Clients"):
iot_hof = Tablet("hof-E402NA\n192.168.1.214")
iot_s380 = Tablet("S380HB\n192.168.1.59")
iot_ecb5 = Tablet("ecb5faa56c90\n192.168.1.49")
iot_unknown = Tablet("Unknown\n192.168.1.240")
# -------------------- Tether subnet 192.168.137.x --------------------
with Cluster("Tether subnet 192.168.137.0/24"):
hermes = Server("hermes.lan\n192.168.137.239\nworker / node")
plato = Server("plato.lan\n192.168.137.163\nworker / node")
# -------------------- Edges / flows --------------------
# Internet naar router + DNS host
internet >> router
internet >> dns_host
# Dev naar Gitea / Traefik / Dokku
dev >> Edge(label="git / HTTPS") >> traefik
dev >> Edge(label="SSH / HTTPS") >> gitea
dev >> Edge(label="Dokku deploys") >> dokku
# Router naar alle LAN-nodes
router >> core_os
router >> dns_host
router >> ha_host
router >> atlas
router >> iot_hof
router >> iot_s380
router >> iot_ecb5
router >> iot_unknown
# Core server services
core_os >> traefik
core_os >> gitea
core_os >> dokku
core_os >> auction_fe
core_os >> aupi_be
core_os >> mi50
# Infra/DNS services
dns_host >> adguard
dns_host >> artifactory
dns_host >> runner
# Home Assistant
ha_host >> hass
# DNS-queries
core_os >> Edge(label="DNS") >> adguard
ha_host >> Edge(label="DNS") >> adguard
atlas >> Edge(label="DNS") >> adguard
hermes >> Edge(label="DNS") >> adguard
plato >> Edge(label="DNS") >> adguard
# Web traffic / reverse proxy flows
internet >> Edge(label="HTTP/HTTPS") >> traefik
traefik >> Edge(label="git.appmodel.nl") >> gitea
traefik >> Edge(label="auction.appmodel.nl") >> auction_fe
traefik >> Edge(label="aupi.appmodel.nl") >> aupi_be
traefik >> Edge(label="dokku.lan / apps") >> dokku
# App-level flow
auction_fe >> Edge(label="REST API") >> aupi_be
# AI workloads
dev >> Edge(label="LLM / Tuning / Inference") >> mi50
# Tether workers verbonden met core (bv. jobs / agents)
core_os >> Edge(label="jobs / ssh") >> hermes
core_os >> Edge(label="jobs / ssh") >> plato

159
main.py Normal file
View File

@@ -0,0 +1,159 @@
from graphviz import Digraph
def make_network_diagram(output_name: str = "network-architecture"):
g = Digraph(
"network",
filename=f"{output_name}.gv",
format="png",
)
# Globale stijl
g.attr(rankdir="LR", fontname="Segoe UI")
# ---------- WAN / Internet ----------
with g.subgraph(name="cluster_wan") as wan:
wan.attr(
label="🌐 Internet / Cloud",
style="rounded",
color="lightgrey",
fontsize="16",
)
wan.node("extDNS", "📡 Public DNS", shape="rectangle")
wan.node("extGit", "☁️ Externe registries / Git\n(GitHub / Docker Hub)", shape="rectangle")
# ---------- LAN 192.168.1.0/24 ----------
with g.subgraph(name="cluster_lan") as lan:
lan.attr(
label="🏠 LAN 192.168.1.0/24",
style="rounded",
color="lightgrey",
fontsize="16",
)
# Router / gateway
lan.node(
"hub",
"🛜 Router / Gateway\nhub.lan\n192.168.1.1",
shape="rectangle",
style="filled",
fillcolor="#f0f0ff",
)
# ---- Core server / desktop ----
with lan.subgraph(name="cluster_core") as core:
core.attr(
label="💻 Hoofdserver / Desktop\nTour / hephaestus / ollama / dokku.lan\n192.168.1.159",
style="rounded",
color="#aaccee",
)
core.node("traefik", "🚦 Traefik\nReverse Proxy", shape="rectangle")
core.node("gitea", "📚 Gitea\ngit.appmodel.nl", shape="rectangle")
core.node("dokku", "🐳 Dokku\nPaaS / build", shape="rectangle")
core.node("auctionFE", "🧱 Auction Frontend\nauction.appmodel.nl", shape="rectangle")
core.node("aupiAPI", "🧱 Auction Backend API\naupi.appmodel.nl", shape="rectangle")
core.node("mi50", "🧠 MI50 / Ollama\nAI workloads", shape="rectangle")
# Aanvulling: monitoring / logging
core.node("monitoring", "📈 Monitoring / Logging\nPrometheus / Loki / Grafana", shape="rectangle")
# ---- Infra & DNS ----
with lan.subgraph(name="cluster_infra_dns") as infra:
infra.attr(
label="🧭 Infra & DNS\nodroid / dns.lan\n192.168.1.163",
style="rounded",
color="#aaddaa",
)
infra.node("adguard", "🧭 AdGuard Home\nDNS / *.lan / *.appmodel.nl", shape="rectangle")
infra.node("artifactory", "📦 Artifactory", shape="rectangle")
infra.node("runner", "⚙️ Build runners\nCI/CD", shape="rectangle")
# ---- Home Automation ----
with lan.subgraph(name="cluster_ha") as ha:
ha.attr(
label="🏡 Home Automation\nha.lan\n192.168.1.193",
style="rounded",
color="#ffddaa",
)
ha.node("hass", "🏠 Home Assistant", shape="rectangle")
# Overige LAN-hosts / IoT
lan.node("atlas", "🧱 atlas.lan\n192.168.1.100", shape="rectangle")
lan.node("iot1", "📺 hof-E402NA\n192.168.1.214", shape="rectangle")
lan.node("iot2", "🎧 S380HB\n192.168.1.59", shape="rectangle")
lan.node("iot3", "📟 ecb5faa56c90\n192.168.1.49", shape="rectangle")
lan.node("iot4", "❓ Unknown\n192.168.1.240", shape="rectangle")
# ---------- Tether subnet ----------
with g.subgraph(name="cluster_tether") as tether:
tether.attr(
label="📶 Tether subnet 192.168.137.0/24",
style="rounded",
color="lightgrey",
fontsize="16",
)
tether.node("hermes", "🛰️ hermes.lan\n192.168.137.239\nworker / node", shape="rectangle")
tether.node("plato", "🛰️ plato.lan\n192.168.137.163\nworker / node", shape="rectangle")
# ---------- Externe gebruikers (aanvulling) ----------
g.node(
"users",
"👨‍💻 Developers / Users\nlaptops / mobiel",
shape="rectangle",
style="dashed",
)
# ==================== VERBINDINGEN ====================
# Basis LAN connecties (ongeveer jouw '---' links)
for target in ["core", "infraDNS", "ha", "atlas", "iot1", "iot2", "iot3", "iot4"]:
# We linken naar representatieve node binnen subgraph
if target == "core":
g.edge("hub", "traefik", dir="both", label="LAN")
elif target == "infraDNS":
g.edge("hub", "adguard", dir="both", label="LAN")
elif target == "ha":
g.edge("hub", "hass", dir="both", label="LAN")
else:
g.edge("hub", target, dir="both", label="LAN")
# WAN koppeling
g.edge("hub", "extDNS", label="📶 Internet")
g.edge("adguard", "extDNS", label="Upstream DNS")
# DNS-resolutie (core / ha / atlas / tether -> AdGuard)
for client in ["traefik", "hass", "atlas", "hermes", "plato"]:
g.edge(client, "adguard", label="DNS", style="dashed")
# Websites / reverse proxy
g.edge("extDNS", "traefik", label="DNS → Traefik")
g.edge("traefik", "gitea", label="HTTP(S)")
g.edge("traefik", "auctionFE", label="HTTP(S)")
g.edge("traefik", "aupiAPI", label="HTTP(S)")
g.edge("traefik", "dokku", label="Apps / Deploy")
# App flow
g.edge("auctionFE", "aupiAPI", label="API calls")
g.edge("aupiAPI", "adguard", label="DNS lookups", style="dashed")
# AI workloads
g.edge("traefik", "mi50", label="AI / inference", style="dotted")
# Tether workers
g.edge("traefik", "hermes", dir="both", label="Jobs / tasks")
g.edge("traefik", "plato", dir="both", label="Jobs / tasks")
# Monitoring / logging (aanvulling)
for observed in ["traefik", "gitea", "dokku", "auctionFE", "aupiAPI", "mi50", "adguard", "atlas", "hass"]:
g.edge(observed, "monitoring", style="dotted", label="metrics / logs")
# Developers / gebruikers
g.edge("users", "traefik", label="HTTPS")
g.edge("users", "gitea", style="dashed", label="Git / HTTP")
# Genereer bestanden (PNG + .gv)
g.render(cleanup=True)
print(f"Diagram gegenereerd als {output_name}.png")
if __name__ == "__main__":
make_network_diagram()

107
public/dia.html Normal file
View File

@@ -0,0 +1,107 @@
<!doctype html>
<html lang="nl">
<head>
<meta charset="utf-8" />
<title>Netwerk architectuur</title>
<!-- Mermaid via CDN -->
<script type="module">
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
mermaid.initialize({ startOnLoad: true, theme: "default" });
</script>
<style>
body { font-family: system-ui, sans-serif; margin: 0; padding: 1rem; }
.mermaid { max-width: 100%; overflow: auto; }
</style>
</head>
<body>
<h1>Netwerk architectuur</h1>
<div class="mermaid">
flowchart LR
%% ============ Internet ============
subgraph WAN[🌐 Internet / Cloud]
extDNS[(📡 Public DNS)]
extGit[(☁️ Externe registries / Git)]
end
%% ============ LAN 192.168.1.x ============
subgraph LAN[🏠 LAN 192.168.1.0/24]
hub[🛜 Router / Gateway\nhub.lan\n192.168.1.1]
subgraph core[💻 Hoofdserver / Desktop\nTour / hephaestus / ollama / dokku.lan\n192.168.1.159]
traefik[🚦 Traefik\nReverse Proxy]
gitea[📚 Gitea\n git.appmodel.nl]
dokku[🐳 Dokku\nPaaS / build]
auctionFE[🧱 Auction Frontend\n auction.appmodel.nl]
aupiAPI[🧱 Auction Backend API\n aupi.appmodel.nl]
mi50[🧠 MI50 / Ollama\nAI workloads]
end
subgraph infraDNS[🧭 Infra & DNS\nodroid / dns.lan\n192.168.1.163]
adguard[🧭 AdGuard Home\nDNS / *.lan / *.appmodel.nl]
artifactory[📦 Artifactory]
runner[⚙️ Build runners]
end
subgraph ha[🏡 Home Automation\nha.lan\n192.168.1.193]
hass[🏠 Home Assistant]
end
atlas[🧱 atlas.lan\n192.168.1.100\n]
iot1[📺 hof-E402NA\n192.168.1.214]
iot2[🎧 S380HB\n192.168.1.59]
iot3[📟 ecb5faa56c90\n192.168.1.49]
iot4[❓ Unknown\n192.168.1.240]
end
%% ============ Tether subnet ============
subgraph TETHER[📶 Tether subnet 192.168.137.0/24]
hermes[🛰️ hermes.lan\n192.168.137.239\nworker / node]
plato[🛰️ plato.lan\n192.168.137.163\nworker / node]
end
%% ============ Verkeer ============
%% Basis LAN connecties
hub --- core
hub --- infraDNS
hub --- ha
hub --- atlas
hub --- iot1
hub --- iot2
hub --- iot3
hub --- iot4
%% WAN koppeling
hub --> WAN
infraDNS --> WAN
%% DNS-resolutie
core --> adguard
ha --> adguard
atlas --> adguard
TETHER --> adguard
%% Websites / reverse proxy
extDNS --> traefik
traefik --> gitea
traefik --> auctionFE
traefik --> aupiAPI
traefik --> dokku
%% App flow
auctionFE --> aupiAPI
aupiAPI --> adguard
%% AI workloads
core --> mi50
%% Tether workers
core --- TETHER
</div>
</body>
</html>

141
readme-pipe.md Normal file
View File

@@ -0,0 +1,141 @@
1. Nieuwe app aanmaken
1.1 Gitea repo
Log in op Gitea: https://git.appmodel.nl
Maak een nieuwe repo aan, bijvoorbeeld:
Owner: Tour
Name: viewer
1.2 Skeleton genereren op de server
Op de server:
apps-create viewer static-fe
Dit doet:
/opt/apps/viewer klaarzetten (proberen te clonen uit git@git.appmodel.nl:Tour/viewer.git)
een multi-stage Dockerfile voor een Node-based static frontend aanmaken
~/infra/viewer/docker-compose.yml aanmaken met:
service viewer
koppeling aan traefik_net
Traefik route: https://viewer.appmodel.nl
2. Develop & build
Op je dev machine:
git clone git@git.appmodel.nl:Tour/viewer.git
cd viewer
# bouw je app zoals normaal
npm install
npm run build # output in dist/
git add .
git commit -m "First version"
git push
De Dockerfile verwacht een dist/ map met static files.
3. Deploy pipeline
3.1 app-deploy <app>
Op de server verzorgt app-deploy de generieke deploy:
app-deploy viewer
Doet:
cd /opt/apps/viewer
git pull --ff-only
cd /home/tour/infra/viewer
docker compose up -d --build viewer
Logs komen in:
/var/log/app-deploy-viewer.log
3.2 Automatisch deployen via Gitea hook
In Gitea (repo Tour/viewer):
Ga naar Instellingen → Git Hooks
Kies post-receive
Gebruik:
#!/usr/bin/env bash
/usr/local/bin/app-deploy viewer
Vanaf nu:
Elke git push naar Tour/viewer triggert automatisch een deploy.
4. Traefik & DNS
Traefik draait in de traefik stack op dezelfde server en beheert:
git.appmodel.nl
auction.appmodel.nl
aupi.appmodel.nl
… (nieuwe apps via labels)
Nieuwe app viewer krijgt via apps-create al de juiste labels:
labels:
- "traefik.enable=true"
- "traefik.http.routers.viewer.rule=Host(`viewer.appmodel.nl`)"
- "traefik.http.routers.viewer.entrypoints=websecure"
- "traefik.http.routers.viewer.tls=true"
- "traefik.http.routers.viewer.tls.certresolver=letsencrypt"
- "traefik.http.services.viewer.loadbalancer.server.port=80"
Je moet alleen in DNS nog een record maken:
viewer.appmodel.nl → publieke IP van de server
Traefik + Lets Encrypt regelen het certificaat automatisch.
5. Nieuwe app types (toekomst)
Het apps-create script ondersteunt nu:
static-fe Node build → Nginx → static frontend
Later kun je extra types toevoegen, bijvoorbeeld:
api-py Python (Flask/FastAPI) API
worker-py background worker / crawler
Door per type een eigen Dockerfile-sjabloon en standaard docker-compose.yml te genereren, wordt een nieuw project neerzetten:
apps-create stats api-py
apps-create crawler worker-py
en blijft de pipeline (app-deploy <naam>) identiek.
Je kunt nu:
apps-create viewer static-fe
app-deploy viewer

2
requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
diagrams==0.25.1
graphviz==0.20.3

40
test.md Normal file
View File

@@ -0,0 +1,40 @@
┌─────────────────────────────────────────────────────────────────────┐
│ 🌐 Internet / Cloud │
└────────────────┬────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 🛜 Router (hub.lan) - 192.168.1.1 │
│ Gateway & DHCP │
└────────────────┬────────────────────────────────────────────────────┘
┌────────────┴────────────┬──────────────┬──────────────┐
▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ LAN │ │ LAN │ │ LAN │ │ LAN │
│ 192.168.│ │ Infra & │ │ Home │ │ IoT & │
│ 1.x │ │ DNS │ │ Assist │ │ Workers │
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │
│ ┌────┐ │ │ ┌────┐ │ │ ┌────┐ │ │ ┌────┐ │
│ │Core│ │ │ │DNS │ │ │ │ HA │ │ │ │Atlas│ │
│ │ Srv│ │ │ │Host│ │ │ │Host│ │ │ │ │ │
│ └┬───┘ │ │ └┬───┘ │ │ └┬───┘ │ │ └┬───┘ │
│ │ │ │ │ │ │ │ │ │ │ │
│ ┌─▼──┐ │ │ ┌─▼──┐ │ │ ┌─▼──┐ │ │ │ │
│ │Dock│ │ │ │AdGr│ │ │ │Hass│ │ │ │ │
│ │er │ │ │ │d │ │ │ │ │ │ │ │ │
│ └────┘ │ │ └────┘ │ │ └────┘ │ │ │ │
│ │ │ │ │ │ │ │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
│ ┌─────────────────────────────────────────────────────┐
└──►│ Tether Subnet 192.168.137.x │
│ ┌──────────┐ ┌──────────┐ │
│ │ Hermes │ │ Plato │ │
│ │ Worker │ │ Worker │ │
│ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────┘
Developer Laptops
(Windows/WSL)