heyer.systems

Eigene App erstellen

Schritt für Schritt vom leeren Repo zur App mit LXC, VM-Image und Update – am Beispiel iac-example

Das Prinzip

Eine iac-App beschreibt nur, was sie braucht: Pakete, Dateien, Ports und ein paar Funktionen („Hooks“). Den Ablauf hat allein iac-core: Container anlegen, Image bauen, Firewall, Update, Übernahme bestehender Systeme, Tests. Eine App enthält deshalb keinen eigenen Installationsablauf und keinen kopierten Code aus anderen Apps.

Alle Wege landen beim selben, wiederholbaren Schritt bin/iac-install (dem „Sollzustand“):

Proxmox-Node                    Gast (LXC, VM oder Image-Build)
────────────                    ───────────────────────────────────────────────────────
ct.sh ──── pct exec ──────────▶ bin/iac-install
vm.sh ──── fertiges Image ────▶   (wurde beim Image-Build im chroot ausgeführt)
                                update      → Debian-Upgrade → app_update → bin/iac-install
                                adopt.sh    → app_adopt_check → Backup → app_adopt_migrate
                                              → bin/iac-install
                                Boot-Test   → bin/iac-smoke

bin/iac-install:
  Baseline (SSH, Benutzer, Firewall-Grundregeln, update)
  → packages.list installieren
  → app_install            Benutzer, Verzeichnisse, Downloads
  → rootfs/ einspielen     Konfiguration, systemd-Units (+ Firewall aus iac.json)
  → app_configure          Konfiguration prüfen, neu laden/starten
  → APP_SERVICES           aktivieren und starten
  → app_healthy            läuft die App?
  → /etc/iac/instance.json

Weil bin/iac-install bei jedem Lauf denselben Zustand herstellt, muss alles in deiner App wiederholbar sein: Ein zweiter Lauf darf nichts kaputt machen und nichts doppelt anlegen.

Vorbereitung

Die Repos nebeneinander auschecken:

mkdir -p ~/repositories/iac && cd ~/repositories/iac
git clone https://git.heyer.systems/iac/iac-core.git
git clone https://git.heyer.systems/iac/iac-example.git

Lokal gebraucht werden shellcheck und jq (für Lint) und Docker (zum Ausprobieren ohne Proxmox).

iac-example ist eine absichtlich kleine App: busybox httpd liefert eine Seite auf Port 8080 aus. Jeder Hook kommt darin genau einmal vor, jede Datei ist kommentiert. Lies sie in der Reihenfolge aus ihrem README, bevor du anfängst. Größere Vorbilder: iac-nginx (Debian-Paket) und iac-traefik (Binary von GitHub, mit Update und Adopt).

1. Gerüst anlegen

iac-core/tools/new-app.sh meineapp        # legt iac-meineapp/ an

new-app.sh überschreibt nie etwas. Ein angefangenes Repo ergänzt es um die fehlenden Dateien (new-app.sh meineapp iac-meineapp). Danach gibt es:

