Mon infra as codé avec ansible, merci claude
  • Shell 84.5%
  • Jinja 8.4%
  • Makefile 5.2%
  • Dockerfile 1.9%
Find a file
chipster c6f930c444
All checks were successful
Deploy ShroudedTools to prod / deploy (push) Successful in 1m27s
deploy / deploy (push) Successful in 2m2s
shrouded-tools: bump submodule → v0.6.5 (map contrast + WIP gisements)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 11:46:46 +02:00
.forgejo/workflows feat(deploy): submodule update --remote scribe au checkout (auto-pull Scrib infra/**) 2026-05-19 18:49:16 +02:00
ansible docker: élargir default-address-pools à 172.16.0.0/12 size 24 2026-05-22 22:11:52 +02:00
docs refactor(runner): nouveau flow Forgejo 15+ (server.connections, bump v12.9.0) 2026-05-02 16:59:42 +02:00
scripts ci(shrouded-tools): smoke test post-deploy 2026-05-12 20:14:16 +02:00
stacks shrouded-tools: bump submodule → v0.6.5 (map contrast + WIP gisements) 2026-05-30 11:46:46 +02:00
.ansible-lint feat(nginx): auto-disable vhosts dont le cert LE manque (anti-crashloop) 2026-05-02 19:00:44 +02:00
.gitignore secu: .gitignore stacks/**/.env (FIX: scrub .env du dernier commit, secrets à rotate) 2026-05-12 14:05:02 +02:00
.gitmodules feat(scribe): submodule chipster/Scrib + wiring nginx pour endpoint updater 2026-05-19 18:14:26 +02:00
CLAUDE.md docs: Debian 12 Bookworm → Debian 13 Trixie partout 2026-05-02 13:28:33 +02:00
Makefile refactor(runner): nouveau flow Forgejo 15+ (server.connections, bump v12.9.0) 2026-05-02 16:59:42 +02:00
README.md feat(forgejo-runner-scribe): runner Docker dédié Scribe (CI + deploy-infra) 2026-05-19 18:47:11 +02:00

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/&lt;name&gt;/<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 submodule Printer.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/&lt;name&gt;/"]
    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 repo tofu-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 :

  1. Durcit SSH (PermitRootLogin no, password auth off après ajout de la clé)
  2. Crée le user chipster avec sudo
  3. Installe les paquets de base (vim curl git ufw fail2ban borgbackup wireguard)
  4. Installe Docker engine + compose plugin
  5. Crée le réseau Docker web
  6. 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_SERVERhttps://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 key argenteuil fournie par Mat (Headscale chez maison B)
  • CHESSIA_CLAUDE_CREDENTIALS_JSON — OAuth Claude Code (abonnement Max) du conteneur ChessIA
  • CHESSIA_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)