GNAI-007: Basisfunktionen für Ansible erstellt.
This commit is contained in:
@@ -1,2 +1,7 @@
|
||||
# Local operator context for Codex; never commit machine-specific memory.
|
||||
.codex/LOCAL_MEMORY.md
|
||||
|
||||
# Local Python/Ansible control environment and runtime artifacts.
|
||||
.venv/
|
||||
.ansible/
|
||||
*.retry
|
||||
|
||||
@@ -26,7 +26,7 @@ Die drei vorgesehenen VPN-Netzwerkrollen sind **Familie** (gesamtes Heimnetz), *
|
||||
|
||||
| Bereich | Belegter Stand | Nächster Nachweis oder Schritt |
|
||||
| --- | --- | --- |
|
||||
| KI-Server | Debian-Basissystem und getrennte SSH-Konten; lesender Teilabgleich am 18.09.2026 | LLM-Anwendungsstack später mit Ansible einrichten und abnehmen |
|
||||
| KI-Server | Debian-Basissystem, lesender Preflight, verifizierter `ansible-deploy`-Account und idempotent angewendete minimale Host-Baseline | Podman-Runtime als nächste getrennte Rolle planen und kontrolliert abnehmen |
|
||||
| VPN | wg-easy v15.4.0 auf CT 105 aktiv; ein externer Laptop-Teiltest liegt vor | Rollenrechte serverseitig umsetzen und mit erlaubten sowie gesperrten Zielen testen |
|
||||
| Reverse Proxy | NPM auf CT 103 dokumentiert; Fail2Ban laut Betreiber fest gepinnt und wieder in Betrieb | Effektive Jails, Sperren und Wiederanlauf prüfen |
|
||||
| Identität | Keycloak/OIDC laut Betreiber produktiv für Vaultwarden | Öffentlich erreichbare Keycloak-Administration sperren und den OIDC-Login erneut prüfen |
|
||||
@@ -46,10 +46,12 @@ Der [Dokumentationsindex](docs/GNAI-000-DOC_Index.md) enthält die datierten Que
|
||||
| [Client-Verwaltung](docs/GNAI-006-SEC_VPN_Client-Management.md) | Profile ausgeben, ändern, widerrufen und Rollen prüfen |
|
||||
| [Windows 11](docs/GNAI-006-SEC_VPN_Clients_Windows11.md) und [Linux](docs/GNAI-006-SEC_VPN_Clients_Linux.md) | Einrichtung und Nutzung auf Endgeräten |
|
||||
| [WireGuard-Implementation-Log](docs/GNAI-006-SEC_VPN_Implementation-Log.md) | Historie, Tests und Korrekturen früherer Befunde |
|
||||
| [Ansible-Deployment-Planung](docs/GNAI-007-PLAN_Ansible-Deployment.md) | nicht finaler Arbeitsentwurf zur reproduzierbaren Bereitstellung, Gitea-Integration und geplanten Folgedokumentation |
|
||||
| [Ansible-Control-Projekt](ansible/README.md) | lokale Einrichtung, lesender Server-Preflight, Deployment-Account und gemeinsame Host-Baseline |
|
||||
|
||||
## Arbeiten mit dem Repository
|
||||
|
||||
Dieses Repository enthält derzeit Dokumentation und noch keine Ansible-Rollen oder ausführbare Bereitstellung des KI-Anwendungsstacks. Vor einer Infrastrukturänderung den Ist-Stand am betroffenen Host prüfen, die passende Fachanleitung lesen und die dort genannten Abnahmeschritte dokumentieren. Der KI-Server soll bis zum geplanten Ansible-Sprint abgesehen von den eingerichteten Benutzerzugängen ein Basissystem bleiben.
|
||||
Dieses Repository enthält Dokumentation und ein Ansible-Control-Projekt. Es prüft Inventar, SSH-Zugang, Hostidentität, Betriebssystem und die dokumentierten GPUs. Eine erste gemeinsame Rolle verwaltet ausschließlich die Konfigurations- und Datenwurzeln für Production und Testing; der KI-Anwendungsstack ist noch nicht bereitgestellt. Vor einer Infrastrukturänderung den Ist-Stand am betroffenen Host prüfen, die passende Fachanleitung lesen und die dort genannten Abnahmeschritte dokumentieren.
|
||||
|
||||
WireGuard-Profile enthalten private Schlüssel. Sie gehören nicht in Git, Ausgaben oder freigegebene Dokumente. Das lokale Verzeichnis `docs/GNAI-006-profiles/` schließt Profil-Dateien über eine eigene `.gitignore` aus; es ist kein Verteiler- oder Backup-Ort für Zugangsdaten.
|
||||
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
# Ansible control project
|
||||
|
||||
This directory contains the reproducible control project for `ghostnet-ki`.
|
||||
The read-only `playbooks/preflight.yml` verifies the documented host identity
|
||||
and hardware. The first writing milestone, `playbooks/host.yml`, applies a
|
||||
small shared host baseline through the dedicated deployment account.
|
||||
|
||||
## Control-node setup
|
||||
|
||||
Use Python 3.12 through 3.14 and create a virtual environment at the repository
|
||||
root:
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
.venv/bin/python -m pip install --upgrade pip
|
||||
.venv/bin/python -m pip install -r ansible/requirements-control.txt
|
||||
```
|
||||
|
||||
Load the private key for the read-only `ghost-agent` account into an SSH agent.
|
||||
Do not store private keys or passphrases in this repository:
|
||||
|
||||
```bash
|
||||
ssh-add /path/to/ghost-agent-private-key
|
||||
```
|
||||
|
||||
The workstation must already trust the verified SSH host key for `10.0.0.90`.
|
||||
An unexpected host-key change must be checked through a trusted console before
|
||||
continuing.
|
||||
|
||||
## Read-only preflight
|
||||
|
||||
Run the commands from the `ansible/` directory:
|
||||
|
||||
```bash
|
||||
cd ansible
|
||||
../.venv/bin/ansible-inventory --graph
|
||||
../.venv/bin/ansible-playbook playbooks/preflight.yml --syntax-check
|
||||
../.venv/bin/ansible-playbook playbooks/preflight.yml
|
||||
```
|
||||
|
||||
The final recap must report `changed=0` and `failed=0`. The playbook verifies:
|
||||
|
||||
- hostname `ghostnet-ki`;
|
||||
- Debian major version 13;
|
||||
- default IPv4 address `10.0.0.90`;
|
||||
- remote Python availability; and
|
||||
- two AMD Navi 21 PCI devices with ID `1002:73bf`.
|
||||
|
||||
The inventory deliberately contains no private-key path. Each operator supplies
|
||||
their authorized key through `ssh-agent` or a local Ansible override.
|
||||
|
||||
## One-time deployment-account bootstrap
|
||||
|
||||
Status 2026-09-19: The initial bootstrap has been applied to `ghostnet-ki`, and
|
||||
the separate verification playbook confirmed SSH access and non-interactive
|
||||
effective UID 0 with `changed=0` and `failed=0`. Run the bootstrap again only
|
||||
for an intentional repair, rotation or idempotence check.
|
||||
|
||||
The bootstrap creates the dedicated `ansible-deploy` account. Its password is
|
||||
locked, its committed public key is restricted to the LAN and WireGuard source
|
||||
networks, and `/etc/sudoers.d/90-ansible-deploy` is validated with `visudo`
|
||||
before installation. The account receives `NOPASSWD` root escalation because
|
||||
the planned host, package, systemd and container management cannot be expressed
|
||||
as a small stable command allowlist.
|
||||
|
||||
The matching private key stays outside this repository. On the workstation on
|
||||
which the initial key was created, it is stored as:
|
||||
|
||||
```text
|
||||
~/.ssh/id_ed25519_ghostnet_ai_ansible_deploy
|
||||
```
|
||||
|
||||
The automation key has no interactive passphrase so a future runner can use it
|
||||
non-interactively. It is protected by filesystem mode `0600`, SSH source-network
|
||||
restrictions and the dedicated account. Compromise of this private key would
|
||||
nevertheless grant root-equivalent deployment access; rotate it when moving the
|
||||
deployment identity to the runner or after any suspected disclosure.
|
||||
|
||||
Load the existing personal administrator key into an SSH agent, then run the
|
||||
bootstrap interactively from the `ansible/` directory. Ansible will ask for the
|
||||
personal administrator's existing sudo password:
|
||||
|
||||
```bash
|
||||
ssh-add /path/to/personal-ghost-private-key
|
||||
../.venv/bin/ansible-playbook \
|
||||
--inventory inventories/bootstrap/hosts.yml \
|
||||
playbooks/bootstrap-deploy-account.yml \
|
||||
--ask-become-pass
|
||||
```
|
||||
|
||||
Do not send the password through chat, store it in Git, or place it in a shell
|
||||
history entry. The bootstrap is idempotent; a second run should report no
|
||||
changes.
|
||||
|
||||
After a successful bootstrap, verify the new account independently:
|
||||
|
||||
```bash
|
||||
../.venv/bin/ansible-playbook \
|
||||
playbooks/verify-deploy-access.yml \
|
||||
--extra-vars ansible_user=ansible-deploy \
|
||||
--private-key ~/.ssh/id_ed25519_ghostnet_ai_ansible_deploy
|
||||
```
|
||||
|
||||
The verification must report `changed=0` and `failed=0`. It confirms both the
|
||||
unprivileged SSH identity and non-interactive escalation to effective UID 0.
|
||||
|
||||
## Shared host baseline
|
||||
|
||||
The host baseline is common to Production and Testing. It verifies the target
|
||||
identity and at least 100 GiB free on the root filesystem, then manages these
|
||||
root-owned directory roots:
|
||||
|
||||
```text
|
||||
/etc/ghostnet-ai/{production,testing}
|
||||
/var/lib/ghostnet-ai/{production,testing,shared}
|
||||
```
|
||||
|
||||
It does not install software or start services. Use the dedicated host
|
||||
inventory and supply the private deployment key outside the repository:
|
||||
|
||||
```bash
|
||||
../.venv/bin/ansible-playbook \
|
||||
--inventory inventories/host/hosts.yml \
|
||||
playbooks/host.yml \
|
||||
--syntax-check
|
||||
|
||||
../.venv/bin/ansible-playbook \
|
||||
--inventory inventories/host/hosts.yml \
|
||||
playbooks/host.yml \
|
||||
--check --diff \
|
||||
--private-key ~/.ssh/id_ed25519_ghostnet_ai_ansible_deploy
|
||||
|
||||
../.venv/bin/ansible-playbook \
|
||||
--inventory inventories/host/hosts.yml \
|
||||
playbooks/host.yml \
|
||||
--private-key ~/.ssh/id_ed25519_ghostnet_ai_ansible_deploy
|
||||
```
|
||||
|
||||
Run the final command a second time. The second recap must report `changed=0`
|
||||
and `failed=0`; otherwise the role has not demonstrated convergence.
|
||||
@@ -0,0 +1,13 @@
|
||||
[defaults]
|
||||
inventory = inventories/testing/hosts.yml
|
||||
home = .ansible
|
||||
host_key_checking = True
|
||||
interpreter_python = auto_silent
|
||||
local_tmp = .ansible/tmp
|
||||
retry_files_enabled = False
|
||||
roles_path = roles
|
||||
timeout = 15
|
||||
|
||||
[ssh_connection]
|
||||
pipelining = True
|
||||
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
|
||||
@@ -0,0 +1 @@
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJE3UGGHyB+CMA2sqPpI/6efn9u3eLelL2MkHqdt2naB ansible-deploy@ghostnet-control-2026-09
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
all:
|
||||
children:
|
||||
llm_servers:
|
||||
hosts:
|
||||
ghostnet-ki:
|
||||
ansible_host: 10.0.0.90
|
||||
ansible_user: ghost
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
all:
|
||||
children:
|
||||
llm_servers:
|
||||
hosts:
|
||||
ghostnet-ki:
|
||||
ansible_host: 10.0.0.90
|
||||
ansible_user: ansible-deploy
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
all:
|
||||
children:
|
||||
llm_servers:
|
||||
hosts:
|
||||
ghostnet-ki:
|
||||
ansible_host: 10.0.0.90
|
||||
ansible_user: ghost-agent
|
||||
@@ -0,0 +1,98 @@
|
||||
---
|
||||
- name: Bootstrap the dedicated Ansible deployment account
|
||||
hosts: llm_servers
|
||||
become: true
|
||||
gather_facts: true
|
||||
|
||||
vars:
|
||||
ansible_deploy_user: ansible-deploy
|
||||
ansible_deploy_public_key_file: >-
|
||||
{{ playbook_dir }}/../files/ssh/ansible-deploy.pub
|
||||
ansible_deploy_ssh_sources:
|
||||
- 10.0.0.0/24
|
||||
- 10.8.0.0/24
|
||||
|
||||
pre_tasks:
|
||||
- name: Refuse to bootstrap through the target deployment account
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ansible_user != ansible_deploy_user
|
||||
fail_msg: >-
|
||||
Run this one-time bootstrap through an existing personal administrator
|
||||
account, not through {{ ansible_deploy_user }}.
|
||||
|
||||
- name: Verify the local deployment public key
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- lookup("ansible.builtin.file", ansible_deploy_public_key_file) is match("^ssh-ed25519 ")
|
||||
fail_msg: >-
|
||||
The expected Ed25519 public key is missing or malformed at
|
||||
{{ ansible_deploy_public_key_file }}.
|
||||
|
||||
tasks:
|
||||
- name: Create the dedicated deployment account with a locked password
|
||||
ansible.builtin.user:
|
||||
name: "{{ ansible_deploy_user }}"
|
||||
comment: Ghostnet AI Ansible deployment account
|
||||
create_home: true
|
||||
home: "/home/{{ ansible_deploy_user }}"
|
||||
password_lock: true
|
||||
shell: /bin/bash
|
||||
state: present
|
||||
|
||||
- name: Protect the deployment account home directory
|
||||
ansible.builtin.file:
|
||||
path: "/home/{{ ansible_deploy_user }}"
|
||||
state: directory
|
||||
owner: "{{ ansible_deploy_user }}"
|
||||
group: "{{ ansible_deploy_user }}"
|
||||
mode: "0750"
|
||||
|
||||
- name: Create the deployment SSH directory
|
||||
ansible.builtin.file:
|
||||
path: "/home/{{ ansible_deploy_user }}/.ssh"
|
||||
state: directory
|
||||
owner: "{{ ansible_deploy_user }}"
|
||||
group: "{{ ansible_deploy_user }}"
|
||||
mode: "0700"
|
||||
|
||||
- name: Install the restricted deployment public key
|
||||
ansible.builtin.copy:
|
||||
content: |
|
||||
restrict,from="{{ ansible_deploy_ssh_sources | join(',') }}" {{ lookup('ansible.builtin.file', ansible_deploy_public_key_file) | trim }}
|
||||
dest: "/home/{{ ansible_deploy_user }}/.ssh/authorized_keys"
|
||||
owner: "{{ ansible_deploy_user }}"
|
||||
group: "{{ ansible_deploy_user }}"
|
||||
mode: "0600"
|
||||
|
||||
- name: Grant explicit non-interactive root escalation for Ansible
|
||||
ansible.builtin.copy:
|
||||
content: |
|
||||
# Managed by Ghostnet AI Ansible bootstrap.
|
||||
{{ ansible_deploy_user }} ALL=(root) NOPASSWD: ALL
|
||||
dest: "/etc/sudoers.d/90-{{ ansible_deploy_user }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0440"
|
||||
validate: /usr/sbin/visudo -cf %s
|
||||
|
||||
- name: Read the effective deployment sudo policy
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /usr/bin/sudo
|
||||
- -l
|
||||
- -U
|
||||
- "{{ ansible_deploy_user }}"
|
||||
changed_when: false
|
||||
register: ansible_deploy_sudo_policy
|
||||
|
||||
- name: Verify non-interactive root escalation is effective
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- 'ansible_deploy_sudo_policy.stdout is search("NOPASSWD: ALL")'
|
||||
fail_msg: >-
|
||||
The deployment account exists, but its validated NOPASSWD sudo policy
|
||||
is not effective.
|
||||
success_msg: >-
|
||||
The deployment account and its validated sudo policy are ready for
|
||||
the separate access verification playbook.
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
- name: Apply the shared Ghostnet AI host baseline
|
||||
hosts: llm_servers
|
||||
become: true
|
||||
gather_facts: true
|
||||
gather_subset:
|
||||
- min
|
||||
- network
|
||||
- hardware
|
||||
|
||||
roles:
|
||||
- role: host_baseline
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
- name: Verify read-only access to the Ghostnet AI server
|
||||
hosts: llm_servers
|
||||
become: false
|
||||
gather_facts: true
|
||||
gather_subset:
|
||||
- min
|
||||
- hardware
|
||||
- network
|
||||
|
||||
tasks:
|
||||
- name: Verify the expected host identity and operating system
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ansible_facts["hostname"] == "ghostnet-ki"
|
||||
- ansible_facts["distribution"] == "Debian"
|
||||
- ansible_facts["distribution_major_version"] == "13"
|
||||
- ansible_facts["default_ipv4"]["address"] == "10.0.0.90"
|
||||
- ansible_facts["python"]["version"]["major"] >= 3
|
||||
fail_msg: >-
|
||||
The connected host does not match the documented Ghostnet AI server
|
||||
baseline. Stop before using any deployment playbook.
|
||||
success_msg: The expected Debian 13 host ghostnet-ki is connected.
|
||||
|
||||
- name: Read the expected AMD GPU PCI devices
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- lspci
|
||||
- -D
|
||||
- -nn
|
||||
- -d
|
||||
- "1002:73bf"
|
||||
check_mode: false
|
||||
changed_when: false
|
||||
register: preflight_gpu_devices
|
||||
|
||||
- name: Verify both documented AMD GPUs are present
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- preflight_gpu_devices.stdout_lines | length == 2
|
||||
fail_msg: >-
|
||||
Expected two AMD Navi 21 PCI devices with ID 1002:73bf, but found
|
||||
{{ preflight_gpu_devices.stdout_lines | length }}.
|
||||
success_msg: Both documented AMD Navi 21 PCI devices are present.
|
||||
|
||||
- name: Report the verified read-only baseline
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
hostname: "{{ ansible_facts['hostname'] }}"
|
||||
operating_system: >-
|
||||
{{ ansible_facts['distribution'] }}
|
||||
{{ ansible_facts['distribution_version'] }}
|
||||
default_ipv4: "{{ ansible_facts['default_ipv4']['address'] }}"
|
||||
processor_vcpus: "{{ ansible_facts['processor_vcpus'] }}"
|
||||
memory_mib: "{{ ansible_facts['memtotal_mb'] }}"
|
||||
python_version: >-
|
||||
{{ ansible_facts['python']['version']['major'] }}.
|
||||
{{- ansible_facts['python']['version']['minor'] }}.
|
||||
{{- ansible_facts['python']['version']['micro'] }}
|
||||
amd_gpu_devices: "{{ preflight_gpu_devices.stdout_lines }}"
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
- name: Verify the dedicated Ansible deployment account
|
||||
hosts: llm_servers
|
||||
become: false
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
- name: Verify Ansible module execution without privilege escalation
|
||||
ansible.builtin.ping:
|
||||
|
||||
- name: Read the SSH account identity
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /usr/bin/id
|
||||
- -un
|
||||
changed_when: false
|
||||
register: deploy_account_identity
|
||||
|
||||
- name: Verify the expected SSH account
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- deploy_account_identity.stdout == "ansible-deploy"
|
||||
fail_msg: The SSH connection did not use the ansible-deploy account.
|
||||
|
||||
- name: Read the effective identity through sudo
|
||||
become: true
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /usr/bin/id
|
||||
- -u
|
||||
changed_when: false
|
||||
register: deploy_root_identity
|
||||
|
||||
- name: Verify non-interactive root escalation
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- deploy_root_identity.stdout == "0"
|
||||
fail_msg: Non-interactive sudo did not produce effective UID 0.
|
||||
success_msg: >-
|
||||
The dedicated deployment account can connect and escalate privileges
|
||||
non-interactively.
|
||||
@@ -0,0 +1,2 @@
|
||||
# ansible-core 2.21 supports Python 3.12 through 3.14 on the control node.
|
||||
ansible-core==2.21.4
|
||||
@@ -0,0 +1,17 @@
|
||||
# Shared host baseline
|
||||
|
||||
The `host_baseline` role manages state shared by the Production and Testing
|
||||
environments on `ghostnet-ki`.
|
||||
|
||||
Its first version deliberately has a narrow scope:
|
||||
|
||||
- refuse changes when hostname, Debian major version or primary IPv4 address
|
||||
do not match the declared target;
|
||||
- require at least 100 GiB free on the root filesystem; and
|
||||
- create root-owned configuration and persistent-data directory roots for both
|
||||
environments and future shared data.
|
||||
|
||||
It does not install packages, drivers, Podman or applications and does not
|
||||
start or stop services. Ownership below the directory roots will be assigned
|
||||
to dedicated service accounts when the corresponding application roles are
|
||||
implemented.
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
host_baseline_expected_hostname: ghostnet-ki
|
||||
host_baseline_expected_distribution: Debian
|
||||
host_baseline_expected_distribution_major_version: "13"
|
||||
host_baseline_expected_ipv4_address: 10.0.0.90
|
||||
|
||||
# Preserve enough headroom for host operation and controlled model handling.
|
||||
host_baseline_minimum_root_free_gib: 100
|
||||
|
||||
host_baseline_directories:
|
||||
- path: /etc/ghostnet-ai
|
||||
mode: "0755"
|
||||
- path: /etc/ghostnet-ai/production
|
||||
mode: "0750"
|
||||
- path: /etc/ghostnet-ai/testing
|
||||
mode: "0750"
|
||||
- path: /var/lib/ghostnet-ai
|
||||
mode: "0755"
|
||||
- path: /var/lib/ghostnet-ai/production
|
||||
mode: "0750"
|
||||
- path: /var/lib/ghostnet-ai/testing
|
||||
mode: "0750"
|
||||
- path: /var/lib/ghostnet-ai/shared
|
||||
mode: "0755"
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
- name: Verify the target host before changing shared state
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ansible_facts["hostname"] == host_baseline_expected_hostname
|
||||
- ansible_facts["distribution"] == host_baseline_expected_distribution
|
||||
- >-
|
||||
ansible_facts["distribution_major_version"] ==
|
||||
host_baseline_expected_distribution_major_version
|
||||
- >-
|
||||
ansible_facts["default_ipv4"]["address"] ==
|
||||
host_baseline_expected_ipv4_address
|
||||
fail_msg: >-
|
||||
The connected host does not match the declared Ghostnet AI host. No
|
||||
baseline changes may be applied.
|
||||
success_msg: The expected Debian host ghostnet-ki is connected.
|
||||
|
||||
- name: Select the root filesystem facts
|
||||
ansible.builtin.set_fact:
|
||||
host_baseline_root_mount: >-
|
||||
{{ (ansible_facts["mounts"]
|
||||
| selectattr("mount", "equalto", "/")
|
||||
| list
|
||||
| first)
|
||||
| default({}) }}
|
||||
|
||||
- name: Verify the root filesystem was discovered
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- host_baseline_root_mount | length > 0
|
||||
- host_baseline_root_mount["size_available"] is defined
|
||||
fail_msg: Root filesystem capacity could not be determined. No changes were made.
|
||||
|
||||
- name: Verify minimum free capacity on the shared root filesystem
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- >-
|
||||
host_baseline_root_mount["size_available"] | int >=
|
||||
host_baseline_minimum_root_free_gib | int * 1024 ** 3
|
||||
fail_msg: >-
|
||||
The root filesystem has only
|
||||
{{ (host_baseline_root_mount["size_available"] | int / 1024 ** 3) | round(1) }}
|
||||
GiB free. At least {{ host_baseline_minimum_root_free_gib }} GiB are
|
||||
required before applying the shared host baseline.
|
||||
success_msg: >-
|
||||
The root filesystem has
|
||||
{{ (host_baseline_root_mount["size_available"] | int / 1024 ** 3) | round(1) }}
|
||||
GiB free.
|
||||
|
||||
- name: Create the shared configuration and data directory roots
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.path }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "{{ item.mode }}"
|
||||
loop: "{{ host_baseline_directories }}"
|
||||
loop_control:
|
||||
label: "{{ item.path }}"
|
||||
@@ -17,6 +17,8 @@
|
||||
| [GNAI-006-SEC_VPN_Clients_Windows11.md](GNAI-006-SEC_VPN_Clients_Windows11.md) | offizieller Windows-Client, Import und Verwendung; Junior IT |
|
||||
| [GNAI-006-SEC_VPN_Clients_Linux.md](GNAI-006-SEC_VPN_Clients_Linux.md) | Debian, Arch/CachyOS, Fedora/Kinoite; Junior IT |
|
||||
| [GNAI-006-SEC_VPN_Implementation-Log.md](GNAI-006-SEC_VPN_Implementation-Log.md) | historische v14/v15-Umsetzung und Evidenzkorrekturen; Intermediate IT |
|
||||
| [GNAI-007-PLAN_Ansible-Deployment.md](GNAI-007-PLAN_Ansible-Deployment.md) | nicht finaler Arbeitsentwurf zum Ansible-/Gitea-Deployment und zur geplanten Folgedokumentation; Infrastrukturadministratoren und Mitwirkende |
|
||||
| [Ansible-Control-Projekt](../ansible/README.md) | Control-Node, Preflight, Deployment-Account und gemeinsame Host-Baseline; Intermediate IT |
|
||||
|
||||
## 2. Verbindliche, bestätigte Entscheidungen
|
||||
|
||||
@@ -32,6 +34,14 @@ Die getrennten Schlüsselzugänge für `ghost` und `ghost-agent` wurden am 19.09
|
||||
|
||||
Der KI-Server ist laut Betreiber frisch eingerichtet. Die LLM-Anwendungen sollen erst in einem späteren Sprint mit Ansible installiert werden. Bis dahin beschreiben GNAI-003 und GNAI-004 für diesen Host ein Zielbild; der lesende SSH-Abgleich betraf nur Basisbetrieb und Zugänge.
|
||||
|
||||
Der [Ansible-Deployment-Entwurf](GNAI-007-PLAN_Ansible-Deployment.md) persistiert den Planungsstand vom 19.09.2026. Er ist ausdrücklich keine verbindliche Spezifikation und kein Nachweis einer eingerichteten Deploymentkette. Die dort genannten Runner-, Account-, Freigabe- und Secret-Verfahren sind bis zu ihrer Prüfung und formalen Übernahme vorläufig.
|
||||
|
||||
Das erste Ansible-Control-Projekt mit Testing-Inventar und lesendem Preflight wurde am 19.09.2026 angelegt. Der Live-Lauf über `ghost-agent` bestätigte Hostidentität, Debian 13.6, IPv4-Adresse, Python, CPU, RAM und zwei Navi-21-Geräte mit `changed=0`, `unreachable=0` und `failed=0`. Dieser Nachweis umfasst weder privilegierten Zugriff noch eine eingerichtete Production-/Testing-Laufzeitumgebung.
|
||||
|
||||
Der separate Ed25519-Schlüssel mit Fingerprint `SHA256:IKEInbIo8VVI1USg5+a3ypGK8H8FweJiQu+v7KvhQDU` und das Bootstrap-Playbook wurden am 19.09.2026 verwendet, um `ansible-deploy` einzurichten. Der private Schlüssel liegt ausschließlich außerhalb des Repositories. Der Account besitzt ein gesperrtes Passwort, einen auf LAN- und WireGuard-Quellnetze beschränkten Public Key und eine mit `visudo` validierte Regel `NOPASSWD: ALL`. Ein getrenntes Prüfplaybook bestätigte anschließend Anmeldung, Modulausführung und effektive UID 0 über sudo mit `ok=5 changed=0 unreachable=0 failed=0`.
|
||||
|
||||
Die erste schreibende Rolle `host_baseline` wurde am 19.09.2026 nach erfolgreichem Syntax- und Check-Modus-Lauf angewendet. Sie prüft Hostname, Debian-Hauptversion, Standard-IPv4 sowie mindestens 100 GiB freien Speicher und verwaltet ausschließlich `/etc/ghostnet-ai/{production,testing}` und `/var/lib/ghostnet-ai/{production,testing,shared}` mit Root-Eigentum. Der erste reale Lauf meldete `ok=6 changed=1 unreachable=0 failed=0`; der unmittelbar folgende identische Lauf belegte Idempotenz mit `ok=6 changed=0 unreachable=0 failed=0`. Pakete, Dienste und Anwendungen wurden dabei nicht verändert.
|
||||
|
||||
## 3. Sicherheitsbefunde mit Priorität
|
||||
|
||||
| Befund | dokumentiertes Verfahren / Status |
|
||||
@@ -59,4 +69,10 @@ Bei der Dokumentationskonsolidierung wurden keine Serverkonfigurationen geänder
|
||||
|
||||
Am 18.09.2026 wurden WireGuard-LXC und KI-Server ergänzend lesend per SSH geprüft. Der KI-Server-Teilabgleich bestätigt Hostname, Konten, Netzkonfiguration, Debian-Version, CPU, RAM, zwei Navi-21-GPUs und SSD-Partitionierung; BIOS, ROCm und die Anwendungsdienste wurden dabei nicht abgenommen. Details stehen in [GNAI-001](GNAI-001-INFRA_BaseSystem.md) und [GNAI-002](GNAI-002-INFRA_Users.md).
|
||||
|
||||
Am 19.09.2026 bestätigte ein erster reproduzierbarer Ansible-Preflight über das unprivilegierte Konto `ghost-agent` erneut `ghostnet-ki`, Debian 13.6, `10.0.0.90`, 14 vCPUs, 64.123 MiB RAM, Remote-Python 3.13.5 und zwei PCI-Geräte `1002:73bf`. Der Play Recap lautete `ok=5 changed=0 unreachable=0 failed=0`. Das Playbook verwendete kein `sudo` und nahm keine Serverkonfiguration vor.
|
||||
|
||||
Beim anschließenden Bootstrap wurden Benutzer, Home-/SSH-Verzeichnisse, `authorized_keys` und sudoers-Datei erfolgreich angelegt. Nur die letzte Assertion des ersten Laufs schlug wegen einer als Mapping interpretierten YAML-Bedingung fehl; die Bedingung wurde anschließend als String korrigiert. Da alle schreibenden Tasks vorher erfolgreich waren, bestätigte das unabhängige Prüfplaybook den wirksamen Endzustand ohne weitere Änderung. Ein vollständiger zweiter Bootstrap-Lauf zur Idempotenzprüfung steht noch aus.
|
||||
|
||||
Für die gemeinsame Host-Baseline bestätigte ein vorgelagerter Check-Modus genau sieben neu anzulegende Verzeichnisse und rund 1.677,7 GiB freien Platz auf `/`. Der anschließende reale Lauf legte die Verzeichniswurzeln an. Eine sofortige Wiederholung ergab `changed=0`; die Rolle konvergiert damit für ihren derzeitigen Umfang. Dieser Nachweis umfasst keine Paket-, Runtime-, GPU- oder Anwendungsinstallation.
|
||||
|
||||
Die WireGuard-Evidenz bleibt der Teiltest vom 12.09.2026: CachyOS-Laptop von WAN, Handshake, Ping auf Gateway/Proxmox und direkter IPv4-Internetpfad bestanden; KI-Server war ausgeschaltet. Neue Rollen, weitere Clientvarianten, Gateway-GUI-Zugriff, WAN-/IPv6-Isolation, Neustart und Restore sind noch nicht abgenommen. GNAI-006 ist erst nach Klärung der offenen Werte, Umsetzung und dokumentierter Abnahme technisch abgeschlossen.
|
||||
|
||||
@@ -0,0 +1,711 @@
|
||||
# Arbeitsentwurf
|
||||
|
||||
**Dokumenten-ID:** GNAI-007-PLAN
|
||||
**Titel:** Ansible-Deployment für den KI-Server – Planungsstand
|
||||
**Stand:** 2026-09-19
|
||||
**Status:** Nicht finaler Arbeitsentwurf zur Persistierung des aktuellen Planungsstands
|
||||
**Referenzen:** [Projektstand](GNAI-000-DOC_Index.md), [Zielarchitektur](GNAI-003-INFRA_Architecture.md), [Zugriffskonzept](GNAI-004-SEC_Access-Control.md)
|
||||
|
||||
> **Abgrenzung:** Dieses Dokument hält die Ergebnisse der Planungssession vom
|
||||
> 19.09.2026 fest. Es ist weder eine freigegebene technische Spezifikation noch
|
||||
> eine Installationsanleitung oder ein Nachweis über bereits umgesetzte
|
||||
> Komponenten. Entscheidungen, die noch geprüft oder bestätigt werden müssen,
|
||||
> sind ausdrücklich als vorläufig oder offen gekennzeichnet.
|
||||
|
||||
---
|
||||
|
||||
## Änderungsverlauf
|
||||
|
||||
| Datum | Version | Autor | Beschreibung |
|
||||
| --- | --- | --- | --- |
|
||||
| 2026-09-19 | 0.1 | spektr / Codex | Planungsstand zu Ansible, Gitea Actions, Runner, Freigaben und künftiger Dokumentation aus der Session festgehalten |
|
||||
| 2026-09-19 | 0.2 | spektr / Codex | vorläufige Ein-Repository-Strategie sowie logisch getrennte Production- und Testing-Umgebungen auf dem KI-Server ergänzt |
|
||||
| 2026-09-19 | 0.3 | spektr / Codex | exklusiven zeitlich gestaffelten Betrieb von Production und Testing ohne physische Ressourcenaufteilung als Planungsentscheidung übernommen |
|
||||
| 2026-09-19 | 0.4 | spektr / Codex | erstes Ansible-Control-Projekt und erfolgreich ausgeführten lesenden Live-Preflight als belegten Implementierungsstand ergänzt |
|
||||
| 2026-09-19 | 0.5 | spektr / Codex | technischen Schlüssel sowie Bootstrap- und Prüfplaybook für `ansible-deploy` vorbereitet; Ausführung bleibt interaktiv offen |
|
||||
| 2026-09-19 | 0.6 | spektr / Codex | `ansible-deploy` auf dem KI-Server eingerichtet und SSH-/sudo-Zugriff unabhängig erfolgreich verifiziert |
|
||||
| 2026-09-19 | 0.7 | spektr / Codex | minimale gemeinsame Host-Baseline mit Schutzprüfungen und getrennten Verzeichniswurzeln implementiert, angewendet und auf Idempotenz geprüft |
|
||||
|
||||
---
|
||||
|
||||
## 1. Zweck
|
||||
|
||||
Der KI-Server `ghostnet-ki` soll reproduzierbar mit Ansible aufgebaut und
|
||||
aktualisiert werden. Mehrere berechtigte Personen sollen Änderungen gemeinsam
|
||||
über Gitea entwickeln, prüfen, freigeben und nachvollziehbar ausrollen können.
|
||||
|
||||
Gesucht wird ein Verfahren, das:
|
||||
|
||||
- den vollständigen Sollzustand des Servers versioniert,
|
||||
- wiederholbare und möglichst idempotente Deployments ermöglicht,
|
||||
- Änderungen vor der Anwendung prüft,
|
||||
- einen nachvollziehbaren Freigabeweg für mehrere Mitwirkende bietet,
|
||||
- Geheimnisse und privilegierte Zugänge vom Quelltext trennt,
|
||||
- Fehler und Rollbacks betrieblich beherrschbar macht und
|
||||
- für einen einzelnen KI-Server ohne unnötig große Managementplattform
|
||||
betrieben werden kann.
|
||||
|
||||
Die Planung umfasst sowohl die spätere technische Implementierung als auch die
|
||||
Dokumentation für Administratoren, Entwickler und Deployment-Berechtigte.
|
||||
|
||||
---
|
||||
|
||||
## 2. Ausgangslage
|
||||
|
||||
Nach dem dokumentierten Stand ist der KI-Server ein Debian-Basissystem unter
|
||||
`10.0.0.90`. Der geplante Anwendungsstack aus `llama.cpp`, LiteLLM, OpenWebUI
|
||||
und weiteren Anwendungen ist noch nicht installiert. Gitea ist im bestehenden
|
||||
Serviceinventar unter `10.0.0.17:3000` geführt; Version, Actions-Konfiguration
|
||||
und Registry-Funktionen müssen vor der Implementierung live geprüft werden.
|
||||
|
||||
Der bestehende Account `ghost-agent` ist für lesende Prüfungen vorgesehen und
|
||||
besitzt nach dem letzten Abgleich keine `sudo`-Berechtigung. Er soll nach dem
|
||||
aktuellen Plan nicht zum privilegierten Deployment-Account erweitert werden.
|
||||
|
||||
Das Repository enthält inzwischen ein minimales Ansible-Control-Projekt mit
|
||||
Testing-Inventar, einem ausschließlich lesenden Preflight sowie Bootstrap- und
|
||||
Prüfplaybooks für `ansible-deploy`. Der privilegierte Deployment-Account ist
|
||||
eingerichtet und verifiziert. Eine erste schreibende Rolle verwaltet die
|
||||
gemeinsamen Konfigurations- und Datenwurzeln des Hosts; Anwendungsrollen und ein
|
||||
Production-Inventar existieren noch nicht. Der Preflight selbst nahm keine
|
||||
Änderungen vor; Bootstrap und Host-Baseline blieben auf die nachfolgend
|
||||
dokumentierten Zugangs- und Verzeichniszustände begrenzt.
|
||||
|
||||
---
|
||||
|
||||
## 3. Vorläufiges Zielbild
|
||||
|
||||
Ansible bleibt die einzige technische Quelle für den gewünschten Zustand des
|
||||
KI-Servers. Gitea verwaltet Quelltext, Reviews, Freigaben und Jobprotokolle.
|
||||
Getrennte Runner- beziehungsweise Berechtigungsstufen führen CI-Prüfungen,
|
||||
Testing-Deployments und Production-Deployments aus. Die genaue Anzahl der
|
||||
Runner wird nach Prüfung der Gitea-Berechtigungsgrenzen festgelegt.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
DEV[Feature-Branch] --> PR[Pull Request]
|
||||
PR --> CI[statische CI ohne Serverzugang]
|
||||
CI --> TESTOK[freigegebener Testing-Lauf]
|
||||
TESTOK --> TESTRUNNER[Testing-Berechtigung]
|
||||
TESTRUNNER --> TEST[Testing auf ghostnet-ki]
|
||||
TEST --> REVIEW[Review und Merge nach main]
|
||||
REVIEW --> RELEASE[bewusste Production-Freigabe]
|
||||
RELEASE --> PRODRUNNER[Production-Berechtigung]
|
||||
PRODRUNNER --> PROD[Production auf ghostnet-ki]
|
||||
PROD --> VERIFY[Health- und Funktionstests]
|
||||
|
||||
UPSTREAM[versionierte Upstream-Artefakte] --> UPDATE[Versions-Pin im Infrastruktur-Repository]
|
||||
UPDATE --> PR
|
||||
```
|
||||
|
||||
Runner sollen keine eingehenden Verbindungen aus dem Internet benötigen. Sie
|
||||
holen Jobs bei Gitea ab und benötigen ausgehend nur die für Gitea, Paket- oder
|
||||
Imagequellen und den jeweiligen SSH-Zugriff zum KI-Server erforderlichen
|
||||
Verbindungen. CI-Jobs erhalten keinen SSH-Zugriff zum KI-Server.
|
||||
|
||||
---
|
||||
|
||||
## 4. Ergebnisse der bisherigen Planung
|
||||
|
||||
Die folgende Tabelle unterscheidet bewusst zwischen einer gewählten
|
||||
Planungsrichtung und noch nicht bestätigten Implementierungsdetails.
|
||||
|
||||
| Thema | Aktueller Planungsstand | Verbindlichkeit / Nachweis |
|
||||
| --- | --- | --- |
|
||||
| Konfigurationsmanagement | Ansible beschreibt Basissystem, Laufzeitumgebung, Dienste und Konfiguration | minimales Control-Projekt, lesender Preflight und angewendete erste `host_baseline` vorhanden; Runtime- und Anwendungsrollen fehlen noch |
|
||||
| Quell- und Freigabeverfahren | Änderungen über Gitea-Branches und Pull Requests; kein regulärer Direkt-Push auf `main` | vorgesehen; Branch-Schutz noch nicht geprüft oder eingerichtet |
|
||||
| CI | Pull Requests erhalten Lint-, YAML- und Ansible-Syntaxprüfungen ohne produktive Zugangsdaten | vorgesehen; Workflow noch nicht vorhanden |
|
||||
| Produktionsausführung | Gitea Actions startet Ansible über eine dedizierte Production-Berechtigungsstufe | bevorzugte Lösung; Gitea-Version, Runner-Trennung und Actions-Funktion noch zu prüfen |
|
||||
| Runner-Platzierung | eigener kleiner LXC, getrennt von Gitea und KI-Server | bevorzugt; Host, Adresse und Ressourcen noch offen |
|
||||
| Job-Isolation | containerisierte Jobs; für den Gitea Runner ist nach aktueller Herstellerdokumentation Docker der unterstützte Standard | bevorzugt; lokale Freigabe und LXC-Eignung noch offen |
|
||||
| Parallelität | Produktionsrunner führt höchstens einen Job gleichzeitig aus | vorgesehen; Runner-Konfiguration noch nicht vorhanden |
|
||||
| Deployment-Freigabe | nach erfolgreichem Review bewusste Freigabe über geschützten `deploy-*`-Tag | bevorzugte Startvariante; Namensschema und berechtigte Teams noch offen |
|
||||
| Zielzugang | eigener technischer SSH-Account `ansible-deploy` mit separatem, quellnetzbeschränktem Schlüssel und validiertem `NOPASSWD: ALL` | auf dem KI-Server eingerichtet und unabhängig verifiziert; spätere Runner-Übernahme und Rotation offen |
|
||||
| Geheimnisse | verschlüsselte Ansible-Variablen plus getrennt gespeichertes Vault-Passwort und Runner-Zugangsdaten | Richtung angenommen; konkreter Secret-Workflow noch offen |
|
||||
| Artefaktversionen | Pakete, Collections und Container-Images werden auf konkrete Versionen, nach Möglichkeit Digests, festgelegt | vorgesehen |
|
||||
| Repository-Zuschnitt | vorerst ausschließlich das bestehende Repository `infrastructure`; weitere Quellcode-Repositories erst bei tatsächlich eigenständig entwickelten Anwendungen oder MCP-Komponenten | als aktuelle Planungsentscheidung angenommen |
|
||||
| Anwendungsupdates | Upstream-Artefakte werden im Infrastruktur-Repository auf eine geprüfte Version festgelegt; eigene Build-Repositories sind vorerst nicht Bestandteil des Projekts | vorgesehen; Registry- und Buildverfahren bei späterem Eigenentwicklungsbedarf neu bewerten |
|
||||
| Automatische Updates | kein unbeaufsichtigtes Produktionsupdate über `latest` oder Watchtower | vorläufige Sicherheitsentscheidung |
|
||||
| Laufzeitumgebungen | Production und Testing werden als getrennte Stacks auf demselben physischen KI-Server betrieben | als aktuelle Planungsentscheidung angenommen |
|
||||
| Production | stabiler, extern nutzbarer Stand mit bewusst freigegebenen Versionen | Zielbild; Dienste noch nicht eingerichtet |
|
||||
| Testing | interner, kurzlebiger oder zurücksetzbarer Stand für kontrollierte Tests vor dem Merge nach `main`; nutzt während eines Wartungsfensters die vollständige Hardware exklusiv | Zielbild; Dienste, Zugriffsweg und Umschaltverfahren noch nicht eingerichtet |
|
||||
| Ressourcenbetrieb | keine feste physische Aufteilung; Production und Testing laufen nicht gleichzeitig und werden über einen exklusiven Umschaltvorgang zeitlich gestaffelt | als aktuelle Planungsentscheidung angenommen |
|
||||
| Betriebsnachweis | erfolgreicher Lauf dokumentiert Git-Commit, Zielhost, Zeitpunkt, Prüfungen und Ergebnis | vorgesehen |
|
||||
| Rollback | Konfigurationsänderung über Revert und erneute Freigabe; persistente Daten zusätzlich über getestete Backups wiederherstellen | Grundprinzip; konkrete SOP noch zu erstellen |
|
||||
|
||||
Eine spätere Umstellung auf ein automatisches Deployment nach jedem Merge in
|
||||
`main` bleibt möglich. Zu Beginn soll die Freigabe jedoch bewusst und
|
||||
nachvollziehbar erfolgen, während die technische Ausführung vollständig
|
||||
automatisiert wird.
|
||||
|
||||
---
|
||||
|
||||
## 5. Verworfene oder zurückgestellte Varianten
|
||||
|
||||
### 5.1 Ausschließliche Ausführung vom Admin-PC
|
||||
|
||||
Lokale Ansible-Ausführung bleibt für Entwicklung, Diagnose und Notfälle
|
||||
sinnvoll. Sie soll nicht der reguläre Produktionsweg werden, weil Ausführung,
|
||||
Werkzeugversionen und Protokolle sonst von einem einzelnen Arbeitsplatz
|
||||
abhängen.
|
||||
|
||||
### 5.2 `ansible-pull` auf dem KI-Server
|
||||
|
||||
Der Zielserver könnte das Repository periodisch selbst abrufen und lokal
|
||||
anwenden. Für den einzelnen zentral erreichbaren Server bietet das derzeit
|
||||
weniger klare Freigaben und eine ungünstigere Trennung zwischen Zielsystem und
|
||||
Deployment-Steuerung. Die Variante bleibt zurückgestellt.
|
||||
|
||||
### 5.3 AWX
|
||||
|
||||
AWX bietet eine eigene Oberfläche, Rollen, Zeitpläne und Inventarverwaltung.
|
||||
Der zusätzliche Plattformbetrieb erscheint für den aktuellen Umfang zu groß.
|
||||
AWX kann erneut bewertet werden, wenn mehrere Zielsysteme, feinere delegierte
|
||||
Rechte oder umfangreiche Jobpläne verwaltet werden müssen.
|
||||
|
||||
---
|
||||
|
||||
## 6. Repository-Verantwortung
|
||||
|
||||
Vorerst wird ausschließlich das bestehende Repository `infrastructure`
|
||||
verwendet. Es soll den vollständigen Sollzustand des KI-Servers, beide
|
||||
Laufzeitumgebungen und die zugehörigen Workflows besitzen. Damit verändern
|
||||
nicht mehrere unabhängige Deployment-Repositories denselben Host.
|
||||
|
||||
Vorgesehene grobe Struktur:
|
||||
|
||||
```text
|
||||
ansible/
|
||||
├── ansible.cfg
|
||||
├── collections/
|
||||
│ └── requirements.yml
|
||||
├── inventories/
|
||||
│ ├── production/
|
||||
│ └── testing/
|
||||
├── playbooks/
|
||||
│ ├── host.yml
|
||||
│ ├── environment.yml
|
||||
│ └── verify.yml
|
||||
└── roles/
|
||||
├── host_baseline/
|
||||
├── environment_stack/
|
||||
└── weitere_fachrollen/
|
||||
|
||||
.gitea/workflows/
|
||||
├── validate.yml
|
||||
├── deploy-testing.yml
|
||||
└── deploy-production.yml
|
||||
```
|
||||
|
||||
Diese Struktur ist ein Ausgangspunkt und noch keine Festlegung aller Rollen.
|
||||
`host.yml` und `environment.yml` sollen gemeinsam den unterstützten
|
||||
Gesamtzustand herstellen. Ansible-Tags können Diagnose und gezielte Wartung
|
||||
unterstützen, dürfen aber nicht zu ungetesteten alternativen Installationswegen
|
||||
führen.
|
||||
|
||||
Unveränderte Drittanbieterkomponenten wie `llama.cpp`, LiteLLM oder OpenWebUI
|
||||
erhalten derzeit kein eigenes Repository. Ihre freigegebenen Versionen oder
|
||||
Image-Digests werden im Infrastruktur-Repository geführt. Ein weiteres
|
||||
Repository wird erst angelegt, wenn tatsächlich eigener, unabhängig
|
||||
versionierter Quellcode für eine Anwendung, einen MCP Router oder MCP Server
|
||||
entwickelt wird. Diese spätere Erweiterung ändert nichts daran, dass das
|
||||
Infrastruktur-Repository allein festlegt, welche Artefaktversion in welcher
|
||||
Umgebung läuft.
|
||||
|
||||
---
|
||||
|
||||
## 7. Production und Testing auf einem Host
|
||||
|
||||
### 7.1 Geltungsbereich der Trennung
|
||||
|
||||
Production und Testing laufen zunächst auf demselben physischen KI-Server.
|
||||
Damit können Anwendungscontainer, Konfigurationen und Daten sauber logisch
|
||||
getrennt werden. Die Hardware kann wegen des hohen Leistungsbedarfs nicht
|
||||
dauerhaft zwischen beiden Umgebungen aufgeteilt werden. Stattdessen erhält die
|
||||
jeweils aktive Umgebung CPU, RAM und beide GPUs exklusiv. Es entstehen dennoch
|
||||
keine getrennten Fehlerdomänen:
|
||||
|
||||
- Kernel, AMD-Treiber beziehungsweise ROCm, Podman und systemd werden gemeinsam
|
||||
verwendet;
|
||||
- ein Hostausfall betrifft beide Umgebungen;
|
||||
- ein fehlerhafter Testing-Stand kann den gemeinsamen Host beeinträchtigen und
|
||||
dadurch den Wiederanlauf von Production verzögern;
|
||||
- Kernel-, Treiber- und Host-Runtime-Updates können nicht realitätsnah in
|
||||
Testing geprüft werden, ohne den gemeinsamen Host zu verändern.
|
||||
|
||||
Testing ist deshalb für neue Anwendungsstände, Konfigurationen, MCP-Komponenten
|
||||
und Integrationen vorgesehen. Für risikoarme Vorabtests von Kernel-, Treiber-
|
||||
oder Podman-Upgrades wäre später ein zweiter Host oder eine ausreichend
|
||||
vergleichbare separate Maschine erforderlich.
|
||||
|
||||
Die von externen Nutzern erwartete Stabilität von Production gilt außerhalb
|
||||
angekündigter Test- und Wartungsfenster. Solange nur dieser eine ausgelastete
|
||||
Host verfügbar ist, verursachen realistische Tests mit voller Hardware eine
|
||||
geplante Unterbrechung von Production.
|
||||
|
||||
### 7.2 Isolationsziele
|
||||
|
||||
| Bereich | Production | Testing |
|
||||
| --- | --- | --- |
|
||||
| Erreichbarkeit | für freigegebene Dienste über NPM; Backend- und Managementports bleiben intern | nur aus freigegebenem LAN/VPN; keine öffentliche NPM-Route |
|
||||
| Container- und Unit-Namen | eigener eindeutiger Präfix | eigener eindeutiger Präfix |
|
||||
| Podman-Netz | eigenes internes Netz | eigenes internes Netz ohne Verbindung zum Production-Netz |
|
||||
| Konfiguration | eigener Verzeichnisbaum | eigener Verzeichnisbaum |
|
||||
| Persistente Daten | eigene Volumes mit Backup- und Restore-Verfahren | eigene zurücksetzbare Volumes; keine Production-Datenkopie ohne Bereinigung und Freigabe |
|
||||
| Secrets und API-Keys | ausschließlich Production-Credentials | eigene eingeschränkte Test-Credentials |
|
||||
| Ports und Hostnamen | stabile freigegebene Endpunkte | getrennte interne Ports und noch festzulegende Testhostnamen |
|
||||
| Ressourcen | erhält im Normalbetrieb die vollständige Hardware exklusiv | erhält ausschließlich im freigegebenen Testfenster die vollständige Hardware exklusiv |
|
||||
| GPU | beide GPUs, solange Production aktiv ist | beide GPUs, nachdem Production vollständig beendet wurde |
|
||||
| Ausführungszustand | standardmäßig aktiv | standardmäßig gestoppt |
|
||||
| Verfügbarkeit | stabiler Betriebsstand außerhalb angekündigter Wartungsfenster | best effort; wird nach jedem Testlauf wieder vollständig gestoppt |
|
||||
|
||||
Eine gleichzeitige Inferenzlast beider Umgebungen ist nicht vorgesehen. Vor dem
|
||||
Start von Testing muss nachweisbar sein, dass Production beendet wurde und
|
||||
keine zugehörigen Prozesse mehr CPU-, RAM- oder GPU-Ressourcen halten. Vor dem
|
||||
Wiederanlauf von Production muss Testing entsprechend vollständig beendet und
|
||||
sein temporärer Zustand bereinigt sein. Speicherplatz- und Prozessgrenzen
|
||||
bleiben auch bei exklusiver Rechenleistung sinnvoll, damit ein Test nicht den
|
||||
gemeinsamen Datenträger füllt oder den Host dauerhaft beeinträchtigt.
|
||||
|
||||
Große schreibgeschützte Modelldateien können später aus einem gemeinsamen,
|
||||
nur lesbar eingebundenen Modellspeicher bereitgestellt werden. Datenbanken,
|
||||
Uploads, Caches mit Schreibzugriff, Benutzerkonten und Anwendungsschlüssel
|
||||
bleiben vollständig getrennt. Ob ein gemeinsamer Modellspeicher technisch und
|
||||
betrieblich sinnvoll ist, wird erst mit dem Modell- und Backupkonzept
|
||||
entschieden.
|
||||
|
||||
### 7.3 Exklusives Umschaltverfahren
|
||||
|
||||
Ein Testing-Deployment ist zugleich ein geplantes Wartungsfenster für
|
||||
Production. Der vorläufige Ablauf lautet:
|
||||
|
||||
1. konkreten PR-Commit und Testumfang festlegen;
|
||||
2. Testing-Lauf durch eine berechtigte Person freigeben und Wartungsfenster
|
||||
dokumentieren;
|
||||
3. letzten funktionsfähigen Production-Stand und erforderlichen Backupstatus
|
||||
feststellen;
|
||||
4. neue externe Anfragen nach Möglichkeit kontrolliert abweisen und Production
|
||||
geordnet beenden;
|
||||
5. prüfen, dass alle Production-Units, -Container und GPU-Prozesse beendet
|
||||
sind;
|
||||
6. einen hostweiten exklusiven Deployment-Lock setzen;
|
||||
7. Testing für den festgelegten Commit ausrollen und mit der vollständigen
|
||||
Hardware starten;
|
||||
8. Health Checks und fachliche Tests ausführen und Ergebnisse dem Pull Request
|
||||
zuordnen;
|
||||
9. Testing unabhängig vom Testergebnis vollständig stoppen;
|
||||
10. prüfen, dass alle Testing-Units, -Container und GPU-Prozesse beendet sind;
|
||||
11. Production unverändert auf dem zuletzt freigegebenen Stand starten;
|
||||
12. Production-Health- und Inferenztests ausführen und erst danach das
|
||||
Wartungsfenster beenden.
|
||||
|
||||
Der Wiederanlauf von Production darf nicht ausschließlich von einem letzten
|
||||
Schritt im Gitea-Job abhängen. Runner-Abbruch, Netzwerkverlust oder ein
|
||||
fehlgeschlagener Test müssen durch einen hostseitigen Recovery-Mechanismus und
|
||||
eine dokumentierte manuelle SOP beherrscht werden. Die konkrete Umsetzung kann
|
||||
beispielsweise einen systemd-gesteuerten Umschaltdienst, einen exklusiven Lock,
|
||||
Timeouts und einen definierten Rückfall auf Production kombinieren; sie wird
|
||||
erst in der technischen Spezifikation festgelegt.
|
||||
|
||||
Der Testing-Deployment-Account soll Production nicht frei verwalten können. Er
|
||||
darf lediglich eine eng definierte hostseitige Umschaltoperation starten und
|
||||
deren Status abfragen. Der Umschaltdienst besitzt die notwendigen Rechte zum
|
||||
geordneten Stoppen und Wiederanlaufen der beiden Stacks, stellt dem Testing-Job
|
||||
aber weder Production-Secrets noch allgemeine Production-Verwaltungsrechte zur
|
||||
Verfügung.
|
||||
|
||||
Production und Testing dürfen niemals gleichzeitig als aktiv markiert sein.
|
||||
Jeder Start prüft den Gegenstatus und bricht bei einem widersprüchlichen oder
|
||||
unklaren Zustand ab.
|
||||
|
||||
### 7.4 Vorgesehene Ansible-Abbildung
|
||||
|
||||
Gemeinsame Hostaufgaben und umgebungsspezifische Anwendungsaufgaben werden
|
||||
getrennt:
|
||||
|
||||
```text
|
||||
ansible/
|
||||
├── inventories/
|
||||
│ ├── production/
|
||||
│ │ ├── hosts.yml
|
||||
│ │ ├── group_vars/
|
||||
│ │ └── host_vars/
|
||||
│ └── testing/
|
||||
│ ├── hosts.yml
|
||||
│ ├── group_vars/
|
||||
│ └── host_vars/
|
||||
├── playbooks/
|
||||
│ ├── host.yml
|
||||
│ ├── environment.yml
|
||||
│ └── verify.yml
|
||||
└── roles/
|
||||
├── host_baseline/
|
||||
└── environment_stack/
|
||||
```
|
||||
|
||||
Beide Inventare zeigen auf `ghostnet-ki`, enthalten aber ausschließlich die
|
||||
für ihre Umgebung zulässigen Variablen und Secrets. `host.yml` verwaltet
|
||||
gemeinsame Pakete, Treiber, Benutzer und Runtime-Komponenten einmalig.
|
||||
`environment.yml` muss genau eine explizit gewählte Umgebung verwalten und vor
|
||||
Änderungen prüfen, ob Namen, Pfade, Ports und Zielvariablen zur Auswahl passen.
|
||||
Hoständerungen dürfen nicht beiläufig durch einen Testing-Lauf erfolgen.
|
||||
|
||||
Für den ersten umgesetzten Baseline-Stand gelten getrennte Konfigurationsbäume
|
||||
unter `/etc/ghostnet-ai/{production,testing}` und getrennte persistente
|
||||
Datenbäume unter `/var/lib/ghostnet-ai/{production,testing}`. Der Pfad
|
||||
`/var/lib/ghostnet-ai/shared` ist für später ausdrücklich freigegebene
|
||||
gemeinsame Daten reserviert. Konkrete Unterverzeichnisse, Eigentümer sowie
|
||||
Unit- und Container-Namen werden mit den jeweiligen Fachrollen festgelegt.
|
||||
|
||||
---
|
||||
|
||||
## 8. Vorgesehener Workflow
|
||||
|
||||
### 8.1 Änderung und Prüfung
|
||||
|
||||
1. Repository klonen und dokumentierte lokale Ansible-Umgebung einrichten.
|
||||
2. Feature-Branch anlegen.
|
||||
3. Rollen, Variablen, Templates und zugehörige Dokumentation ändern.
|
||||
4. lokale Lint-, Syntax- und nach Möglichkeit Check-Mode-Prüfungen ausführen.
|
||||
5. Pull Request erstellen.
|
||||
6. automatisierte CI-Prüfungen ohne produktive SSH- oder Vault-Zugangsdaten
|
||||
ausführen.
|
||||
7. erste menschliche Prüfung und einen Testing-Lauf für den konkreten Commit
|
||||
freigeben;
|
||||
8. Testing-Ergebnis im Pull Request dokumentieren;
|
||||
9. fachliches Abschlussreview und Freigabe durch eine zweite berechtigte
|
||||
Person und
|
||||
10. nach erfolgreichem Statuscheck in den geschützten `main`-Branch mergen.
|
||||
|
||||
### 8.2 Testing-Deployment vor dem Merge
|
||||
|
||||
Ein Pull Request führt zunächst nur statische CI-Prüfungen in einer isolierten
|
||||
Jobumgebung ohne SSH-, Vault- oder Serverzugang aus. Nach einer ersten
|
||||
menschlichen Prüfung kann eine berechtigte Person einen kontrollierten
|
||||
Testing-Lauf für den konkreten Commit auslösen.
|
||||
|
||||
Der Testing-Lauf:
|
||||
|
||||
1. verwendet ausschließlich den Testing-Deployment-Account und
|
||||
Testing-Secrets;
|
||||
2. erhält erst nach einem freigegebenen Wartungsfenster Zugriff auf den
|
||||
Umschaltvorgang;
|
||||
3. fordert über die fest definierte hostseitige Umschaltoperation den
|
||||
kontrollierten Production-Stopp an und prüft den vollständigen Stillstand;
|
||||
4. darf im aktiven Testabschnitt nur Testing-Pfade, -Units, -Container und
|
||||
-Netze verwalten;
|
||||
5. zeichnet den getesteten Commit unverwechselbar auf;
|
||||
6. führt technische Health Checks und die für die Änderung notwendigen
|
||||
Funktionstests aus;
|
||||
7. veröffentlicht keine Testdienste im Internet;
|
||||
8. kann die Testing-Daten vor oder nach dem Lauf auf einen bekannten Stand
|
||||
zurücksetzen und
|
||||
9. muss Testing stoppen sowie den letzten freigegebenen Production-Stand
|
||||
wieder starten und prüfen, auch wenn der eigentliche Test fehlschlägt.
|
||||
|
||||
Beliebiger Pull-Request-Code darf nicht automatisch mit privilegierten
|
||||
Serverzugängen ausgeführt werden. Runner-Labels allein gelten nicht als
|
||||
ausreichende Berechtigungsgrenze. Vor der Implementierung ist zu prüfen, wie
|
||||
Gitea in der eingesetzten Version Secrets, interne Branches, Forks und manuell
|
||||
freigegebene Jobs behandelt. Production-Credentials werden keinem
|
||||
Pull-Request-Workflow bereitgestellt.
|
||||
|
||||
### 8.3 Production-Deployment
|
||||
|
||||
1. berechtigte Person erzeugt einen geschützten Deployment-Tag auf einem
|
||||
geprüften Commit;
|
||||
2. der repository-spezifische Runner übernimmt den Job mit einer Parallelität
|
||||
von eins;
|
||||
3. der Job prüft erneut Syntax und Abhängigkeiten;
|
||||
4. optionaler Check-Modus zeigt erwartete Änderungen, soweit die verwendeten
|
||||
Module ihn zuverlässig unterstützen;
|
||||
5. `environment.yml` wendet den Production-Sollzustand auf `ghostnet-ki` an;
|
||||
6. `verify.yml` prüft Dienste, interne Endpunkte, Containerzustand und später
|
||||
einen kleinen Inferenztest;
|
||||
7. der Lauf hält Commit, Imageversionen oder Digests sowie Prüfergebnis fest;
|
||||
8. erst nach allen Prüfungen gilt das Deployment als erfolgreich.
|
||||
|
||||
Ein Production-Deployment verwendet ausschließlich einen Commit aus dem
|
||||
geschützten `main`-Branch und eine eigene bewusste Freigabe. Ein erfolgreicher
|
||||
Testing-Lauf ist Voraussetzung für fachlich relevante Änderungen, ersetzt aber
|
||||
weder Review noch Production-Backup und Freigabe.
|
||||
|
||||
Check Mode ist nur eine zusätzliche Kontrolle. Module und Shell-Kommandos, die
|
||||
keine vollständige Simulation unterstützen, müssen durch gezielte Tests und
|
||||
idempotente Implementierung abgesichert werden.
|
||||
|
||||
---
|
||||
|
||||
## 9. Zugangsdaten und Sicherheitsgrenzen
|
||||
|
||||
Der Produktionsrunner verarbeitet privilegierte Zugangsdaten und muss daher
|
||||
wie ein Administrationssystem behandelt werden. Vorgesehen sind:
|
||||
|
||||
- repository-spezifische Runner-Registrierung und eindeutiges Runner-Label,
|
||||
- keine Ausführung fremder oder öffentlicher Repositories,
|
||||
- ein technischer Zielaccount getrennt von persönlichen Konten und
|
||||
`ghost-agent`,
|
||||
- getrennte technische Berechtigungen und Schlüssel für Production und
|
||||
Testing, soweit dies mit der gewählten Podman-/systemd-Verwaltung belastbar
|
||||
durchsetzbar ist,
|
||||
- ein eigener SSH-Schlüssel ausschließlich für das Deployment,
|
||||
- geprüfter und fest hinterlegter SSH-Host-Key,
|
||||
- keine Passwort- oder Root-Anmeldung per SSH,
|
||||
- möglichst eng begrenzter Netzwerkzugang des Runner-LXC,
|
||||
- keine Secrets in Repository, Jobausgaben oder diff-fähigen Templates,
|
||||
- `no_log` und deaktivierte Diffs für geheimnishaltige Tasks,
|
||||
- getrennte Aufbewahrung des Vault-Passworts im Runner und im administrativen
|
||||
Passwortmanager sowie
|
||||
- dokumentierte Rotation und Sperrung von Runner-, SSH- und Vault-Zugangsdaten.
|
||||
|
||||
Der Testing-Zugang darf keinen Zugriff auf Production-Secrets oder
|
||||
Production-Daten erhalten. Die Produktionsberechtigung darf nicht allein durch
|
||||
die Auswahl eines frei benennbaren Runner-Labels erreichbar sein. Das genaue
|
||||
Verfahren wird nach Prüfung der eingesetzten Gitea-Version festgelegt. Die
|
||||
notwendige zeitliche Umschaltung wird über eine eng definierte hostseitige
|
||||
Operation vermittelt und ist keine allgemeine Production-Berechtigung des
|
||||
Testing-Accounts.
|
||||
|
||||
Der Deployment-Account besitzt derzeit bewusst `NOPASSWD: ALL`, weil
|
||||
Paketverwaltung, systemd, Treiber und rootful Containerverwaltung weitreichende
|
||||
Rechte erfordern. Eine formal kleine Befehlsliste wäre nur sinnvoll, wenn sie
|
||||
den tatsächlichen Ansible-Betrieb nicht durch unsichere Umgehungen oder fragile
|
||||
Sonderfälle ersetzt. Schlüssel, Account und berechtigte Workflows sind deshalb
|
||||
als root-äquivalente Sicherheitsgrenze zu behandeln. Die spätere Trennung von
|
||||
Production- und Testing-Berechtigungen bleibt davon unabhängig zu spezifizieren.
|
||||
|
||||
---
|
||||
|
||||
## 10. Zu erstellende technische Dokumentation und SOPs
|
||||
|
||||
Die folgenden Dokumente sind vorgesehen. Titel, Dokumenten-IDs und Zuschnitt
|
||||
werden bei der Ausarbeitung final festgelegt.
|
||||
|
||||
| Arbeitsname | Inhalt | Zielgruppe |
|
||||
| --- | --- | --- |
|
||||
| Technische Spezifikation der Ansible-Plattform | Komponenten, Vertrauensgrenzen, Repository-Struktur, Inventar, Rollenmodell, Runner, Netzpfade, Production-/Testing-Isolation, Secret-Konzept, Versionierung und Abnahmekriterien | Infrastrukturadministratoren und Reviewer |
|
||||
| SOP: Gitea Runner einrichten | LXC-Voraussetzungen, Installation, Registrierung, Labels, Job-Isolation, Netzwerkregeln, Logging, Updates, Backup und Funktionsabnahme | Server Admin |
|
||||
| SOP: KI-Server für Ansible vorbereiten | Deployment-Account, SSH-Key, Host-Key, `sudo`, Python-Voraussetzungen, Bootstrap und negative Zugriffstests | Server Admin |
|
||||
| SOP: Lokales Ansible-Environment einrichten | unterstütztes Betriebssystem, Repository-Checkout, Python-/Ansible-Versionen, Collections, Linting, Vault-Zugriff und lokale Prüfkommandos | Mitwirkende und Reviewer |
|
||||
| SOP: Änderung und Pull Request | Branching, Rollen- und Variablenänderungen, Dokumentationspflicht, lokale Tests, Review und Merge | Mitwirkende |
|
||||
| SOP: Deployment ausführen und prüfen | Freigabe-Tag, Actions-Lauf, erwartete Prüfungen, Auswertung, Betriebsnachweis und Fehlerbehandlung | Deployment-Berechtigte |
|
||||
| SOP: Testing-Umgebung verwenden | Teststand und Wartungsfenster auswählen, kontrollierten Lauf freigeben, Production geordnet umschalten, interne Endpunkte verwenden, Testdaten zurücksetzen, Production wiederherstellen und Ergebnisse im Pull Request dokumentieren | Mitwirkende und Reviewer |
|
||||
| SOP: Rollback und Wiederherstellung | Revert, erneutes Deployment, Image-Rückkehr, Backup/Restore, Datenmigrationen und Abnahmetests | Server Admin |
|
||||
| SOP: Geheimnisse verwalten | Vault-Dateien, Passwortaufbewahrung, Runner-Secrets, Rotation, Widerruf und Verhalten bei möglicher Offenlegung | Server Admin und Security-Verantwortliche |
|
||||
|
||||
Die Nutzer-SOPs sollen konkrete Voraussetzungen, Befehle, erwartete Ergebnisse,
|
||||
Fehlerfälle und Abnahmekriterien enthalten. Eine Zielbeschreibung allein gilt
|
||||
nicht als Nachweis einer erfolgreichen Einrichtung.
|
||||
|
||||
---
|
||||
|
||||
## 11. Vorgesehene Umsetzungsphasen
|
||||
|
||||
### Phase A – Ist-Aufnahme und Festlegung
|
||||
|
||||
- Gitea-Version, Actions, Runner-Kompatibilität und Registry prüfen;
|
||||
- vorbereitete Repositories und Zuständigkeiten aufnehmen;
|
||||
- Runner-LXC, Netzwerkpfad und Ressourcen festlegen;
|
||||
- Isolation, exklusiven Umschaltmechanismus, Wartungsfenster und interne
|
||||
Erreichbarkeit von Production und Testing festlegen;
|
||||
- eingerichteten Deployment-Account in das endgültige Secret-, Rotations- und
|
||||
Runner-Verfahren überführen;
|
||||
- Backup- und Restore-Voraussetzungen klären.
|
||||
|
||||
### Phase B – Ansible-Grundgerüst
|
||||
|
||||
- Repository-Struktur, Inventar und `ansible.cfg` anlegen;
|
||||
- Werkzeug- und Collection-Versionen festlegen;
|
||||
- Basisrolle und Verifikationsplaybook entwickeln;
|
||||
- getrennte Inventare und Variablenvalidierung für Production und Testing
|
||||
erstellen;
|
||||
- lokale Entwickler-SOP erstellen und gegen einen frischen Checkout testen.
|
||||
|
||||
### Phase C – Gitea-Integration
|
||||
|
||||
- Runner-LXC reproduzierbar einrichten und abnehmen;
|
||||
- Validierungsworkflow ohne Produktionszugang einführen;
|
||||
- Branch- und Tag-Schutz konfigurieren;
|
||||
- Deployment-Workflow mit einem ungefährlichen Testplaybook abnehmen;
|
||||
- Testing-Deployment vor dem Merge und Production-Freigabe nach dem Merge
|
||||
getrennt abnehmen.
|
||||
|
||||
### Phase D – KI-Laufzeit
|
||||
|
||||
- Podman und systemd-Integration;
|
||||
- AMD-GPU-/ROCm- oder alternative Laufzeit nach technischer Prüfung;
|
||||
- `llama.cpp` und lokaler Funktionstest;
|
||||
- Wiederanlauf nach geplantem Reboot prüfen.
|
||||
|
||||
### Phase E – Anwendungen und Veröffentlichung
|
||||
|
||||
- LiteLLM, OpenWebUI und weitere beschlossene Anwendungen;
|
||||
- persistente Daten, Backups und Restore-Test;
|
||||
- interne Dienste und NPM-Veröffentlichung getrennt abnehmen;
|
||||
- Authentifizierung, API-Keys, Health Monitoring und Betriebs-SOPs ergänzen.
|
||||
|
||||
---
|
||||
|
||||
## 12. Offene Entscheidungen und Nachweise
|
||||
|
||||
Vor der technischen Spezifikation sind mindestens folgende Punkte zu klären:
|
||||
|
||||
1. Welche Gitea-Version läuft, und sind Actions sowie geschützte Tags in der
|
||||
eingesetzten Version aktiviert und geeignet?
|
||||
2. Welche Komponenten werden zunächst ausschließlich als versionierte
|
||||
Upstream-Artefakte eingebunden, und für welche ist mittelfristig eigener
|
||||
Quellcode zu erwarten?
|
||||
3. Wo wird der Runner-LXC betrieben, welche Adresse erhält er, und darf dort
|
||||
Docker für isolierte Jobs eingesetzt werden?
|
||||
4. Soll die erste Produktionsfreigabe über einen geschützten Tag oder zunächst
|
||||
über einen manuell gestarteten Workflow erfolgen?
|
||||
5. Welche Personen oder Gitea-Teams dürfen reviewen, mergen und Deployments
|
||||
freigeben?
|
||||
6. Wie wird der bestehende root-äquivalente Schlüssel von der lokalen
|
||||
Control-Umgebung auf den späteren Runner überführt beziehungsweise rotiert,
|
||||
und wie werden Production- und Testing-Berechtigungen dabei getrennt?
|
||||
7. Wird Ansible Vault verwendet, oder soll ein anderer vorhandener
|
||||
Secret-Store angebunden werden?
|
||||
8. Werden eigene Images in der Gitea Registry gebaut, und wie werden
|
||||
Registry-Credentials und Image-Signaturen behandelt?
|
||||
9. Welche Backupziele, Aufbewahrungsfristen und nachgewiesenen Restore-Verfahren
|
||||
gelten für Konfiguration, Modelle und Anwendungsdaten?
|
||||
10. Welche Versionen und Installationswege werden für AMD-GPU-Laufzeit,
|
||||
`llama.cpp`, LiteLLM, OpenWebUI und weitere Anwendungen unterstützt?
|
||||
11. Welche ausgehenden Internetzugriffe erhalten Runner und KI-Server für
|
||||
Pakete, Collections, Images und Modelle?
|
||||
12. Welche Health Checks und fachlichen Inferenztests bilden die verbindliche
|
||||
Abnahme eines Deployments?
|
||||
13. Welche maximale Dauer, Häufigkeit und Ankündigungsfrist gelten für
|
||||
Testing-Wartungsfenster, in denen Production nicht verfügbar ist?
|
||||
14. Welcher hostseitige Lock-, Timeout- und Recovery-Mechanismus stellt den
|
||||
Wiederanlauf von Production sicher, wenn Runner oder Testing ausfallen?
|
||||
15. Welche internen Hostnamen und Ports erhält Testing, und welche LAN-/VPN-
|
||||
Rollen dürfen darauf zugreifen?
|
||||
16. Können Production und Testing mit getrennten unprivilegierten Accounts
|
||||
verwaltet werden, oder erfordert die gewählte Podman-/systemd-Architektur
|
||||
einen stärker privilegierten gemeinsamen Deploymentpfad?
|
||||
17. Wie wird ein Testing-Deployment eines Pull-Request-Commits in der
|
||||
eingesetzten Gitea-Version autorisiert, ohne Production-Secrets oder
|
||||
Production-Runner für veränderbaren PR-Code verfügbar zu machen?
|
||||
18. Welche Testdaten dürfen verwendet werden, und wie wird ausgeschlossen,
|
||||
dass personenbezogene oder geheime Production-Daten nach Testing kopiert
|
||||
werden?
|
||||
|
||||
Diese Fragen werden nicht durch Annahmen in Playbooks ersetzt. Entscheidungen
|
||||
werden zunächst in der technischen Spezifikation dokumentiert und anschließend
|
||||
in Implementierung und SOPs übernommen.
|
||||
|
||||
---
|
||||
|
||||
## 13. Referenzstand der Planung
|
||||
|
||||
Für die weitere Ausarbeitung wurden am 19.09.2026 insbesondere folgende
|
||||
Herstellerdokumentationen herangezogen:
|
||||
|
||||
- [Gitea Actions – Überblick](https://docs.gitea.com/usage/actions/)
|
||||
- [Gitea Runner](https://docs.gitea.com/runner/)
|
||||
- [Gitea Actions – Secrets](https://docs.gitea.com/usage/actions/secrets/)
|
||||
- [Gitea Actions – Job-Token-Berechtigungen](https://docs.gitea.com/usage/actions/token-permissions/)
|
||||
- [Gitea – geschützte Tags](https://docs.gitea.com/usage/protected-tags/)
|
||||
- [Ansible Playbooks](https://docs.ansible.com/projects/ansible/latest/playbook_guide/playbooks_intro.html)
|
||||
- [Ansible Check- und Diff-Modus](https://docs.ansible.com/projects/ansible/latest/playbook_guide/playbooks_checkmode.html)
|
||||
- [Ansible Vault](https://docs.ansible.com/projects/ansible/latest/vault_guide/vault.html)
|
||||
|
||||
Vor der Implementierung sind versionsspezifische Aussagen gegen die dann
|
||||
tatsächlich eingesetzten Gitea-, Runner- und Ansible-Versionen zu prüfen.
|
||||
|
||||
---
|
||||
|
||||
## 14. Belegter Ansible-Implementierungsstand
|
||||
|
||||
Am 19.09.2026 wurde `ansible-core` 2.21.4 in einer lokalen, von Git
|
||||
ausgeschlossenen virtuellen Umgebung installiert. Das neue Control-Projekt
|
||||
enthält:
|
||||
|
||||
- `ansible.cfg` mit festem Testing-Inventar, Host-Key-Prüfung und lokalem
|
||||
Ansible-Arbeitsverzeichnis;
|
||||
- ein Testing-Inventar für `ghostnet-ki` unter `10.0.0.90` mit dem
|
||||
unprivilegierten Konto `ghost-agent`;
|
||||
- ein Preflight-Playbook ohne `become` und ohne konfigurationsändernde Tasks;
|
||||
- eine kurze Anleitung für Control-Node-Setup, SSH-Agent, Syntaxprüfung und
|
||||
Live-Lauf.
|
||||
|
||||
Inventardarstellung und Syntaxprüfung waren erfolgreich. Der Live-Lauf
|
||||
bestätigte:
|
||||
|
||||
| Merkmal | Ergebnis |
|
||||
| --- | --- |
|
||||
| Hostname | `ghostnet-ki` |
|
||||
| Betriebssystem | Debian 13.6 |
|
||||
| Standard-IPv4 | `10.0.0.90` |
|
||||
| CPU | 14 vCPUs |
|
||||
| RAM | 64.123 MiB |
|
||||
| Remote-Python | 3.13.5 |
|
||||
| GPUs | zwei Navi-21-PCI-Geräte `1002:73bf` |
|
||||
| Play Recap | `ok=5 changed=0 unreachable=0 failed=0` |
|
||||
|
||||
Dieser Lauf ist ein Zugriffs- und Identitätsnachweis. Er bestätigt weder ROCm
|
||||
noch Inferenzfunktion, privilegierte Deploymentrechte, Containerbetrieb oder
|
||||
die geplante Umschaltung zwischen Production und Testing.
|
||||
|
||||
Für den folgenden Bootstrap wurden ein eigener Ed25519-Schlüssel, ein
|
||||
Account-/sudoers-Playbook und ein getrenntes Prüfplaybook erstellt. Der
|
||||
öffentliche Schlüssel ist mit Fingerprint
|
||||
`SHA256:IKEInbIo8VVI1USg5+a3ypGK8H8FweJiQu+v7KvhQDU` versioniert; der private
|
||||
Schlüssel liegt mit Modus `0600` außerhalb des Repositories. Syntaxprüfung und
|
||||
Bootstrap-Inventar waren erfolgreich.
|
||||
|
||||
Der Bootstrap wurde anschließend über das persönliche Administratorkonto
|
||||
ausgeführt. Benutzer, Home-/SSH-Verzeichnisse, eingeschränkter Public Key und
|
||||
die mit `visudo` geprüfte sudoers-Datei wurden erfolgreich angelegt. Die letzte
|
||||
Assertion des ersten Laufs schlug ausschließlich fehl, weil die nicht vollständig
|
||||
quotierte Bedingung mit `NOPASSWD: ALL` als YAML-Mapping statt als String
|
||||
interpretiert wurde. Die Bedingung wurde korrigiert. Das getrennte Prüfplaybook
|
||||
bestätigte danach die Anmeldung als `ansible-deploy`, normale Modulausführung
|
||||
und nicht-interaktive Eskalation auf UID 0 mit
|
||||
`ok=5 changed=0 unreachable=0 failed=0`. Ein vollständiger zweiter
|
||||
Bootstrap-Lauf zur praktischen Idempotenzprüfung wurde noch nicht ausgeführt.
|
||||
|
||||
Als erste gemeinsame Hostrolle wurde anschließend `host_baseline` mit eigenem
|
||||
Host-Inventar und `playbooks/host.yml` implementiert. Vor Änderungen prüft sie
|
||||
Hostname, Debian-Hauptversion, Standard-IPv4, die Erkennung des
|
||||
Root-Dateisystems und mindestens 100 GiB freien Speicher. Sie verwaltet derzeit
|
||||
ausschließlich folgende root-eigenen Verzeichniswurzeln:
|
||||
|
||||
```text
|
||||
/etc/ghostnet-ai/production
|
||||
/etc/ghostnet-ai/testing
|
||||
/var/lib/ghostnet-ai/production
|
||||
/var/lib/ghostnet-ai/testing
|
||||
/var/lib/ghostnet-ai/shared
|
||||
```
|
||||
|
||||
Inventar- und Syntaxprüfung waren erfolgreich. Der Check-Modus zeigte nur die
|
||||
sieben erwarteten Verzeichnisse einschließlich ihrer Eltern als Änderungen und
|
||||
meldete 1.677,7 GiB freien Platz auf `/`. Der erste reale Lauf endete mit
|
||||
`ok=6 changed=1 unreachable=0 failed=0`. Der unmittelbar folgende identische
|
||||
Lauf endete mit `ok=6 changed=0 unreachable=0 failed=0` und belegt die
|
||||
Idempotenz des aktuellen Rollenumfangs. Es wurden keine Pakete installiert und
|
||||
keine Dienste, Container oder GPU-Komponenten verändert.
|
||||
|
||||
---
|
||||
|
||||
## 15. Abschlussgrenze dieses Entwurfs
|
||||
|
||||
Mit Version 0.7 sind der aktuelle Planungsstand, der ausdrücklich begrenzte
|
||||
Ansible-Preflight, der verifizierte Deployment-Account und die angewendete
|
||||
minimale Host-Baseline persistiert.
|
||||
Insbesondere wurden noch nicht:
|
||||
|
||||
- ein Gitea Runner angelegt,
|
||||
- Gitea Actions oder Schutzregeln konfiguriert,
|
||||
- Runtime- oder Anwendungsrollen, ein Production-Inventar oder ein Vault erstellt,
|
||||
- eine Production- oder Testing-Umgebung eingerichtet oder voneinander
|
||||
abgenommen,
|
||||
- Pakete oder Anwendungen auf dem KI-Server installiert,
|
||||
- Backups oder Rollbacks praktisch getestet oder
|
||||
- die geplante Deploymentkette technisch abgenommen.
|
||||
|
||||
Der nächste Dokumentationsschritt ist die Ist-Aufnahme aus Phase A. Auf deren
|
||||
Ergebnissen baut die verbindliche technische Spezifikation auf.
|
||||
Reference in New Issue
Block a user