LLM-Server: IPv6 deaktiviert (host_baseline) + Swift-Repin und wget-4-Download nach Live-Befunden
Betreiberentscheidung 31.10.2026 (GNAI-002 Abschnitt 6): Der KI-Host wird kohärent IPv4-only betrieben. host_baseline verwaltet /etc/sysctl.d/99-ghostnet-disable-ipv6.conf (all+default), wendet es bei Änderung an und verifiziert den Laufzeitwert. Anlass: hängender get_url- Download durch AAAA-Antworten ohne nutzbare IPv6-Route (Messungen im Dokument; Pi-hole filtert AAAA nicht, der Host nutzt Pi-hole nicht). model_store nach Live-Befunden: Swift-Repin auf Repository-Revision 98a6100 (alte Revision entfernt, Datei laut Herausgeber-SHA256SUMS unverändert; GNAI-010 v0.5) und Download über wget -4 mit expliziter SHA-256-Verifikation vor dem atomaren Move. Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
| 2026-09-21 | 0.3 | spektr / Codex | je Modell ein festes Quadlet vorgesehen; Swift und Unsloth durch kontrollierten Stop/Start gegenseitig ausgeschlossen; Router-Modus zurückgestellt |
|
||||
| 2026-09-21 | 0.2 | spektr / Codex | Unsloth UD-Q6_K als zweites installiertes Modellprofil und kontrollierten Profilwechsel für A/B-Tests ergänzt |
|
||||
| 2026-09-21 | 0.1 | spektr / Codex | erstes technisches Profil für Swift Q6_K, 128k, Q8-KV, rootless Quadlet und gestufte MTP-Abnahme erstellt |
|
||||
| 2026-10-31 | 0.5 | spektr / Mistral Vibe | Swift-Repository-Revision aktualisiert: `eb0e3a7…` wurde vom Herausgeber aus dem Repository entfernt (resolve liefert 404); Repin auf den aktuellen `main`-Commit `98a6100…`. Die Q6_K-Datei ist laut Herausgeber-SHA256SUMS unverändert (`7f4de8…`), der Vision-Projektor ebenfalls (`daa111…`); beide Prüfsummen bleiben unverändert und werden beim Download verifiziert. |
|
||||
|
||||
---
|
||||
|
||||
@@ -80,11 +81,11 @@ Testing zur Recherche aufgelöst, aber nicht in ein freigegebenes Profil
|
||||
| Feld | Wert |
|
||||
| --- | --- |
|
||||
| Repository | `ukisai/Swift-Qwen3.8-27B-GGUF` |
|
||||
| Repository-Revision | `eb0e3a7dc70643c2ab920f5133750d02c20849ff` |
|
||||
| Repository-Revision | `98a6100719e3aafa186041089b71c7b48390dbe1` (am 31.10.2026 repinnt; die ursprüngliche Revision `eb0e3a7dc70643c2ab920f5133750d02c20849ff` wurde vom Herausgeber entfernt, die Datei-Inhalte sind laut Prüfsummen unverändert) |
|
||||
| Datei | `Swift-Qwen3.8-27B-Q6_K.gguf` |
|
||||
| veröffentlichte Größe | 22,9 GB |
|
||||
| SHA-256 | `7f4de8abd5446c08b0f975a1b38e43d02639f4ae9ecdd2ddfd5c5b0f612bda59` |
|
||||
| Download | `https://huggingface.co/ukisai/Swift-Qwen3.8-27B-GGUF/resolve/eb0e3a7dc70643c2ab920f5133750d02c20849ff/Swift-Qwen3.8-27B-Q6_K.gguf` |
|
||||
| Download | `https://huggingface.co/ukisai/Swift-Qwen3.8-27B-GGUF/resolve/98a6100719e3aafa186041089b71c7b48390dbe1/Swift-Qwen3.8-27B-Q6_K.gguf` |
|
||||
| API-Alias | `swift-qwen3.8-27b-q6k` |
|
||||
|
||||
Die Prüfsumme stammt aus der vom Herausgeber veröffentlichten
|
||||
@@ -123,7 +124,7 @@ Für eine spätere Vision-Stufe ist vorgesehen:
|
||||
| Datei | `mmproj-Swift-Qwen3.8-27B-F16.gguf` |
|
||||
| veröffentlichte Größe | 928 MB |
|
||||
| SHA-256 | `daa1116c9422fa390cc8688495da0e91781f92841dfc3b31a378ff252571745a` |
|
||||
| Download | `https://huggingface.co/ukisai/Swift-Qwen3.8-27B-GGUF/resolve/eb0e3a7dc70643c2ab920f5133750d02c20849ff/mmproj-Swift-Qwen3.8-27B-F16.gguf` |
|
||||
| Download | `https://huggingface.co/ukisai/Swift-Qwen3.8-27B-GGUF/resolve/98a6100719e3aafa186041089b71c7b48390dbe1/mmproj-Swift-Qwen3.8-27B-F16.gguf` |
|
||||
|
||||
Der Projektor wird erst heruntergeladen und mit `--mmproj` eingebunden, wenn
|
||||
die Vision-Teststufe ausdrücklich aktiviert wird.
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
| 2026-09-18 | 1.3 | spektr | GPU-Abnahme auf nachweisbare Geräteerkennung begrenzt; Firmwarepaket präzisiert. |
|
||||
| 2026-09-19 | 1.4 | spektr | Getrennte SSH-Zugänge für `ghost` und `ghost-agent` als erfolgreich getestet übernommen. |
|
||||
| 2026-09-22 | 1.5 | Codex | In die hostbezogene Struktur `platform/ghostnet-ai/` verschoben und Geltungsbereich präzisiert. |
|
||||
| 2026-10-31 | 1.6 | spektr / Mistral Vibe | Betreiberentscheidung IPv4-only: IPv6 wird auf dem Host über die `host_baseline`-Rolle systematisch deaktiviert (Abschnitt 6); Anlass war der hängende Modell-Download am 31.10.2026 durch den Halbdual-Stack-Zustand. |
|
||||
|
||||
---
|
||||
|
||||
@@ -157,3 +158,46 @@ Vor der Übergabe an das KI-Team (Phase 2) muss das System folgende Punkte beste
|
||||
## 5. Lesender Teilabgleich des bestehenden Hosts am 18.09.2026
|
||||
|
||||
Per SSH wurden Debian 13.6, Hostname `ghostnet-ki`, Intel Core i5-13600K, 62 GiB nutzbarer RAM und zwei AMD Navi-21-GPUs mit PCI-ID `1002:73bf` beobachtet. `lsblk` zeigt eine 1,8-TiB-NVMe mit EFI-, ext4-Root- und Swap-Partition; `df -h /` zeigte 1,8 TiB Dateisystemgröße. `lspci` bestätigt die Gerätefamilie, aber weder das genaue Kartenmodell noch ROCm-Funktion und ReBAR-Leistung. BIOS-Version, XMP, Firmwarestand und Restore wurden nicht geprüft.
|
||||
|
||||
---
|
||||
|
||||
## 6. Betreiberentscheidung: IPv6 auf dem Host deaktiviert (31.10.2026)
|
||||
|
||||
**Entscheidung (Betreiber, 31.10.2026):** Der KI-Host wird bewusst IPv4-only
|
||||
betrieben. IPv6 wird über die `host_baseline`-Rolle systematisch deaktiviert
|
||||
(`/etc/sysctl.d/99-ghostnet-disable-ipv6.conf` mit
|
||||
`net.ipv6.conf.all.disable_ipv6 = 1` und
|
||||
`net.ipv6.conf.default.disable_ipv6 = 1`).
|
||||
|
||||
**Anlass:** Während der ersten Modell-Downloads am 31.10.2026 hing der
|
||||
Ansible-`get_url`-Download auf dem Host minutenlang ohne TCP-Verbindung und
|
||||
ohne Datenträgeraktivität, obwohl dieselbe URL vom Host aus mit `wget -4` in
|
||||
Sekunden erreichbar war. Messungen dazu:
|
||||
|
||||
| Prüfung | Ergebnis |
|
||||
| --- | --- |
|
||||
| Host-Netzwerk | nur link-lokales IPv6 auf `enp8s0`; keine globale Adresse, keine IPv6-Default-Route |
|
||||
| Resolver des Hosts | Fritzbox `10.0.0.1` und `1.1.1.1` (der Host nutzt Pi-hole zu diesem Zeitpunkt nicht) |
|
||||
| AAAA-Antworten | Fritzbox, 1.1.1.1 und Pi-hole `10.0.0.14` liefern alle AAAA-Records; AAAA wird nirgends gefiltert |
|
||||
|
||||
Programme mit IPv6-Präferenz (wie Pythons `urllib`) laufen bei dieser
|
||||
Inkonsistenz aus AAAA-Antworten ohne nutzbare IPv6-Route in Timeouts oder
|
||||
Hänger; die 302-Weiterleitung der Hugging-Face-Downloads zum
|
||||
CloudFront-CDN traf genau darauf. Die Download-Rolle `model_store` erzwingt
|
||||
daher zusätzlich IPv4 (`wget -4`) und verifiziert die SHA-256 explizit.
|
||||
|
||||
**Einordnung:** Die Entscheidung beendet diesen Halbdual-Stack-Zustand
|
||||
zugunsten eines kohärenten IPv4-only-Betriebs. Sie erhält die dokumentierte
|
||||
Sicherheitseigenschaft „keine IPv6-Exposition" (GNAI-007 Offenpunkt O7,
|
||||
teilbestätigt am 31.10.2026) und entspricht dem bestehenden
|
||||
LAN-Betriebsmodell. Eine echte Dual-Stack-Einführung (Fritzbox-Präfix,
|
||||
Router Advertisements, Firewall-Regeln, neue Perimeter-Abnahme) bleibt ein
|
||||
eigener Netzwerk-Sprint und ist für den KI-Stack nicht erforderlich. Ein
|
||||
optionales Folgeziel ist die Umstellung des Host-Resolvers auf Pi-hole, um
|
||||
Split-DNS konsistent auch auf dem Host zu nutzen; das ist ein separater
|
||||
kleiner Schritt und kein Bestandteil dieser Entscheidung.
|
||||
|
||||
**Umsetzung:** `llm-server/host/ansible/roles/host_baseline` verwaltet die
|
||||
Drop-in-Datei, wendet sie bei Änderung über `sysctl --system` an und
|
||||
verifiziert den Laufzeitwert `net.ipv6.conf.all.disable_ipv6 = 1`. Ein
|
||||
zweiter Lauf muss `changed=0` melden.
|
||||
|
||||
@@ -10,6 +10,14 @@ Its first version deliberately has a narrow scope:
|
||||
- require at least 100 GiB free on the root filesystem; and
|
||||
- create root-owned parent directories and the future shared-data root.
|
||||
|
||||
Since 31.10.2026 the role also disables IPv6 on the host
|
||||
(`host_baseline_disable_ipv6`, default `true`) via
|
||||
`/etc/sysctl.d/99-ghostnet-disable-ipv6.conf`, applies it with
|
||||
`sysctl --system` when changed and verifies the runtime value. Rationale:
|
||||
the LAN has no usable global IPv6 path while all resolvers return AAAA
|
||||
records, which made dual-stack-preferring applications hang on outbound
|
||||
connections (operator decision, GNAI-002 section 6).
|
||||
|
||||
Environment-specific directory ownership is managed by `workload_accounts` so
|
||||
the shared baseline and workload identities do not compete over permissions.
|
||||
|
||||
|
||||
@@ -21,3 +21,12 @@ host_baseline_directories:
|
||||
# (AGENTS.md) — with (root) alone, runas to other users would prompt.
|
||||
host_baseline_deploy_sudoers_path: /etc/sudoers.d/90-ansible-deploy
|
||||
host_baseline_deploy_sudoers_content: "ansible-deploy ALL=(ALL:ALL) NOPASSWD: ALL\n"
|
||||
|
||||
# IPv6 is deliberately disabled on this host (operator decision 2026-10-31,
|
||||
# GNAI-002 section 6): the LAN provides no usable global IPv6 path, but all
|
||||
# available resolvers still return AAAA records. Dual-stack-preferring
|
||||
# applications then hang on outbound connections (observed 2026-10-31 during
|
||||
# the first model downloads). A coherent IPv4-only host keeps the documented
|
||||
# "no IPv6 exposure" property (GNAI-007 open item O7) and makes name
|
||||
# resolution and connect behaviour deterministic.
|
||||
host_baseline_disable_ipv6: true
|
||||
|
||||
@@ -66,3 +66,50 @@
|
||||
group: root
|
||||
mode: "0440"
|
||||
validate: /usr/sbin/visudo -cf %s
|
||||
|
||||
- name: Configure IPv6 as disabled on the host
|
||||
ansible.builtin.copy:
|
||||
content: |
|
||||
# Managed by the host_baseline role (GNAI-002 decision 2026-10-31).
|
||||
net.ipv6.conf.all.disable_ipv6 = 1
|
||||
net.ipv6.conf.default.disable_ipv6 = 1
|
||||
dest: /etc/sysctl.d/99-ghostnet-disable-ipv6.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: host_baseline_disable_ipv6
|
||||
register: host_baseline_ipv6_sysctl_file
|
||||
|
||||
- name: Apply the IPv6 sysctl drop-in
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /usr/sbin/sysctl
|
||||
- --system
|
||||
changed_when: false
|
||||
when:
|
||||
- host_baseline_disable_ipv6
|
||||
- host_baseline_ipv6_sysctl_file is changed
|
||||
- not ansible_check_mode
|
||||
|
||||
- name: Read the runtime IPv6 state
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /usr/sbin/sysctl
|
||||
- -n
|
||||
- net.ipv6.conf.all.disable_ipv6
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
when: host_baseline_disable_ipv6
|
||||
register: host_baseline_ipv6_state
|
||||
|
||||
- name: Verify IPv6 is disabled at runtime
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- host_baseline_ipv6_state.stdout | trim == "1"
|
||||
fail_msg: >-
|
||||
IPv6 is still enabled despite the sysctl drop-in; outbound
|
||||
dual-stack connections may hang (GNAI-002 section 6).
|
||||
success_msg: IPv6 is disabled on the host (net.ipv6.conf.all.disable_ipv6 = 1).
|
||||
when:
|
||||
- host_baseline_disable_ipv6
|
||||
- not ansible_check_mode
|
||||
|
||||
@@ -23,8 +23,11 @@ profile directory read-only.
|
||||
- Manifest is validated (unique profiles, 64-hex checksums, HTTPS URLs).
|
||||
- Directories are `root:root 0755`, artifact files `root:root 0644`; a
|
||||
`SOURCE.yml` provenance record is rendered per directory.
|
||||
- Downloads go to a hidden `.part` file with `get_url` checksum verification
|
||||
and are moved into place only after the checksum passed (atomic).
|
||||
- Downloads use `wget -4` into a hidden `.part` file, followed by explicit
|
||||
SHA-256 verification; only after the checksum passed is the file moved
|
||||
into place (atomic). IPv4 is forced because the host resolves AAAA first
|
||||
but has no global IPv6 route (dual-stack CDN redirects would hang;
|
||||
observed 31.10.2026).
|
||||
- An existing file with a mismatching checksum fails the run; it is never
|
||||
overwritten or used.
|
||||
- Check mode downloads nothing; already-present files are still verified.
|
||||
|
||||
@@ -17,9 +17,9 @@ model_store_artifacts:
|
||||
directory: swift-qwen3.8-27b
|
||||
file: Swift-Qwen3.8-27B-Q6_K.gguf
|
||||
sha256: 7f4de8abd5446c08b0f975a1b38e43d02639f4ae9ecdd2ddfd5c5b0f612bda59
|
||||
url: https://huggingface.co/ukisai/Swift-Qwen3.8-27B-GGUF/resolve/eb0e3a7dc70643c2ab920f5133750d02c20849ff/Swift-Qwen3.8-27B-Q6_K.gguf
|
||||
url: https://huggingface.co/ukisai/Swift-Qwen3.8-27B-GGUF/resolve/98a6100719e3aafa186041089b71c7b48390dbe1/Swift-Qwen3.8-27B-Q6_K.gguf
|
||||
repository: ukisai/Swift-Qwen3.8-27B-GGUF
|
||||
revision: eb0e3a7dc70643c2ab920f5133750d02c20849ff
|
||||
revision: 98a6100719e3aafa186041089b71c7b48390dbe1
|
||||
origin: Swift efficiency finetune of Qwen3.8-27B (UkisAI), Q6_K
|
||||
license: >-
|
||||
Qwen base model Apache-2.0; Swift changes under Swift Open License 1.0
|
||||
@@ -40,9 +40,9 @@ model_store_vision_artifact:
|
||||
directory: swift-qwen3.8-27b
|
||||
file: mmproj-Swift-Qwen3.8-27B-F16.gguf
|
||||
sha256: daa1116c9422fa390cc8688495da0e91781f92841dfc3b31a378ff252571745a
|
||||
url: https://huggingface.co/ukisai/Swift-Qwen3.8-27B-GGUF/resolve/eb0e3a7dc70643c2ab920f5133750d02c20849ff/mmproj-Swift-Qwen3.8-27B-F16.gguf
|
||||
url: https://huggingface.co/ukisai/Swift-Qwen3.8-27B-GGUF/resolve/98a6100719e3aafa186041089b71c7b48390dbe1/mmproj-Swift-Qwen3.8-27B-F16.gguf
|
||||
repository: ukisai/Swift-Qwen3.8-27B-GGUF
|
||||
revision: eb0e3a7dc70643c2ab920f5133750d02c20849ff
|
||||
revision: 98a6100719e3aafa186041089b71c7b48390dbe1
|
||||
origin: Swift vision projector, F16 (vision stage only)
|
||||
license: >-
|
||||
Qwen base model Apache-2.0; Swift changes under Swift Open License 1.0.
|
||||
|
||||
@@ -121,15 +121,38 @@
|
||||
- models
|
||||
- service
|
||||
|
||||
- name: Require the download utility
|
||||
ansible.builtin.stat:
|
||||
path: /usr/bin/wget
|
||||
register: model_store_wget_tool
|
||||
tags:
|
||||
- models
|
||||
- service
|
||||
|
||||
- name: Verify the download utility is executable
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- model_store_wget_tool.stat.exists
|
||||
- model_store_wget_tool.stat.executable
|
||||
fail_msg: /usr/bin/wget is required to download model artifacts.
|
||||
tags:
|
||||
- models
|
||||
- service
|
||||
|
||||
- name: Download missing model artifacts atomically
|
||||
ansible.builtin.get_url:
|
||||
url: "{{ item.item.url }}"
|
||||
dest: "{{ model_store_root }}/{{ item.item.directory }}/.{{ item.item.file }}.part"
|
||||
checksum: "sha256:{{ item.item.sha256 }}"
|
||||
mode: "0644"
|
||||
owner: root
|
||||
group: root
|
||||
timeout: 120
|
||||
# wget with forced IPv4: the host currently resolves AAAA first but has no
|
||||
# global IPv6 route, which blackholes redirects to dual-stack CDNs
|
||||
# (observed 31.10.2026 with the Hugging Face xet/CloudFront bridge).
|
||||
# The SHA-256 is verified explicitly after the download, before the move.
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /usr/bin/wget
|
||||
- -4
|
||||
- --timeout=60
|
||||
- --tries=3
|
||||
- -O
|
||||
- "{{ model_store_root }}/{{ item.item.directory }}/.{{ item.item.file }}.part"
|
||||
- "{{ item.item.url }}"
|
||||
loop: "{{ model_store_present.results | selectattr('stat.exists', 'equalto', false) | list }}"
|
||||
loop_control:
|
||||
label: "{{ item.item.profile }}"
|
||||
@@ -138,6 +161,23 @@
|
||||
tags:
|
||||
- models
|
||||
|
||||
- name: Verify the checksum of freshly downloaded artifacts
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /usr/bin/sha256sum
|
||||
- "{{ model_store_root }}/{{ item.item.directory }}/.{{ item.item.file }}.part"
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
loop: "{{ model_store_present.results | selectattr('stat.exists', 'equalto', false) | list }}"
|
||||
loop_control:
|
||||
label: "{{ item.item.profile }}"
|
||||
when:
|
||||
- not ansible_check_mode
|
||||
register: model_store_fresh_checksums
|
||||
failed_when: model_store_fresh_checksums.stdout.split()[0] != item.item.sha256
|
||||
tags:
|
||||
- models
|
||||
|
||||
- name: Move verified downloads to their final model path
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
|
||||
Reference in New Issue
Block a user