- Go 82.1%
- Go Template 9.3%
- Dockerfile 4.5%
- Shell 4.1%
Distributio
🇫🇷 Version française · 🇬🇧 English version
Français
API REST en Go qui génère à la volée des fichiers preseed Debian depuis les données Netbox, identifiées par adresse MAC, et qui sert des fichiers statiques (noyau, initrd, scripts iPXE...) résolus selon le rôle du device.
Fonctionnalités
- Génération de preseed Debian depuis les données Netbox (hostname, IP, rôle).
- Surcharges de configuration par rôle (DNS, NTP, paquets, gateway, etc.).
- Service de fichiers statiques avec résolution par rôle via
static_dir, type MIME automatique et support des requêtes Range (http.ServeContent). - Rechargement à chaud de la configuration (modification de
config.yamlsans redémarrage). - Protection contre le path traversal sur les fichiers statiques.
- Variables d'environnement dans
config.yaml(${VAR}) pour les secrets. - Cache TTL des résolutions Netbox (MAC→device) pour réduire la charge API.
- Logs structurés (slog, texte ou JSON) et métriques Prometheus sur
/metrics. - Arrêt propre (graceful shutdown) sur SIGINT/SIGTERM et timeouts HTTP.
Architecture
netbox-pxe-api/
├── cmd/main.go # Point d'entrée
├── config.yaml # Configuration
├── Dockerfile
├── internal/
│ ├── config/config.go # Chargement, validation, hot reload, env vars
│ ├── netbox/client.go # Client HTTP Netbox + cache TTL
│ ├── preseed/generator.go # Génération preseed (text/template)
│ ├── shared/template_data.go # Construction des données de template
│ ├── metrics/metrics.go # Compteurs au format Prometheus
│ └── api/ # Handlers HTTP, middleware (logs/metrics)
├── templates/
│ └── preseed/ansible.preseed.tmpl # Template preseed
└── static/ # Fichiers statiques
├── boot.ipxe
└── <static_dir>/... # Sous-répertoires par rôle
Configuration (config.yaml)
server:listen:"0.0.0.0:8080"debug:falsestatic_dir:"./static"# racine des fichiers statiquestemplate_dir:"./templates"# racine des templatesnetbox:url:"https://netbox.example.com"token:"${NETBOX_TOKEN}"# ou valeur en clair ; ${VAR} = variable d'envtimeout_seconds:10tls_skip_verify:truecache_ttl_seconds:30# cache MAC→device (0 = désactivé)api_paths:device_by_mac:"/api/dcim/interfaces/?mac_address={mac}"device_detail:"/api/dcim/devices/{id}/"ip_addresses:"/api/ipam/ip-addresses/?interface_id={interface_id}"defaults:# valeurs appliquées à tous les devicesdns_servers:["8.8.8.8","8.8.4.4"]ntp_servers:["pool.ntp.org"]domain:"local.domain"locale:"fr_FR.UTF-8"timezone:"Europe/Paris"gateway:"192.168.1.254"root_password_crypted:"6ドル$..."ansible_password_crypted:"6ドル$..."ansible_ssh_key:"ssh-ed25519 AAAA..."system_disk_max_size:"500T"# disques > seuil exclus du partitionnementpackages:["curl","vim","sudo"]short_hostname:falseroles:# surcharges par rôle Netbox (slug)osd:dns_servers:["1.1.1.1"]# concaténé aux defaultsgateway:"192.168.1.253"# remplace le defaultsystem_disk_max_size:"100G"short_hostname:truestatic_dir:"debian13"# static/debian13/<filename>Règles de fusion defaults ↔ roles
| Type | Comportement |
|---|---|
| Chaîne | Remplacée si le rôle la définit |
| Slice | Concaténée (defaults + role) |
| Booléen | Remplacé uniquement si explicitement défini |
Endpoints
| Méthode | URL | Description |
|---|---|---|
| GET | /api/v1/preseed/:mac |
Fichier preseed Debian |
| GET | /api/v1/device/:mac |
Infos Netbox + valeurs effectives (debug) |
| GET | /api/v1/static/:filename?mac=... |
Fichier statique (MAC optionnel) |
| GET | /api/v1/health |
Healthcheck |
| GET | /metrics |
Métriques Prometheus |
Résolution des fichiers statiques :
GET /api/v1/static/boot.ipxe→static/boot.ipxeGET /api/v1/static/linux?mac=AA:BB:CC:DD:EE:FF→ si le rôle du device définitstatic_dir: debian13, sertstatic/debian13/linux; sinonstatic/linux.
Formats MAC acceptés : AA:BB:CC:DD:EE:FF, AA-BB-CC-DD-EE-FF, AABBCCDDEEFF.
Exploitation
- Secrets :
NETBOX_TOKEN=... ./netbox-pxe-apiavectoken: "${NETBOX_TOKEN}"dansconfig.yaml. - Logs : texte par défaut, JSON via
LOG_FORMAT=json. - Métriques :
curl http://localhost:8080/metrics(compteurs requêtes, erreurs, preseed, fichiers servis, hits/miss du cache). - Arrêt propre : SIGINT/SIGTERM déclenchent un graceful shutdown (10 s max).
Build & Run
go mod tidy
go test ./... # tests
go run ./cmd/main.go --config config.yaml # local
go build -o netbox-pxe-api ./cmd/main.go # binaire
NETBOX_TOKEN=xxx LOG_FORMAT=json ./netbox-pxe-api # secret + logs JSON
# Docker (API seule)
docker build -t distributio .
docker run -p 8080:8080 -e NETBOX_TOKEN=xxx \
-v $(pwd)/config.yaml:/app/config.yaml \
-v $(pwd)/templates:/app/templates \
-v $(pwd)/static:/app/static \
distributio
Déploiement PXE complet (dnsmasq + iPXE + distributio)
L'image embarque dnsmasq (proxyDHCP + TFTP), les binaires iPXE et le
serveur distributio. Un seul conteneur orchestre l'ensemble via
deploy/entrypoint.sh. Le déploiement recommandé passe par Docker Compose :
docker compose up -d --build
docker compose logs -f
Le conteneur tourne en network_mode: host (indispensable : le proxyDHCP/TFTP
repose sur des broadcasts réseau qui ne traversent pas le bridge NAT de Docker).
Ports utilisés sur l'hôte : 67/udp (DHCP), 69/udp (TFTP), 4011/udp (PXE),
8080/tcp (API HTTP).
Fichiers accessibles depuis l'hôte (bind-mounts, voir docker-compose.yml) :
| Hôte | Conteneur | Rôle |
|---|---|---|
deploy/dnsmasq.conf |
/etc/dnsmasq.d/distributio.conf |
Conf dnsmasq (proxyDHCP/TFTP) |
deploy/tftp/ |
/srv/tftp |
Racine TFTP (binaires iPXE) |
config.yaml |
/app/config.yaml |
Conf distributio |
templates/ |
/app/templates |
Templates preseed |
static/ |
/app/static |
Fichiers statiques (kernel/initrd/boot.ipxe) |
À adapter avant le premier démarrage :
deploy/dnsmasq.conf:dhcp-range=<RÉSEAU>,proxy→ adresse réseau de votre sous-réseau PXE ;- éventuellement
interface=<iface>; - l'URL de chainload iPXE (
dhcp-boot=tag:ipxe,http://<IP_DISTRIBUTIO>:8080/api/v1/static/boot.ipxe).
static/boot.ipxe: l'IP du serveur distributio dans les URLkernel/initrd/url.config.yaml: URL/token Netbox (le token peut venir de${NETBOX_TOKEN}).
Chaîne d'amorçage :
Client PXE --(proxyDHCP)--> dnsmasq --(TFTP)--> undionly.kpxe / ipxe.efi
| |
| iPXE chargé, re-DHCP avec user-class "iPXE" |
v v
dnsmasq renvoie http://<distributio>:8080/api/v1/static/boot.ipxe (HTTP)
|
v
boot.ipxe --> kernel + initrd + preseed servis par distributio (par MAC)
Exemple iPXE
#!ipxe
dhcp
kernel http://SERVER:8080/api/v1/static/linux?mac=${mac} \
auto=true priority=critical \
url=http://SERVER:8080/api/v1/preseed/${mac}
initrd http://SERVER:8080/api/v1/static/initrd.gz?mac=${mac}
boot
Licence
Ce projet est distribué sous la licence Apache License 2.0.
Voir le fichier LICENSE-2.0.txt pour le texte complet.
Copyright 2026 distributio
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
English
A Go REST API that generates Debian preseed files on the fly from Netbox data, keyed by MAC address, and serves static files (kernel, initrd, iPXE scripts...) resolved according to the device role.
Features
- Debian preseed generation from Netbox data (hostname, IP, role).
- Per-role configuration overrides (DNS, NTP, packages, gateway, etc.).
- Static file serving with per-role resolution via
static_dir, automatic MIME type and Range request support (http.ServeContent). - Hot reload of the configuration (edit
config.yamlwithout restart). - Path-traversal protection for static files.
- Environment variables in
config.yaml(${VAR}) for secrets. - TTL cache of Netbox lookups (MAC→device) to reduce API load.
- Structured logs (slog, text or JSON) and Prometheus metrics at
/metrics. - Graceful shutdown on SIGINT/SIGTERM and HTTP timeouts.
Architecture
netbox-pxe-api/
├── cmd/main.go # Entry point
├── config.yaml # Configuration
├── Dockerfile
├── internal/
│ ├── config/config.go # Loading, validation, hot reload, env vars
│ ├── netbox/client.go # Netbox HTTP client + TTL cache
│ ├── preseed/generator.go # Preseed generation (text/template)
│ ├── shared/template_data.go # Template data builder
│ ├── metrics/metrics.go # Prometheus-format counters
│ └── api/ # HTTP handlers, middleware (logs/metrics)
├── templates/
│ └── preseed/ansible.preseed.tmpl # Preseed template
└── static/ # Static files
├── boot.ipxe
└── <static_dir>/... # Per-role subdirectories
Configuration (config.yaml)
server:listen:"0.0.0.0:8080"debug:falsestatic_dir:"./static"# static files roottemplate_dir:"./templates"# templates rootnetbox:url:"https://netbox.example.com"token:"${NETBOX_TOKEN}"# plain value or ${VAR} = environment variabletimeout_seconds:10tls_skip_verify:truecache_ttl_seconds:30# MAC→device cache (0 = disabled)api_paths:device_by_mac:"/api/dcim/interfaces/?mac_address={mac}"device_detail:"/api/dcim/devices/{id}/"ip_addresses:"/api/ipam/ip-addresses/?interface_id={interface_id}"defaults:# applied to every devicedns_servers:["8.8.8.8","8.8.4.4"]ntp_servers:["pool.ntp.org"]domain:"local.domain"locale:"en_US.UTF-8"timezone:"Europe/Paris"gateway:"192.168.1.254"root_password_crypted:"6ドル$..."ansible_password_crypted:"6ドル$..."ansible_ssh_key:"ssh-ed25519 AAAA..."system_disk_max_size:"500T"# disks above threshold are excluded from partitioningpackages:["curl","vim","sudo"]short_hostname:falseroles:# per-role overrides (Netbox slug)osd:dns_servers:["1.1.1.1"]# concatenated with defaultsgateway:"192.168.1.253"# replaces the defaultsystem_disk_max_size:"100G"short_hostname:truestatic_dir:"debian13"# static/debian13/<filename>Merge rules defaults ↔ roles
| Type | Behaviour |
|---|---|
| String | Replaced if the role defines it |
| Slice | Concatenated (defaults + role) |
| Boolean | Replaced only when explicitly set |
Endpoints
| Method | URL | Description |
|---|---|---|
| GET | /api/v1/preseed/:mac |
Debian preseed file |
| GET | /api/v1/device/:mac |
Netbox info + effective values (debug) |
| GET | /api/v1/static/:filename?mac=... |
Static file (MAC optional) |
| GET | /api/v1/health |
Healthcheck |
| GET | /metrics |
Prometheus metrics |
Static file resolution:
GET /api/v1/static/boot.ipxe→static/boot.ipxeGET /api/v1/static/linux?mac=AA:BB:CC:DD:EE:FF→ if the device role definesstatic_dir: debian13, servesstatic/debian13/linux; otherwisestatic/linux.
Accepted MAC formats: AA:BB:CC:DD:EE:FF, AA-BB-CC-DD-EE-FF, AABBCCDDEEFF.
Operations
- Secrets:
NETBOX_TOKEN=... ./netbox-pxe-apiwithtoken: "${NETBOX_TOKEN}"inconfig.yaml. - Logs: text by default, JSON via
LOG_FORMAT=json. - Metrics:
curl http://localhost:8080/metrics(request, error, preseed, static-served and cache hit/miss counters). - Graceful shutdown: SIGINT/SIGTERM trigger a clean shutdown (10 s max).
Build & Run
go mod tidy
go test ./... # tests
go run ./cmd/main.go --config config.yaml # local
go build -o netbox-pxe-api ./cmd/main.go # binary
NETBOX_TOKEN=xxx LOG_FORMAT=json ./netbox-pxe-api # secret + JSON logs
# Docker
docker build -t netbox-pxe-api .
docker run -p 8080:8080 -e NETBOX_TOKEN=xxx \
-v $(pwd)/config.yaml:/app/config.yaml \
-v $(pwd)/templates:/app/templates \
-v $(pwd)/static:/app/static \
netbox-pxe-api
iPXE example
#!ipxe
dhcp
kernel http://SERVER:8080/api/v1/static/linux?mac=${mac} \
auto=true priority=critical \
url=http://SERVER:8080/api/v1/preseed/${mac}
initrd http://SERVER:8080/api/v1/static/initrd.gz?mac=${mac}
boot
License
This project is distributed under the Apache License 2.0.
See the LICENSE-2.0.txt file for the full text.
Copyright 2026 distributio
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.