diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..9b4181e --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,13 @@ +# Copilot Instructions + +Repository-specific conventions live in `AGENTS.md` at the repository root. + + +## Project Wiki (Automatic Instructions) + +- Before implementing, modifying, debugging, refactoring, testing, documenting, or planning code, read `.project-wiki/INDEX.md` and follow its routing to the smallest relevant set of wiki documents (automatic context preflight). +- After making source changes, update the affected `.project-wiki/` records (technical docs, traceability maps, section indexes, `REGISTRY.yml`, `STATUS.md`) as part of the same task. +- Use the wiki `sync` mode only for manual or externally changed code; agent-made changes are updated directly (automatic post-implementation wiki update). +- After every meaningful wiki edit, reconcile affected indexes, `REGISTRY.yml`, `STATUS.md`, traceability, and append an entry to `.project-wiki/logs/wiki-log-YYYY-MM.md`. +- Write all wiki content and generated instruction blocks in English; reply to the user in their chat language. + diff --git a/.gitignore b/.gitignore index bfceb24..5679673 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ proxmox.* .vault_pass logs .zabbix-psk -setenv.sh \ No newline at end of file +setenv.sh +.agents diff --git a/.project-wiki/GLOSSARY.md b/.project-wiki/GLOSSARY.md new file mode 100644 index 0000000..a4331bd --- /dev/null +++ b/.project-wiki/GLOSSARY.md @@ -0,0 +1,17 @@ +--- +id: GLOSSARY +type: note +status: placeholder +title: Glossary +created: '2026-09-09' +updated: '2026-09-09' +tags: +- glossary +related: [] +source_paths: [] +confidence: unknown +--- + +# Glossary + +No project-specific terms have been captured yet. diff --git a/.project-wiki/INDEX.md b/.project-wiki/INDEX.md new file mode 100644 index 0000000..e0f1a6e --- /dev/null +++ b/.project-wiki/INDEX.md @@ -0,0 +1,48 @@ +# Project Wiki Index + +This wiki stores the project knowledge base for requirements, changes, technical documentation, implementation state, and traceability. Use this file as the routing entrypoint; open only the linked files needed for the current task. + +Last updated: 2026-09-09 + +## Current State + +- Status: [STATUS.md](./STATUS.md) +- Project brief: [PROJECT.md](./PROJECT.md) +- Registry: [REGISTRY.yml](./REGISTRY.yml) + +## Task Routing + +| Task | Start Here | +| --- | --- | +| Understand project goals | [PROJECT.md](./PROJECT.md), [requirements/INDEX.md](./requirements/INDEX.md) | +| Work with requirements | [requirements/INDEX.md](./requirements/INDEX.md), [traceability/requirement-map.md](./traceability/requirement-map.md) | +| Process meeting notes or new docs | [changes/INDEX.md](./changes/INDEX.md), [implementation/INDEX.md](./implementation/INDEX.md) | +| Process external requirements document | [sources/INDEX.md](./sources/INDEX.md), [intake/INDEX.md](./intake/INDEX.md), [changes/INDEX.md](./changes/INDEX.md), [requirements/INDEX.md](./requirements/INDEX.md) | +| Review active alerts | [alerts/INDEX.md](./alerts/INDEX.md), [STATUS.md](./STATUS.md) | +| Review durable analyses | [analysis/INDEX.md](./analysis/INDEX.md) | +| Run wiki maintenance lint | [maintenance/INDEX.md](./maintenance/INDEX.md) | +| Analyze change impact | [changes/CHANGELOG.md](./changes/CHANGELOG.md), [traceability/change-impact-map.md](./traceability/change-impact-map.md) | +| Modify code | [technical/codebase-map.md](./technical/codebase-map.md), [traceability/code-map.md](./traceability/code-map.md) | +| Work on architecture | [technical/architecture.md](./technical/architecture.md), [changes/decisions/](./changes/decisions/) | +| Work on APIs | [technical/api/](./technical/api/) | +| Work on data | [technical/data/](./technical/data/) | +| Work on tests | [technical/testing.md](./technical/testing.md) | +| Work on deployment | [technical/deployment.md](./technical/deployment.md) | +| Work on security | [technical/security.md](./technical/security.md) | +| Review active work | [implementation/current-plan.md](./implementation/current-plan.md), [implementation/work-items/](./implementation/work-items/) | +| Sync manual code changes | [technical/codebase-map.md](./technical/codebase-map.md), [traceability/code-map.md](./traceability/code-map.md), [implementation/scans/](./implementation/scans/) | +| Review wiki audit history | [logs/INDEX.md](./logs/INDEX.md) | + +## Sections + +- [Requirements](./requirements/INDEX.md) +- [Changes](./changes/INDEX.md) +- [Technical Documentation](./technical/INDEX.md) +- [Implementation](./implementation/INDEX.md) +- [Traceability](./traceability/INDEX.md) +- [Analysis](./analysis/INDEX.md) +- [Maintenance](./maintenance/INDEX.md) +- [Alerts](./alerts/INDEX.md) +- [Sources](./sources/INDEX.md) +- [Document Intake](./intake/INDEX.md) +- [Wiki Audit Log](./logs/INDEX.md) diff --git a/.project-wiki/PROJECT.md b/.project-wiki/PROJECT.md new file mode 100644 index 0000000..45e701c --- /dev/null +++ b/.project-wiki/PROJECT.md @@ -0,0 +1,66 @@ +--- +id: PROJECT +type: project +status: active +title: Project Overview +created: '2026-09-09' +updated: '2026-09-09' +tags: +- project +- ansible +- infrastructure +related: +- PRODUCT-BRIEF +- CONSTRAINTS +source_paths: +- AGENTS.md +- README.md +confidence: confirmed +--- + +# Project Overview + +## Name + +`ansible-playbooks` - central Ansible collection for Debian host management. + +## Goal + +Provide a single, reusable Ansible repository to provision, harden, update, +and monitor Debian hosts in a consistent, documented, and repeatable way. + +## Domain + +Infrastructure automation and IT operations: + +- Target hosts are Debian systems, partially running as LXC containers on + Proxmox. +- Execution happens in a controlled Docker runtime environment + (`execute.sh`, `docker/`). +- Secrets are managed via Ansible Vault (`vault.yml`, git-ignored `.vault_pass`). + +## Roles (building blocks) + +| Role | Purpose | +|--------------------|----------------------------------------------------------------| +| `bootstrap` | Initial setup: packages, admin user, SSH, MOTD, sysctl, Docker, monitoring | +| `docker` | Docker Engine installation from the official repository | +| `hawser` | Update of the Hawser Docker Compose stack | +| `healthcheck` | Quality gate: verify running containers after updates | +| `manage-ssh-keys` | SSH key management (hardening) | +| `monitoring` | Zabbix Agent 2: installation, TLS-PSK, Docker plugin, API registration | +| `os-updates` | Debian package updates with mirror switch, reboot, and logging | + +## Constraints + +See [Constraints](./requirements/constraints.md). Key points: Debian-only +playbooks, LXC-aware skips for `sysctl`/`reboot`, mandatory documentation of +every change, FQCN module usage, secrets never committed in plain text. + +## Stakeholders + +Not explicitly documented yet. See [STATUS.md](./STATUS.md) open questions. + +## Success Criteria + +Not explicitly documented yet. See [STATUS.md](./STATUS.md) open questions. diff --git a/.project-wiki/REGISTRY.yml b/.project-wiki/REGISTRY.yml new file mode 100644 index 0000000..0b86d3d --- /dev/null +++ b/.project-wiki/REGISTRY.yml @@ -0,0 +1,364 @@ +version: 1 +updated: '2026-09-09' +documents: +- id: GLOSSARY + type: note + title: Glossary + path: GLOSSARY.md + status: placeholder + tags: + - glossary + related: [] + source_paths: [] + confidence: unknown +- id: PROJECT + type: project + title: Project Overview + path: PROJECT.md + status: active + tags: + - project + - ansible + - infrastructure + related: + - PRODUCT-BRIEF + - CONSTRAINTS + source_paths: + - AGENTS.md + - README.md + confidence: confirmed +- id: CURRENT-PLAN + type: note + title: Current Plan + path: implementation/current-plan.md + status: placeholder + tags: + - implementation + - plan + related: [] + source_paths: [] + confidence: unknown +- id: SCAN-20260909 + type: scan-report + title: Codebase Scan 2026-09-09 + path: implementation/scans/scan-20260909.md + status: active + tags: + - scan + related: + - PROJECT + - TECH-CODEBASE-MAP + - ALERT-20260909-001 + - ALERT-20260909-002 + source_paths: + - README.md + - roles/ + - playbooks/ + - inventory/ + - docker/ + confidence: confirmed +- id: CONSTRAINTS + type: note + title: Constraints + path: requirements/constraints.md + status: active + tags: + - requirements + - constraints + related: + - PROJECT + source_paths: + - AGENTS.md + confidence: confirmed +- id: OPEN-QUESTIONS + type: note + title: Open Questions + path: requirements/open-questions.md + status: active + tags: + - requirements + - open-questions + related: + - SCAN-20260909 + - ALERT-20260909-001 + - ALERT-20260909-002 + source_paths: + - AGENTS.md + - README.md + - inventory/group_vars/external.yml + confidence: confirmed +- id: PRODUCT-BRIEF + type: note + title: Product Brief + path: requirements/product-brief.md + status: placeholder + tags: + - requirements + - product + related: [] + source_paths: [] + confidence: unknown +- id: REQ-FUNC-HOST-LIFECYCLE + type: requirements + title: Host Lifecycle Operations + path: requirements/functional/host-lifecycle-operations.md + status: active + tags: + - requirements + - functional + related: + - PROJECT + - CONSTRAINTS + source_paths: + - README.md + - AGENTS.md + confidence: confirmed +- id: NFR-OPERATIONAL-POLICIES + type: requirements + title: Operational Policies + path: requirements/non-functional/operational-policies.md + status: active + tags: + - requirements + - non-functional + related: + - PROJECT + - CONSTRAINTS + source_paths: + - README.md + - AGENTS.md + confidence: confirmed +- id: TECH-ARCHITECTURE + type: technical + title: Architecture + path: technical/architecture.md + status: active + tags: + - technical + - architecture + - ansible + related: + - TECH-CODEBASE-MAP + - TECH-DEPLOYMENT + source_paths: + - ansible.cfg + - execute.sh + - roles/ + confidence: confirmed +- id: TECH-CODEBASE-MAP + type: technical + title: Codebase Map + path: technical/codebase-map.md + status: active + tags: + - technical + - codebase + related: + - TECH-ARCHITECTURE + source_paths: + - roles/ + - playbooks/ + - inventory/ + confidence: confirmed +- id: TECH-DEPLOYMENT + type: technical + title: Deployment + path: technical/deployment.md + status: active + tags: + - technical + - deployment + - docker + related: + - TECH-ARCHITECTURE + source_paths: + - execute.sh + - docker/ + confidence: confirmed +- id: TECH-SECURITY + type: technical + title: Security + path: technical/security.md + status: active + tags: + - technical + - security + related: + - ALERT-20260909-001 + - NFR-002 + source_paths: + - vault.yml + - roles/bootstrap/defaults/main.yml + - ansible.cfg + confidence: confirmed +- id: TECH-TESTING + type: technical + title: Testing + path: technical/testing.md + status: active + tags: + - technical + - testing + related: + - MOD-005 + source_paths: [] + confidence: confirmed +- id: TECH-INTEGRATIONS-ZABBIX + type: technical + title: Integrations - Zabbix + path: technical/integrations/zabbix.md + status: active + tags: + - technical + - integration + - zabbix + related: + - MOD-003 + source_paths: + - roles/monitoring/ + - inventory/group_vars/ + confidence: confirmed +- id: MOD-001 + type: module + title: Bootstrap Role + path: technical/modules/MOD-001-bootstrap-role.md + status: active + tags: + - module + - bootstrap + related: + - MOD-002 + - MOD-003 + - REQ-001 + source_paths: + - roles/bootstrap/ + confidence: confirmed +- id: MOD-002 + type: module + title: Docker Role + path: technical/modules/MOD-002-docker-role.md + status: active + tags: + - module + - docker + related: + - MOD-001 + - MOD-006 + - REQ-002 + source_paths: + - roles/docker/ + confidence: confirmed +- id: MOD-003 + type: module + title: Monitoring Role (Zabbix Agent 2) + path: technical/modules/MOD-003-monitoring-role.md + status: active + tags: + - module + - monitoring + - zabbix + related: + - MOD-001 + - TECH-INTEGRATIONS-ZABBIX + - REQ-003 + source_paths: + - roles/monitoring/ + confidence: confirmed +- id: MOD-004 + type: module + title: OS Updates Role + path: technical/modules/MOD-004-os-updates-role.md + status: active + tags: + - module + - os-updates + related: + - MOD-005 + - REQ-004 + - NFR-006 + source_paths: + - roles/os-updates/ + confidence: confirmed +- id: MOD-005 + type: module + title: Healthcheck Role + path: technical/modules/MOD-005-healthcheck-role.md + status: active + tags: + - module + - healthcheck + - docker + related: + - MOD-004 + - REQ-004 + source_paths: + - roles/healthcheck/ + confidence: confirmed +- id: MOD-006 + type: module + title: Hawser Role + path: technical/modules/MOD-006-hawser-role.md + status: active + tags: + - module + - hawser + - docker + related: + - MOD-002 + - REQ-005 + source_paths: + - roles/hawser/ + confidence: confirmed +- id: MOD-007 + type: module + title: Manage SSH Keys Role + path: technical/modules/MOD-007-manage-ssh-keys-role.md + status: active + tags: + - module + - ssh + - hardening + related: + - REQ-006 + source_paths: + - roles/manage-ssh-keys/ + confidence: confirmed +- id: ALERT-20260909-001 + type: alert + title: Plaintext admin password hash committed outside vault + path: alerts/ALERT-20260909-001.md + status: resolved + resolved: '2026-09-09' + severity: high + tags: + - alert + - security + - secrets + related: + - NFR-002 + - OQ-006 + - TECH-SECURITY + source_paths: + - roles/bootstrap/defaults/main.yml + - inventory/group_vars/external.yml + confidence: confirmed +- id: ALERT-20260909-002 + type: alert + title: Uncommitted dmc12-to-cloudlorean rename and stale documentation + path: alerts/ALERT-20260909-002.md + status: resolved + resolved: '2026-09-09' + severity: medium + tags: + - alert + - consistency + - git + related: + - OQ-003 + - SCAN-20260909 + source_paths: + - inventory/dmc12.yml + - inventory/cloudlorean.yml + - README.md + - AGENTS.md + confidence: confirmed diff --git a/.project-wiki/STATUS.md b/.project-wiki/STATUS.md new file mode 100644 index 0000000..1bd1ba7 --- /dev/null +++ b/.project-wiki/STATUS.md @@ -0,0 +1,43 @@ +# Project Status + +Last updated: 2026-09-09 + +## Scaffold State + +- Canonical scaffold created: yes +- Project initialization complete: yes +- Scan workflow: performed 2026-09-09 ([scan report](./implementation/scans/scan-20260909.md)) +- Technical documentation, requirements, traceability: populated + +## Current State + +- Repository fully scanned (root config, docker runtime, 3 inventories, + 6 playbooks, 7 roles). Findings and module docs confirmed against source. +- Open alerts: 0 (2 resolved on 2026-09-09) - see + [alerts/INDEX.md](./alerts/INDEX.md). +- Open questions: 2 open / 2 partially-resolved / 2 resolved - see + [open-questions.md](./requirements/open-questions.md). + +## Next Documentation Steps + +- Confirm OQ-004 (external Zabbix passive server) and OQ-005 (Docker + template name) with the Zabbix server. +- Fill `requirements/product-brief.md` and `GLOSSARY.md` when product-intent + sources allow, or remove them as placeholders. + +## Active Alerts + +- None. ALERT-20260909-001 (high) and ALERT-20260909-002 (medium) were + resolved on 2026-09-09: `admin_password` moved to encrypted `vault.yml` + (loaded via `vars_files` in `bootstrap.yml`), documentation updated to the + `cloudlorean` inventory, and the rename/change set committed coherently. + +## Blockers And Open Questions + +- OQ-004: external site Zabbix passive server points to the `healthcheck` + host's own IPv6 address - intentional or misconfiguration? +- OQ-005: Docker Zabbix template name mismatch (`Docker by Zabbix agent2` + vs `Docker by Zabbix agent 2`). +- OQ-001 / OQ-002: formal stakeholders and success criteria remain + undocumented (partially resolved: operators inferred as Niklas/Dennis; + update-run success defined by the healthcheck gate). diff --git a/.project-wiki/WIKI_VERSION.yml b/.project-wiki/WIKI_VERSION.yml new file mode 100644 index 0000000..433218b --- /dev/null +++ b/.project-wiki/WIKI_VERSION.yml @@ -0,0 +1,6 @@ +schema: project-wiki +schema_version: 1.5.1 +schema_updated: '2026-09-02' +last_migrated: '2026-09-09' +maintained_by_skill: project-wiki +notes: Current schema applied. diff --git a/.project-wiki/alerts/ALERT-20260909-001.md b/.project-wiki/alerts/ALERT-20260909-001.md new file mode 100644 index 0000000..5db7ed7 --- /dev/null +++ b/.project-wiki/alerts/ALERT-20260909-001.md @@ -0,0 +1,82 @@ +--- +id: ALERT-20260909-001 +type: alert +status: resolved +severity: high +title: Plaintext admin password hash committed outside vault +created: 2026-09-09 +updated: 2026-09-09 +resolved: 2026-09-09 +tags: [alert, security, secrets] +related: [NFR-002, OQ-006, TECH-SECURITY] +source_paths: +- roles/bootstrap/defaults/main.yml +- inventory/group_vars/external.yml +- README.md +confidence: confirmed +--- + +# ALERT-20260909-001 - Plaintext admin password hash committed outside vault + +## Summary + +The SHA-512 hash of the admin password (`admin_password`) is committed in +plaintext in `roles/bootstrap/defaults/main.yml` and +`inventory/group_vars/external.yml`, while the root README states that +`admin_password` lives encrypted in `vault.yml`. The documented secret +policy (AGENTS.md, README "Beitragen") forbids plaintext secrets in +`defaults/` or `group_vars/`. The same hash is reused across both locations. + +## Why It Matters + +- A committed password hash allows offline guessing of the shared admin + password; reusing one hash across hosts amplifies the impact. +- It contradicts the project's own documented vault policy and misleads + contributors (README claims the value is vaulted). + +## Evidence + +- `roles/bootstrap/defaults/main.yml` lines 12-13 (`admin_password` hash) +- `inventory/group_vars/external.yml` lines 10-11 (same hash) +- `README.md` "Secrets & Vault" section claims `admin_password` is in + `vault.yml` +- AGENTS.md "Secrets": no plaintext secrets in `defaults/`/`group_vars/` + +## Suggested Resolution + +Move `admin_password` to `vault.yml` in both locations; keep no plaintext +fallback in role defaults. If a default is truly needed for bootstrap, +document it explicitly as an accepted risk and set a unique hash per site. +Verify whether `vault.yml` already overrides it (OQ-006). + +## Resolution + +Resolved 2026-09-09. OQ-006 was verified first: decrypting `vault.yml` +confirmed it did **not** define `admin_password` (no override existed), so the +committed hash was a policy violation, not an intended fallback. Fix applied: + +- `admin_password` (SHA-512 hash) moved into encrypted `vault.yml`. +- Plaintext hash removed from `roles/bootstrap/defaults/main.yml` and + `inventory/group_vars/external.yml` (comment now points to `vault.yml`). +- `bootstrap.yml` loads `vault.yml` via `vars_files` (same pattern as + `playbooks/monitoring.yml`), so the bootstrap role still resolves the + variable for every inventory. +- Documentation updated: root `README.md` "Secrets & Vault" and + `roles/bootstrap/README.md` (variable now documented as vault-sourced). +- NFR-002 acceptance notes and evidence updated accordingly. + +Remaining recommendation (hardening, not blocking): the same hash is still +shared across sites; rotating to a unique hash per site would remove the +single shared credential. + +## Resolution Evidence + +- `ansible-vault view vault.yml` (decrypted during resolution) now lists + `admin_password` among `PROXMOX_USER_NAME`, `PROXMOX_USER_PASS`, + `monitoring_zabbix_api_password`. +- Repository-wide search for the former hash string (salt fragment) returns + no matches - the hash exists only inside the encrypted vault file. +- `ansible-playbook --syntax-check bootstrap.yml` passes (with + `--vault-password-file .vault_pass`). +- `rg -n "admin_password"` shows the variable now only in `vault.yml` + (encrypted) and consumed at `roles/bootstrap/tasks/create-admin-user.yml:9`. diff --git a/.project-wiki/alerts/ALERT-20260909-002.md b/.project-wiki/alerts/ALERT-20260909-002.md new file mode 100644 index 0000000..267b553 --- /dev/null +++ b/.project-wiki/alerts/ALERT-20260909-002.md @@ -0,0 +1,85 @@ +--- +id: ALERT-20260909-002 +type: alert +status: resolved +severity: medium +title: Uncommitted dmc12-to-cloudlorean rename and stale documentation +created: 2026-09-09 +updated: 2026-09-09 +resolved: 2026-09-09 +tags: [alert, consistency, git] +related: [OQ-003, SCAN-20260909, TECH-CODEBASE-MAP] +source_paths: +- inventory/dmc12.yml +- inventory/cloudlorean.yml +- inventory/group_vars/cloudlorean.yml +- README.md +- AGENTS.md +confidence: confirmed +--- + +# ALERT-20260909-002 - Uncommitted dmc12-to-cloudlorean rename and stale documentation + +## Summary + +The working tree carries a large uncommitted change set (23 modified files, +203 insertions / 148 deletions) including the deletion of +`inventory/dmc12.yml` and the addition of untracked +`inventory/cloudlorean.yml` + `inventory/group_vars/cloudlorean.yml` +(git log confirms no rename commit). `README.md` (inventory table, call +examples) and `AGENTS.md` (fallstricke) still reference `inventory/dmc12.yml` +and `group_vars/dmc12.yml`, which no longer exist. + +## Why It Matters + +- Documentation, examples, and agent instructions point to a non-existent + inventory; commands copied from README fail. +- The rename plus modifications exist only in the working tree; losing or + partially committing them would leave the repo internally inconsistent. +- Git history will not record a rename (delete + untracked add), which + complicates tracing inventory history. + +## Evidence + +- `git status --short`: `D inventory/dmc12.yml`, `?? inventory/cloudlorean.yml`, + `?? inventory/group_vars/cloudlorean.yml`, plus modified roles/ansible.cfg/ + execute.sh/README/AGENTS +- `git log --follow -- inventory/dmc12.yml`: only add commit `d2e8ce4` +- `README.md` inventory table lists `dmc12.yml` / `group_vars/dmc12.yml` +- `logs/cloudlorean/dmc12/` and `.zabbix-psk/dmc12.psk` exist for host + `dmc12` under the new site name + +## Suggested Resolution + +1. Update `README.md` and `AGENTS.md` to the `cloudlorean` inventory. +2. Review and commit the rename together with the modified files as one + coherent change (owner decision). + +## Resolution + +Resolved 2026-09-09 (owner decision confirmed during the session): + +1. Documentation updated to the `cloudlorean` inventory: + - `README.md` - inventory table (Cloudlorean row: + `cloudlorean.yml` / `group_vars/cloudlorean.yml` + `debian.yml`), + all call examples, "Secrets & Vault" example command. + - `AGENTS.md` - "Häufige Fallstricke": `ssh_service_name` reference now + points to `inventory/cloudlorean.yml` (`gitea`, `ipam`). + - Role READMEs (bootstrap, monitoring, docker, os-updates, healthcheck, + manage-ssh-keys, hawser): all `inventory/dmc12.yml` call examples + replaced with `inventory/cloudlorean.yml`. +2. The rename and the full working-tree change set (23 modified files, + untracked `inventory/cloudlorean.yml` + `inventory/group_vars/ + cloudlorean.yml`, plus this resolution's documentation/vault fixes) were + committed together as one coherent change, as suggested. + +## Resolution Evidence + +- `rg -n "dmc12\.yml"` across `README.md`, `AGENTS.md`, and `roles/` + returns no matches. +- `git status --short` shows a clean tree after the commit (no deleted + `inventory/dmc12.yml` and no untracked `inventory/cloudlorean.yml` + remaining); `git log` records the coherent change set including the + inventory rename. +- README inventory table lists Cloudlorean with `group_vars/cloudlorean.yml` + and `debian.yml`, matching `inventory/` contents. diff --git a/.project-wiki/alerts/INDEX.md b/.project-wiki/alerts/INDEX.md new file mode 100644 index 0000000..6c44131 --- /dev/null +++ b/.project-wiki/alerts/INDEX.md @@ -0,0 +1,22 @@ +# Alerts + +This section tracks significant warning conditions: risks, contradictions, blocking gaps, undocumented assumptions, and inconsistencies that could affect requirements, architecture, implementation, security, compliance, data handling, or project planning. + +## Open Alerts + +- None. + +## Resolved, Dismissed, Or Accepted-Risk Alerts + +- [ALERT-20260909-001](./ALERT-20260909-001.md) - **resolved (2026-09-09)**: + plaintext `admin_password` SHA-512 hash moved to encrypted `vault.yml` + (`bootstrap.yml` loads it via `vars_files`); plaintext copies removed. +- [ALERT-20260909-002](./ALERT-20260909-002.md) - **resolved (2026-09-09)**: + README/AGENTS/role READMEs updated to the `cloudlorean` inventory; the + rename and the working-tree change set committed as one coherent change. + +## Rules + +- Do not create alerts for every suggested question. +- Resolve alerts with evidence instead of deleting them. +- Keep `STATUS.md` updated with compact active alert counts. diff --git a/.project-wiki/analysis/INDEX.md b/.project-wiki/analysis/INDEX.md new file mode 100644 index 0000000..912caaf --- /dev/null +++ b/.project-wiki/analysis/INDEX.md @@ -0,0 +1,18 @@ +# Analysis + +This section stores durable, non-canonical synthesis produced from meaningful project questions or agent analysis. Prefer updating canonical docs when the information clearly belongs in requirements, changes, ADRs, technical docs, implementation docs, or traceability maps. + +## When To Read + +- Review stable tradeoff analyses, impact maps, or exploratory syntheses. +- Understand connections across requirements, changes, decisions, technical docs, alerts, and source paths. + +## Rules + +- Do not create isolated analysis pages. +- Every analysis page must link to related wiki records or source paths. +- Do not store routine chat answers, generic explanations, transient debugging notes, or duplicated content here. + +## Analysis Pages + +- None yet. diff --git a/.project-wiki/changes/CHANGELOG.md b/.project-wiki/changes/CHANGELOG.md new file mode 100644 index 0000000..6d51816 --- /dev/null +++ b/.project-wiki/changes/CHANGELOG.md @@ -0,0 +1,3 @@ +# Project Changelog + +No project changes have been captured yet. diff --git a/.project-wiki/changes/INDEX.md b/.project-wiki/changes/INDEX.md new file mode 100644 index 0000000..734d3f9 --- /dev/null +++ b/.project-wiki/changes/INDEX.md @@ -0,0 +1,17 @@ +# Changes + +This section preserves project changes, change requests, and architectural decisions. + +## When To Read + +- Review how requirements, scope, or technical direction changed over time. + +## Key Files + +- [Project changelog](./CHANGELOG.md) +- [Change requests](./requests/) +- [Architectural decisions](./decisions/) + +## Placeholders Or Stale Areas + +- No project changes or decisions have been captured yet. diff --git a/.project-wiki/implementation/INDEX.md b/.project-wiki/implementation/INDEX.md new file mode 100644 index 0000000..0f020ae --- /dev/null +++ b/.project-wiki/implementation/INDEX.md @@ -0,0 +1,18 @@ +# Implementation + +This section tracks current plans, work items, scans, and synchronization reports. + +## When To Read + +- Review active implementation work or repository scan history. + +## Key Files + +- [Current plan](./current-plan.md) +- [Work items](./work-items/) +- [Scans and sync reports](./scans/) +- [Scan 2026-09-09](./scans/scan-20260909.md) - full repository scan with confirmed/inferred/unknown findings + +## Placeholders Or Stale Areas + +- `current-plan.md` is still a placeholder; no active plan captured yet. diff --git a/.project-wiki/implementation/current-plan.md b/.project-wiki/implementation/current-plan.md new file mode 100644 index 0000000..b4a4693 --- /dev/null +++ b/.project-wiki/implementation/current-plan.md @@ -0,0 +1,18 @@ +--- +id: CURRENT-PLAN +type: note +status: placeholder +title: Current Plan +created: '2026-09-09' +updated: '2026-09-09' +tags: +- implementation +- plan +related: [] +source_paths: [] +confidence: unknown +--- + +# Current Plan + +No project-specific implementation plan has been captured yet. diff --git a/.project-wiki/implementation/scans/scan-20260909.md b/.project-wiki/implementation/scans/scan-20260909.md new file mode 100644 index 0000000..0431bcb --- /dev/null +++ b/.project-wiki/implementation/scans/scan-20260909.md @@ -0,0 +1,163 @@ +--- +id: SCAN-20260909 +type: scan-report +status: active +title: Codebase Scan 2026-09-09 +created: 2026-09-09 +updated: 2026-09-09 +tags: [scan] +related: [PROJECT, TECH-CODEBASE-MAP, TECH-ARCHITECTURE, ALERT-20260909-001, ALERT-20260909-002] +source_paths: +- README.md +- AGENTS.md +- ansible.cfg +- bootstrap.yml +- execute.sh +- docker/ +- inventory/ +- playbooks/ +- roles/ +confidence: confirmed +--- + +# Codebase Scan 2026-09-09 + +## Scope + +Root configuration (`ansible.cfg`, `execute.sh`), Docker runtime +(`docker/`), all inventories and group_vars, all 6 playbooks plus +`bootstrap.yml`, and all 7 roles (`tasks/`, `defaults/`, `templates/`, +`handlers/`). Git history inspected for inventory renames. Vault-encrypted +files (`vault.yml`) were not decrypted. + +## Confirmed Findings + +### Project Shape + +- Central Ansible collection for provisioning, hardening, updating, and + monitoring Debian hosts; 7 roles, 1 top-level playbook (`bootstrap.yml`) + and 5 playbooks under `playbooks/`. +- Execution environment: Docker image `ansible:deb13` built from + `debian:trixie-slim` with `ansible-core~=2.20.6`; collections pinned in + `docker/requirements.yml` (`ansible.posix` 2.2.0, `community.general` + 13.0.1, `community.docker` 5.2.0, `community.zabbix` 4.2.0). +- `execute.sh` creates an IPv6-enabled bridge network `ansible-dualstack`, + starts an interactive container with the repo mounted at `/ansible` and an + `ansible_ssh_keys` volume at `/root/.ssh`, injects SSH client config with + host-key checking disabled, then attaches. +- `ansible.cfg`: `host_key_checking = False`, `roles_path = ./roles`, + `forks = 20`, `gathering = smart`, `internal_poll_interval = 0.001`, + SSH pipelining and `ControlPersist=60s`, known hosts set to `/dev/null`. + +### Playbooks + +- All playbooks run `hosts: all`, `user: admin`, `become: true`, and start + with a Debian `os_family` guard that fails non-Debian hosts. +- Exception: `playbooks/hardening/manage-ssh-keys.yml` has **no** Debian + guard, no `become`, and no `vars_files`; it binds the `manage-ssh-keys` + role directly. +- `playbooks/monitoring.yml` loads `../vault.yml` via a playbook-relative + path; other playbooks do not load vault.yml explicitly. +- `playbooks/os-updates-deb.yml` chains `os-updates` then `healthcheck` + (post-update quality gate). + +### Roles + +- `bootstrap`: subtasks for basic packages, admin user (+sudo, authorized + keys), MOTD, keyboard layout, OpenSSH install/config/keys, bashrc, sysctl; + then composes the `docker` role (skippable via `skip_docker`) and the + `monitoring` role (skippable via `skip_monitoring`). Tags: `motd`, `ssh`, + `bashrc`, `sysctl`. +- `docker`: official Docker apt repository + `docker-ce`, CLI, + `containerd.io`, buildx and compose plugins; template + `sources.list.j2`; codename derived from facts. +- `os-updates`: optional mirror rewrite (`os_also_update_mirror`, deb822 + + legacy templates, codename whitelist `trixie|bookworm|bullseye`), full apt + upgrade, kernel-comparison reboot decision with async reboot + + `wait_for_connection` (600s timeout), reboot **deferred** on hosts in the + `proxmox` group unless `os_update_reboot_on_proxmox` is true, LXC hosts + skip kernel/reboot logic; preflight/postflight logging delegated to the + controller (`logs///update.log`). +- `healthcheck`: compares `docker ps -a` vs running containers, writes a + `quality_gate` section into the same update log, fails the play when + `healthcheck_fail_on_unhealthy` is true (default false). +- `monitoring`: Zabbix 7.0 release package from repo.zabbix.com, + `zabbix-agent2`, Docker plugin config when socket exists, per-host PSK + generated on the controller (persisted in `.zabbix-psk/.psk`, + mode 0700, `openssl rand -hex 32`), deployed to `/etc/zabbix/ + zabbix_agent2.psk` (0600), TLS connect/accept = PSK, and API host + registration via `community.zabbix.zabbix_host` over httpapi with + template union including `Docker by Zabbix agent 2` when the Docker socket + is present. +- `hawser`: requires existing Docker host + compose file at + `/opt/hawser/docker-compose.yml`, re-renders it from template, then + `docker_compose_v2` with `pull: always`. +- `manage-ssh-keys`: ensures `~/.ssh` (0700), adds `good_keys`, removes + `bad_keys` from the admin user's authorized keys. + +### Inventories And Variables + +- `inventory/cloudlorean.yml` (untracked): site with groups `debian` (13 + hosts, 192.168.212.0/24) and `proxmox` (`dmc12` 192.168.212.10, + `pbs` 192.168.212.11); per-host `ssh_service_name: ssh` on `gitea` and + `ipam`. +- `inventory/home.yml`: site `home`, 6 Debian hosts + `tardis` (Proxmox), + 10.13.37.0/24. +- `inventory/external.yml`: 4 IPv6-only Debian hosts. +- `inventory/dmc12.yml` is **deleted in the working tree but not committed** + (`git status: D`); `inventory/cloudlorean.yml` and + `inventory/group_vars/cloudlorean.yml` are untracked. Git history confirms + `dmc12.yml` was added in `d2e8ce4` and never tracked as renamed. +- group_vars per site override Zabbix servers, API URL + (`https://mon.cloudlorean.de/api_jsonrpc.php`), proxy + (`gatekeeper.cloudlorean.de` / `gatekeeper.meyer-kempen.de`), host groups + and templates. `group_vars/debian.yml` hardcodes + `os_update_version_codename: "trixie"` (AGENTS.md warns about this). +- `admin_authorized_keys` and the SHA-512 `admin_password` hash appear in + **plaintext** in `roles/bootstrap/defaults/main.yml` and + `inventory/group_vars/external.yml` (see ALERT-20260909-001). +- `vault.yml` is tracked but encrypted; `.vault_pass`, `setenv.sh`, + `.zabbix-psk/`, `logs/` are git-ignored (verified via `git check-ignore`). + +## Inferred Findings + +- The environment formerly called `dmc12` was renamed to `cloudlorean`; + host `dmc12` is now a Proxmox node within the `cloudlorean` site + (confidence: inferred from logs/.zabbix-psk paths and git status). +- Operators appear to be "Niklas" and "Dennis" (SSH key comments in + bootstrap defaults). Formal ownership not documented. +- All sites share one Zabbix server (`mon.cloudlorean.de`) with optional + per-site proxies (confidence: inferred from group_vars). +- Home site is referred to as "Meyer-Kempen" in Zabbix host groups/proxy + name (confidence: inferred). + +## Unknowns + +- Whether `vault.yml` also defines `admin_password` (overriding the + committed defaults hash) - vault not decrypted. +- Whether `monitoring_zabbix_passive_servers` in + `inventory/group_vars/external.yml` (`2a01:4f8:1c1e:62dd::1`, the + `healthcheck` host's own address) is intentional (Zabbix server/proxy on + that host) or a misconfiguration. +- Which Docker template name exists on the Zabbix server: role default + `Docker by Zabbix agent2` vs group_vars `Docker by Zabbix agent 2`. +- Formal stakeholders and success criteria beyond the healthcheck gate. + +## Important Paths + +- `bootstrap.yml`, `playbooks/*.yml` - entrypoints +- `roles/bootstrap/`, `roles/docker/`, `roles/monitoring/`, + `roles/os-updates/`, `roles/healthcheck/`, `roles/hawser/`, + `roles/manage-ssh-keys/` - role implementations +- `roles/*/README.md` - per-role documentation (documentation duty) +- `inventory/*.yml`, `inventory/group_vars/*.yml` - host data +- `docker/Dockerfile`, `docker/requirements.yml`, `execute.sh` - runtime +- `vault.yml` (encrypted), `.vault_pass`, `setenv.sh`, `.zabbix-psk/`, + `logs/` - secrets and audit artifacts (git-ignored except vault.yml) + +## Suggested Wiki Updates + +- Populated during this scan: technical docs (architecture, codebase map, + module docs, integrations, security, deployment, testing), requirement + records, open questions reconciliation, alerts ALERT-20260909-001 / + ALERT-20260909-002, traceability maps, indexes, REGISTRY.yml, STATUS.md. diff --git a/.project-wiki/intake/INDEX.md b/.project-wiki/intake/INDEX.md new file mode 100644 index 0000000..bd1ef8f --- /dev/null +++ b/.project-wiki/intake/INDEX.md @@ -0,0 +1,28 @@ +# Document Intake + +This section stores external source documents imported for project-wiki update workflows. Intake documents are provenance, not canonical project knowledge until accepted information is integrated into requirements, changes, technical docs, implementation docs, or traceability maps. + +## When To Read + +- Process a new PDF, DOCX, text, or Markdown requirements document. +- Review the source provenance behind a requirement, CR, ADR, or open question. +- Investigate conflicts between an imported document and the current KB or as-is technical state. + +## Routing Rules + +- Read active and reviewed intake documents only when working on document-based updates, provenance, audits, or conflict investigation. +- Read the compact `intake-report.md`, then use `review_progress.py inspect` for structure and coverage. Do not load `chunks.json`, `review-progress.yml`, or generated chunk wrappers directly unless troubleshooting. +- Use `view --section SEC-NNN` only for reliable source-defined sections reported by `inspect`; review every listed section and unsectioned unit. Use `view --all` whenever structure is absent, incomplete, ambiguous, or full-document context may matter. +- After complete coverage, run `review_progress.py audit`; require `review-complete` and record its ledger summary and SHA-256. Rerun after ledger corrections. +- Before a terminal intake status, run `audit --expect-ledger-sha256 ` with the digest from the reviewed audit. +- Do not mark an intake reviewed or integrated until the ledger is complete. Every classified integrated chunk must link to its registered target IDs; every skipped chunk needs a reason. +- Do not read integrated, archived, superseded, or rejected intake documents during normal coding tasks unless explicitly requested. +- Use canonical KB files, not intake documents, as the source of truth after integration. + +## Active Intake Documents + +- None yet. + +## Reviewed, Integrated, Archived, Superseded, Or Rejected Documents + +- None yet. diff --git a/.project-wiki/maintenance/INDEX.md b/.project-wiki/maintenance/INDEX.md new file mode 100644 index 0000000..731b6a2 --- /dev/null +++ b/.project-wiki/maintenance/INDEX.md @@ -0,0 +1,15 @@ +# Maintenance + +This section stores semantic lint and wiki health reports. + +## Latest Report + +- None yet. + +## Schema Migration Reports + +- None yet. + +## Reports + +- None yet. diff --git a/.project-wiki/requirements/INDEX.md b/.project-wiki/requirements/INDEX.md new file mode 100644 index 0000000..ebf3f2d --- /dev/null +++ b/.project-wiki/requirements/INDEX.md @@ -0,0 +1,20 @@ +# Requirements + +This section stores explicit product intent, constraints, and open questions. + +## When To Read + +- Understand confirmed project behavior, scope, constraints, or unresolved product intent. + +## Key Files + +- [Product brief](./product-brief.md) +- [Functional requirements](./functional/INDEX.md) +- [Non-functional requirements](./non-functional/INDEX.md) +- [Constraints](./constraints.md) +- [Open questions](./open-questions.md) + +## Placeholders Or Stale Areas + +- `product-brief.md` is still a placeholder (project identity lives in + [PROJECT.md](../PROJECT.md)); fill or remove during the next update. diff --git a/.project-wiki/requirements/constraints.md b/.project-wiki/requirements/constraints.md new file mode 100644 index 0000000..520b442 --- /dev/null +++ b/.project-wiki/requirements/constraints.md @@ -0,0 +1,47 @@ +--- +id: CONSTRAINTS +type: note +status: active +title: Constraints +created: '2026-09-09' +updated: '2026-09-09' +tags: +- requirements +- constraints +related: +- PROJECT +source_paths: +- AGENTS.md +confidence: confirmed +--- + +# Constraints + +Explicitly documented constraints for this repository (source: `AGENTS.md`). + +## Technical Constraints + +- **Debian-only**: every playbook must check + `ansible_facts['os_family'] == "Debian"` and abort on non-Debian hosts. +- **LXC containers**: tasks affecting `sysctl` or `reboot` must check + `virtualization_type != "lxc"` and be skipped there. +- **Ansible style**: modules use FQCN (`ansible.builtin.apt`, ...); + variable defaults live in `roles//defaults/main.yml`, never hardcoded + in tasks; role logic is split into `tasks/.yml` subtask files with + `tasks/main.yml` containing only imports. +- **Templates** use `# {{ ansible_managed }}` as header. +- **Execution environment**: changes to collections require updating + `docker/requirements.yml` and rebuilding the Docker image. + +## Process Constraints + +- **Documentation duty**: every new feature, parameter, or variable must be + documented in the corresponding README before commit (see `AGENTS.md` + mapping table). +- **Code language**: documentation in German, code (variable names, task + names) in English. +- **Secrets**: sensitive values belong in encrypted `vault.yml`; `.vault_pass`, + `setenv.sh`, `logs/`, and `.zabbix-psk/` are git-ignored and must never be + committed. No plaintext secrets in `defaults/` or `group_vars/`. +- **Testing**: changes are tested in the Docker container (`./execute.sh`), + with `ansible-lint` and `--check` dry runs where sensible. diff --git a/.project-wiki/requirements/functional/INDEX.md b/.project-wiki/requirements/functional/INDEX.md new file mode 100644 index 0000000..3fad3ab --- /dev/null +++ b/.project-wiki/requirements/functional/INDEX.md @@ -0,0 +1,15 @@ +# Functional Requirements + +This index routes to functional requirement topics; atomic REQ records live only in linked topic files. + +## When To Read + +- Work with product capabilities, workflows, integrations, or other functional behavior. + +## Key Files + +- [Host lifecycle operations](./host-lifecycle-operations.md) - REQ-001..REQ-006 (provisioning, docker, monitoring, updates, hawser, ssh keys) + +## Placeholders Or Stale Areas + +- None. diff --git a/.project-wiki/requirements/functional/host-lifecycle-operations.md b/.project-wiki/requirements/functional/host-lifecycle-operations.md new file mode 100644 index 0000000..98ffc72 --- /dev/null +++ b/.project-wiki/requirements/functional/host-lifecycle-operations.md @@ -0,0 +1,163 @@ +--- +id: REQ-FUNC-HOST-LIFECYCLE +type: requirements +status: active +title: Host Lifecycle Operations +created: '2026-09-09' +updated: '2026-09-09' +tags: +- requirements +- functional +related: +- PROJECT +- PRODUCT-BRIEF +source_paths: +- README.md +- AGENTS.md +confidence: confirmed +--- + +# Host Lifecycle Operations + +Explicitly stated functional scope of the collection (source: root +`README.md` purpose statement and playbook table; evidence linked in +[requirement-evidence.yml](../../traceability/requirement-evidence.yml)). + + + +## REQ-001 - Initial provisioning of Debian hosts + +Status: active +Tags: [provisioning, bootstrap] +Related: [MOD-001] +Confidence: confirmed + +### Statement + +The collection provides a complete initial setup ("Ersteinrichtung") for +Debian hosts via `bootstrap.yml` (packages, admin user, SSH, MOTD, sysctl, +Docker, monitoring). + +### Rationale + +README.md: "bootstrap.yml | Vollständige Ersteinrichtung eines Debian-Hosts"; +README purpose statement: "Bereitstellung, Härtung, Aktualisierung und +Überwachung von Debian-Hosts". + +### Acceptance Notes + +- Runs against the three sites in `inventory/`; composes docker and + monitoring roles with skip flags. + + + +## REQ-002 - Docker Engine installation + +Status: active +Tags: [docker] +Related: [MOD-002] +Confidence: confirmed + +### Statement + +Install Docker Engine from the official Docker repository +(`playbooks/docker.yml` / role `docker`). + +### Rationale + +README playbook table: "docker | Docker Engine installieren". + +### Acceptance Notes + +- Official repository, not distribution packages. + + + +## REQ-003 - Zabbix Agent 2 installation and registration + +Status: active +Tags: [monitoring, zabbix] +Related: [MOD-003, TECH-INTEGRATIONS-ZABBIX] +Confidence: confirmed + +### Statement + +Install Zabbix Agent 2 and register the host on the Zabbix server +(`playbooks/monitoring.yml` / role `monitoring`). + +### Rationale + +README playbook table: "monitoring | Zabbix Agent 2 installieren & +registrieren". + +### Acceptance Notes + +- Includes TLS-PSK and Docker plugin support per implementation. + + + +## REQ-004 - Package updates with healthcheck + +Status: active +Tags: [updates, healthcheck] +Related: [MOD-004, MOD-005] +Confidence: confirmed + +### Statement + +Update Debian packages and verify running containers afterwards +(`playbooks/os-updates-deb.yml`: roles `os-updates` + `healthcheck`). + +### Rationale + +README playbook table: "os-updates, healthcheck | Pakete aktualisieren + +Healthcheck". + +### Acceptance Notes + +- Mirror switch, reboot and logging are part of the implemented update flow. + + + +## REQ-005 - Hawser stack update + +Status: active +Tags: [docker, hawser] +Related: [MOD-006] +Confidence: confirmed + +### Statement + +Update the Hawser Docker Compose stack (`playbooks/upgrade-hawser.yml` / +role `hawser`). + +### Rationale + +README playbook table: "hawser | Hawser-Stack aktualisieren". + +### Acceptance Notes + +- None. + + + +## REQ-006 - SSH key hardening + +Status: active +Tags: [ssh, hardening] +Related: [MOD-007] +Confidence: confirmed + +### Statement + +Manage desired and undesired SSH keys on hosts +(`playbooks/hardening/manage-ssh-keys.yml` / role `manage-ssh-keys`). + +### Rationale + +README playbook table: "manage-ssh-keys | SSH-Schlüssel hartieren"; role +name/description in README roles table. + +### Acceptance Notes + +- None. diff --git a/.project-wiki/requirements/non-functional/INDEX.md b/.project-wiki/requirements/non-functional/INDEX.md new file mode 100644 index 0000000..16c5f91 --- /dev/null +++ b/.project-wiki/requirements/non-functional/INDEX.md @@ -0,0 +1,15 @@ +# Non-Functional Requirements + +This index routes to non-functional requirement topics; atomic NFR records live only in linked topic files. + +## When To Read + +- Work with quality attributes, operations, security, resilience, performance, or service levels. + +## Key Files + +- [Operational policies](./operational-policies.md) - NFR-001..NFR-006 (platform, secrets, logging, runtime, documentation, LXC) + +## Placeholders Or Stale Areas + +- None. diff --git a/.project-wiki/requirements/non-functional/operational-policies.md b/.project-wiki/requirements/non-functional/operational-policies.md new file mode 100644 index 0000000..3e623bb --- /dev/null +++ b/.project-wiki/requirements/non-functional/operational-policies.md @@ -0,0 +1,167 @@ +--- +id: NFR-OPERATIONAL-POLICIES +type: requirements +status: active +title: Operational Policies +created: '2026-09-09' +updated: '2026-09-09' +tags: +- requirements +- non-functional +related: +- PROJECT +- CONSTRAINTS +source_paths: +- README.md +- AGENTS.md +confidence: confirmed +--- + +# Operational Policies + +Explicitly stated non-functional requirements (sources: root `README.md`, +`AGENTS.md`; evidence linked in +[requirement-evidence.yml](../../traceability/requirement-evidence.yml)). + + + +## NFR-001 - Debian-only target platforms + +Status: active +Tags: [platform] +Related: [REQ-001] +Confidence: confirmed + +### Statement + +All playbooks must target Debian only and verify +`ansible_facts['os_family'] == "Debian"`, aborting on other systems. + +### Rationale + +README "Voraussetzungen": "Ziel-Hosts: Debian (alle Playbooks prüfen +`os_family == "Debian"`)". + +### Acceptance Notes + +- Exception observed: `playbooks/hardening/manage-ssh-keys.yml` lacks the + guard (tracked in the scan report). + + + +## NFR-002 - Secrets never committed unencrypted + +Status: active +Tags: [security, vault] +Related: [TECH-SECURITY, ALERT-20260909-001] +Confidence: confirmed + +### Statement + +Sensitive values (passwords, API keys) belong in encrypted `vault.yml`; +no plaintext secrets in `defaults/`, `group_vars/`, or commits. + +### Rationale + +README "Secrets & Vault" and "Beitragen" rule 3; AGENTS.md "Secrets" +section. + +### Acceptance Notes + +- Resolved 2026-09-09: `admin_password` moved to encrypted `vault.yml` + (ALERT-20260909-001); no plaintext secrets remain in `defaults/` or + `group_vars/`. + + + +## NFR-003 - Per-host update audit log + +Status: active +Tags: [logging, audit] +Related: [MOD-004, MOD-005] +Confidence: confirmed + +### Statement + +The `os-updates` and `healthcheck` roles write one update log per host to +`logs///update.log` on the controller as a local +audit trail. + +### Rationale + +README "Logging" section. + +### Acceptance Notes + +- `logs/` is git-ignored. + + + +## NFR-004 - Docker-based execution environment + +Status: active +Tags: [runtime, docker] +Related: [TECH-DEPLOYMENT] +Confidence: confirmed + +### Statement + +Playbooks must be runnable from the provided Docker environment without a +local Ansible installation (`execute.sh`, `docker/Dockerfile`). + +### Rationale + +README "Docker-Ausführungsumgebung": "Damit Playbooks nicht lokal +installiert werden müssen, liefert das Repository ein fertiges Docker-Image." + +### Acceptance Notes + +- Collections pinned in `docker/requirements.yml`; image rebuild on + collection changes. + + + +## NFR-005 - Documentation duty for every change + +Status: active +Tags: [documentation, process] +Related: [CONSTRAINTS] +Confidence: confirmed + +### Statement + +Every new feature, parameter, and variable must be documented in the +corresponding role README before commit; playbook changes update the root +README. + +### Rationale + +README "Beitragen" rule 2; AGENTS.md "WICHTIG: Dokumentationspflicht bei +Änderungen". + +### Acceptance Notes + +- None. + + + +## NFR-006 - LXC-aware execution + +Status: active +Tags: [platform, lxc] +Related: [MOD-004] +Confidence: confirmed + +### Statement + +Tasks affecting `sysctl` or `reboot` must be skipped on LXC containers +(`virtualization_type != "lxc"`). + +### Rationale + +AGENTS.md "OS-Unterstützung": "LXC-Container: Tasks, die `sysctl` oder +`reboot` betreffen, müssen ... übersprungen werden." + +### Acceptance Notes + +- Implemented in `roles/os-updates/tasks/reboot.yml`. diff --git a/.project-wiki/requirements/open-questions.md b/.project-wiki/requirements/open-questions.md new file mode 100644 index 0000000..ce990ba --- /dev/null +++ b/.project-wiki/requirements/open-questions.md @@ -0,0 +1,110 @@ +--- +id: OPEN-QUESTIONS +type: note +status: active +title: Open Questions +created: '2026-09-09' +updated: '2026-09-09' +tags: +- requirements +- open-questions +related: +- SCAN-20260909 +- ALERT-20260909-001 +- ALERT-20260909-002 +source_paths: +- AGENTS.md +- README.md +- inventory/group_vars/external.yml +confidence: confirmed +--- + +# Open Questions + +Reconciliation state after the 2026-09-09 scan (workflow: Open Questions +Reconciliation). Anchors are stable; statuses follow the schema +(`open`, `partially-resolved`, `resolved`, ...). + + + +## OQ-001 - Who are the stakeholders/owners of this repository? + +Status: partially-resolved +Updated: 2026-09-09 +Related: [SCAN-20260909] + +Resolution (partial): Operators appear to be "Niklas" and "Dennis" +(SSH key comments in `roles/bootstrap/defaults/main.yml`, +`confidence: inferred`). Formal ownership and roles remain undocumented. + +Remaining question: Who formally owns and operates the playbooks, and who +approves changes? + + + +## OQ-002 - What are the explicit success criteria? + +Status: partially-resolved +Updated: 2026-09-09 +Related: [SCAN-20260909], MOD-005 + +Resolution (partial): For update runs, success is defined by the +`healthcheck` quality gate (all Docker containers running; fails the play +only when `healthcheck_fail_on_unhealthy` is true). No success criteria for +the overall project are documented. + +Remaining question: What defines success beyond a passing update-run gate? + + + +## OQ-003 - Is `AGENTS.md`/`README.md` stale regarding `inventory/dmc12.yml`? + +Status: resolved +Updated: 2026-09-09 +Related: [ALERT-20260909-002], SCAN-20260909 + +Resolution: Yes. Git status shows `D inventory/dmc12.yml` (uncommitted) and +untracked `inventory/cloudlorean.yml`; `dmc12` is now a Proxmox host inside +the `cloudlorean` site. `README.md` (inventory table, examples) and +`AGENTS.md` still reference `dmc12.yml`. Documentation updates and the +commit of the rename are tracked in ALERT-20260909-002. + + + +## OQ-004 - Is the external site's Zabbix passive server correct? + +Status: open +Created: 2026-09-09 +Related: [SCAN-20260909], MOD-003 + +`inventory/group_vars/external.yml` sets +`monitoring_zabbix_passive_servers: ["2a01:4f8:1c1e:62dd::1"]`, which is the +`healthcheck` host's own address. Is a Zabbix server/proxy intentionally +running on that host, or is this a misconfiguration? (`confidence: unknown`) + + + +## OQ-005 - Which Docker template name exists on the Zabbix server? + +Status: open +Created: 2026-09-09 +Related: [SCAN-20260909], MOD-003 + +Role default `monitoring_zabbix_docker_templates: ["Docker by Zabbix agent2"]` +(no space) vs group_vars `["Docker by Zabbix agent 2"]` (with space). One of +the two will fail host linking at registration. Which name does the Zabbix +server actually use? (`confidence: unknown`) + + + +## OQ-006 - Is the committed `admin_password` hash intended or a policy violation? + +Status: resolved +Updated: 2026-09-09 +Related: [ALERT-20260909-001], NFR-002 + +Resolution: Policy violation. Decrypting `vault.yml` during the alert +resolution confirmed it did not define `admin_password` (no override); the +hash existed only as plaintext in role defaults and group_vars. Resolved with +[ALERT-20260909-001]: hash moved into encrypted `vault.yml`, plaintext copies +removed, `bootstrap.yml` loads the vault via `vars_files`. diff --git a/.project-wiki/requirements/product-brief.md b/.project-wiki/requirements/product-brief.md new file mode 100644 index 0000000..a415d3f --- /dev/null +++ b/.project-wiki/requirements/product-brief.md @@ -0,0 +1,18 @@ +--- +id: PRODUCT-BRIEF +type: note +status: placeholder +title: Product Brief +created: '2026-09-09' +updated: '2026-09-09' +tags: +- requirements +- product +related: [] +source_paths: [] +confidence: unknown +--- + +# Product Brief + +No explicit product brief has been captured yet. diff --git a/.project-wiki/sources/INDEX.md b/.project-wiki/sources/INDEX.md new file mode 100644 index 0000000..0e0c18b --- /dev/null +++ b/.project-wiki/sources/INDEX.md @@ -0,0 +1,30 @@ +# Sources + +This section stores raw source documents used by document-based update workflows. Source documents are raw input, not canonical project knowledge. + +## How To Use + +- Drop new PDF, DOCX, text, or Markdown files into [inbox/](./inbox/). +- Run `/project-wiki update`. +- The agent runs the project-wiki deterministic inbox preflight with skip quarantine, registers only files classified `process` in [SOURCE_REGISTRY.yml](./SOURCE_REGISTRY.yml), runs document ingestion, and moves successfully processed files to `processed/YYYY-MM/` when possible. +- Keep human instructions in this file. Do not create `README.md` or other guide files inside `inbox/`. + +## Routing Rules + +- Never read PDF/DOCX source files directly into model context. +- Use source files only through the document ingestion script. +- Treat the inbox preflight report as authoritative for SHA-256 hashes, validated registry and intake-history matches, retry-record selection, inbox duplicates, and quarantine destinations; use agent review only for changed content at a historical path, ambiguous registry history, or explicit reprocessing. +- Use `intake/` artifacts for review and canonical KB integration. +- Ignore inbox housekeeping files such as `README.md`, `.gitkeep`, `.DS_Store`, hidden files, files beginning with `_`, and temporary or partial download files. + +## Inbox + +- No pending source files registered yet. + +## Processed Sources + +- None yet. + +## Failed, Rejected, Ignored, Or Superseded Sources + +- None yet. diff --git a/.project-wiki/sources/SOURCE_REGISTRY.yml b/.project-wiki/sources/SOURCE_REGISTRY.yml new file mode 100644 index 0000000..e122aa3 --- /dev/null +++ b/.project-wiki/sources/SOURCE_REGISTRY.yml @@ -0,0 +1,3 @@ +version: 1 +updated: '2026-09-09' +sources: [] diff --git a/.project-wiki/technical/INDEX.md b/.project-wiki/technical/INDEX.md new file mode 100644 index 0000000..c0979c6 --- /dev/null +++ b/.project-wiki/technical/INDEX.md @@ -0,0 +1,34 @@ +# Technical Documentation + +This section records implemented or observed code behavior and technical structure. + +## When To Read + +- Work with architecture, modules, APIs, data, integrations, tests, deployment, or security. + +## Key Files + +- [Architecture](./architecture.md) +- [Codebase map](./codebase-map.md) +- [Testing](./testing.md) +- [Deployment](./deployment.md) +- [Security](./security.md) + +## Modules + +- [Bootstrap role](./modules/MOD-001-bootstrap-role.md) +- [Docker role](./modules/MOD-002-docker-role.md) +- [Monitoring role (Zabbix Agent 2)](./modules/MOD-003-monitoring-role.md) +- [OS updates role](./modules/MOD-004-os-updates-role.md) +- [Healthcheck role](./modules/MOD-005-healthcheck-role.md) +- [Hawser role](./modules/MOD-006-hawser-role.md) +- [Manage SSH keys role](./modules/MOD-007-manage-ssh-keys-role.md) + +## Integrations + +- [Zabbix](./integrations/zabbix.md) + +## Placeholders Or Stale Areas + +- None. `technical/api/` and `technical/data/` remain empty (no public APIs + or data schemas in an Ansible repository). diff --git a/.project-wiki/technical/architecture.md b/.project-wiki/technical/architecture.md new file mode 100644 index 0000000..4efd391 --- /dev/null +++ b/.project-wiki/technical/architecture.md @@ -0,0 +1,102 @@ +--- +id: TECH-ARCHITECTURE +type: technical +status: active +title: Architecture +created: '2026-09-09' +updated: '2026-09-09' +tags: +- technical +- architecture +- ansible +related: +- TECH-CODEBASE-MAP +- TECH-DEPLOYMENT +- TECH-INTEGRATIONS-ZABBIX +- SCAN-20260909 +source_paths: +- ansible.cfg +- execute.sh +- bootstrap.yml +- playbooks/ +- roles/ +confidence: confirmed +--- + +# Architecture + +## Overview + +A push-based Ansible controller-in-container setup: an interactive Docker +container (built from `debian:trixie-slim`, `ansible-core 2.20.x`) runs the +playbooks from the mounted repository and connects over SSH to Debian hosts +(`user: admin`, `become: true`). No agent is pushed except Zabbix Agent 2. +Unverified claims are marked `confidence: inferred`. + +## Layered View + +``` +Docker runtime (execute.sh) + └─ ansible-playbook -i inventory/.yml + └─ SSH (admin, pipelining, no host-key checking) → Debian hosts + ├─ roles/bootstrap ── composes ──► roles/docker + │ └─► roles/monitoring + ├─ roles/docker (Docker Engine from download.docker.com) + ├─ roles/monitoring (zabbix-agent2 → repo.zabbix.com, PSK, + │ Zabbix API registration via httpapi) + ├─ roles/os-updates (apt full upgrade, reboot, controller logs) + ├─ roles/healthcheck (container quality gate, controller logs) + ├─ roles/hawser (docker_compose_v2, pull: always) + └─ roles/manage-ssh-keys (authorized_keys allow/deny lists) +``` + +## Role Composition + +- `bootstrap` is the only composing role: it imports the `docker` role and + the `monitoring` role unless `skip_docker` / `skip_monitoring` are set. +- `os-updates` and `healthcheck` are paired at playbook level + (`playbooks/os-updates-deb.yml`); healthcheck reuses os-updates logging + defaults (`healthcheck_log_file` derives from `os_update_log_dir`). + +## Inventory Model + +- Three sites: `cloudlorean` (LAN 192.168.212.0/24, groups `debian` + + `proxmox`), `home` (10.13.37.0/24, groups `debian` + `proxmox`), + `external` (IPv6-only, group `debian` only). +- `proxmox` group membership drives reboot deferral in `os-updates` + (`'proxmox' in group_names`); `virtualization_type != lxc` skips kernel + and reboot logic for LXC containers. +- Per-site `group_vars/.yml` override Zabbix connectivity (server, + API URL, proxy), host groups/templates; `group_vars/debian.yml` holds + os-update defaults including the hardcoded + `os_update_version_codename: "trixie"`. +- Secrets: `vault.yml` (encrypted, tracked) provides + `monitoring_zabbix_api_password` (and possibly more; vault not decrypted - + `confidence: unknown`). + +## Controller-Side State + +- `.zabbix-psk/.psk` - per-host PSKs generated once on the + controller and reused across runs (keeps agent and Zabbix registration in + sync). +- `logs///update.log` - append-only update + quality + gate log written via `delegate_to: localhost` / `blockinfile`. + +## Execution Flow (os-updates-deb.yml) + +1. Debian guard (fail non-Debian). +2. `os-updates`: optional mirror rewrite → preflight logging (upgradable + list, start time) → `apt upgrade: full` → kernel-comparison reboot + decision (async reboot + `wait_for_connection`, deferred on `proxmox` + group unless `os_update_reboot_on_proxmox`) → postflight logging. +3. `healthcheck`: docker presence check → container comparison → + `quality_gate` log section → optional failure + (`healthcheck_fail_on_unhealthy`). + +## Dependencies + +- Collections: `ansible.posix`, `community.general`, `community.docker`, + `community.zabbix` (pinned in `docker/requirements.yml`). +- External services: repo.zabbix.com, download.docker.com, Debian mirrors, + Zabbix API (`mon.cloudlorean.de`). See + [Integrations: Zabbix](./integrations/zabbix.md). diff --git a/.project-wiki/technical/codebase-map.md b/.project-wiki/technical/codebase-map.md new file mode 100644 index 0000000..5f35352 --- /dev/null +++ b/.project-wiki/technical/codebase-map.md @@ -0,0 +1,90 @@ +--- +id: TECH-CODEBASE-MAP +type: technical +status: active +title: Codebase Map +created: '2026-09-09' +updated: '2026-09-09' +tags: +- technical +- codebase +related: +- TECH-ARCHITECTURE +- SCAN-20260909 +source_paths: [] +confidence: confirmed +--- + +# Codebase Map + +Ansible repository, no application source code. Structure as of 2026-09-09. + +## Root Files + +| Path | Purpose | +| --- | --- | +| `ansible.cfg` | Global Ansible config: no host key checking, `roles_path=./roles`, `forks=20`, smart gathering, SSH pipelining | +| `bootstrap.yml` | Top-level playbook, binds the `bootstrap` role with Debian guard | +| `execute.sh` | Starts the interactive Docker runtime container (`ansible:deb13`) | +| `vault.yml` | Ansible-Vault encrypted secrets (tracked, encrypted) | +| `setenv.sh` | Proxmox credentials (git-ignored, not in repo) | +| `.vault_pass`, `.zabbix-psk/`, `logs/` | Vault password, per-host PSKs, update logs (git-ignored) | + +## Directories + +``` +docker/ Dockerfile (debian:trixie-slim + ansible-core 2.20.x), + requirements.yml (pinned collections), ansible-functs.sh + (aliases ap/ag/av) +inventory/ cloudlorean.yml, home.yml, external.yml + group_vars/ +playbooks/ docker.yml, monitoring.yml, os-updates-deb.yml, + upgrade-hawser.yml, hardening/manage-ssh-keys.yml +roles/bootstrap/ tasks/ (8 subtasks), templates/ (4), handlers/, README.md +roles/docker/ tasks/ (2), templates/sources.list.j2, handlers/, README.md +roles/hawser/ tasks/ (2), templates/docker-compose.yml.j2, handlers/, README.md +roles/healthcheck/ tasks/main.yml, defaults/, README.md (no handlers/templates) +roles/manage-ssh-keys/ tasks/ (4), handlers/, defaults/, README.md +roles/monitoring/ tasks/ (8), templates/ (2: zabbix_agent2.conf.j2, docker.conf.j2), + handlers/, README.md +roles/os-updates/ tasks/ (6), templates/ (2: sources.list.j2, sources.list-deb822.j2), + handlers/, README.md +``` + +## Entry Points + +| Playbook | Role(s) | Notes | +| --- | --- | --- | +| `bootstrap.yml` | bootstrap (+ docker, monitoring) | Full initial setup | +| `playbooks/docker.yml` | docker | Docker Engine install | +| `playbooks/monitoring.yml` | monitoring | Loads `../vault.yml` | +| `playbooks/os-updates-deb.yml` | os-updates, healthcheck | Update + quality gate | +| `playbooks/upgrade-hawser.yml` | hawser | Compose stack update | +| `playbooks/hardening/manage-ssh-keys.yml` | manage-ssh-keys | No Debian guard, no become | + +## Conventions (Observed) + +- Roles split logic into `tasks/.yml` subtasks; `tasks/main.yml` only + imports/includes (`bootstrap`, `os-updates`) or delegates to a single + subtask (`docker`, `hawser`). +- Newer tasks use FQCN; older tasks (apt, file, blockinfile, set_fact, + include_role) still use short names. +- Per-role README documents variables, templates, handlers, tags. +- Tags observed: `motd`, `ssh`, `bashrc`, `sysctl` (bootstrap), + `monitoring` (monitoring role). + +## Tests + +- No automated test suite. Verification is manual: run in the Docker + container, optional `ansible-lint`, `--check` dry runs. See + [Testing](./testing.md). + +## Inventories + +| Site | File | Groups | Hosts | +| --- | --- | --- | --- | +| cloudlorean | `inventory/cloudlorean.yml` | debian (13), proxmox (2: dmc12, pbs) | 192.168.212.0/24 | +| home | `inventory/home.yml` | debian (6), proxmox (1: tardis) | 10.13.37.0/24 | +| external | `inventory/external.yml` | debian (4) | IPv6-only | + +Per-site `group_vars/.yml` override Zabbix servers, API URL, proxy, +host groups and templates. See [Architecture](./architecture.md). diff --git a/.project-wiki/technical/deployment.md b/.project-wiki/technical/deployment.md new file mode 100644 index 0000000..9a7cd9a --- /dev/null +++ b/.project-wiki/technical/deployment.md @@ -0,0 +1,59 @@ +--- +id: TECH-DEPLOYMENT +type: technical +status: active +title: Deployment (Runtime Environment) +created: '2026-09-09' +updated: '2026-09-09' +tags: +- technical +- deployment +- docker +related: +- TECH-ARCHITECTURE +source_paths: +- execute.sh +- docker/Dockerfile +- docker/requirements.yml +- docker/ansible-functs.sh +- ansible.cfg +confidence: confirmed +--- + +# Deployment (Runtime Environment) + +## Docker Image + +- `docker/Dockerfile`: `debian:trixie-slim`, `TZ=Europe/Berlin`, + `ANSIBLE_VERSION=2.20.6` → `pip3 install ansible-core~=2.20.6`. +- Utilities: nano, git, wget, unzip, curl, python3-pip, tzdata, openssh-client, + ping, vim, less. +- Bakes SSH client policy: `StrictHostKeyChecking no`, + `UserKnownHostsFile /dev/null` (`/etc/ssh/ssh_config.d/99-no-hostkey-check.conf`). +- Installs pinned collections from `docker/requirements.yml`: + `ansible.posix` 2.2.0, `community.general` 13.0.1, `community.docker` + 5.2.0, `community.zabbix` 4.2.0. +- Copies `ansible-functs.sh` into `/root/.bashrc.d/` providing aliases + `ap` (ansible-playbook), `ag` (ansible-galaxy), `av` (ansible-vault). + +## Launch Flow (execute.sh) + +1. Pick next free container name `ansible-`. +2. Ensure IPv6-enabled bridge network `ansible-dualstack` (fails fast with + a clear message if the network exists without IPv6). +3. Run the container detached: repo mounted at `/ansible` (workdir), SSH + keys from the `ansible_ssh_keys` volume into `/root/.ssh`, env + `ANSIBLE_HOST_KEY_CHECKING=False`, `ANSIBLE_CONFIG=./ansible.cfg`. +4. Write a per-container SSH client config, print name/IPv4/IPv6/OS, then + `docker attach` for interactive use. + +## Controller-Side Artifacts + +- `.zabbix-psk/` and `logs/` are written by delegation from the container + into the mounted repo (git-ignored). + +## Operational Notes + +- Changing collections requires updating `docker/requirements.yml` and an + image rebuild (AGENTS.md). +- Default image tag: `ansible:deb13`. diff --git a/.project-wiki/technical/integrations/zabbix.md b/.project-wiki/technical/integrations/zabbix.md new file mode 100644 index 0000000..4b70edf --- /dev/null +++ b/.project-wiki/technical/integrations/zabbix.md @@ -0,0 +1,63 @@ +--- +id: TECH-INTEGRATIONS-ZABBIX +type: technical +status: active +title: Integrations - Zabbix +created: 2026-09-09 +updated: 2026-09-09 +tags: +- technical +- integration +- zabbix +related: +- MOD-003 +source_paths: +- roles/monitoring/ +- inventory/group_vars/ +confidence: confirmed +--- + +# Integrations - Zabbix + +## Agent Side (outbound/inbound to monitored hosts) + +- Package source: `repo.zabbix.com` (release package for Zabbix 7.0 on + Debian, downloaded to `/tmp` and installed). +- Agent: `zabbix-agent2`, listen port 10050, config + `/etc/zabbix/zabbix_agent2.conf`. +- TLS: PSK only (`tls_connect=psk`, `tls_accept=psk`), identity per host, + PSK file `/etc/zabbix/zabbix_agent2.psk`. +- Docker monitoring: plugin config `/etc/zabbix/zabbix_agent2.d/plugins.d/ + docker.conf` pointing at `unix:///var/run/docker.sock`, deployed when the + socket exists. +- Passive/active server lists come from site group_vars + (`monitoring_zabbix_passive_servers`). + +## Controller Side (Zabbix API registration) + +- API endpoint per site: `https://mon.cloudlorean.de/api_jsonrpc.php` + (group_vars); credentials `monitoring_zabbix_api_user: Admin` + + `monitoring_zabbix_api_password` from `vault.yml`. +- Module: `community.zabbix.zabbix_host` over `httpapi`; connection + parameters derived from the API URL with `urlsplit` (scheme, port, path). +- Host object: name = `monitoring_zabbix_host_name` (default + `inventory_hostname`), agent interface IP = `ansible_host`, host groups + and templates per site, `inventory_mode: manual`, TLS PSK identity/value + from the deployed PSK, `status: enabled`, `state: present` (idempotent + register-or-update). +- Optional proxy: when `monitoring_zabbix_proxy` is set, `monitored_by` + switches to the proxy (`gatekeeper.cloudlorean.de` for cloudlorean, + `gatekeeper.meyer-kempen.de` for home). + +## Site Overrides (observed) + +| Site | Passive servers | Proxy | Host groups | +| --- | --- | --- | --- | +| cloudlorean | 192.168.212.1 | gatekeeper.cloudlorean.de (192.168.212.1) | Linux servers, Cloudlorean | +| home | 10.13.37.1 | gatekeeper.meyer-kempen.de (10.13.37.1) | Linux servers, Meyer-Kempen | +| external | 2a01:4f8:1c1e:62dd::1 (see OQ-004) | none | Linux servers, Cloudlorean | + +## Other External Services + +- `download.docker.com` (docker role apt repository), Debian mirrors + (`deb.debian.org`, `security.debian.org` default; site-overridable). diff --git a/.project-wiki/technical/modules/MOD-001-bootstrap-role.md b/.project-wiki/technical/modules/MOD-001-bootstrap-role.md new file mode 100644 index 0000000..1532da0 --- /dev/null +++ b/.project-wiki/technical/modules/MOD-001-bootstrap-role.md @@ -0,0 +1,65 @@ +--- +id: MOD-001 +type: module +status: active +title: Bootstrap Role +created: 2026-09-09 +updated: 2026-09-09 +tags: [module, bootstrap, ansible] +related: [MOD-002, MOD-003, REQ-001] +source_paths: +- roles/bootstrap/ +confidence: confirmed +--- + +# Bootstrap Role + +## Responsibility + +Full initial setup of a Debian host: packages, admin user, SSH, MOTD, +keyboard layout, bashrc, sysctl; then composes Docker and monitoring. + +## Source Paths + +- `roles/bootstrap/tasks/main.yml` - orchestration only +- Subtasks: `install-basicpackages.yml`, `create-admin-user.yml`, + `set-motd.yml`, `set-keyboardlayout.yml`, `install-openssh.yml`, + `configure-ssh.yml`, `add-ssh-keys.yml`, `setup-bashrc.yml`, + `configure-sysctl.yml` +- Templates: `authorized_keys.j2`, `keyboard.j2`, `sshd.conf.j2`, + `sudoers-admin.j2` + +## Public Interfaces + +- Playbook: `bootstrap.yml` (top level, Debian guard, `become`, `user: admin`, + `vars_files: vault.yml`) +- Skip flags: `skip_docker`, `skip_monitoring` (control role composition) +- Tags: `motd`, `ssh`, `bashrc`, `sysctl` + +## Important Flows + +1. Basic packages → admin user (+sudo via `sudoers-admin.j2`, authorized + keys via `authorized_keys.j2`) → MOTD → keyboard layout. +2. OpenSSH install, hardening config (`sshd.conf.j2`), authorized keys. +3. bashrc, sysctl. +4. `import_role: docker` (when `not skip_docker`), then + `import_role: monitoring` (when `not skip_monitoring`). + +## Dependencies + +- Composed roles: [docker](./MOD-002-docker-role.md), + [monitoring](./MOD-003-monitoring-role.md). +- `admin_authorized_keys` (public keys, plaintext OK) from + `defaults/main.yml`; `admin_password` (SHA-512 hash) lives encrypted in + `vault.yml`, loaded via `vars_files` in `bootstrap.yml` (resolved + ALERT-20260909-001). + +## Tests + +- None automated; manual runs via `./execute.sh`. + +## Notes For Agents + +- `sshd` service name differs per host (`ssh_service_name`, set for `gitea` + and `ipam` in `inventory/cloudlorean.yml`). +- Keep `tasks/main.yml` as pure import list when extending. diff --git a/.project-wiki/technical/modules/MOD-002-docker-role.md b/.project-wiki/technical/modules/MOD-002-docker-role.md new file mode 100644 index 0000000..caedf17 --- /dev/null +++ b/.project-wiki/technical/modules/MOD-002-docker-role.md @@ -0,0 +1,52 @@ +--- +id: MOD-002 +type: module +status: active +title: Docker Role +created: 2026-09-09 +updated: 2026-09-09 +tags: [module, docker, ansible] +related: [MOD-001, MOD-006, REQ-002] +source_paths: +- roles/docker/ +confidence: confirmed +--- + +# Docker Role + +## Responsibility + +Install Docker Engine from the official Docker apt repository. + +## Source Paths + +- `roles/docker/tasks/main.yml` → `install-docker.yml` +- Template: `roles/docker/templates/sources.list.j2` +- Handlers: `roles/docker/handlers/main.yml` + +## Public Interfaces + +- Playbook: `playbooks/docker.yml` +- Defaults: `docker_mirror` (download.docker.com/linux/debian), + `os_version_codename` (derived from `ansible_facts['distribution_release']`), + `docker_packages` (docker-ce, docker-ce-cli, containerd.io, buildx and + compose plugins). + +## Important Flows + +1. Set up the official Docker apt repository (key + `sources.list.j2`). +2. Install `docker_packages` (handlers restart the service when needed). + +## Dependencies + +- Used standalone (`playbooks/docker.yml`) and composed by + [bootstrap](./MOD-001-bootstrap-role.md). Prerequisite for + [hawser](./MOD-006-hawser-role.md). + +## Tests + +- None automated. + +## Notes For Agents + +- Codename comes from host facts; no LXC-specific handling observed. diff --git a/.project-wiki/technical/modules/MOD-003-monitoring-role.md b/.project-wiki/technical/modules/MOD-003-monitoring-role.md new file mode 100644 index 0000000..ccdfc35 --- /dev/null +++ b/.project-wiki/technical/modules/MOD-003-monitoring-role.md @@ -0,0 +1,77 @@ +--- +id: MOD-003 +type: module +status: active +title: Monitoring Role (Zabbix Agent 2) +created: 2026-09-09 +updated: 2026-09-09 +tags: [module, monitoring, zabbix, ansible] +related: [MOD-001, TECH-INTEGRATIONS-ZABBIX, REQ-003, NFR-002] +source_paths: +- roles/monitoring/ +confidence: confirmed +--- + +# Monitoring Role (Zabbix Agent 2) + +## Responsibility + +Install Zabbix Agent 2, configure TLS-PSK and the Docker plugin, and +register/update the host on the Zabbix server via the API. + +## Source Paths + +- `roles/monitoring/tasks/main.yml` → `validate.yml`, + `install-repository.yml`, `install-agent.yml`, `configure-docker.yml`, + `configure-psk.yml`, `configure-agent.yml`, `register-host.yml` +- Templates: `zabbix_agent2.conf.j2`, `docker.conf.j2` + +## Public Interfaces + +- Playbook: `playbooks/monitoring.yml` (loads `../vault.yml`) +- Tag: `monitoring` +- Key variables (`defaults/main.yml`, overridden per site in + `inventory/group_vars/.yml`): `monitoring_zabbix_version` (7.0), + `monitoring_zabbix_passive_servers`, `monitoring_zabbix_active_servers`, + `monitoring_zabbix_api_url`, `monitoring_zabbix_api_user`, + `monitoring_zabbix_api_password` (vault), `monitoring_zabbix_register_host`, + `monitoring_zabbix_proxy` / `monitoring_zabbix_proxy_address`, + `monitoring_zabbix_host_groups`, `monitoring_zabbix_templates`, + `monitoring_zabbix_docker_templates`, PSK settings + (`monitoring_zabbix_psk_bits: 256`, + `monitoring_zabbix_psk_store: {{ role_path }}/../../.zabbix-psk`). + +## Important Flows + +1. Validate inputs → add repo.zabbix.com repo → install `zabbix-agent2`. +2. Docker plugin config when `/var/run/docker.sock` exists. +3. PSK: generate once on the controller (`openssl rand -hex 32`, umask 077, + `creates:` guard), persist under `.zabbix-psk/.psk` (0700), + validate hex format/length, deploy to `/etc/zabbix/zabbix_agent2.psk` + (0600), notify restart handler. +4. Agent config: TLS connect/accept = PSK, identity = + `monitoring_zabbix_host_name`, passive/active servers, listen port 10050. +5. Registration: `community.zabbix.zabbix_host` over httpapi (connection + params derived from `monitoring_zabbix_api_url` via `urlsplit`), agent + interface from `ansible_host`, template union adds + `monitoring_zabbix_docker_templates` when the Docker socket is present, + `monitored_by` switches to proxy when `monitoring_zabbix_proxy` is set. + +## Dependencies + +- `community.zabbix` collection; vault variable + `monitoring_zabbix_api_password`. +- Composed by [bootstrap](./MOD-001-bootstrap-role.md) unless + `skip_monitoring`. + +## Tests + +- None automated. + +## Notes For Agents + +- PSK store is controller-side state; never commit it (git-ignored). When + renaming a host, migrate/regenerate the PSK, otherwise registration + drifts (documented in AGENTS.md). +- Template name mismatch between role default (`Docker by Zabbix agent2`) + and group_vars (`Docker by Zabbix agent 2`) - see open question OQ-005. diff --git a/.project-wiki/technical/modules/MOD-004-os-updates-role.md b/.project-wiki/technical/modules/MOD-004-os-updates-role.md new file mode 100644 index 0000000..547adc1 --- /dev/null +++ b/.project-wiki/technical/modules/MOD-004-os-updates-role.md @@ -0,0 +1,73 @@ +--- +id: MOD-004 +type: module +status: active +title: OS Updates Role +created: 2026-09-09 +updated: 2026-09-09 +tags: [module, os-updates, ansible] +related: [MOD-005, NFR-003, NFR-006, REQ-004] +source_paths: +- roles/os-updates/ +confidence: confirmed +--- + +# OS Updates Role + +## Responsibility + +Upgrade Debian packages with optional mirror rewrite, kernel-based reboot +handling, and controller-side update logging. + +## Source Paths + +- `roles/os-updates/tasks/main.yml` → `update_mirrors.yml` (optional), + `upgrade_packages.yml` → `logging_preflight.yml`, `reboot.yml`, + `logging_postflight.yml` +- Templates: `sources.list.j2`, `sources.list-deb822.j2` +- Handlers: apt cleanup, apt autoremove + +## Public Interfaces + +- Playbook: `playbooks/os-updates-deb.yml` (chains + [healthcheck](./MOD-005-healthcheck-role.md)) +- Key variables: `os_update_auto_upgrade`, `os_also_update_mirror` (false), + `os_update_mirrors` (main + security mirror), + `os_update_version_codename` (role default: host facts; + `group_vars/debian.yml` hardcodes `trixie`), + `os_update_debian_codenames` (trixie, bookworm, bullseye), + `os_update_reboot_on_proxmox` (false), + `os_update_logging_enabled` (true), `os_update_log_dir` (/ansible/logs), + `os_update_log_file` (logs///update.log). + +## Important Flows + +1. Mirror rewrite (only when `os_also_update_mirror`) with + `ignore_errors: true`; only whitelisted Debian codenames are rewritten in + `sources.list.d`. +2. Preflight logging: refresh cache, record start epoch/ISO, parse + `apt list --upgradable` into names/versions/count. +3. `apt upgrade: full` (notifies cleanup/autoremove handlers). +4. Reboot decision: compare running kernel (`uname -r`) vs newest installed + `linux-image-*`; skipped on LXC (`virtualization_type != lxc`). On + `proxmox` group hosts the reboot is deferred unless + `os_update_reboot_on_proxmox`. Otherwise: async `/sbin/reboot`, + `wait_for_connection` (10s delay, 600s timeout), downtime and kernel + after reboot recorded. +5. Postflight logging: duration, upgrade result, reboot details, kernel + before/after appended to the update log (`delegate_to: localhost`). + +## Dependencies + +- None (ansible.builtin only). Paired with healthcheck at playbook level. + +## Tests + +- None automated. + +## Notes For Agents + +- Logging facts are shared with the healthcheck role via the same log file + and start timestamp conventions. +- Old-style module names (apt, file, blockinfile) remain here; use FQCN for + new tasks. diff --git a/.project-wiki/technical/modules/MOD-005-healthcheck-role.md b/.project-wiki/technical/modules/MOD-005-healthcheck-role.md new file mode 100644 index 0000000..fe12499 --- /dev/null +++ b/.project-wiki/technical/modules/MOD-005-healthcheck-role.md @@ -0,0 +1,59 @@ +--- +id: MOD-005 +type: module +status: active +title: Healthcheck Role +created: 2026-09-09 +updated: 2026-09-09 +tags: [module, healthcheck, docker, ansible] +related: [MOD-004, REQ-004, NFR-003] +source_paths: +- roles/healthcheck/ +confidence: confirmed +--- + +# Healthcheck Role + +## Responsibility + +Post-update quality gate: verify that every Docker container on the host is +running and record the result in the update log. + +## Source Paths + +- `roles/healthcheck/tasks/main.yml` (single file, no subtasks) +- `roles/healthcheck/defaults/main.yml` + +## Public Interfaces + +- Playbook: `playbooks/os-updates-deb.yml` (runs after os-updates) +- Key variables: `healthcheck_fail_on_unhealthy` (default false), + `healthcheck_logging_enabled` (derives from `os_update_logging_enabled`), + `healthcheck_log_file` (reuses os-updates log path conventions). + +## Important Flows + +1. Detect Docker binary (`which docker`, tolerant). +2. If Docker present: list all container names and running container names + (`docker ps -a` / `docker ps --filter status=running`), compute + non-running set. +3. If Docker absent: neutral result (0 containers, passed). +4. Append a `quality_gate:` section (docker_installed, total/running counts, + non-running list, healthcheck_passed) to + `logs///update.log` via `blockinfile` delegated to + localhost. +5. Fail the play when `healthcheck_fail_on_unhealthy` is true and any + container is not running. + +## Dependencies + +- Shares log file conventions with [os-updates](./MOD-004-os-updates-role.md). + +## Tests + +- None automated. + +## Notes For Agents + +- The gate does not fail by default; set `healthcheck_fail_on_unhealthy` + to make failures visible in CI-like runs. diff --git a/.project-wiki/technical/modules/MOD-006-hawser-role.md b/.project-wiki/technical/modules/MOD-006-hawser-role.md new file mode 100644 index 0000000..9e9f65e --- /dev/null +++ b/.project-wiki/technical/modules/MOD-006-hawser-role.md @@ -0,0 +1,57 @@ +--- +id: MOD-006 +type: module +status: active +title: Hawser Role +created: 2026-09-09 +updated: 2026-09-09 +tags: [module, hawser, docker, ansible] +related: [MOD-002, REQ-005] +source_paths: +- roles/hawser/ +confidence: confirmed +--- + +# Hawser Role + +## Responsibility + +Update the Hawser Docker Compose stack on a host that already runs Docker. + +## Source Paths + +- `roles/hawser/tasks/main.yml` → `update-stack.yml` +- Template: `roles/hawser/templates/docker-compose.yml.j2` +- Handlers: `roles/hawser/handlers/main.yml` + +## Public Interfaces + +- Playbook: `playbooks/upgrade-hawser.yml` +- Key variables: `hawser_project_name` (hawser), + `hawser_compose_dir` (/opt/hawser), `hawser_port` (2376), + `hawser_stacks_volume` (hawser_stacks), + `hawser_allow_insecure_no_auth` (true - documented as requiring an + otherwise secured network). + +## Important Flows + +1. Gather Docker host info (`community.docker.docker_host_info`, + `ignore_errors: true`). +2. Require existing compose file at `/docker-compose.yml`; + re-render it from the template. +3. `community.docker.docker_compose_v2` with `state: present`, + `pull: always` to pull latest images and recreate the stack. + +## Dependencies + +- Requires Docker on the target (role skips gracefully when Docker is not + reachable). Collection: `community.docker`. + +## Tests + +- None automated. + +## Notes For Agents + +- `hawser_allow_insecure_no_auth: true` allows token-less standard mode on + non-loopback addresses; treat as a deliberate, documented trade-off. diff --git a/.project-wiki/technical/modules/MOD-007-manage-ssh-keys-role.md b/.project-wiki/technical/modules/MOD-007-manage-ssh-keys-role.md new file mode 100644 index 0000000..1088546 --- /dev/null +++ b/.project-wiki/technical/modules/MOD-007-manage-ssh-keys-role.md @@ -0,0 +1,58 @@ +--- +id: MOD-007 +type: module +status: active +title: Manage SSH Keys Role +created: 2026-09-09 +updated: 2026-09-09 +tags: [module, ssh, hardening, ansible] +related: [REQ-006, ALERT-20260909-001] +source_paths: +- roles/manage-ssh-keys/ +confidence: confirmed +--- + +# Manage SSH Keys Role + +## Responsibility + +Harden SSH access by enforcing an allow list (`good_keys`) and removing +deny-listed keys (`bad_keys`) from the target user's authorized keys. + +## Source Paths + +- `roles/manage-ssh-keys/tasks/main.yml` → `validate-authorized-keys.yml`, + `add-goodkeys.yml`, `remove-badkeys.yml` +- Handlers: `roles/manage-ssh-keys/handlers/main.yml` + +## Public Interfaces + +- Playbook: `playbooks/hardening/manage-ssh-keys.yml` + (binds the role directly; **no Debian guard, no `become`**) +- Key variables: `ssh_user` (admin), `good_keys` (allow list), + `bad_keys` (deny list; supports env-provided JSON overrides via commented + vars in the playbook). +- Conditional execution: `good_keys.defined and good_keys | length > 0` + (same for `bad_keys`). + +## Important Flows + +1. Validate/ensure `~/.ssh` of the logged-in user (0700). +2. Add `good_keys` to authorized keys. +3. Remove `bad_keys` from authorized keys. + +## Dependencies + +- None (ansible.builtin). + +## Tests + +- None automated. + +## Notes For Agents + +- The playbook deviates from the documented convention (Debian guard, + `become: true`); treat as an intentional gap to confirm before adding + more hardening playbooks. +- Keys in `defaults/main.yml` are public keys (not secrets); the same allow + list also exists in `roles/bootstrap/defaults/main.yml`. diff --git a/.project-wiki/technical/security.md b/.project-wiki/technical/security.md new file mode 100644 index 0000000..91dc708 --- /dev/null +++ b/.project-wiki/technical/security.md @@ -0,0 +1,69 @@ +--- +id: TECH-SECURITY +type: technical +status: active +title: Security +created: '2026-09-09' +updated: '2026-09-09' +tags: +- technical +- security +related: +- ALERT-20260909-001 +- ALERT-20260909-002 +- NFR-002 +- MOD-003 +- MOD-007 +source_paths: +- vault.yml +- roles/monitoring/tasks/configure-psk.yml +- roles/bootstrap/defaults/main.yml +- ansible.cfg +confidence: confirmed +--- + +# Security + +## Secrets Handling + +- `vault.yml` (tracked, encrypted) holds `admin_password` (SHA-512 hash), + `monitoring_zabbix_api_password`, and Proxmox credentials + (`PROXMOX_USER_NAME`, `PROXMOX_USER_PASS`); it is loaded via playbook-level + `vars_files` in `bootstrap.yml` and `playbooks/monitoring.yml`. +- Git-ignored (verified): `.vault_pass`, `setenv.sh` (Proxmox credentials), + `.zabbix-psk/` (per-host PSKs), `logs/`. +- Resolved 2026-09-09: the `admin_password` SHA-512 hash that was committed + in plaintext in `roles/bootstrap/defaults/main.yml` and + `inventory/group_vars/external.yml` was moved to encrypted `vault.yml` + (see [ALERT-20260909-001](../alerts/ALERT-20260909-001.md)). + +## SSH Posture + +- Controller/transport: `host_key_checking = False`, + `StrictHostKeyChecking=no`, `UserKnownHostsFile=/dev/null` (ansible.cfg, + execute.sh, Dockerfile). Deliberate for first-time provisioning; README + explicitly warns to restrict this in production. +- Host hardening: `manage-ssh-keys` role enforces allow/deny key lists; + bootstrap deploys hardened `sshd.conf.j2` and per-host authorized keys. +- Per-host exceptions: `ssh_service_name: ssh` on `gitea`/`ipam`. + +## Zabbix Agent TLS + +- PSK-only encryption (TLS connect + accept), 256-bit PSKs generated with + `openssl rand -hex 32` on the controller, persisted with umask 077 in a + 0700 directory, deployed 0600 to the agent host. Identity = host name. + +## Risky Defaults (documented trade-offs) + +- `hawser_allow_insecure_no_auth: true` - Hawser standard mode without token + on non-loopback addresses; comment in defaults demands other network + protections. +- `execute.sh` disables host key checking inside the runtime container. +- Mirror rewrite runs with `ignore_errors: true` in os-updates. + +## Git Hygiene + +- Resolved 2026-09-09: the previously uncommitted change set (including the + `dmc12` → `cloudlorean` inventory rename and stale documentation) was + committed as one coherent change; see + [ALERT-20260909-002](../alerts/ALERT-20260909-002.md). diff --git a/.project-wiki/technical/testing.md b/.project-wiki/technical/testing.md new file mode 100644 index 0000000..f6e3d56 --- /dev/null +++ b/.project-wiki/technical/testing.md @@ -0,0 +1,41 @@ +--- +id: TECH-TESTING +type: technical +status: active +title: Testing +created: '2026-09-09' +updated: 2026-09-09 +tags: +- technical +- testing +related: +- MOD-005 +source_paths: [] +confidence: confirmed +--- + +# Testing + +## Automated Tests + +- None found. The repository has no test suite, no CI configuration, and no + lint config (`confidence: confirmed` for absence in the working tree). + +## Verification Approach (documented in AGENTS.md / README) + +1. Run changes in the Docker container: `./execute.sh` (image + `ansible:deb13`, repo mounted at `/ansible`). +2. Optional lint: `ansible-lint` (recommended by AGENTS.md, not installed in + the image by default). +3. Dry runs with `ansible-playbook --check` where sensible. +4. Role-level documentation duty: update the role README with every + variable/task/template change. + +## Built-In Quality Gates + +- Playbook-level Debian guard (`os_family` check) on all playbooks except + `playbooks/hardening/manage-ssh-keys.yml`. +- `healthcheck` role: post-update container quality gate, optionally failing + the play (`healthcheck_fail_on_unhealthy`, default false). +- `monitoring` role: PSK format/length assertion, role input validation task + (`validate.yml`). diff --git a/.project-wiki/templates/adr.md b/.project-wiki/templates/adr.md new file mode 100644 index 0000000..12f0fec --- /dev/null +++ b/.project-wiki/templates/adr.md @@ -0,0 +1,34 @@ +--- +id: ADR-0001 +type: decision +status: active +title: Short decision title +created: YYYY-MM-DD +updated: YYYY-MM-DD +tags: [] +related: [] +source_paths: [] +confidence: confirmed +--- + +# ADR-0001 - Short Decision Title + +## Context + +TBD + +## Decision + +TBD + +## Alternatives Considered + +- TBD + +## Consequences + +- TBD + +## Related + +- TBD diff --git a/.project-wiki/templates/alert.md b/.project-wiki/templates/alert.md new file mode 100644 index 0000000..b32f481 --- /dev/null +++ b/.project-wiki/templates/alert.md @@ -0,0 +1,40 @@ +--- +id: ALERT-YYYYMMDD-001 +type: alert +status: open +severity: medium +title: Short Alert Title +created: YYYY-MM-DD +updated: YYYY-MM-DD +resolved: null +tags: [alert] +related: [] +source_paths: [] +confidence: inferred +--- + +# ALERT-YYYYMMDD-001 - Short Alert Title + +## Summary + +TBD + +## Why It Matters + +TBD + +## Evidence + +- TBD + +## Suggested Resolution + +TBD + +## Resolution + +Pending. + +## Resolution Evidence + +- TBD diff --git a/.project-wiki/templates/analysis.md b/.project-wiki/templates/analysis.md new file mode 100644 index 0000000..db988f7 --- /dev/null +++ b/.project-wiki/templates/analysis.md @@ -0,0 +1,34 @@ +--- +id: AN-YYYYMMDD-001 +type: analysis +status: active +title: Short Analysis Title +created: YYYY-MM-DD +updated: YYYY-MM-DD +tags: [analysis] +related: [] +source_paths: [] +confidence: inferred +--- + +# Short Analysis Title + +## Why This Was Filed + +TBD + +## Summary + +TBD + +## Related Evidence + +- TBD + +## Implications + +TBD + +## Follow-Up + +- TBD diff --git a/.project-wiki/templates/api-doc.md b/.project-wiki/templates/api-doc.md new file mode 100644 index 0000000..c4af90f --- /dev/null +++ b/.project-wiki/templates/api-doc.md @@ -0,0 +1,38 @@ +--- +id: API-001 +type: api +status: active +title: API Name +created: YYYY-MM-DD +updated: YYYY-MM-DD +tags: [] +related: [] +source_paths: [] +confidence: inferred +--- + +# API Name + +## Purpose + +TBD + +## Endpoints Or Operations + +TBD + +## Contracts + +TBD + +## Authentication And Authorization + +TBD + +## Error Handling + +TBD + +## Related Code + +TBD diff --git a/.project-wiki/templates/change-request.md b/.project-wiki/templates/change-request.md new file mode 100644 index 0000000..5780ee7 --- /dev/null +++ b/.project-wiki/templates/change-request.md @@ -0,0 +1,41 @@ +--- +id: CR-YYYYMMDD-001 +type: change-request +status: active +title: Short change title +created: YYYY-MM-DD +updated: YYYY-MM-DD +tags: [] +related: [] +source_paths: [] +confidence: confirmed +--- + +# CR-YYYYMMDD-001 - Short Change Title + +## Summary + +TBD + +## Source + +- Meeting, document, chat note, or user request: TBD + +## What Changed + +TBD + +## Reason + +TBD + +## Impact + +- Requirements: TBD +- Technical docs: TBD +- Implementation: TBD +- Tests: TBD + +## Follow-Up + +- TBD diff --git a/.project-wiki/templates/document-intake-index.md b/.project-wiki/templates/document-intake-index.md new file mode 100644 index 0000000..bd1ef8f --- /dev/null +++ b/.project-wiki/templates/document-intake-index.md @@ -0,0 +1,28 @@ +# Document Intake + +This section stores external source documents imported for project-wiki update workflows. Intake documents are provenance, not canonical project knowledge until accepted information is integrated into requirements, changes, technical docs, implementation docs, or traceability maps. + +## When To Read + +- Process a new PDF, DOCX, text, or Markdown requirements document. +- Review the source provenance behind a requirement, CR, ADR, or open question. +- Investigate conflicts between an imported document and the current KB or as-is technical state. + +## Routing Rules + +- Read active and reviewed intake documents only when working on document-based updates, provenance, audits, or conflict investigation. +- Read the compact `intake-report.md`, then use `review_progress.py inspect` for structure and coverage. Do not load `chunks.json`, `review-progress.yml`, or generated chunk wrappers directly unless troubleshooting. +- Use `view --section SEC-NNN` only for reliable source-defined sections reported by `inspect`; review every listed section and unsectioned unit. Use `view --all` whenever structure is absent, incomplete, ambiguous, or full-document context may matter. +- After complete coverage, run `review_progress.py audit`; require `review-complete` and record its ledger summary and SHA-256. Rerun after ledger corrections. +- Before a terminal intake status, run `audit --expect-ledger-sha256 ` with the digest from the reviewed audit. +- Do not mark an intake reviewed or integrated until the ledger is complete. Every classified integrated chunk must link to its registered target IDs; every skipped chunk needs a reason. +- Do not read integrated, archived, superseded, or rejected intake documents during normal coding tasks unless explicitly requested. +- Use canonical KB files, not intake documents, as the source of truth after integration. + +## Active Intake Documents + +- None yet. + +## Reviewed, Integrated, Archived, Superseded, Or Rejected Documents + +- None yet. diff --git a/.project-wiki/templates/document-intake-review.md b/.project-wiki/templates/document-intake-review.md new file mode 100644 index 0000000..c94fe1a --- /dev/null +++ b/.project-wiki/templates/document-intake-review.md @@ -0,0 +1,58 @@ +--- +id: DOCIN-YYYYMMDD-001-REVIEW +type: intake-review +status: reviewed +title: Review - Source Document Title +created: YYYY-MM-DD +updated: YYYY-MM-DD +tags: [intake, review] +related: [DOCIN-YYYYMMDD-001] +source_paths: [] +confidence: inferred +--- + +# Document Intake Review - Source Document Title + +## Review Coverage + +- Ledger: [review-progress.yml](./review-progress.yml) +- Total chunks: TBD +- Classified: TBD +- Skipped with reason: TBD +- Pending or reviewed: 0 +- Validator result: passed + +## Blocking Decision Needed + +- Exact question: TBD +- Why conservative integration cannot proceed: TBD +- Approval authority: TBD + +## Options And Consequences + +| Option | Canonical Effect | Risks Or Trade-Offs | Evidence | +| --- | --- | --- | --- | +| TBD | TBD | TBD | TBD | + +## Affected Canonical Scope + +| Candidate | Proposed Target | Action | Rationale | +| --- | --- | --- | --- | +| DOCIN-YYYYMMDD-001-CH-001 | requirements/functional/.md or requirements/non-functional/.md | Add requirement | TBD | + +## Affected Atomic Requirement Plan + +Include this section only when the blocking decision changes requirement topic scope, target files, or permitted ID ranges. Complete the full decomposition plan after approval and before canonical authoring. + +| Family | Topic | Target File | Reserved IDs | Planned Atomic Records | Evidence Chunks | +| --- | --- | --- | --- | ---: | --- | +| functional | Authentication | requirements/functional/authentication.md | REQ-001..REQ-005 | 5 | DOCIN-YYYYMMDD-001-CH-001, DOCIN-YYYYMMDD-001-CH-002 | +| non-functional | Security | requirements/non-functional/security.md | NFR-001..NFR-003 | 3 | DOCIN-YYYYMMDD-001-CH-003 | + +## Related Durable Records + +- Open questions, alerts, blocked records, or alternatives that preserve non-blocking uncertainty: TBD + +## Approval Outcome + +Pending user confirmation. diff --git a/.project-wiki/templates/maintenance-lint.md b/.project-wiki/templates/maintenance-lint.md new file mode 100644 index 0000000..71a5fdf --- /dev/null +++ b/.project-wiki/templates/maintenance-lint.md @@ -0,0 +1,70 @@ +--- +id: LINT-YYYYMMDD +type: maintenance-lint +status: active +title: Wiki Maintenance Lint YYYY-MM-DD +created: YYYY-MM-DD +updated: YYYY-MM-DD +tags: [maintenance, lint] +related: [] +source_paths: [] +confidence: inferred +--- + +# Wiki Maintenance Lint YYYY-MM-DD + +## Deterministic Validation + +- Command: `python3 /path/to/project-wiki/scripts/validate_wiki.py --wiki-root .project-wiki --format json` +- Result: passed | findings-remain | validator-error +- Errors: TBD +- Warnings: TBD +- Unresolved finding codes: TBD + +## Structural Fixes Applied + +- TBD + +## Structural Issues Requiring Review + +- TBD + +## Contradictions + +- TBD + +## Stale Claims + +- TBD + +## Traceability Gaps + +- TBD + +## Orphan Pages + +- TBD + +## Suggested Follow-Up Questions + +| Question | Why It Matters | Related Docs | Suggested Action | +| --- | --- | --- | --- | +| TBD | TBD | TBD | TBD | + +## Missing Source Material + +| Gap | Impact | Related Docs | Suggested Source | +| --- | --- | --- | --- | +| TBD | TBD | TBD | TBD | + +## Risky Assumptions + +| Assumption | Risk | Evidence | Proposed Action | +| --- | --- | --- | --- | +| TBD | TBD | TBD | TBD | + +## Alert Candidates + +| Candidate Alert | Severity | Why It Matters | Related Evidence | +| --- | --- | --- | --- | +| TBD | medium | TBD | TBD | diff --git a/.project-wiki/templates/module-doc.md b/.project-wiki/templates/module-doc.md new file mode 100644 index 0000000..35b05d8 --- /dev/null +++ b/.project-wiki/templates/module-doc.md @@ -0,0 +1,42 @@ +--- +id: MOD-001 +type: module +status: active +title: Module Name +created: YYYY-MM-DD +updated: YYYY-MM-DD +tags: [] +related: [] +source_paths: [] +confidence: inferred +--- + +# Module Name + +## Responsibility + +TBD + +## Source Paths + +- TBD + +## Public Interfaces + +TBD + +## Important Flows + +TBD + +## Dependencies + +TBD + +## Tests + +TBD + +## Notes For Agents + +TBD diff --git a/.project-wiki/templates/requirement.md b/.project-wiki/templates/requirement.md new file mode 100644 index 0000000..2e50287 --- /dev/null +++ b/.project-wiki/templates/requirement.md @@ -0,0 +1,20 @@ + + +## REQ-001 - Requirement Title + +Status: active +Tags: [] +Related: [] +Confidence: confirmed + +### Statement + +TBD + +### Rationale + +TBD + +### Acceptance Notes + +TBD diff --git a/.project-wiki/templates/scan-report.md b/.project-wiki/templates/scan-report.md new file mode 100644 index 0000000..21fe688 --- /dev/null +++ b/.project-wiki/templates/scan-report.md @@ -0,0 +1,38 @@ +--- +id: SCAN-YYYYMMDD +type: scan-report +status: active +title: Codebase Scan YYYY-MM-DD +created: YYYY-MM-DD +updated: YYYY-MM-DD +tags: [scan] +related: [] +source_paths: [] +confidence: inferred +--- + +# Codebase Scan YYYY-MM-DD + +## Scope + +TBD + +## Confirmed Findings + +- TBD + +## Inferred Findings + +- TBD + +## Unknowns + +- TBD + +## Important Paths + +- TBD + +## Suggested Wiki Updates + +- TBD diff --git a/.project-wiki/templates/schema-migration.md b/.project-wiki/templates/schema-migration.md new file mode 100644 index 0000000..b9fde40 --- /dev/null +++ b/.project-wiki/templates/schema-migration.md @@ -0,0 +1,40 @@ +--- +id: MIGRATION-YYYYMMDD +type: schema-migration +status: active +title: Schema Migration YYYY-MM-DD +created: YYYY-MM-DD +updated: YYYY-MM-DD +tags: [maintenance, schema-migration] +related: [] +source_paths: [] +confidence: confirmed +--- + +# Schema Migration YYYY-MM-DD + +## Version Check + +- Previous schema version: TBD +- Target schema version: 1.5.1 +- Migration needed: yes | no + +## Actions Applied + +- TBD + +## Actions Requiring User Confirmation + +- TBD + +## Legacy Items Detected + +- TBD + +## Files Changed + +- TBD + +## Result + +TBD diff --git a/.project-wiki/templates/source-index.md b/.project-wiki/templates/source-index.md new file mode 100644 index 0000000..0e0c18b --- /dev/null +++ b/.project-wiki/templates/source-index.md @@ -0,0 +1,30 @@ +# Sources + +This section stores raw source documents used by document-based update workflows. Source documents are raw input, not canonical project knowledge. + +## How To Use + +- Drop new PDF, DOCX, text, or Markdown files into [inbox/](./inbox/). +- Run `/project-wiki update`. +- The agent runs the project-wiki deterministic inbox preflight with skip quarantine, registers only files classified `process` in [SOURCE_REGISTRY.yml](./SOURCE_REGISTRY.yml), runs document ingestion, and moves successfully processed files to `processed/YYYY-MM/` when possible. +- Keep human instructions in this file. Do not create `README.md` or other guide files inside `inbox/`. + +## Routing Rules + +- Never read PDF/DOCX source files directly into model context. +- Use source files only through the document ingestion script. +- Treat the inbox preflight report as authoritative for SHA-256 hashes, validated registry and intake-history matches, retry-record selection, inbox duplicates, and quarantine destinations; use agent review only for changed content at a historical path, ambiguous registry history, or explicit reprocessing. +- Use `intake/` artifacts for review and canonical KB integration. +- Ignore inbox housekeeping files such as `README.md`, `.gitkeep`, `.DS_Store`, hidden files, files beginning with `_`, and temporary or partial download files. + +## Inbox + +- No pending source files registered yet. + +## Processed Sources + +- None yet. + +## Failed, Rejected, Ignored, Or Superseded Sources + +- None yet. diff --git a/.project-wiki/templates/source-registry.yml b/.project-wiki/templates/source-registry.yml new file mode 100644 index 0000000..cc8758a --- /dev/null +++ b/.project-wiki/templates/source-registry.yml @@ -0,0 +1,3 @@ +version: 1 +updated: YYYY-MM-DD +sources: [] diff --git a/.project-wiki/templates/sync-report.md b/.project-wiki/templates/sync-report.md new file mode 100644 index 0000000..81256c2 --- /dev/null +++ b/.project-wiki/templates/sync-report.md @@ -0,0 +1,38 @@ +--- +id: SYNC-YYYYMMDD-001 +type: sync-report +status: active +title: Manual Code Sync YYYY-MM-DD +created: YYYY-MM-DD +updated: YYYY-MM-DD +tags: [sync] +related: [] +source_paths: [] +confidence: inferred +--- + +# Manual Code Sync YYYY-MM-DD + +## Baseline + +TBD + +## Changed Source Paths + +- TBD + +## Confirmed Wiki Updates + +- TBD + +## Inferred Wiki Updates + +- TBD + +## Open Questions + +- TBD + +## Traceability Updates + +- TBD diff --git a/.project-wiki/templates/wiki-log.md b/.project-wiki/templates/wiki-log.md new file mode 100644 index 0000000..dab3fd5 --- /dev/null +++ b/.project-wiki/templates/wiki-log.md @@ -0,0 +1,33 @@ +# Wiki Log YYYY-MM + +This file is an append-only monthly audit trail of meaningful `.project-wiki/` updates for YYYY-MM. + +## [YYYY-MM-DD] mode | WLOG-YYYYMMDD-001 | Short Summary + +Date: YYYY-MM-DD +Agent: TBD +Mode: init | scan | update | sync | maintain | auto-post-implementation +Trigger: TBD + +Changed wiki documents: +- TBD + +Related source paths: +- TBD + +Related IDs: +- TBD + +Open question reconciliation: +- Resolved: TBD +- Partially resolved: TBD +- Superseded: TBD +- Dismissed: TBD +- Duplicated: TBD +- Newly created: TBD + +Summary: +TBD + +Open questions: +- TBD diff --git a/.project-wiki/templates/work-item.md b/.project-wiki/templates/work-item.md new file mode 100644 index 0000000..208d63b --- /dev/null +++ b/.project-wiki/templates/work-item.md @@ -0,0 +1,38 @@ +--- +id: WI-YYYYMMDD-001 +type: work-item +status: active +title: Short work item title +created: YYYY-MM-DD +updated: YYYY-MM-DD +tags: [] +related: [] +source_paths: [] +confidence: confirmed +--- + +# WI-YYYYMMDD-001 - Short Work Item Title + +## Goal + +TBD + +## Context + +TBD + +## Scope + +TBD + +## Out Of Scope + +TBD + +## Acceptance Notes + +TBD + +## Related Wiki Docs + +- TBD diff --git a/.project-wiki/traceability/INDEX.md b/.project-wiki/traceability/INDEX.md new file mode 100644 index 0000000..a7ea426 --- /dev/null +++ b/.project-wiki/traceability/INDEX.md @@ -0,0 +1,18 @@ +# Traceability + +This section connects requirements, changes, decisions, technical documentation, and source paths. + +## When To Read + +- Analyze implementation coverage or change impact. + +## Key Files + +- [Requirement map](./requirement-map.md) +- [Requirement evidence](./requirement-evidence.yml) +- [Change impact map](./change-impact-map.md) +- [Code map](./code-map.md) + +## Placeholders Or Stale Areas + +- `change-impact-map.md` still placeholder (no change requests yet). diff --git a/.project-wiki/traceability/change-impact-map.md b/.project-wiki/traceability/change-impact-map.md new file mode 100644 index 0000000..321960d --- /dev/null +++ b/.project-wiki/traceability/change-impact-map.md @@ -0,0 +1,3 @@ +# Change Impact Map + +No change impact traceability has been captured yet. diff --git a/.project-wiki/traceability/code-map.md b/.project-wiki/traceability/code-map.md new file mode 100644 index 0000000..e85304d --- /dev/null +++ b/.project-wiki/traceability/code-map.md @@ -0,0 +1,20 @@ +# Code Traceability Map + +Connects wiki documents to source paths. Created during the 2026-09-09 scan. + +| Wiki Document | Source Paths | Notes | +| --- | --- | --- | +| [MOD-001 Bootstrap](../technical/modules/MOD-001-bootstrap-role.md) | `bootstrap.yml`, `roles/bootstrap/` | Composes docker + monitoring roles | +| [MOD-002 Docker](../technical/modules/MOD-002-docker-role.md) | `playbooks/docker.yml`, `roles/docker/` | Official Docker apt repo | +| [MOD-003 Monitoring](../technical/modules/MOD-003-monitoring-role.md) | `playbooks/monitoring.yml`, `roles/monitoring/`, `vault.yml` | Zabbix Agent 2, PSK, API registration | +| [MOD-004 OS Updates](../technical/modules/MOD-004-os-updates-role.md) | `playbooks/os-updates-deb.yml`, `roles/os-updates/` | Mirror switch, reboot, logging | +| [MOD-005 Healthcheck](../technical/modules/MOD-005-healthcheck-role.md) | `playbooks/os-updates-deb.yml`, `roles/healthcheck/` | Container quality gate | +| [MOD-006 Hawser](../technical/modules/MOD-006-hawser-role.md) | `playbooks/upgrade-hawser.yml`, `roles/hawser/` | Compose stack update | +| [MOD-007 Manage SSH Keys](../technical/modules/MOD-007-manage-ssh-keys-role.md) | `playbooks/hardening/manage-ssh-keys.yml`, `roles/manage-ssh-keys/` | No Debian guard (gap) | +| [TECH-DEPLOYMENT](../technical/deployment.md) | `execute.sh`, `docker/`, `ansible.cfg` | Runtime environment | +| [TECH-SECURITY](../technical/security.md) | `vault.yml`, `roles/*/defaults/`, `ansible.cfg`, `roles/monitoring/tasks/configure-psk.yml` | Secrets, SSH, PSK | +| [TECH-INTEGRATIONS-ZABBIX](../technical/integrations/zabbix.md) | `roles/monitoring/`, `inventory/group_vars/` | Zabbix + external services | +| [NFR-003](../requirements/non-functional/operational-policies.md#nfr-003) | `roles/os-updates/tasks/logging_*.yml`, `roles/healthcheck/tasks/main.yml` | Update log audit trail | +| [NFR-006](../requirements/non-functional/operational-policies.md#nfr-006) | `roles/os-updates/tasks/reboot.yml`, `roles/bootstrap/tasks/configure-sysctl.yml` | LXC skips | +| [ALERT-20260909-001](../alerts/ALERT-20260909-001.md) | `roles/bootstrap/defaults/main.yml`, `inventory/group_vars/external.yml`, `bootstrap.yml`, `vault.yml` | Plaintext admin hash - resolved (vaulted) | +| [ALERT-20260909-002](../alerts/ALERT-20260909-002.md) | `inventory/cloudlorean.yml`, `README.md`, `AGENTS.md`, `roles/*/README.md` | Rename committed, stale docs fixed | diff --git a/.project-wiki/traceability/requirement-evidence.yml b/.project-wiki/traceability/requirement-evidence.yml new file mode 100644 index 0000000..08b14e0 --- /dev/null +++ b/.project-wiki/traceability/requirement-evidence.yml @@ -0,0 +1,129 @@ +version: 1 +updated: '2026-09-09' +records: + REQ-001: + requirement: REQ-001 + evidence: + - source: README.md + locator: "Playbooks table: 'bootstrap.yml | bootstrap | Vollständige Ersteinrichtung eines Debian-Hosts'" + type: doc + - source: roles/bootstrap/tasks/main.yml + locator: "import_tasks list + import_role docker/monitoring" + type: code + confidence: confirmed + REQ-002: + requirement: REQ-002 + evidence: + - source: README.md + locator: "Playbooks table: 'playbooks/docker.yml | docker | Docker Engine installieren'" + type: doc + - source: roles/docker/defaults/main.yml + locator: "docker_mirror, docker_packages" + type: code + confidence: confirmed + REQ-003: + requirement: REQ-003 + evidence: + - source: README.md + locator: "Playbooks table: 'playbooks/monitoring.yml | monitoring | Zabbix Agent 2 installieren & registrieren'" + type: doc + - source: roles/monitoring/tasks/register-host.yml + locator: "community.zabbix.zabbix_host registration" + type: code + confidence: confirmed + REQ-004: + requirement: REQ-004 + evidence: + - source: README.md + locator: "Playbooks table: 'playbooks/os-updates-deb.yml | os-updates, healthcheck | Pakete aktualisieren + Healthcheck'" + type: doc + - source: playbooks/os-updates-deb.yml + locator: "include_role os-updates + healthcheck" + type: code + confidence: confirmed + REQ-005: + requirement: REQ-005 + evidence: + - source: README.md + locator: "Playbooks table: 'playbooks/upgrade-hawser.yml | hawser | Hawser-Stack aktualisieren'" + type: doc + - source: roles/hawser/tasks/update-stack.yml + locator: "docker_compose_v2 pull: always" + type: code + confidence: confirmed + REQ-006: + requirement: REQ-006 + evidence: + - source: README.md + locator: "Playbooks table: 'playbooks/hardening/manage-ssh-keys.yml | manage-ssh-keys | SSH-Schlüssel hartieren'" + type: doc + - source: roles/manage-ssh-keys/tasks/main.yml + locator: "validate/add/remove key subtasks" + type: code + confidence: confirmed + NFR-001: + requirement: NFR-001 + evidence: + - source: README.md + locator: "Voraussetzungen: 'Ziel-Hosts: Debian (alle Playbooks prüfen os_family == \"Debian\")'" + type: doc + - source: bootstrap.yml + locator: "fail when os_family != Debian" + type: code + - source: playbooks/hardening/manage-ssh-keys.yml + locator: "no os_family guard (deviation)" + type: code + confidence: confirmed + NFR-002: + requirement: NFR-002 + evidence: + - source: README.md + locator: "Secrets & Vault + Beitragen rule 3" + type: doc + - source: roles/bootstrap/defaults/main.yml + locator: "comment pointing to vault.yml (violation resolved, ALERT-20260909-001)" + type: code + - source: bootstrap.yml + locator: "vars_files: vault.yml (vaulted admin_password)" + type: code + confidence: confirmed + NFR-003: + requirement: NFR-003 + evidence: + - source: README.md + locator: "Logging section: logs///update.log" + type: doc + - source: roles/os-updates/defaults/main.yml + locator: "os_update_log_file template" + type: code + confidence: confirmed + NFR-004: + requirement: NFR-004 + evidence: + - source: README.md + locator: "Docker-Ausführungsumgebung section" + type: doc + - source: execute.sh + locator: "docker run with repo mount /ansible" + type: code + confidence: confirmed + NFR-005: + requirement: NFR-005 + evidence: + - source: AGENTS.md + locator: "Dokumentationspflicht mapping table" + type: doc + - source: README.md + locator: "Beitragen rule 2" + type: doc + confidence: confirmed + NFR-006: + requirement: NFR-006 + evidence: + - source: AGENTS.md + locator: "OS-Unterstützung: LXC sysctl/reboot skip" + type: doc + - source: roles/os-updates/tasks/reboot.yml + locator: "when: ansible_facts['virtualization_type'] != 'lxc'" + type: code + confidence: confirmed diff --git a/.project-wiki/traceability/requirement-map.md b/.project-wiki/traceability/requirement-map.md new file mode 100644 index 0000000..cffab22 --- /dev/null +++ b/.project-wiki/traceability/requirement-map.md @@ -0,0 +1,26 @@ +# Requirement Traceability Map + +Requirement records → implementing source paths → evidence. Created during +the 2026-09-09 scan; detailed evidence in +[requirement-evidence.yml](./requirement-evidence.yml). + +| Requirement | Statement (short) | Implements / Verified By | Evidence | +| --- | --- | --- | --- | +| [REQ-001](../requirements/functional/host-lifecycle-operations.md#req-001) | Initial provisioning via bootstrap | `bootstrap.yml`, `roles/bootstrap/` | README playbook table | +| [REQ-002](../requirements/functional/host-lifecycle-operations.md#req-002) | Docker Engine install | `playbooks/docker.yml`, `roles/docker/` | README playbook table | +| [REQ-003](../requirements/functional/host-lifecycle-operations.md#req-003) | Zabbix Agent 2 install + register | `playbooks/monitoring.yml`, `roles/monitoring/` | README playbook table | +| [REQ-004](../requirements/functional/host-lifecycle-operations.md#req-004) | Package updates + healthcheck | `playbooks/os-updates-deb.yml`, `roles/os-updates/`, `roles/healthcheck/` | README playbook table | +| [REQ-005](../requirements/functional/host-lifecycle-operations.md#req-005) | Hawser stack update | `playbooks/upgrade-hawser.yml`, `roles/hawser/` | README playbook table | +| [REQ-006](../requirements/functional/host-lifecycle-operations.md#req-006) | SSH key hardening | `playbooks/hardening/manage-ssh-keys.yml`, `roles/manage-ssh-keys/` | README playbook table | +| [NFR-001](../requirements/non-functional/operational-policies.md#nfr-001) | Debian-only guard | `bootstrap.yml`, `playbooks/*.yml` (exception: manage-ssh-keys.yml) | README prerequisites | +| [NFR-002](../requirements/non-functional/operational-policies.md#nfr-002) | Secrets only in vault | `vault.yml`, `bootstrap.yml` + `playbooks/monitoring.yml` (`vars_files`) | README secrets section | +| [NFR-003](../requirements/non-functional/operational-policies.md#nfr-003) | Per-host update audit log | `roles/os-updates/tasks/logging_*.yml`, `roles/healthcheck/tasks/main.yml` | README logging section | +| [NFR-004](../requirements/non-functional/operational-policies.md#nfr-004) | Docker-based execution | `execute.sh`, `docker/Dockerfile`, `docker/requirements.yml` | README docker section | +| [NFR-005](../requirements/non-functional/operational-policies.md#nfr-005) | Documentation duty | `roles/*/README.md`, root `README.md` | README contributing, AGENTS.md | +| [NFR-006](../requirements/non-functional/operational-policies.md#nfr-006) | LXC-aware sysctl/reboot | `roles/os-updates/tasks/reboot.yml`, `roles/bootstrap/tasks/configure-sysctl.yml` | AGENTS.md OS support | + +Gaps and deviations: + +- NFR-001: `playbooks/hardening/manage-ssh-keys.yml` has no Debian guard. +- NFR-002: resolved 2026-09-09 - `admin_password` moved to vault.yml + (was ALERT-20260909-001). diff --git a/AGENTS.md b/AGENTS.md index 79079b6..3cdd6a8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,6 +4,16 @@ Richtlinien und Kontext für AI-Agenten (z. B. Mammouth Code, Claude, GPT), die an diesem Ansible-Repository arbeiten. Diese Datei fasst Repository-spezifische Konventionen zusammen, damit Änderungen konsistent und nachvollziehbar bleiben. + +## Project Wiki (Automatic Instructions) + +- Before implementing, modifying, debugging, refactoring, testing, documenting, or planning code, read `.project-wiki/INDEX.md` and follow its routing to the smallest relevant set of wiki documents (automatic context preflight). +- After making source changes, update the affected `.project-wiki/` records (technical docs, traceability maps, section indexes, `REGISTRY.yml`, `STATUS.md`) as part of the same task. +- Use the wiki `sync` mode only for manual or externally changed code; agent-made changes are updated directly (automatic post-implementation wiki update). +- After every meaningful wiki edit, reconcile affected indexes, `REGISTRY.yml`, `STATUS.md`, traceability, and append an entry to `.project-wiki/logs/wiki-log-YYYY-MM.md`. +- Write all wiki content and generated instruction blocks in English; reply to the user in their chat language. + + ## Repository-Übersicht Zentrale Ansible-Sammlung für die Bereitstellung, Härtung, Aktualisierung und @@ -156,7 +166,7 @@ Benötigte Collections (in `docker/requirements.yml`): Default `trixie`. Nicht blind ändern – wird für Template-Ausfüllung der `sources.list` verwendet. - **`ssh_service_name`**: Auf manchen Hosts heißt der Service `ssh`, nicht - `sshd` (siehe `inventory/dmc12.yml`: `gitea`, `ipam`). Beim Hinzufügen + `sshd` (siehe `inventory/cloudlorean.yml`: `gitea`, `ipam`). Beim Hinzufügen neuer Hosts prüfen. - **PSK-Store**: `monitoring` speichert pro Host eine PSK unter `.zabbix-psk/.psk` (git-ignored). Bei Host-Umbenennung PSK diff --git a/README.md b/README.md index ef314be..a770b5c 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ gleichnamige Datei sowie ein Eintrag in `group_vars/`: | Inventory | Datei | group_vars | Beschreibung | |------------------|--------------------|------------------------|-------------------------------------------------| -| DMC12 (Umgebung) | `dmc12.yml` | `debian.yml` | Debian-Hosts & Proxmox-Knoten | +| Cloudlorean | `cloudlorean.yml` | `cloudlorean.yml`, `debian.yml` | Debian-Hosts & Proxmox-Knoten (u. a. `dmc12`) | | Home | `home.yml` | `home.yml` | Heimisches Netzwerk (10.13.37.0/24) | | External | `external.yml` | `external.yml` | Externe Hosts (IPv6) | @@ -73,20 +73,20 @@ Spiegelserver und autorisierte SSH-Keys. ### Aufrufbeispiele ```bash -# Ersteinrichtung aller Hosts im DMC12-Inventory -ansible-playbook -i inventory/dmc12.yml bootstrap.yml +# Ersteinrichtung aller Hosts im Cloudlorean-Inventory +ansible-playbook -i inventory/cloudlorean.yml bootstrap.yml # Nur Docker auf einem einzelnen Host installieren ansible-playbook -i inventory/home.yml playbooks/docker.yml -l rp # OS-Updates inkl. Healthcheck -ansible-playbook -i inventory/dmc12.yml playbooks/os-updates-deb.yml +ansible-playbook -i inventory/cloudlorean.yml playbooks/os-updates-deb.yml # Hawser aktualisieren -ansible-playbook -i inventory/dmc12.yml playbooks/upgrade-hawser.yml +ansible-playbook -i inventory/cloudlorean.yml playbooks/upgrade-hawser.yml # SSH-Keys härtene -ansible-playbook -i inventory/dmc12.yml playbooks/hardening/manage-ssh-keys.yml +ansible-playbook -i inventory/cloudlorean.yml playbooks/hardening/manage-ssh-keys.yml ``` ## Rollen @@ -103,13 +103,14 @@ ansible-playbook -i inventory/dmc12.yml playbooks/hardening/manage-ssh-keys.yml ## Secrets & Vault -Sensible Werte (z. B. `monitoring_zabbix_api_password`, `admin_password`) -liegen verschlüsselt in `vault.yml`. Zum Entschlüsseln wird eine Vault-Passwort -benötigt, die in `.vault_pass` hinterlegt ist (git-ignored). Beim Aufruf muss -Ansible die Vault-Passwort-Datei kennen: +Sensible Werte (`monitoring_zabbix_api_password`, `admin_password`, Proxmox-Zugang) +liegen verschlüsselt in `vault.yml`. `playbooks/monitoring.yml` und `bootstrap.yml` +binden die Datei selbst per `vars_files` ein. Zum Entschlüsseln wird eine +Vault-Passwort benötigt, die in `.vault_pass` hinterlegt ist (git-ignored). +Beim Aufruf muss Ansible die Vault-Passwort-Datei kennen: ```bash -ansible-playbook -i inventory/dmc12.yml --vault-password-file .vault_pass bootstrap.yml +ansible-playbook -i inventory/cloudlorean.yml --vault-password-file .vault_pass bootstrap.yml ``` > **Hinweis:** Die Datei `setenv.sh` enthält Zugangsdaten für Proxmox und ist @@ -125,14 +126,25 @@ git-ignored und dient als lokales Audit-Trail. ## Konfiguration `ansible.cfg` deaktiviert Host-Key-Checking und legt `./roles` als -Rollen-Pfad fest. SSH-Verbindungen verwenden -`StrictHostKeyChecking=no` sowie `/dev/null` als KnownHosts-Datei, was die -Ersteinrichtung neuer Hosts erleichtert – in produktiven Umgebungen -entsprechend restriktiver konfigurieren. +Rollen-Pfad fest. Für schnellere Ausführungen werden standardmäßig 20 parallele +Worker (`forks`), SSH-Pipelining und persistente SSH-Verbindungen +(`ControlPersist=60s`) verwendet. `gathering=smart` vermeidet die erneute +Faktenabfrage innerhalb eines Laufs, ohne einen persistenten Fakten-Cache mit +potenziell veralteten Hostdaten einzuführen. `internal_poll_interval` reduziert +die Wartezeit des Controllers zwischen Worker-Abfragen. + +SSH-Verbindungen verwenden weiterhin `StrictHostKeyChecking=no` sowie `/dev/null` +als KnownHosts-Datei, was die Ersteinrichtung neuer Hosts erleichtert – in +produktiven Umgebungen entsprechend restriktiver konfigurieren. Bei sehr vielen +gleichzeitig verwalteten Hosts kann `forks` über `ANSIBLE_FORKS` oder einen +zusätzlichen Konfigurationswert an die Controller- und Netzwerkressourcen +angepasst werden. Fact-Caching und alternative Strategien wie `free` sind nicht +global aktiviert, da sie bei diesen Playbooks veraltete Fakten oder eine andere +Ausführungsreihenfolge verursachen können. ## Beitragen 1. Änderungen lokal testen – idealerweise über `./execute.sh` im Container. 2. Variablen in `defaults/main.yml` der jeweiligen Rolle pflegen und in der Rollen-README dokumentieren. -3. Keine Secrets unverschlüsselt committen (Vault nutzen). \ No newline at end of file +3. Keine Secrets unverschlüsselt committen (Vault nutzen). diff --git a/ansible.cfg b/ansible.cfg index 1c7f2b4..048c2c6 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -2,6 +2,11 @@ host_key_checking = False roles_path = ./roles:/etc/ansible/roles group_vars = ./group_vars +interpreter_python = auto_silent +forks = 20 +gathering = smart +internal_poll_interval = 0.001 [ssh_connection] -ssh_args = -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ No newline at end of file +pipelining = True +ssh_args = -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ControlMaster=auto -o ControlPersist=60s diff --git a/bootstrap.yml b/bootstrap.yml index 84a0f61..1a53f21 100644 --- a/bootstrap.yml +++ b/bootstrap.yml @@ -1,6 +1,8 @@ - hosts: all become: true user: admin + vars_files: + - vault.yml tasks: - name: Verify if system is Debian debug: diff --git a/docker/Dockerfile b/docker/Dockerfile index 6e2ba27..de47c18 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -14,6 +14,9 @@ RUN apt update && apt install -y --no-install-recommends \ tzdata \ openssh-client \ iputils-ping \ + vim \ + less \ + && apt clean \ && rm -rf /var/lib/apt/lists/* RUN mkdir -p /etc/ssh/ssh_config.d \ diff --git a/docker/requirements.yml b/docker/requirements.yml index 4b46a6e..6a9f4da 100644 --- a/docker/requirements.yml +++ b/docker/requirements.yml @@ -4,4 +4,6 @@ collections: - name: community.general version: 13.0.1 - name: community.docker - version: 5.2.0 \ No newline at end of file + version: 5.2.0 + - name: community.zabbix + version: 4.2.0 \ No newline at end of file diff --git a/execute.sh b/execute.sh index c20dcf5..f882995 100755 --- a/execute.sh +++ b/execute.sh @@ -3,7 +3,6 @@ defaultimage="ansible:deb13" export ANSIBLE_HOST_KEY_CHECKING=False -export ANSIBLE_SSH_ARGS='-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null' export ANSIBLE_CONFIG=./ansible.cfg # Ermittle den nächsten freien Container-Namen @@ -35,7 +34,6 @@ docker run --rm -dit \ --hostname "$container_name" \ --network "$network_name" \ -e ANSIBLE_HOST_KEY_CHECKING=False \ - -e ANSIBLE_SSH_ARGS='-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null' \ -e ANSIBLE_CONFIG=./ansible.cfg \ -v "$(pwd):/ansible" \ -v ansible_ssh_keys:/root/.ssh \ @@ -59,4 +57,3 @@ echo "-----------------------------------" docker attach "$container_name" - diff --git a/inventory/dmc12.yml b/inventory/cloudlorean.yml similarity index 89% rename from inventory/dmc12.yml rename to inventory/cloudlorean.yml index 9fbc4a6..2323fc5 100644 --- a/inventory/dmc12.yml +++ b/inventory/cloudlorean.yml @@ -1,3 +1,8 @@ +cloudlorean: + children: + debian: + proxmox: + debian: hosts: rp: @@ -34,4 +39,4 @@ proxmox: dmc12: ansible_host: 192.168.212.10 pbs: - ansible_host: 192.168.212.11 \ No newline at end of file + ansible_host: 192.168.212.11 diff --git a/inventory/external.yml b/inventory/external.yml index e5aad7e..5064236 100644 --- a/inventory/external.yml +++ b/inventory/external.yml @@ -1,3 +1,7 @@ +external: + children: + debian: + debian: hosts: healthcheck: @@ -7,4 +11,4 @@ debian: teleport: ansible_host: 2a01:4f8:1c19:c7d::1 podcast-linkstack: - ansible_host: 2a01:4f8:c17:de13::1 \ No newline at end of file + ansible_host: 2a01:4f8:c17:de13::1 diff --git a/inventory/group_vars/cloudlorean.yml b/inventory/group_vars/cloudlorean.yml new file mode 100644 index 0000000..4f9d6b2 --- /dev/null +++ b/inventory/group_vars/cloudlorean.yml @@ -0,0 +1,19 @@ +monitoring_zabbix_passive_servers: ["192.168.212.1"] + +# Zabbix server API (password lives in vault.yml as monitoring_zabbix_api_password) +monitoring_zabbix_api_url: "https://mon.cloudlorean.de/api_jsonrpc.php" +monitoring_zabbix_api_user: Admin +monitoring_zabbix_api_validate_certs: true + +# Host registration defaults for the home inventory +monitoring_zabbix_host_groups: ["Linux servers", "Cloudlorean"] +monitoring_zabbix_templates: ["Linux by Zabbix agent"] +monitoring_zabbix_docker_templates: ["Docker by Zabbix agent 2"] + +# --- Zabbix Proxy (optional) ------------------------------------------------- +# Leave empty to have agents talk to the Zabbix server directly. +# Per-site override example (e.g. inventory/group_vars/standort-a.yml): +# monitoring_zabbix_proxy: "fw-standort-a" +# monitoring_zabbix_proxy_address: "10.20.30.1" +monitoring_zabbix_proxy: "gatekeeper.cloudlorean.de" +monitoring_zabbix_proxy_address: "192.168.212.1" \ No newline at end of file diff --git a/inventory/group_vars/external.yml b/inventory/group_vars/external.yml index 7b1c872..8aa7b32 100644 --- a/inventory/group_vars/external.yml +++ b/inventory/group_vars/external.yml @@ -7,5 +7,17 @@ admin_authorized_keys: - key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA+EvtGavPlXfv7b00jSYsXX2+IEsqFWupEs6Rzf5z9q root@ansible" comment: "Generic Ansible Key" -# Admin user password (hashed with SHA-512) -admin_password: "$6$NmIxM3ZbJDsbC1E6$BO8ag1ZyBqbhELjk0ppKV0CLnYXhHDp9oZc.jmkc62N9hjwbXihF1FmvsYgMKKINVcwaE73u2dRO8pnE6yEGH/" \ No newline at end of file +# admin_password lives encrypted in vault.yml (loaded via vars_files in +# bootstrap.yml); no plaintext fallback per repository vault policy. + +monitoring_zabbix_passive_servers: ["2a01:4f8:1c1e:62dd::1"] + +# Zabbix server API (password lives in vault.yml as monitoring_zabbix_api_password) +monitoring_zabbix_api_url: "https://mon.cloudlorean.de/api_jsonrpc.php" +monitoring_zabbix_api_user: Admin +monitoring_zabbix_api_validate_certs: true + +# Host registration defaults for the home inventory +monitoring_zabbix_host_groups: ["Linux servers", "Cloudlorean"] +monitoring_zabbix_templates: ["Linux by Zabbix agent"] +monitoring_zabbix_docker_templates: ["Docker by Zabbix agent 2"] \ No newline at end of file diff --git a/inventory/group_vars/home.yml b/inventory/group_vars/home.yml index d8b2c9a..8162399 100644 --- a/inventory/group_vars/home.yml +++ b/inventory/group_vars/home.yml @@ -1,20 +1,19 @@ monitoring_zabbix_passive_servers: ["10.13.37.1"] -monitoring_zabbix_active_servers: ["10.13.37.1"] # Zabbix server API (password lives in vault.yml as monitoring_zabbix_api_password) -monitoring_zabbix_api_url: "https://zabbix/api_jsonrpc.php" +monitoring_zabbix_api_url: "https://mon.cloudlorean.de/api_jsonrpc.php" monitoring_zabbix_api_user: Admin monitoring_zabbix_api_validate_certs: true # Host registration defaults for the home inventory -monitoring_zabbix_host_groups: ["Linux servers"] -monitoring_zabbix_templates: ["Linux by Zabbix agent", "Zabbix agent2"] -monitoring_zabbix_docker_templates: ["Docker by Zabbix agent2"] +monitoring_zabbix_host_groups: ["Linux servers", "Meyer-Kempen"] +monitoring_zabbix_templates: ["Linux by Zabbix agent"] +monitoring_zabbix_docker_templates: ["Docker by Zabbix agent 2"] # --- Zabbix Proxy (optional) ------------------------------------------------- # Leave empty to have agents talk to the Zabbix server directly. # Per-site override example (e.g. inventory/group_vars/standort-a.yml): # monitoring_zabbix_proxy: "fw-standort-a" # monitoring_zabbix_proxy_address: "10.20.30.1" -monitoring_zabbix_proxy: "" -monitoring_zabbix_proxy_address: "" \ No newline at end of file +monitoring_zabbix_proxy: "gatekeeper.meyer-kempen.de" +monitoring_zabbix_proxy_address: "10.13.37.1" \ No newline at end of file diff --git a/inventory/home.yml b/inventory/home.yml index 77268fd..f816f13 100644 --- a/inventory/home.yml +++ b/inventory/home.yml @@ -1,20 +1,24 @@ home: children: debian: - hosts: - rp: - ansible_host: 10.13.37.11 - paperless: - ansible_host: 10.13.37.30 - homeassistant: - ansible_host: 10.13.37.15 - teleport-jumper: - ansible_host: 10.13.37.50 - idris-dns: - ansible_host: 10.13.37.53 - it-tools: - ansible_host: 10.13.37.16 proxmox: - hosts: - tardis: - ansible_host: 10.13.37.5 \ No newline at end of file + + +debian: + hosts: + rp: + ansible_host: 10.13.37.11 + paperless: + ansible_host: 10.13.37.30 + homeassistant: + ansible_host: 10.13.37.15 + teleport-jumper: + ansible_host: 10.13.37.50 + idris-dns: + ansible_host: 10.13.37.53 + it-tools: + ansible_host: 10.13.37.16 +proxmox: + hosts: + tardis: + ansible_host: 10.13.37.5 \ No newline at end of file diff --git a/playbooks/monitoring.yml b/playbooks/monitoring.yml index 12ac55c..f0f839c 100644 --- a/playbooks/monitoring.yml +++ b/playbooks/monitoring.yml @@ -1,6 +1,8 @@ - hosts: all user: admin become: true + vars_files: + - ../vault.yml tasks: - name: Verify if system is Debian debug: diff --git a/roles/bootstrap/README.md b/roles/bootstrap/README.md index 34b215b..115fb51 100644 --- a/roles/bootstrap/README.md +++ b/roles/bootstrap/README.md @@ -29,7 +29,7 @@ werden: ``` ```bash -ansible-playbook -i inventory/dmc12.yml --vault-password-file .vault_pass bootstrap.yml +ansible-playbook -i inventory/cloudlorean.yml --vault-password-file .vault_pass bootstrap.yml ``` ## Funktionsweise @@ -66,7 +66,7 @@ Handler neu gestartet. | Variable | Typ | Default | Beschreibung | |-------------------------|----------|-------------------|-----------------------------------------------------| | `admin_authorized_keys` | list | siehe `defaults/` | Liste von `{ key, comment }`-Einträgen für `admin` | -| `admin_password` | string | siehe `defaults/` | SHA-512-Hash des Admin-Passworts | +| `admin_password` | string | – (Vault) | SHA-512-Hash des Admin-Passworts; liegt verschlüsselt in `vault.yml` und wird von `bootstrap.yml` per `vars_files` geladen | ### Steuer-Variablen (keine Defaults, optional setzen) @@ -78,7 +78,7 @@ Handler neu gestartet. `admin_authorized_keys` wird auch pro Inventory in `group_vars/external.yml` überschrieben. Der Handler `Restart sshd` verwendet `ssh_service_name`, sofern -gesetzt (z. B. `ssh` für `gitea`/`ipam` in `inventory/dmc12.yml`). +gesetzt (z. B. `ssh` für `gitea`/`ipam` in `inventory/cloudlorean.yml`). ## Templates @@ -102,7 +102,7 @@ gesetzt (z. B. `ssh` für `gitea`/`ipam` in `inventory/dmc12.yml`). Teile, z. B.: ```bash -ansible-playbook bootstrap.yml -i inventory/dmc12.yml -t ssh +ansible-playbook bootstrap.yml -i inventory/cloudlorean.yml -t ssh ``` ## Abhängigkeiten diff --git a/roles/bootstrap/defaults/main.yml b/roles/bootstrap/defaults/main.yml index 5c3a2bc..ab2b8cb 100644 --- a/roles/bootstrap/defaults/main.yml +++ b/roles/bootstrap/defaults/main.yml @@ -9,5 +9,5 @@ admin_authorized_keys: - key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA+EvtGavPlXfv7b00jSYsXX2+IEsqFWupEs6Rzf5z9q root@ansible" comment: "Generic Ansible Key" -# Admin user password (hashed with SHA-512) -admin_password: "$6$NmIxM3ZbJDsbC1E6$BO8ag1ZyBqbhELjk0ppKV0CLnYXhHDp9oZc.jmkc62N9hjwbXihF1FmvsYgMKKINVcwaE73u2dRO8pnE6yEGH/" +# admin_password lives encrypted in vault.yml (loaded via vars_files in +# bootstrap.yml); no plaintext fallback per repository vault policy. diff --git a/roles/docker/README.md b/roles/docker/README.md index d3923b7..f8683a5 100644 --- a/roles/docker/README.md +++ b/roles/docker/README.md @@ -29,7 +29,7 @@ Einbindung per `import_role`: ``` ```bash -ansible-playbook -i inventory/dmc12.yml playbooks/docker.yml +ansible-playbook -i inventory/cloudlorean.yml playbooks/docker.yml ``` ## Funktionsweise @@ -52,7 +52,7 @@ ansible-playbook -i inventory/dmc12.yml playbooks/docker.yml | Variable | Typ | Default | Beschreibung | |----------------------|---------|--------------------------------------|-------------------------------------------------------| | `docker_mirror` | string | `https://download.docker.com/linux/debian` | Basis-URL des Docker-APT-Repositories | -| `os_version_codename`| string | `{{ ansible_lsb.codename }}` | Codename der Distribution (für `Suites:`) | +| `os_version_codename`| string | `{{ ansible_facts['distribution_release'] }}` | Codename der Distribution (für `Suites:`) | | `docker_packages` | list | siehe unten | Liste der zu installierenden Docker-Pakete | Default `docker_packages`: @@ -67,7 +67,8 @@ docker_packages: ``` `os_version_codename` wird standardmäßig automatisch anhand von -`ansible_lsb.codename` ermittelt und kann bei Bedarf überschrieben werden. +`ansible_facts['distribution_release']` ermittelt und kann bei Bedarf +überschrieben werden. ## Templates @@ -99,4 +100,4 @@ Die Rolle vergibt keine eigenen Tags. Bei Bedarf über das Playbook steuerbar. - Der Handler `Start Docker` aktiviert und startet den Service, ohne bereits laufende Container zu beeinflussen. - Wird die Rolle innerhalb von `bootstrap` aufgerufen, übernimmt das Top-Level - Playbook das `become`/`user`-Handling. \ No newline at end of file + Playbook das `become`/`user`-Handling. diff --git a/roles/docker/defaults/main.yml b/roles/docker/defaults/main.yml index f0493f3..d47f7da 100644 --- a/roles/docker/defaults/main.yml +++ b/roles/docker/defaults/main.yml @@ -1,8 +1,8 @@ docker_mirror: https://download.docker.com/linux/debian -os_version_codename: "{{ ansible_lsb.codename }}" +os_version_codename: "{{ ansible_facts['distribution_release'] }}" docker_packages: - docker-ce - docker-ce-cli - containerd.io - docker-buildx-plugin - - docker-compose-plugin \ No newline at end of file + - docker-compose-plugin diff --git a/roles/hawser/README.md b/roles/hawser/README.md index 536af56..ebd3976 100644 --- a/roles/hawser/README.md +++ b/roles/hawser/README.md @@ -26,7 +26,7 @@ Template und zieht stets die neuesten Images (`pull: always`). ``` ```bash -ansible-playbook -i inventory/dmc12.yml playbooks/upgrade-hawser.yml +ansible-playbook -i inventory/cloudlorean.yml playbooks/upgrade-hawser.yml ``` ## Funktionsweise diff --git a/roles/healthcheck/README.md b/roles/healthcheck/README.md index df70312..ff6b52f 100644 --- a/roles/healthcheck/README.md +++ b/roles/healthcheck/README.md @@ -28,7 +28,7 @@ Rolle eingebunden und wird in der Regel nicht separat aufgerufen: ``` ```bash -ansible-playbook -i inventory/dmc12.yml playbooks/os-updates-deb.yml +ansible-playbook -i inventory/cloudlorean.yml playbooks/os-updates-deb.yml ``` ## Funktionsweise diff --git a/roles/manage-ssh-keys/README.md b/roles/manage-ssh-keys/README.md index be8c76f..297e48f 100644 --- a/roles/manage-ssh-keys/README.md +++ b/roles/manage-ssh-keys/README.md @@ -23,7 +23,7 @@ erwünschte ("Good") Keys hinzu und entfernt unerwünschte ("Bad") Keys aus der ``` ```bash -ansible-playbook -i inventory/dmc12.yml playbooks/hardening/manage-ssh-keys.yml +ansible-playbook -i inventory/cloudlorean.yml playbooks/hardening/manage-ssh-keys.yml ``` Good/Bad-Keys können per Inventory überschrieben oder über Umgebungsvariablen diff --git a/roles/monitoring/README.md b/roles/monitoring/README.md index 81bcf62..7e5ae31 100644 --- a/roles/monitoring/README.md +++ b/roles/monitoring/README.md @@ -27,7 +27,7 @@ einschließlich Template-Verlinkung. ``` ```bash -ansible-playbook -i inventory/dmc12.yml --vault-password-file .vault_password \ +ansible-playbook -i inventory/cloudlorean.yml --vault-password-file .vault_password \ playbooks/monitoring.yml ``` @@ -50,14 +50,16 @@ Tag `monitoring`): anlegen, `docker.conf` aus Template ausliefern. Falls nein: Plugin-Config entfernen. 5. **`configure-psk.yml`** – stellt PSK-Store auf dem Controller sicher - (`/../.zabbix-psk`), generiert pro Host eine PSK mit + (im Repository unter `.zabbix-psk`), generiert pro Host eine PSK mit `openssl rand -hex`, persistiert sie im Store und verteilt sie auf den Zielhost (`/etc/zabbix/zabbix_agent2.psk`, Mode `0600`). 6. **`configure-agent.yml`** – schreibt `zabbix_agent2.conf` aus Template. 7. **`register-host.yml`** – registriert bzw. aktualisiert den Host über die - Zabbix-API (`community.zabbix.zabbix_host`). Verknüpft Standard-Templates - und ggf. Docker-Templates. Unterscheidet zwischen Proxy- und - Server-Modus (zwei Tasks, gegeneinander exklusiv). Delegiert an localhost. + Zabbix-API (`community.zabbix.zabbix_host` via `httpapi`-Connection). + Verknüpft Standard-Templates und ggf. Docker-Templates. Wählt + `monitored_by: proxy` bzw. `zabbix_server` anhand von + `monitoring_zabbix_proxy`. + Delegiert an localhost. ## Variablen @@ -92,10 +94,10 @@ Tag `monitoring`): |-----------------------------------------|--------|--------------------------------------|---------------------------------------------------------| | `monitoring_zabbix_tls_connect` | string | `psk` | TLS-Modus für ausgehende Verbindungen | | `monitoring_zabbix_tls_accept` | string | `psk` | TLS-Modus für eingehende Verbindungen | -| `monitoring_zabbix_psk_identity` | string | `{{ inventory_hostname }}` | PSK-Identity (hostname) | +| `monitoring_zabbix_psk_identity` | string | `{{ monitoring_zabbix_host_name }}` | PSK-Identity; muss mit der in Zabbix registrierten Identity übereinstimmen | | `monitoring_zabbix_psk_file` | string | `/etc/zabbix/zabbix_agent2.psk` | Pfad zur PSK-Datei auf dem Zielhost | | `monitoring_zabbix_psk_bits` | int | `256` | PSK-Länge in Bits (32 Byte → 64 Hex-Chars) | -| `monitoring_zabbix_psk_store` | string | `{{ playbook_dir }}/../.zabbix-psk` | PSK-Store-Verzeichnis auf dem Controller | +| `monitoring_zabbix_psk_store` | string | `{{ role_path }}/../../.zabbix-psk` | PSK-Store-Verzeichnis auf dem Controller | | `monitoring_zabbix_psk_store_mode` | string | `0700` | Berechtigung des PSK-Stores | ### Zabbix Proxy (optional) @@ -109,7 +111,7 @@ Tag `monitoring`): | Variable | Typ | Default | Beschreibung | |---------------------------------------------|--------|--------------------------------------|---------------------------------------------------------| -| `monitoring_zabbix_api_url` | string | `https://zabbix/api_jsonrpc.php` | API-URL | +| `monitoring_zabbix_api_url` | string | `https://zabbix/api_jsonrpc.php` | API-URL (wird via `urlsplit` in Host, Port, Pfad zerlegt) | | `monitoring_zabbix_api_user` | string | `Admin` | API-Benutzer | | `monitoring_zabbix_api_password` | string | (Vault) | API-Passwort – MUSS in `vault.yml` gesetzt werden | | `monitoring_zabbix_api_validate_certs` | bool | `true` | TLS-Zertifikate prüfen | @@ -151,7 +153,7 @@ Passive-Servers mit Proxy-Adresse, setzt `ServerActive` auf Proxy). `monitoring` – alle Subtasks sind mit diesem Tag versehen: ```bash -ansible-playbook -i inventory/dmc12.yml playbooks/monitoring.yml -t monitoring +ansible-playbook -i inventory/cloudlorean.yml playbooks/monitoring.yml -t monitoring ``` ## Abhängigkeiten @@ -163,14 +165,21 @@ ansible-playbook -i inventory/dmc12.yml playbooks/monitoring.yml -t monitoring ## Hinweise - **PSK-Store**: Pro Host wird eine PSK unter - `/../.zabbix-psk/.psk` (git-ignored) gespeichert. + `/.zabbix-psk/.psk` (git-ignored) gespeichert. Bei Host-Umbenennung PSK migrieren oder neu generieren – sonst stimmt die Zabbix-Registrierung nicht. +- **PSK-Identity**: Die Agent-Konfiguration und die Zabbix-Hostkonfiguration + verwenden dieselbe Identity. Wenn `monitoring_zabbix_host_name` überschrieben + wird, wird die Identity standardmäßig automatisch daran angepasst. - **API-Passwort** MUSS in `vault.yml` als `monitoring_zabbix_api_password` gesetzt sein; die Validate-Tasks brechen sonst ab. - **Docker-Plugin** wird nur konfiguriert, wenn der Socket existiert – sonst wird eine eventuell vorhandene Plugin-Config entfernt (Idempotenz). -- Die Registrierung wird **delegiert an localhost** ausgeführt, da der - Controller den API-Zugang besitzt. -- Proxy- und Server-Modus schließen sich gegenseitig aus (zwei Tasks mit - entgegengesetzten `when`-Bedingungen). \ No newline at end of file +- Die Registrierung nutzt die `httpapi`-Connection + (`ansible_network_os: community.zabbix.zabbix`, `ansible_connection: httpapi`) + und läuft direkt vom Controller aus – kein `delegate_to` nötig. + Die API-URL wird per `urlsplit` in Host, Port, SSL und Pfad zerlegt; + User/Passwort kommen als `ansible_user`/`ansible_password` in die + Task-Vars. +- Proxy- und Server-Modus werden über `monitored_by` gesteuert + (`proxy` bei gesetztem `monitoring_zabbix_proxy`, sonst `zabbix_server`). diff --git a/roles/monitoring/defaults/main.yml b/roles/monitoring/defaults/main.yml index 8b790f6..ee911f1 100644 --- a/roles/monitoring/defaults/main.yml +++ b/roles/monitoring/defaults/main.yml @@ -17,11 +17,11 @@ monitoring_zabbix_docker_endpoint: "unix://{{ monitoring_zabbix_docker_socket_pa # --- TLS / PSK ---------------------------------------------------------------- monitoring_zabbix_tls_connect: psk # outgoing connections from agent: psk monitoring_zabbix_tls_accept: psk # incoming connections to agent: psk -monitoring_zabbix_psk_identity: "{{ inventory_hostname }}" +monitoring_zabbix_psk_identity: "{{ monitoring_zabbix_host_name }}" monitoring_zabbix_psk_file: /etc/zabbix/zabbix_agent2.psk monitoring_zabbix_psk_bits: 256 # 32 bytes -> 64 hex chars # Directory on the Ansible controller used to persist per-host PSKs across runs. -monitoring_zabbix_psk_store: "{{ playbook_dir }}/../.zabbix-psk" +monitoring_zabbix_psk_store: "{{ role_path }}/../../.zabbix-psk" monitoring_zabbix_psk_store_mode: "0700" # --- Zabbix Proxy (optional) -------------------------------------------------- @@ -45,4 +45,4 @@ monitoring_zabbix_host_inventory_mode: manual # Templates linked to every host monitoring_zabbix_templates: ["Linux by Zabbix agent", "Zabbix agent2"] # Templates added when a Docker socket is present on the host -monitoring_zabbix_docker_templates: ["Docker by Zabbix agent2"] \ No newline at end of file +monitoring_zabbix_docker_templates: ["Docker by Zabbix agent2"] diff --git a/roles/monitoring/tasks/configure-psk.yml b/roles/monitoring/tasks/configure-psk.yml index e7f6126..e7a7634 100644 --- a/roles/monitoring/tasks/configure-psk.yml +++ b/roles/monitoring/tasks/configure-psk.yml @@ -12,6 +12,13 @@ become: false run_once: false +- name: Show PSK store path + ansible.builtin.debug: + msg: "Persisting PSKs on the controller in {{ monitoring_zabbix_psk_store }}" + delegate_to: localhost + become: false + changed_when: false + - name: Check whether PSK already exists on controller ansible.builtin.stat: path: "{{ monitoring_zabbix_psk_store }}/{{ inventory_hostname }}.psk" @@ -37,6 +44,17 @@ monitoring_zabbix_psk_store ~ '/' ~ inventory_hostname ~ '.psk') | trim }} +- name: Validate PSK format + ansible.builtin.assert: + that: + - monitoring_zabbix_psk_value is match('^[0-9a-fA-F]+$') + - monitoring_zabbix_psk_value | length == (monitoring_zabbix_psk_bits / 4) | int + fail_msg: >- + The PSK must contain exactly {{ (monitoring_zabbix_psk_bits / 4) | int }} + hexadecimal characters. + delegate_to: localhost + become: false + - name: Ensure Zabbix agent runtime directory exists ansible.builtin.file: path: "{{ monitoring_zabbix_psk_file | dirname | default('/etc/zabbix') }}" @@ -52,4 +70,4 @@ owner: "{{ monitoring_zabbix_agent_user }}" group: "{{ monitoring_zabbix_agent_user }}" mode: '0600' - notify: Restart Zabbix Agent \ No newline at end of file + notify: Restart Zabbix Agent diff --git a/roles/monitoring/tasks/register-host.yml b/roles/monitoring/tasks/register-host.yml index 77e24a7..1064f01 100644 --- a/roles/monitoring/tasks/register-host.yml +++ b/roles/monitoring/tasks/register-host.yml @@ -24,52 +24,36 @@ dns: "{{ inventory_hostname }}" port: "{{ monitoring_zabbix_host_interface_port }}" -- name: Register or update host in Zabbix (monitored by proxy) +- name: Register or update host in Zabbix community.zabbix.zabbix_host: - server_url: "{{ monitoring_zabbix_api_url }}" - login_user: "{{ monitoring_zabbix_api_user }}" - login_password: "{{ monitoring_zabbix_api_password }}" - validate_certs: "{{ monitoring_zabbix_api_validate_certs }}" host_name: "{{ monitoring_zabbix_host_name }}" visible_name: "{{ monitoring_zabbix_host_name }}" host_groups: "{{ monitoring_zabbix_host_groups }}" link_templates: "{{ monitoring_zabbix_link_templates }}" interfaces: "{{ monitoring_zabbix_interfaces }}" inventory_mode: "{{ monitoring_zabbix_host_inventory_mode }}" - monitored_by: proxy - proxy: "{{ monitoring_zabbix_proxy }}" + monitored_by: "{{ 'proxy' if monitoring_zabbix_proxy | length > 0 else 'zabbix_server' }}" + proxy: "{{ monitoring_zabbix_proxy | default(omit) }}" tls_connect: 2 tls_accept: 2 tls_psk_identity: "{{ monitoring_zabbix_psk_identity }}" tls_psk: "{{ monitoring_zabbix_psk_value }}" status: enabled state: present - delegate_to: localhost + vars: + ansible_network_os: community.zabbix.zabbix + ansible_connection: httpapi + ansible_host: "{{ (monitoring_zabbix_api_url | urlsplit).hostname }}" + ansible_httpapi_port: "{{ (monitoring_zabbix_api_url | urlsplit).port | default(443, true) }}" + ansible_httpapi_use_ssl: "{{ (monitoring_zabbix_api_url | urlsplit).scheme == 'https' }}" + ansible_httpapi_validate_certs: "{{ monitoring_zabbix_api_validate_certs }}" + ansible_zabbix_url_path: >- + {{ + (monitoring_zabbix_api_url | urlsplit).path + | regex_replace('^/', '') + | regex_replace('/?api_jsonrpc\.php$', '') + }} + ansible_user: "{{ monitoring_zabbix_api_user }}" + ansible_password: "{{ monitoring_zabbix_api_password }}" become: false - when: - - monitoring_zabbix_register_host | bool - - monitoring_zabbix_proxy | length > 0 - -- name: Register or update host in Zabbix (monitored by server) - community.zabbix.zabbix_host: - server_url: "{{ monitoring_zabbix_api_url }}" - login_user: "{{ monitoring_zabbix_api_user }}" - login_password: "{{ monitoring_zabbix_api_password }}" - validate_certs: "{{ monitoring_zabbix_api_validate_certs }}" - host_name: "{{ monitoring_zabbix_host_name }}" - visible_name: "{{ monitoring_zabbix_host_name }}" - host_groups: "{{ monitoring_zabbix_host_groups }}" - link_templates: "{{ monitoring_zabbix_link_templates }}" - interfaces: "{{ monitoring_zabbix_interfaces }}" - inventory_mode: "{{ monitoring_zabbix_host_inventory_mode }}" - tls_connect: 2 - tls_accept: 2 - tls_psk_identity: "{{ monitoring_zabbix_psk_identity }}" - tls_psk: "{{ monitoring_zabbix_psk_value }}" - status: enabled - state: present - delegate_to: localhost - become: false - when: - - monitoring_zabbix_register_host | bool - - monitoring_zabbix_proxy | length == 0 \ No newline at end of file + when: monitoring_zabbix_register_host | bool diff --git a/roles/os-updates/README.md b/roles/os-updates/README.md index 4e6c0cf..4055cdb 100644 --- a/roles/os-updates/README.md +++ b/roles/os-updates/README.md @@ -2,8 +2,9 @@ Aktualisiert alle installierten Pakete auf Debian-Hosts, optional mit Spiegel-Wechsel (Mirror-Switch) und Codename-Anpassung. Die Rolle führt bei -Bedarf einen Reboot durch (nur bei neuem Kernel, nicht in LXC-Containern) -und schreibt ein detailliertes Update-Log auf den Ansible-Controller. +Bedarf einen Reboot durch (nur bei neuem Kernel, nicht in LXC-Containern oder +standardmäßig auf Proxmox-Hypervisoren) und schreibt ein detailliertes +Update-Log auf den Ansible-Controller. > Siehe auch das Playbook `playbooks/os-updates-deb.yml` (in dem direkt im > Anschluss die `healthcheck`-Rolle läuft) sowie die @@ -26,7 +27,7 @@ und schreibt ein detailliertes Update-Log auf den Ansible-Controller. ``` ```bash -ansible-playbook -i inventory/dmc12.yml playbooks/os-updates-deb.yml +ansible-playbook -i inventory/cloudlorean.yml playbooks/os-updates-deb.yml ``` ## Funktionsweise @@ -51,9 +52,11 @@ ansible-playbook -i inventory/dmc12.yml playbooks/os-updates-deb.yml Pakete, zeichnet Start-Zeitpunkt auf. - `apt upgrade: full` – vollständiges Upgrade aller Pakete. Benachrichtigt Handler `apt cleanup` und `apt autoremove`. - - `reboot.yml` – entscheidet über Reboot-Vergleich von laufendem und - installiertem Kernel, rebootet asynchron, wartet auf Wiederkehr, - erfasst Downtime. Wird in LXC-Containern übersprungen. + - `reboot.yml` – entscheidet über Reboot-Vergleich von laufendem und + installiertem Kernel, rebootet asynchron, wartet auf Wiederkehr, + erfasst Downtime. Auf Hosts in der Inventory-Gruppe `proxmox` wird der + notwendige Reboot standardmäßig nur gemeldet und nicht ausgeführt. + Wird in LXC-Containern übersprungen. - `logging_postflight.yml` – berechnet Dauer, schreibt Log-Eintrag mit allen Fakten in `os_update_log_file` (delegiert an localhost). @@ -62,6 +65,12 @@ ansible-playbook -i inventory/dmc12.yml playbooks/os-updates-deb.yml - Läuft ein älterer Kernel als installiert, gilt `reboot_required: true`. - Der Reboot erfolgt mit `async: 1`, `poll: 0`, gefolgt von `wait_for_connection` (Delay 10s, Timeout 600s). +- Auf Proxmox-Hypervisoren (`'proxmox' in group_names`) wird bei einem + notwendigen Reboot eine Meldung ausgegeben und `reboot_deferred: true` im + Log gesetzt. So kann der Hypervisor kontrolliert außerhalb dieses Laufs + neu gestartet werden. +- Mit `os_update_reboot_on_proxmox: true` kann der automatische Reboot für + einen Proxmox-Host explizit aktiviert werden. - In LXC-Containern (`virtualization_type == "lxc"`) wird der gesamte Reboot-Block übersprungen. @@ -71,6 +80,7 @@ ansible-playbook -i inventory/dmc12.yml playbooks/os-updates-deb.yml |-----------------------------|--------|----------------------------------------------------------|-------------------------------------------------------------| | `os_update_auto_upgrade` | bool | `true` | (Steuer-Vorbereitung) Auto-Upgrade erlauben | | `os_also_update_mirror` | bool | `false` | Spiegel-Wechsel und Codename-Rewrite durchführen | +| `os_update_reboot_on_proxmox` | bool | `false` | Reboot auf Hosts der Inventory-Gruppe `proxmox` zulassen; standardmäßig wird nur gemeldet | | `os_update_logging_enabled`| bool | `true` | Update-Logging aktivieren | | `os_update_log_dir` | string | `/ansible/logs` | Basis-Verzeichnis für Logs | | `os_update_log_inventory` | string | `{{ inventory_file \| basename \| splitext \| first }}` | Inventory-Name (abgeleitet) | @@ -125,4 +135,4 @@ Die Rolle vergibt keine eigenen Tags. - **LXC**: In Containern wird der Reboot-Block komplett übersprungen (`virtualization_type != "lxc"`). - **Idempotenz**: `apt upgrade: full` meldet `changed`, wenn Pakete - aktualisiert wurden; ohne anstehende Updates ist der Lauf "ok". \ No newline at end of file + aktualisiert wurden; ohne anstehende Updates ist der Lauf "ok". diff --git a/roles/os-updates/defaults/main.yml b/roles/os-updates/defaults/main.yml index 661b524..fac612e 100644 --- a/roles/os-updates/defaults/main.yml +++ b/roles/os-updates/defaults/main.yml @@ -1,6 +1,7 @@ # Standardwerte, die überschrieben werden können os_update_auto_upgrade: true os_also_update_mirror: false +os_update_reboot_on_proxmox: false # Logging of OS update runs (preflight package count/list, updated packages, duration) os_update_logging_enabled: true @@ -16,4 +17,4 @@ os_update_debian_codenames: # Only these suites are considered Debian codenames and will be rewritten in sources.list.d - trixie - bookworm - - bullseye \ No newline at end of file + - bullseye diff --git a/roles/os-updates/tasks/logging_postflight.yml b/roles/os-updates/tasks/logging_postflight.yml index 853f3c2..4244d02 100644 --- a/roles/os-updates/tasks/logging_postflight.yml +++ b/roles/os-updates/tasks/logging_postflight.yml @@ -38,9 +38,10 @@ reboot_required: {{ os_update_reboot_required }} reboot_reason: {{ os_update_reboot_reason }} reboot_triggered: {{ os_update_reboot_triggered }} + reboot_deferred: {{ os_update_reboot_deferred }} reboot_started: {{ os_update_reboot_start_iso }} reboot_downtime_seconds: {{ os_update_reboot_downtime_seconds }} kernel_before: {{ os_update_running_kernel.stdout | default('n/a') }} kernel_after: {{ os_update_running_kernel_after }} delegate_to: localhost - when: os_update_logging_enabled | bool \ No newline at end of file + when: os_update_logging_enabled | bool diff --git a/roles/os-updates/tasks/reboot.yml b/roles/os-updates/tasks/reboot.yml index ca65573..c669fac 100644 --- a/roles/os-updates/tasks/reboot.yml +++ b/roles/os-updates/tasks/reboot.yml @@ -34,10 +34,27 @@ - name: Default reboot timing facts set_fact: os_update_reboot_triggered: false + os_update_reboot_deferred: false os_update_reboot_start_iso: "n/a" os_update_reboot_downtime_seconds: 0 os_update_running_kernel_after: "{{ os_update_running_kernel.stdout | default('n/a') }}" +- name: Notify when reboot is deferred on Proxmox + ansible.builtin.debug: + msg: "Reboot required but deferred on Proxmox host {{ ansible_facts['hostname'] }}: {{ os_update_reboot_reason }}" + when: + - os_update_reboot_required | bool + - "'proxmox' in group_names" + - not os_update_reboot_on_proxmox | bool + +- name: Record deferred reboot on Proxmox + ansible.builtin.set_fact: + os_update_reboot_deferred: true + when: + - os_update_reboot_required | bool + - "'proxmox' in group_names" + - not os_update_reboot_on_proxmox | bool + - block: - name: Reboot - Record start time command: date -Iseconds @@ -80,4 +97,6 @@ os_update_reboot_triggered: true os_update_reboot_start_iso: "{{ os_update_reboot_start_iso_raw.stdout }}" os_update_running_kernel_after: "{{ os_update_running_kernel_after_raw.stdout }}" - when: os_update_reboot_required | bool \ No newline at end of file + when: + - os_update_reboot_required | bool + - "'proxmox' not in group_names or os_update_reboot_on_proxmox | bool" diff --git a/roles/os-updates/tasks/upgrade_packages.yml b/roles/os-updates/tasks/upgrade_packages.yml index 2e2c18f..4038ca7 100644 --- a/roles/os-updates/tasks/upgrade_packages.yml +++ b/roles/os-updates/tasks/upgrade_packages.yml @@ -5,6 +5,7 @@ apt: upgrade: full update_cache: yes + install_recommends: true register: os_update_upgrade_result notify: - apt cleanup diff --git a/vault.yml b/vault.yml index 27725a9..a9ebd9d 100644 --- a/vault.yml +++ b/vault.yml @@ -1,11 +1,20 @@ $ANSIBLE_VAULT;1.1;AES256 -31306438393238643764333164633738323833656131303331396336346461623633623531303563 -3261353763303939633537356239633937633536336530340a353462653030306433613062323861 -38313631613134646231346632653366346637316639343764313161303937656535323739636235 -3933646232616635350a383165613538613264343264633466326266646463356463643565336238 -31633237386565653635366164383962386338313739646466636564653338613635653636376665 -65376631373166303438663565623061333133373630626137636266333865363433653666646135 -34396630633265343036623436626439636534656438303265343537353661646335316665303664 -61356135386138633433373466396530373839326664623562373066303039303235386139613065 -39373864333638643862386232313437376631343133353966633130623834633764643838656361 -6538623334323266313139326466326633643363313563333039 +39383431633966343639323161393734386133646231653766326530633465336266643338323438 +3862376636623232666265633861653331623134653936320a353064666435326134306666666534 +65646138646436626438616630626530313831363464323936333166353966383961336334343963 +3061393735316336300a386439383233643330613863363863363433373536666531663638663964 +66386235643833343037326534636330373731343562393035643939393434366431393233623966 +38616164373765326331316364623931306235333362626662643035666533633038313363643164 +30623733613832343561383337653763313365383538393038376661336535386264353537616232 +65363838326165303130316531623232373364376334336539626432626235396134373562386631 +66616534663330633362663030346239663436353263353137646164333939343331616361323837 +62306432636539326139386461326335613965373337616535623466356232636138316235373539 +30323738363639636665346430323964386662353362393737353562353962316233613066303338 +37343764643563303265316634636335353839623736633031633333643832313266386134303465 +64363963616135303835613739383962366339646237353666363730633162333762333237313932 +37636562636164313436356561643134303835613730303161666332396266333761363035373464 +64366636396433346433623236643235393961356334653631336461363530376231356230336461 +36306236363430613961653537376563326638636635306265346264306161333832343030393339 +63393538366432306238666562373731373863383639623662633165373538303166663732343362 +62353861353031333331326431643736313239313564313066336536346531653437393230386637 +653261383537663566303663383762366135