commit f5da6fd3b3427852565dd10b389ebfef26049bf6 Author: Gordon Zimm Date: Wed Sep 23 10:15:16 2026 +0200 Autoinstall-Seed und Repo-Struktur Co-Authored-By: Claude Opus 5 diff --git a/README.md b/README.md new file mode 100644 index 0000000..3d8f598 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# espo-deploy + +Öffentliche, **geheimnisfreie** Artefakte für EspoCRM-Kunden-VMs. Alles hier wird von den VMs ohne Anmeldung abgerufen – +es steht deshalb **kein Passwort, kein Token und keine Kundenangabe** in diesem Repository. + +| Verzeichnis | Inhalt | +|---|---| +| `autoinstall/` | NoCloud-Seed für die Basis-VM (`user-data`, `meta-data`) | +| `rmm-tasks/` | Skripte, die als Task im ConnectWise RMM hinterlegt sind (Quelle für Änderungen) | +| `bin/` | Skripte, die auf der VM unter `/srv/espo/bin/` liegen und von Tasks aufgerufen werden | + +Konzept, Entscheidungen und Runbooks liegen im internen Repository `espoCRM/espo-fleet-ops`. + +## Autoinstall + +``` +autoinstall ds=nocloud-net\;s=https://gitea.subcura.net/EspoCRM_Public/espo-deploy/raw/branch/main/autoinstall/ +``` + +Das Semikolon muss maskiert werden – in GRUB trennt ein unmaskiertes `;` zwei Befehle. + +## Kundenspezifisches + +Gehört **nicht** hierher, sondern in die Custom Fields des RMM (FQDN, Ring, Wartungsfenster) und in die `.env` auf der +jeweiligen VM. Geheimnisse entstehen auf der VM und verlassen sie nicht. diff --git a/autoinstall/README.md b/autoinstall/README.md new file mode 100644 index 0000000..48283a0 --- /dev/null +++ b/autoinstall/README.md @@ -0,0 +1,85 @@ +# Autoinstall-Seed für die EspoCRM-Kunden-VM + +`user-data` und `meta-data` bilden eine NoCloud-Quelle. Das Verzeichnis ist öffentlich abrufbar – es enthält deshalb +**keine Geheimnisse** (kein benutzbares Passwort, kein RMM-Token; siehe [ADR 0004](../docs/decisions/0004-bootstrap-der-kunden-vm.md)). + +## Ablageort + +**Für den Testbetrieb (Entscheidung 22.09.2026, Variante 1):** Der Seed liegt zusätzlich in +`gordon.zimm/ubuntu-autoinstall` unter `espocrm/`, weil der Installer ohne Anmeldung abruft und die Organisation +`espoCRM` anonym nicht lesbar ist. Boot-Zeile: + +``` +autoinstall ds=nocloud-net\;s=https://gitea.subcura.net/EspoCRM_Public/espo-deploy/raw/branch/main/autoinstall/ +``` + +**Dieses Verzeichnis ist die Quelle.** Änderungen hier vornehmen und anschließend nach `ubuntu-autoinstall/espocrm/` +kopieren (`user-data`, `meta-data`). Der endgültige Ablageort bleibt offen → O-26. + +## Hintergrund zur Wahl (O-26) + +Der Installer ruft die Dateien **ohne Anmeldung** ab. Dieses Repo liegt in der Organisation `espoCRM`, die nicht öffentlich +sichtbar ist: Ein anonymer Abruf von +`https://gitea.subcura.net/espoCRM/espo-fleet-ops/raw/branch/main/autoinstall/user-data` liefert **404**, obwohl das Repo +selbst als „nicht privat“ geführt wird (geprüft am 22.09.2026; dasselbe gilt für `espoCRM/espo-dev`). +Anonym erreichbar ist dagegen `gordon.zimm/ubuntu-autoinstall`. + +Solange das nicht entschieden ist, kommt der Seed dort hin, wo er abrufbar ist. Kandidaten: + +| Variante | Boot-Zeile | Bemerkung | +|---|---|---| +| Unterverzeichnis im bestehenden öffentlichen Repo | `autoinstall ds=nocloud-net\;s=https://gitea.subcura.net/EspoCRM_Public/espo-deploy/raw/branch/main/autoinstall/` | schnellster Weg, stört den bestehenden Seed im Wurzelverzeichnis nicht | +| eigenes öffentliches Repo, z. B. `gordon.zimm/espo-autoinstall` | `… ds=nocloud-net\;s=…/gordon.zimm/espo-autoinstall/raw/branch/main/` | klar abgegrenzt, ein Zweck | +| Organisation `espoCRM` auf „öffentlich“ stellen | `… ds=nocloud-net\;s=…/espoCRM/espo-fleet-ops/raw/branch/main/autoinstall/` | macht auch `espoCRM/espo-dev` für jeden lesbar – nicht empfohlen | +| Seed auf der Installationsmedium-Seite (zweites CIDATA-Image) | keine URL nötig | für die Flotte die sauberste Lösung, in Phase 3 zu prüfen | + +### Das Semikolon muss maskiert werden + +In der GRUB-Kommandozeile trennt `;` zwei GRUB-Befehle. Unmaskiert bekommt der Kernel nur `autoinstall ds=nocloud-net`, +die Seed-URL fällt weg – der Installer startet dann ganz normal interaktiv. Deshalb **`\;`** schreiben (so steht es auch +in der README von `ubuntu-autoinstall`). Gleichwertig: die ganze Angabe in Anführungszeichen setzen, +`autoinstall "ds=nocloud-net;s=https://…/espocrm/"`. + +Nach dem Booten der Ubuntu-Server-24.04-ISO im GRUB-Menü `e` drücken, die gewählte Zeile vor `---` einfügen, Strg+X. +Die Installation läuft ohne Rückfragen durch und **schaltet die VM am Ende aus**. Vor dem nächsten Start das ISO trennen, +sonst startet die Installation erneut. + +## Nach dem ersten Start (einmal je VM) + +Die Schritte liegen auch als `/root/ERSTINBETRIEBNAHME.txt` auf der VM: + +1. An der Hyper-V-Konsole anmelden. Das **Initialpasswort** steht über dem Login-Prompt (`/etc/issue`); der Wechsel wird + erzwungen. Neues Passwort im Secure Information Store der Site hinterlegen. +2. RMM-Agent installieren – Token aus *Endpoints → Devices → Manage → Download Agent*: + + ```bash + cd /tmp && curl -fsSLOJ "" && chmod +x *_ITSPlatform_TKN*.run && sudo ./*_ITSPlatform_TKN*.run; rm -f *_ITSPlatform_TKN*.run + ``` + + `-O -J` behält den vom Server vorgegebenen Dateinamen samt `_TKN` – daraus liest der Installer den Token. + Das abschließende `rm` hängt an `;`, damit die Datei mit dem Token auch nach einem Abbruch verschwindet. + + Der Token steckt im Dateinamen; der Installer liest ihn aus seiner eigenen Aufrufzeile. Wird die Datei umbenannt + (`-o name.run`) oder nur mit `-O` geladen (dann heißt sie `setup`), fehlt der Token und muss gesetzt werden: + `sudo env TOKEN= ./datei.run`. + + Prüfen: `systemctl status ITSPlatform` und `tail /var/log/ITSPlatform-install.log`; das Gerät erscheint nach + wenigen Minuten in der Site. Einzelheiten zur Token-Übergabe: + [../docs/quellen/connectwise-rmm-notizen.md](../docs/quellen/connectwise-rmm-notizen.md) + +3. Hinweiszeile mit dem Initialpasswort aus `/etc/issue` entfernen. +4. Prüfen, dass das Gerät binnen weniger Minuten in der richtigen Site erscheint. + +## Was der Seed einrichtet + +| Bereich | Wert | +|---|---| +| Sprache/Tastatur/Zeitzone | de_DE.UTF-8, de, Europe/Berlin | +| Netz | erste Ethernet-Schnittstelle (`match: e*`), DHCPv4, kein DHCPv6 | +| Konto | `administrator`, Passwort je VM zufällig, Wechsel beim ersten Anmelden erzwungen | +| SSH | Server installiert, Passwortanmeldung erlaubt ([ADR 0003](../docs/decisions/0003-zugangsmodell-ssh.md)) | +| Pakete | `linux-cloud-tools-virtual` (Hyper-V-Integrationsdienste), `curl`, `bzip2` | +| Platten | `/` 20 GiB, swap 4 GiB, `/var/lib/docker` 20 GiB, `/srv` 80 % des Rests, ~20 % frei in der VG | +| Zum Schluss | vollständiges `apt full-upgrade`, `autoremove`, `clean`, dann `poweroff` | + +Hintergrund zur Aufteilung: [plattenlayout-erlaeuterung.md](../docs/entwuerfe/plattenlayout-erlaeuterung.md). diff --git a/autoinstall/meta-data b/autoinstall/meta-data new file mode 100644 index 0000000..e69de29 diff --git a/autoinstall/user-data b/autoinstall/user-data new file mode 100644 index 0000000..91a5ad0 --- /dev/null +++ b/autoinstall/user-data @@ -0,0 +1,128 @@ +#cloud-config +# Autoinstall-Seed für die EspoCRM-Kunden-VM (Ubuntu Server 24.04 LTS auf Hyper-V). +# Stand 2026-09-22, abgeleitet aus gitea gordon.zimm/ubuntu-autoinstall (Commit 0cd9fc6). +# Auf einer Test-VM am 22.09.2026 vollständig durchgelaufen (Ubuntu 24.04.5, Hyper-V, 127-GiB-Platte). +# +# Grundsätze: +# * Der Seed wird per NoCloud über HTTP geladen und kann sich nicht authentisieren +# -> er enthält KEIN Geheimnis: kein benutzbares Passwort, kein RMM-Token. +# * Die ISO richtet nur das Minimum ein. Docker, Härtung, Logrotation, Firewall, Hostname und +# Instanz kommen reproduzierbar über RMM-Tasks ("alles Weitere erfolgt über ConnectWise RMM"). +autoinstall: + version: 1 + + source: + id: ubuntu-server + + updates: all + + locale: de_DE.UTF-8 + keyboard: + layout: de + timezone: Europe/Berlin + + # Schnittstellennamen nicht festnageln: Hyper-V meldet je nach Kernel eth0 oder enp*. + network: + version: 2 + ethernets: + primary: + match: + name: "e*" + dhcp4: true + dhcp6: false + + identity: + realname: administrator + username: administrator + hostname: unkonfiguriert # setzt der RMM-Baseline-Task auf crm- + # Hash eines einmalig erzeugten 48-Zeichen-Zufallspassworts; der Klartext wurde nirgends abgelegt. + # Das Konto ist damit faktisch gesperrt, bis die late-commands je VM ein Initialpasswort setzen. + password: '$6$KDLfMzLPV6wWtwA4$9VnUET9kFWUVp07z2hkN9Kv9.WjgdIPGLmm7zb4Te47Ms7lyrw/9Ewpqf759xVfXcOfAkWJSFrxUClfeyxeuI0' + + ssh: + install-server: true + allow-pw: true + authorized-keys: [] + + packages: + - linux-cloud-tools-virtual # hv_kvp_daemon, hv_vss_daemon, hv_fcopy_daemon + - curl # Voraussetzung des RMM-Agenten (libcurl4 kommt mit) + - bzip2 # der .run-Installer entpackt sich damit + # kein linux-azure: der Azure-Kernel ist für Azure gebaut. Soll er trotzdem bleiben, + # gehört linux-cloud-tools-azure dazu, sonst fehlen die Hyper-V-Integrationsdienste. + + # Explizite Aufteilung: klein für das Betriebssystem, groß für die Nutzdaten. + # `layout: {name: lvm, sizing-policy: scaled}` wäre kürzer, gibt aber rund die Hälfte der Platte an "/" – + # auf einer reinen Docker-VM ist das an der falschen Stelle. + storage: + config: + - {type: disk, id: disk0, ptable: gpt, match: {size: largest}, wipe: superblock-recursive, preserve: false, grub_device: false} + + - {type: partition, id: part-esp, device: disk0, size: 1G, flag: boot, grub_device: true, preserve: false} + - {type: format, id: fmt-esp, volume: part-esp, fstype: fat32, preserve: false} + - {type: mount, id: mnt-esp, device: fmt-esp, path: /boot/efi} + + - {type: partition, id: part-boot, device: disk0, size: 2G, preserve: false} + - {type: format, id: fmt-boot, volume: part-boot, fstype: ext4, preserve: false} + - {type: mount, id: mnt-boot, device: fmt-boot, path: /boot} + + - {type: partition, id: part-pv, device: disk0, size: -1, flag: lvm, preserve: false} + - {type: lvm_volgroup, id: vg0, name: ubuntu-vg, devices: [part-pv], preserve: false} + + # Betriebssystem, RMM-Agent, Logs – 20 GiB sind reichlich (Bedarf real 8–12 GiB) + - {type: lvm_partition, id: lv-root, name: root-lv, volgroup: vg0, size: 20G, preserve: false} + - {type: format, id: fmt-root, volume: lv-root, fstype: ext4, preserve: false} + - {type: mount, id: mnt-root, device: fmt-root, path: /} + + # `path: none` für Swap ist am 22.09.2026 auf einer Test-VM bestätigt worden. + - {type: lvm_partition, id: lv-swap, name: swap-lv, volgroup: vg0, size: 4G, preserve: false} + - {type: format, id: fmt-swap, volume: lv-swap, fstype: swap, preserve: false} + - {type: mount, id: mnt-swap, device: fmt-swap, path: none} + + # Images, Container, Container-Logs – reproduzierbar, wird nicht gesichert + - {type: lvm_partition, id: lv-docker, name: docker-lv, volgroup: vg0, size: 20G, preserve: false} + - {type: format, id: fmt-docker, volume: lv-docker, fstype: ext4, preserve: false} + - {type: mount, id: mnt-docker, device: fmt-docker, path: /var/lib/docker} + + # /srv (Nutzdaten) wird in late-commands anteilig angelegt, damit der Seed für jede Plattengröße passt. + + late-commands: + # 1. /srv bekommt 80 % des verbliebenen Platzes; ~20 % bleiben unbelegt als Reserve und für LVM-Snapshots. + # Beispiel 128-GiB-Platte: / 20, swap 4, docker 20, /srv ~65, frei ~16 GiB. + - lvcreate -y -l 80%FREE -n srv-lv ubuntu-vg + - mkfs.ext4 -q -L srv /dev/ubuntu-vg/srv-lv + - mkdir -p /target/srv + # Eintrag über UUID, wie curtin es für die übrigen Dateisysteme tut. + - >- + SRVUUID=$(blkid -s UUID -o value /dev/ubuntu-vg/srv-lv); + printf 'UUID=%s /srv ext4 defaults 0 2\n' "$SRVUUID" >> /target/etc/fstab + # 2. Je VM zufälliges Initialpasswort, am Konsolen-Login sichtbar, Wechsel beim ersten Anmelden erzwungen. + - >- + PW=$(tr -dc 'A-Za-z0-9' > /target/etc/issue + # 3. Hyper-V-Integrationsdienste (Zeit/IP-Meldung, Dateisystem-Quiesce für Veeam). + # Normalerweise startet udev sie selbst; enable schadet nicht und darf fehlschlagen. + - curtin in-target -- systemctl enable hv-kvp-daemon.service hv-vss-daemon.service || true + # 4. Kurze Anleitung für den einen Handgriff nach dem ersten Start (Passwort + RMM-Agent). + - >- + printf '%s\n' + '# Erstinbetriebnahme dieser VM (Entscheidung 2026-09-22: Registrierung im RMM von Hand)' + '# 1. Initialpasswort steht am Konsolen-Login (/etc/issue), Wechsel wird erzwungen.' + '# Neues Passwort im Secure Information Store der Site hinterlegen.' + '# 2. RMM-Agent installieren (Token aus: Endpoints > Devices > Manage > Download Agent):' + '# cd /tmp && curl -fsSLOJ "" && chmod +x *_ITSPlatform_TKN*.run && sudo ./*_ITSPlatform_TKN*.run; rm -f *_ITSPlatform_TKN*.run' + '# Pruefen: systemctl status ITSPlatform ; tail /var/log/ITSPlatform-install.log' + '# 3. Zeile mit dem Initialpasswort aus /etc/issue entfernen.' + '# Alles Weitere erledigt der Baseline-Task, den der Trigger "First check-in" anstoesst.' + > /target/root/ERSTINBETRIEBNAHME.txt + # 5. Zum Schluss alles auf den neuesten Stand bringen. `updates: all` deckt den Stand zu Beginn der + # Installation ab; dieser Durchgang holt, was währenddessen dazugekommen ist, und räumt auf. + # NEEDRESTART_SUSPEND verhindert Dienst-Neustarts im chroot. + - curtin in-target -- sh -c 'DEBIAN_FRONTEND=noninteractive NEEDRESTART_SUSPEND=1 apt-get update' + - curtin in-target -- sh -c 'DEBIAN_FRONTEND=noninteractive NEEDRESTART_SUSPEND=1 apt-get -y -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef full-upgrade' + - curtin in-target -- sh -c 'DEBIAN_FRONTEND=noninteractive apt-get -y autoremove --purge' + - curtin in-target -- sh -c 'apt-get clean' + + shutdown: poweroff