- Shell 84.5%
- Jinja 8.4%
- Makefile 5.2%
- Dockerfile 1.9%
|
|
||
|---|---|---|
| .forgejo/workflows | ||
| ansible | ||
| docs | ||
| scripts | ||
| stacks | ||
| .ansible-lint | ||
| .gitignore | ||
| .gitmodules | ||
| CLAUDE.md | ||
| Makefile | ||
| README.md | ||
infra
Infrastructure as code pour le serveur ThinkCentre (maison A).
Schéma
Topologie réseau & services
flowchart TB
Net((Internet))
LE[/Let's Encrypt/]
NtfySh[/ntfy.sh<br/>APNs upstream/]
Anthropic[/Anthropic API/]
DERP[/Tailscale DERP relays<br/>traversée NAT/]
PeerB[Maison B<br/>tailscale up]
Devices[Devices roaming<br/>laptop/phone]
Box["Box internet maison A<br/>port-forward 80, 443"]
MCU[MCU Ender 3<br/>USB CH340]
Sonoff[Sonoff Zigbee USB]
Net -->|TCP 80, 443| Box
Net <-.->|HTTPS| DERP
subgraph TC["ThinkCentre — Debian 13"]
direction TB
subgraph HOST["network mode: host"]
TS["tailscaled<br/>100.64.0.1"]
HA["Home Assistant<br/>:8123"]
HABR["ha-bridge<br/>127.0.0.1:8765"]
KLIPPER["klipper<br/>privileged"]
CROWS["crowsnest<br/>(profile camera)"]
Runner["forgejo-runner<br/>systemd"]
end
subgraph WEB["réseau Docker ’web’ (172.20.0.0/16)"]
NGINX["nginx + certbot<br/>:80 :443"]
MARIE[mariepro]
CHESS["chessia<br/>:8000"]
NTFY["ntfy<br/>:80"]
MOSQ["mosquitto<br/>:1883"]
Z2M[zigbee2mqtt]
MOON["moonraker<br/>:7125"]
MAIN["mainsail<br/>:80"]
RunnerScribe["forgejo-runner-scribe<br/>Docker — scribe-linux:host<br/>+ scribe-ci:docker://node"]
end
SCRIBE[("/srv/scribe-updates/<br/>bundles + manifests")]
DATA[("/srv/stacks/<name>/<br/>+ /srv/letsencrypt/")]
end
Box --> NGINX
KLIPPER -.- MCU
Z2M -.- Sonoff
PeerB <-.->|tailscale| DERP
Devices <-.->|tailscale| DERP
DERP <-.->|tailscale| TS
NGINX -->|marie.afanderivera.fr| MARIE
NGINX -->|chess.timothy.greil.fr| CHESS
NGINX -->|ntfy.timothy.greil.fr| NTFY
NGINX -->|home.timothy.greil.fr<br/>host.docker.internal:8123| HA
NGINX -->|print.timothy.greil.fr<br/>LAN+Tailscale only| MOON
NGINX -->|print.timothy.greil.fr| MAIN
NGINX -->|print.timothy.greil.fr<br/>/webcam/| CROWS
NGINX -->|scribe.timothy.greil.fr<br/>static updater endpoint| SCRIBE
HA <-->|mqtt 127.0.0.1:1883| MOSQ
Z2M <-->|mqtt mosquitto:1883| MOSQ
HA -->|http 127.0.0.1:8765| HABR
MOON <-->|uds| KLIPPER
NGINX -.->|webroot challenge| LE
NTFY -.->|relay pour iOS| NtfySh
CHESS -.-> Anthropic
HABR -.->|claude CLI| Anthropic
NGINX -.-> DATA
MARIE -.-> DATA
CHESS -.-> DATA
NTFY -.-> DATA
MOSQ -.-> DATA
KLIPPER -.-> DATA
classDef infra fill:#e3f2fd,stroke:#1976d2,color:#0d47a1;
classDef ha fill:#fff3e0,stroke:#f57c00,color:#e65100;
classDef print fill:#f3e5f5,stroke:#8e24aa,color:#4a148c;
classDef ext fill:#f5f5f5,stroke:#9e9e9e,color:#424242,stroke-dasharray:4 3;
classDef data fill:#ede7f6,stroke:#5e35b1,color:#311b92;
class NGINX,MARIE,CHESS,NTFY,TS,Runner,RunnerScribe infra;
class HA,HABR,MOSQ,Z2M ha;
class KLIPPER,MOON,MAIN,CROWS print;
class Net,LE,NtfySh,Anthropic,PeerB,Box,DERP,Devices,MCU,Sonoff ext;
class DATA data;
Légende :
- 🔵 services du repo
Infra(foundation OS + reverse-proxy + stacks non-HA + client Tailscale) - 🟠 services du repo
Homeassistant-timothy(HA + Z2M + MQTT + ha-bridge) - 🟣 services Klipper (compose dans
Infra, configs dans submodulePrinter.git) - ⚪ ressources externes (LE, ntfy.sh, Anthropic, DERP relays, peers VPN, box opérateur, hardware USB)
- 🟣 stockage persistant sur le host (
/srv/stacks/<name>/data/+ certs LE) - Flèches pleines : trafic applicatif (HTTP, MQTT, uds, etc.)
- Flèches pointillées : dépendances externes / hardware / écritures disque / VPN
CI/CD
flowchart LR
Dev[git push origin main]
Forgejo[Forgejo Actions<br/>deploy.yml]
Runner["forgejo-runner<br/>thinkcentre:host"]
Ansible["ansible-playbook site.yml<br/>(common, docker, nginx,<br/>stacks, tailscale)"]
Compose["docker compose up -d<br/>par stack /srv/stacks/<name>/"]
Secrets[/"Forgejo Actions Secrets:<br/>TAILSCALE_AUTH_KEY, CHESSIA_*"/]
Submod[/"Submodules<br/>infra/stacks/mariepro/src → Mariewebsite.git<br/>infra/stacks/chessia/src → chessia.git<br/>infra/stacks/klipper/src → Printer.git"/]
Dev --> Forgejo
Forgejo -->|inject env| Secrets
Forgejo --> Runner
Submod -.->|checkout recursive| Runner
Runner --> Ansible
Ansible --> Compose
classDef ext fill:#f5f5f5,stroke:#9e9e9e,color:#424242,stroke-dasharray:4 3;
classDef step fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20;
class Dev,Secrets,Submod ext;
class Forgejo,Runner,Ansible,Compose step;
Stack
- OS : Debian 13 Trixie
- Orchestration : Ansible (playbooks idempotents) + docker-compose
- Reverse-proxy : nginx (containerisé) + certbot, terminaison TLS directe
- VPN : client Tailscale uniquement. Le control server (Headscale) vit chez maison B (
https://headscale.greil.fr, géré par le repotofu-maison). Tous les peers (ThinkCentre, devices roaming, peer maison B et futures maisons) s'y enregistrent. - CI/CD : Forgejo Actions, runner natif systemd sur le ThinkCentre
- Secrets : Forgejo Actions Secrets (injectés dans l'env du job, lus par Ansible via
lookup('env', …))
Périmètre de ce repo
Foundation OS + reverse-proxy + services "non-HA" :
| Service | Type | Public ? |
|---|---|---|
| nginx + certbot | Docker compose | oui (80/443) |
| ntfy | Docker compose | oui (ntfy.timothy.greil.fr) |
| ChessIA | Docker compose (build depuis submodule chessia.git) | oui |
| Marie (mariepro) | Docker compose (nginx:alpine static, build depuis submodule Mariewebsite) | oui |
| Klipper / Mainsail / Moonraker / Crowsnest | Docker compose (configs depuis submodule Printer.git) | non — print.timothy.greil.fr LAN+Tailscale only |
| Beszel | Docker compose | non — LAN-only sur :8090 (métriques host + containers, alertes) |
| Dozzle | Docker compose | non — LAN-only sur :8888 (logs containers live) |
| CrowdSec | Docker compose | non — agent observer (parse logs nginx + sshd, pas d'enforcement en phase 1) |
| Tailscale | systemd tailscaled (apt) |
non — outbound only, s'enregistre sur Headscale chez maison B |
| Forgejo runner | systemd natif | — |
Hors périmètre (vivent dans le repo Homeassistant-timothy) : Home Assistant, Zigbee2MQTT, Mosquitto MQTT, ha-bridge.
Les deux repos partagent le réseau Docker web créé par ce repo.
Layout
infra/
├── ansible/
│ ├── ansible.cfg
│ ├── inventory.yml # 1 host: thinkcentre (= localhost côté runner)
│ ├── requirements.yml # collections (community.docker, community.general)
│ ├── playbooks/
│ │ ├── site.yml # entrypoint idempotent (CI → ce playbook)
│ │ └── bootstrap.yml # one-shot manuel pour Debian vierge
│ ├── roles/
│ │ ├── common/ # apt, packages base, sudoers, sshd
│ │ ├── docker/ # engine + compose plugin + réseau "web"
│ │ ├── runner/ # Forgejo runner natif systemd
│ │ ├── nginx/ # déploie stack nginx + reload sur changement de conf
│ │ ├── stacks/ # déploie chaque compose dans stacks/*/
│ │ └── tailscale/ # apt repo officiel + tailscaled + register sur Headscale (maison B)
│ └── group_vars/all/
│ └── vars.yml # variables non-secrètes (paths, domaines)
├── stacks/
│ ├── nginx/ # nginx:alpine + certbot, vhosts par domaine
│ ├── ntfy/ # binwiederhier/ntfy:latest
│ ├── chessia/ # build depuis submodule src/ (chessia.git)
│ ├── mariepro/ # Dockerfile nginx:alpine + COPY src/ (Mariewebsite)
│ ├── beszel/ # henrygd/beszel hub + agent (métriques)
│ ├── dozzle/ # amir20/dozzle (logs containers)
│ ├── crowdsec/ # crowdsecurity/crowdsec (IDS, phase 1 observer)
│ └── klipper/ # mkuf/klipper + mkuf/moonraker + mainsail-crew/*
│ └── src/ # submodule Printer.git (configs)
├── scripts/
│ └── migrate-from-rpi.sh # rsync runtime data RPi → ThinkCentre au D-day
├── .forgejo/workflows/
│ └── deploy.yml # push main → runner exécute ansible-playbook
├── Makefile
└── docs/
├── migration.md # runbook D-day RPi → ThinkCentre
└── runbook.md # opérations courantes
Workflows
Bootstrap initial (one-shot, depuis le laptop)
Sur un ThinkCentre fraîchement installé en Debian 13 :
make bootstrap HOST=<ip-thinkcentre> RUNNER_UUID=<uuid> RUNNER_TOKEN=<token>
RUNNER_UUID et RUNNER_TOKEN sont retournés ensemble par Site Administration → Actions → Runners → Create new Runner (Forgejo 15+ crée le runner directement côté backend et affiche les credentials persistants).
Cela exécute ansible/playbooks/bootstrap.yml qui :
- Durcit SSH (PermitRootLogin no, password auth off après ajout de la clé)
- Crée le user
chipsteravec sudo - Installe les paquets de base (
vim curl git ufw fail2ban borgbackup wireguard) - Installe Docker engine + compose plugin
- Crée le réseau Docker
web - Installe le Forgejo runner natif (systemd) et l'enregistre
Déploiement continu
git push origin main → Forgejo Actions déclenche le workflow deploy.yml → le runner local exécute make deploy (= ansible-playbook playbooks/site.yml).
Secrets
Stockés dans Forgejo Actions Secrets. Consommés par .forgejo/workflows/deploy.yml.
User-level (chipster, réutilisables par tous ses repos qui publient sur ntfy) :
- Variable
NTFY_SERVER—https://ntfy.timothy.greil.fr - Variable
NTFY_USER— login d'auth ntfy (publish-only) - Secret
NTFY_PASSWORD— password ntfy
Repo-level (chipster/Infra uniquement) :
TAILSCALE_AUTH_KEY— pre-auth keyargenteuilfournie par Mat (Headscale chez maison B)CHESSIA_CLAUDE_CREDENTIALS_JSON— OAuth Claude Code (abonnement Max) du conteneur ChessIACHESSIA_NTFY_TOPIC— topic ntfy spécifique à ChessIA (ex:chessia-diag)
RUNNER_UUID + RUNNER_TOKEN sont les credentials persistants retournés par Forgejo lors du Create new Runner (Site Admin → Actions → Runners). Ils sont passés au bootstrap et stockés dans Forgejo Actions Secrets pour que la convergence CI puisse re-templater config.yaml à l'identique (idempotent).
Documents
- docs/migration.md — runbook D-day pour migrer RPi → ThinkCentre
- docs/runbook.md — opérations courantes (renew cert manuel, ajouter un vhost, debug Tailscale)