DateiAnpassen?
iac.jsonja – Beschreibung, Ports, Ressourcen
packages.listja – Debian-Pakete
app.funcja – die Hooks
rootfs/ja – Konfigurationsdateien, systemd-Units
tests/smoke.shja – Prüfungen für deine App
image/manifest.ymlselten – Hostname, Image-Größe
ct.sh, vm.sh, adopt.shnein – nur IAC_APP steht darin
.forgejo/workflows/*.ymlnein – in allen Apps identisch

2. iac.json – Steckbrief

{
  "name": "meineapp",
  "title": "Meine App",
  "description": "Ein Satz für die Website.",
  "website": "https://meineapp.example/",
  "upstream": { "type": "github-release", "repo": "hersteller/meineapp" },
  "supports": ["lxc", "vm"],
  "defaults": {
    "lxc": { "cpu": 1, "ram": 512, "disk": 4 },
    "vm":  { "cpu": 1, "ram": 1024, "disk": 8 }
  },
  "ports": [
    { "port": 80,   "proto": "tcp", "description": "Web", "access": "public" },
    { "port": 9090, "proto": "tcp", "description": "Admin", "access": "private" }
  ],
  "modules": [
    { "name": "node-exporter", "title": "Prometheus node exporter", "default": false }
  ],
  "access": "Web: http://{ip}/"
}
FeldBedeutung
nameKleinbuchstaben, Ziffern, -. Das Repo heißt iac-<name>, die URLs /<name>/ct.sh
upstream.typedebian (Paket aus Debian), github-release oder other
supportslxc braucht ct.sh, vm braucht vm.sh und ein Image, docker eine docker-compose.yml
defaultsRAM in MiB, Disk in GiB – je Plattform außer docker
portsdaraus entsteht die Firewall – public für alle, private nur aus privaten Netzen
modulesals Text = immer installiert, als Objekt = wählbar. Nur vorhandene Module (iac-core/modules/)
hiddentrue = erscheint nicht auf der Website

3. packages.list – Debian-Pakete

Ein Paket pro Zeile, # für Kommentare. Es ist die einzige Paketliste: LXC, VM-Image und Adopt nutzen dieselbe. Die Grundpakete (curl, jq, nftables, …) bringt iac-core mit.

4. rootfs/ – Dateien im Gast

Alles unter rootfs/ landet 1:1 im Gast (rootfs/etc/meineapp/config.yml → /etc/meineapp/config.yml) und gehört iac: Bei jedem update wird es abgeglichen, was aus dem Repo verschwindet, wird auch im Gast entfernt. Solche Dateien beginnen mit # managed by iac – do not edit.

5. app.func – die Hooks

HookWannWofür
APP_SERVICES=(…)Installation, Smoke-Testsystemd-Units, die aktiviert, gestartet und geprüft werden
app_installed_versionPflichtinstallierte Version, leer wenn nicht installiert
app_latest_versionPflicht für VMneueste Version – benennt das Image, läuft auf dem CI-Runner
app_installvor rootfs/Benutzer, Verzeichnisse, Downloads (Pakete sind schon da)
app_configurenach rootfs/Konfiguration prüfen, bei Änderung neu laden/starten
app_healthyEnde der Installation, Smoke-Testfunktioniert die App? Ein paar Sekunden Geduld einbauen
app_updatebei update, vor der Installationneue Upstream-Version holen (Debian-Pakete macht iac-core)
app_adopt_checkadopt.sh, vor der Rückfragebestehende Installation prüfen – nichts ändern, sonst iac_adopt_refuse "Grund"
app_adopt_migrateadopt.sh, nach dem Backupvorhandene Daten ins iac-Layout überführen
APP_ADOPT_BACKUP=(…)adopt.shPfade, die vorher gesichert werden

Alles außer den beiden Versionen ist optional – was du nicht brauchst, löschst du.

Nützliche Funktionen aus iac-core: msg_info, msg_ok, msg_warn, die, iac_run "Text" befehl… (Ausgabe nur bei Fehler), iac_download, iac_verify_sha256, iac_changed '/etc/meineapp/*' (hat die Installation diese Dateien gerade geändert?), iac_in_chroot (läuft gerade der Image-Build?), iac_service_restart, iac_version_gt.

Zwei Regeln, die oft übersehen werden:

6. tests/smoke.sh – Prüfungen

iac-core prüft schon: instance.json, APP_SERVICES aktiv, app_healthy, alle Ports aus iac.json in der Firewall. In tests/smoke.sh steht nur, was speziell für deine App ist:

check "API antwortet"            'curl -fsS http://127.0.0.1:9090/api/health | jq -e .ok'
check "läuft als eigener Nutzer" 'ps -o user= -C meineapp | grep -qx meineapp'

check "Beschreibung" 'Befehl' bricht nie ab: Alle Prüfungen laufen, fehlgeschlagene zeigen ihren Befehl und ihre Ausgabe. Derselbe Test läuft im Boot-Test jedes Images und von Hand in jedem Gast: bash /var/lib/iac/src/core/bin/iac-smoke.

7. Testen

Lint – findet Tippfehler, falsche Felder und kopierte Reste:

iac-core/tools/lint.sh iac-meineapp

Ohne Proxmox – in einem Docker-Container mit systemd:

iac-core/tools/dev-container.sh iac-meineapp   # installiert und führt den Smoke-Test aus
docker exec -it iac-dev-meineapp bash          # hineinschauen, Log: /var/log/iac/iac.log
iac-core/tools/dev-container.sh iac-meineapp   # nach Änderungen erneut = wie "update"

Der Container ist kein echtes LXC (privilegiert, kein cloud-init), für die App-Logik reicht er.

Auf Proxmox – einen Branch als LXC, bevor er nach main geht:

IAC_APP_REF=mein-branch bash -c "$(curl -fsSL https://git.heyer.systems/iac/iac-meineapp/raw/branch/mein-branch/ct.sh)"

Im Gast gibt IAC_VERBOSE=1 bash /var/lib/iac/src/core/bin/iac-install alle Ausgaben direkt aus.

CI – passiert automatisch:

WorkflowWannWas
lint.ymljeder Push, jeder Pull Requesttools/lint.sh im Container
image.ymlPush auf main, montagsImage bauen → in QEMU booten → Boot-Test inkl. iac-smoke → veröffentlichen

Nur ein Image, das den Boot-Test besteht, wird veröffentlicht. VM-Images entstehen nur aus main.

8. Veröffentlichen

  1. Repo iac-meineapp in der Organisation iac anlegen (öffentlich) und pushen.
  2. Der erste Push auf main baut das VM-Image (gut 10 Minuten).
  3. Die Website liest stündlich alle öffentlichen iac-*-Repos mit iac.json ein – danach gibt es die App-Seite mit Generator und die kurzen URLs /meineapp/ct.sh und /meineapp/vm.sh.

Häufige Fehler

SymptomUrsache
Workflow läuft nieDatei liegt in .forgejo/ statt in .forgejo/workflows/
Module … not found (Lint: module … does not exist)in modules steht ein Modul, das es (noch) nicht gibt
Fremde Firewall-Regeln, falscher Name im LogDateien aus einer anderen App kopiert – lieber new-app.sh
Image-Build bricht im chroot abDienst wird gestartet, obwohl kein systemd läuft – iac_in_chroot
Dienst startet bei jedem update neuNeustart ohne iac_changed
app_installed_version returns nothingdie Version wird falsch ermittelt oder die Installation in app_install fehlt
Eigene Änderungen nach update wegÄnderung in einer managed by iac-Datei statt in einer lokalen Datei

lint.sh erkennt die ersten drei Fehler selbst.

Wenn die Hooks nicht reichen