Compare commits

..
17 Commits
Author SHA1 Message Date
DerLinkman 7abcd14c94 updaze 2026-10-17 17:16:53 +02:00
DerLinkman ceda1ae4a6 record alert resolution commit in wiki evidence 2026-09-09 14:14:43 +02:00
DerLinkman 30f3e9a4c6 rename dmc12 inventory to cloudlorean and vault admin_password
- replace inventory/dmc12.yml with cloudlorean.yml (+ group_vars) and update
  all README/AGENTS call examples and the inventory table
- move admin_password hash into encrypted vault.yml; remove plaintext copies
  from roles/bootstrap/defaults and group_vars/external.yml
- bootstrap.yml loads vault.yml via vars_files (same pattern as monitoring)
- monitoring: PSK config/registration updates, docker role defaults,
  os-updates logging/reboot refinements, ansible.cfg/execute.sh tweaks
- add .project-wiki knowledge base and .github agent instructions
2026-09-09 14:14:18 +02:00
DerLinkmanandDerLinkman a31751af1d Monitoring psk (#2)
Co-authored-by: DerLinkman <derlinkman@gmail.com>
Reviewed-on: #2
2026-07-18 19:37:52 +00:00
DerLinkmanandDerLinkman cab59c1658 added readmes (#1)
Co-authored-by: DerLinkman <derlinkman@gmail.com>
Reviewed-on: #1
2026-07-18 19:36:54 +00:00
DerLinkman 8d64bf5982 added AGENTS.md 2026-07-18 21:36:16 +02:00
DerLinkman fb779abd06 Add hawser role and playbook for stack upgrades 2026-07-18 00:08:03 +02:00
DerLinkman 5c8af619d3 Add docker role and playbook for Docker installation 2026-07-18 00:08:02 +02:00
DerLinkman 39511ed69f Add monitoring role and playbook for zabbix agent2 (incl. docker config) 2026-07-18 00:07:59 +02:00
DerLinkman 7455a021ea Rework manage-ssh-keys: default to admin user, use ansible facts for home dir, guard key lists 2026-07-18 00:07:57 +02:00
DerLinkman e510ac1f15 Build out bootstrap role: admin user, sudoers, motd, keyboard, openssh, sysctl, bashrc, ssh keys 2026-07-18 00:07:56 +02:00
DerLinkman fbac01eea9 Refactor os-updates: deb822 support, codename alignment, logging, reboot task, drop major-version upgrade; add healthcheck to update playbook 2026-07-18 00:07:49 +02:00
DerLinkman 8f082ba2bd Add healthcheck role as post-update quality gate for Docker containers 2026-07-18 00:07:46 +02:00
DerLinkman a6a96f1498 Add ansible docker execution environment (Dockerfile, requirements, runner script) 2026-07-18 00:07:44 +02:00
DerLinkman d2e8ce40d0 Add inventory files and group_vars for debian, external and home 2026-07-18 00:07:42 +02:00
DerLinkman 194fcb9ad9 Add ansible vault for secrets 2026-07-18 00:07:40 +02:00
DerLinkman 0ebde24d90 Add group_vars path, ssh connection settings, and ignore vault_pass/logs 2026-07-18 00:07:11 +02:00
123 changed files with 5573 additions and 249 deletions
+13
View File
@@ -0,0 +1,13 @@
# Copilot Instructions
Repository-specific conventions live in `AGENTS.md` at the repository root.
<!-- PROJECT-WIKI:BEGIN -->
## 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.
<!-- PROJECT-WIKI:END -->
+4 -1
View File
@@ -1,5 +1,8 @@
hosts
proxmox.*
.vscode
inventory
.vault_pass
logs
.zabbix-psk
setenv.sh
.agents
+17
View File
@@ -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.
+48
View File
@@ -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-10-11
## 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)
+66
View File
@@ -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.
+373
View File
@@ -0,0 +1,373 @@
version: 1
updated: '2026-10-11'
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 (Runtime Environment)
path: technical/deployment.md
status: active
tags:
- technical
- deployment
- docker
related:
- TECH-ARCHITECTURE
source_paths:
- execute.sh
- docker/Dockerfile
- docker/requirements.yml
- docker/ansible-functs.sh
- ansible.cfg
- README.md
confidence: confirmed
- id: TECH-SECURITY
type: technical
title: Security
path: technical/security.md
status: active
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
- execute.sh
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
+46
View File
@@ -0,0 +1,46 @@
# Project Status
Last updated: 2026-10-11
## 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
- Updated 2026-10-11: Docker launcher uses the caller's local SSH directory
read-only (`SSH_KEY_DIR`, default `$HOME/.ssh`); runtime behavior and usage
are documented in [deployment](./technical/deployment.md) and `README.md`.
- 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).
+6
View File
@@ -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.
@@ -0,0 +1,83 @@
---
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`).
- Resolved within coherent change-set commit `30f3e9a` (2026-09-09).
- `rg -n "admin_password"` shows the variable now only in `vault.yml`
(encrypted) and consumed at `roles/bootstrap/tasks/create-admin-user.yml:9`.
@@ -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` is clean after the commit `30f3e9a` ("rename dmc12
inventory to cloudlorean and vault admin_password", 2026-09-09); the
change set - including the inventory rename and this resolution - is
recorded as one commit.
- README inventory table lists Cloudlorean with `group_vars/cloudlorean.yml`
and `debian.yml`, matching `inventory/` contents.
+22
View File
@@ -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.
+18
View File
@@ -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.
+3
View File
@@ -0,0 +1,3 @@
# Project Changelog
No project changes have been captured yet.
+17
View File
@@ -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.
+18
View File
@@ -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.
@@ -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.
@@ -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/<inventory>/<hostname>/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/<hostname>.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.
+28
View File
@@ -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 <final-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.
+15
View File
@@ -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.
+20
View File
@@ -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.
+47
View File
@@ -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/<name>/defaults/main.yml`, never hardcoded
in tasks; role logic is split into `tasks/<name>.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.
@@ -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.
@@ -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)).
<a id="req-001"></a>
## 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.
<a id="req-002"></a>
## 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.
<a id="req-003"></a>
## 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.
<a id="req-004"></a>
## 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.
<a id="req-005"></a>
## 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.
<a id="req-006"></a>
## 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.
@@ -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.
@@ -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)).
<a id="nfr-001"></a>
## 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).
<a id="nfr-002"></a>
## 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/`.
<a id="nfr-003"></a>
## 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/<inventory>/<hostname>/update.log` on the controller as a local
audit trail.
### Rationale
README "Logging" section.
### Acceptance Notes
- `logs/` is git-ignored.
<a id="nfr-004"></a>
## 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.
<a id="nfr-005"></a>
## 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.
<a id="nfr-006"></a>
## 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`.
@@ -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`, ...).
<a id="oq-001"></a>
## 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?
<a id="oq-002"></a>
## 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?
<a id="oq-003"></a>
## 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.
<a id="oq-004"></a>
## 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`)
<a id="oq-005"></a>
## 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`)
<a id="oq-006"></a>
## 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`.
@@ -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.
+30
View File
@@ -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.
@@ -0,0 +1,3 @@
version: 1
updated: '2026-09-09'
sources: []
+34
View File
@@ -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) - image build, launcher, read-only local SSH keys
- [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).
+102
View File
@@ -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/<site>.yml <playbook>
└─ 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/<site>.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/<hostname>.psk` - per-host PSKs generated once on the
controller and reused across runs (keeps agent and Zabbix registration in
sync).
- `logs/<inventory>/<hostname>/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).
+90
View File
@@ -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/<name>.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/<site>.yml` override Zabbix servers, API URL, proxy,
host groups and templates. See [Architecture](./architecture.md).
+72
View File
@@ -0,0 +1,72 @@
---
id: TECH-DEPLOYMENT
type: technical
status: active
title: Deployment (Runtime Environment)
created: '2026-09-09'
updated: '2026-10-11'
tags:
- technical
- deployment
- docker
related:
- TECH-ARCHITECTURE
source_paths:
- execute.sh
- docker/Dockerfile
- docker/requirements.yml
- docker/ansible-functs.sh
- ansible.cfg
- README.md
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. Resolve `SSH_KEY_DIR` (default `$HOME/.ssh`) and reject a missing or
unreadable directory before invoking Docker. Relative paths are resolved
against the current working directory.
2. Pick next free container name `ansible-<n>`.
3. Ensure IPv6-enabled bridge network `ansible-dualstack` (fails fast with
a clear message if the network exists without IPv6).
4. Run the container detached: repo mounted at `/ansible` (workdir), local
SSH directory bind-mounted read-only at `/root/.ssh`, env
`ANSIBLE_HOST_KEY_CHECKING=False`, `ANSIBLE_CONFIG=./ansible.cfg`.
Abort if `docker run` fails.
5. Print name/IPv4/IPv6/OS, then `docker attach` for interactive use.
The launcher preserves the local SSH configuration and permissions. Standard
key names are discovered by OpenSSH; other filenames require `IdentityFile`
or Ansible's `--private-key /root/.ssh/<filename>`. Host-specific absolute
paths in SSH configuration must also exist inside the container. Encrypted
keys can be loaded into an agent started inside the container with
`eval "$(ssh-agent -s)"` followed by `ssh-add /root/.ssh/<filename>`.
## 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`.
- Build from the repository root with `docker build -t ansible:deb13 docker/`.
- SSH keys are supplied at runtime, not embedded in the image.
@@ -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).
@@ -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.
@@ -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.
@@ -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/<site>.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/<hostname>.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.
@@ -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/<inventory>/<hostname>/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.
@@ -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/<inventory>/<hostname>/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.
@@ -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 `<hawser_compose_dir>/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.
@@ -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`.
+73
View File
@@ -0,0 +1,73 @@
---
id: TECH-SECURITY
type: technical
status: active
title: Security
created: '2026-09-09'
updated: '2026-10-11'
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
- execute.sh
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
- Runtime key access: `execute.sh` bind-mounts the caller's `$HOME/.ssh`
(or `SSH_KEY_DIR`) read-only at `/root/.ssh`. It does not rewrite the host
SSH configuration or permissions. Private keys are not baked into the image.
- 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).
+41
View File
@@ -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`).
+34
View File
@@ -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
+40
View File
@@ -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
+34
View File
@@ -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
+38
View File
@@ -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
+41
View File
@@ -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
@@ -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 <final-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.
@@ -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/<project-topic>.md or requirements/non-functional/<project-topic>.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.
@@ -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 |
+42
View File
@@ -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
+20
View File
@@ -0,0 +1,20 @@
<a id="req-001"></a>
## REQ-001 - Requirement Title
Status: active
Tags: []
Related: []
Confidence: confirmed
### Statement
TBD
### Rationale
TBD
### Acceptance Notes
TBD
+38
View File
@@ -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
@@ -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
+30
View File
@@ -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.
@@ -0,0 +1,3 @@
version: 1
updated: YYYY-MM-DD
sources: []
+38
View File
@@ -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
+33
View File
@@ -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
+38
View File
@@ -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
+18
View File
@@ -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).
@@ -0,0 +1,3 @@
# Change Impact Map
No change impact traceability has been captured yet.
+20
View File
@@ -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`, `README.md` | Runtime environment; local SSH directory mounted read-only via `SSH_KEY_DIR` |
| [TECH-SECURITY](../technical/security.md) | `vault.yml`, `roles/*/defaults/`, `ansible.cfg`, `execute.sh`, `roles/monitoring/tasks/configure-psk.yml` | Secrets, SSH, PSK; runtime-only local key access |
| [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 |
@@ -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/<inventory>/<hostname>/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
@@ -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).
+176
View File
@@ -0,0 +1,176 @@
# AGENTS.md
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:BEGIN -->
## 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.
<!-- PROJECT-WIKI:END -->
## Repository-Übersicht
Zentrale Ansible-Sammlung für die Bereitstellung, Härtung, Aktualisierung und
Überwachung von Debian-Hosts.
```
.
├── ansible.cfg # Globale Ansible-Konfiguration
├── bootstrap.yml # Top-Level-Playbook für die Ersteinrichtung
├── execute.sh # Startet die Docker-Ausführungsumgebung
├── setenv.sh # Umgebungsvariablen für Proxmox (git-ignored)
├── vault.yml # Ansible-Vault-verschlüsselte Secrets
├── .vault_pass # Vault-Passwort (git-ignored)
├── docker/ # Dockerfile & Requirements für die Laufzeit-Umgebung
├── inventory/ # Inventories mit group_vars
├── logs/ # Update-Logs (git-ignored)
├── playbooks/ # Playbooks, die die Rollen einbinden
└── roles/ # Wiederverwendbare Ansible-Rollen
```
## Rollen
| Rolle | Kurzbeschreibung |
|---------------------|-----------------------------------------------------------------------------|
| `bootstrap` | Ersteinrichtung: Pakete, Admin-User, SSH, MOTD, sysctl, Docker, Monitoring |
| `docker` | Installation der Docker Engine über das offizielle Repository |
| `hawser` | Aktualisierung des Hawser Docker-Compose-Stacks |
| `healthcheck` | Quality-Gate: prüft laufende Container nach Updates |
| `manage-ssh-keys` | Verwaltung erwünschter/unerwünschter SSH-Keys (Hardening) |
| `monitoring` | Zabbix Agent 2: Installation, TLS-PSK, Docker-Plugin, API-Registrierung |
| `os-updates` | Debian-Paketaktualisierung mit Spiegel-Wechsel, Reboot & Logging |
Jede Rolle besitzt eine eigene `README.md` unter `roles/<name>/README.md`,
die Anforderungen, Variablen, Templates, Handler, Tags und Nutzung
dokumentiert. Die globale `README.md` im Repo-Root verweist auf diese
Rollen-READMEs und beschreibt Inventories, Playbooks und die
Docker-Ausführungsumgebung.
## WICHTIG: Dokumentationspflicht bei Änderungen
> **Jede neue Funktion, jeder neue Parameter und jede neue Variable MUSS
> dokumentiert werden.** Keine undokumentierten Änderungen committen.
### Wann welche README aktualisiert werden muss
| Art der Änderung | Zu aktualisierende Datei(en) |
|-----------------------------------------------------------|-------------------------------------------------------|
| Neue Variable in `defaults/main.yml` einer Rolle | `roles/<rolle>/README.md` (Abschnitt "Variablen") |
| Neue Task-Datei / neuer Subtask in einer Rolle | `roles/<rolle>/README.md` (Abschnitte "Funktionsweise" / "Tasks") |
| Neues Template oder Handler | `roles/<rolle>/README.md` (Abschnitt "Templates" / "Handler") |
| Neuer Tag | `roles/<rolle>/README.md` (Abschnitt "Tags") |
| Neues Playbook oder wesentliche Änderung an bestehendem | Globale `README.md` (Tabelle "Playbooks") |
| Neue Rolle | Globale `README.md` (Tabellen "Inhalt" & "Rollen") + `roles/<neu>/README.md` erstellen |
| Neues Inventory oder group_vars | Globale `README.md` (Abschnitt "Inventories") |
| Änderung an `ansible.cfg`, `execute.sh` oder `docker/` | Globale `README.md` |
| Änderung an Secrets / Vault-Handhabung | Globale `README.md` (Abschnitt "Secrets & Vault") |
### Konventionen für Rollen-READMEs
Jede `roles/<name>/README.md` soll mindestens folgende Abschnitte enthalten:
1. **Titel & Kurzbeschreibung** – was macht die Rolle, Verweis auf Repo-README
2. **Voraussetzungen** – OS, Collections, Zugriff
3. **Einbindung** – Beispiel-Playbook oder Aufruf
4. **Funktionsweise** – Reihenfolge der Tasks, was passiert
5. **Variablen** – Tabelle mit Name, Typ, Default, Beschreibung
- Trennen zwischen Variablen mit Defaults (`defaults/main.yml`) und
Steuer-Variablen ohne Defaults
6. **Templates** – Template-Datei → Ziel-Pfad
7. **Handler** – Handler-Name → Auslöser
8. **Tags** – verfügbare Tags und Beispiel
9. **Abhängigkeiten** – andere Rollen oder Collections
10. **Hinweise** – Besonderheiten, Plattform-Einschränkungen
### Konventionen für die globale README
- Tabellen für Playbooks, Inventories und Rollen aktuell halten
- Neue Rollen-READMEs in der Rollen-Tabelle verlinken
- Aufrufbeispiele bei neuen Playbooks ergänzen
## Technische Konventionen
### Sprache & Stil
- **Dokumentation** (`README.md`, Kommentare in Templates): Deutsch
- **Code**: Englisch (Variablennamen, Task-Names, Module)
- **Keine** Kommentare in Task-Dateien, außer wenn der User es ausdrücklich
wünscht (siehe System-Regeln). Inline-Doku über die READMEs laufen.
### Ansible-Stil
- Module grundsätzlich mit FQCN verwenden (`ansible.builtin.apt`,
`ansible.builtin.template`, …), ausgenommen ältere Tasks, die
kurze Namen nutzen – bei neuen Tasks FQCN verwenden.
- Variablen-Defaults immer in `defaults/main.yml`, nie hart in Tasks.
- Variablen pro Inventory in `inventory/group_vars/<gruppe>.yml` überschreiben.
- Templates nutzen `# {{ ansible_managed }}` als Header.
- Tasks sind in Subtask-Dateien (`tasks/<name>.yml`) ausgelagert, wenn eine
Rolle mehr als eine logische Einheit hat. `tasks/main.yml` enthält nur
`import_tasks`/`include_tasks` und `import_role`.
### Secrets
- Sensible Werte (Passwörter, API-Keys) gehören in `vault.yml` (verschlüsselt).
- `.vault_pass`, `setenv.sh`, `logs/`, `.zabbix-psk` sind git-ignored und
dürfen **niemals** committet werden.
- Klartext-Secrets in `defaults/` oder `group_vars/` vermeiden; stattdessen
Vault-Referenz verwenden.
### OS-Unterstützung
- Alle Playbooks prüfen `ansible_facts['os_family'] == "Debian"` und brechen
bei Nicht-Debian ab. Neue Playbooks müssen diesen Check enthalten.
- LXC-Container: Tasks, die `sysctl` oder `reboot` betreffen, müssen
`virtualization_type != "lxc"` prüfen und übersprungen werden.
### Tags
- Verwendete Tags pro Rolle in der jeweiligen README dokumentieren.
- Gängige Tags: `ssh`, `motd`, `bashrc`, `sysctl`, `monitoring`.
### Collections
Benötigte Collections (in `docker/requirements.yml`):
- `ansible.posix`
- `community.general`
- `community.docker`
- `community.zabbix` (nur in der `monitoring`-Rolle verwendet – ggf. ergänzen)
### Docker-Ausführungsumgebung
- `docker/Dockerfile` baut ein `debian:trixie-slim`-Image mit `ansible-core`.
- `execute.sh` startet einen interaktiven Container, der das Repo nach
`/ansible` mountet und SSH-Keys unter `/root/.ssh` bereithält.
- Bei Änderung der Collections muss `docker/requirements.yml` aktualisiert und
ein Image-Rebuild durchgeführt werden.
## Testing & Verifikation
1. Änderungen im Docker-Container testen: `./execute.sh`
2. Lint prüfen, falls verfügbar: `ansible-lint` (nicht im Repo vorausgesetzt,
aber empfohlen).
3. Playbook Dry-Run: `--check` verwenden, wo sinnvoll.
4. Nach Änderungen an einer Rolle die zugehörige `README.md` aktualisieren.
## Häufige Fallstricke
- **`os_update_version_codename`** in `inventory/group_vars/debian.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/cloudlorean.yml`: `gitea`, `ipam`). Beim Hinzufügen
neuer Hosts prüfen.
- **PSK-Store**: `monitoring` speichert pro Host eine PSK unter
`.zabbix-psk/<hostname>.psk` (git-ignored). Bei Host-Umbenennung PSK
migrieren oder neu generieren – sonst stimmt die Zabbix-Registrierung nicht.
- **Logs**: `os-updates` und `healthcheck` schreiben Logs nach
`logs/<inventory>/<hostname>/update.log` (auf dem Controller, delegiert).
Verzeichnis ist git-ignored.
+178
View File
@@ -0,0 +1,178 @@
# Ansible Playbooks
Zentrale Ansible-Sammlung für die Bereitstellung, Härtung, Aktualisierung und
Überwachung von Debian-Hosts. Das Repository enthält eigenständige Rollen,
zugehörige Playbooks, mehrere Inventories sowie eine Docker-basierte
Ausführungsumgebung, sodass Playbooks direkt aus einem Container heraus
gestartet werden können.
## Inhaltsübersicht
```
.
├── ansible.cfg # Globale Ansible-Konfiguration
├── bootstrap.yml # Top-Level-Playbook für die Ersteinrichtung
├── execute.sh # Startet die Docker-Ausführungsumgebung
├── setenv.sh # Umgebungsvariablen für Proxmox (nicht im Repo)
├── vault.yml # Ansible-Vault-verschlüsselte Secrets
├── docker/ # Dockerfile & Requirements für die Laufzeit-Umgebung
├── inventory/ # Inventories mit group_vars
├── logs/ # Update-Logs (git-ignored)
├── playbooks/ # Playbooks, die die Rollen einbinden
└── roles/ # Wiederverwendbare Ansible-Rollen
```
## Voraussetzungen
- **Ansible** >= 2.20 (im Docker-Image bereits enthalten)
- **Ziel-Hosts**: Debian (alle Playbooks prüfen `os_family == "Debian"`)
- **Ansible-Collections**: `ansible.posix`, `community.general`,
`community.docker`, `community.zabbix` (siehe `docker/requirements.yml`)
- **Zugang**: SSH-Zugang als Benutzer `admin` mit hinterlegtem Public Key
### Docker-Ausführungsumgebung
Damit Playbooks nicht lokal installiert werden müssen, liefert das Repository
ein fertiges Docker-Image. Das Image lässt sich vom Repository-Verzeichnis aus
bauen:
```bash
docker build -t ansible:deb13 docker/
```
`execute.sh` erstellt ein IPv6-fähiges Bridge-Netzwerk, startet einen interaktiven
Container und mountet das Repository nach `/ansible`. Das lokale SSH-Verzeichnis
`~/.ssh` des aufrufenden Benutzers wird schreibgeschützt nach `/root/.ssh`
eingebunden. Damit verwendet SSH im Container deine lokalen Standard-Keys
(z. B. `id_ed25519` oder `id_rsa`) und eine vorhandene SSH-Konfiguration:
```bash
./execute.sh
```
Ein anderes lokales Schlüsselverzeichnis kann über `SSH_KEY_DIR` angegeben
werden (Standard: `$HOME/.ssh`). Das Verzeichnis muss vorhanden und lesbar sein:
```bash
SSH_KEY_DIR=/pfad/zu/ssh ./execute.sh
```
Bei einem abweichenden Key-Dateinamen gibst du im Container beispielsweise
`--private-key /root/.ssh/mein_key` beim Ansible-Aufruf an. Pfade in deiner
SSH-Konfiguration müssen im Container erreichbar sein, z. B.
`IdentityFile ~/.ssh/mein_key`. Für passwortgeschützte Keys kannst du im
Container einen SSH-Agent starten und den Key laden:
```bash
eval "$(ssh-agent -s)"
ssh-add /root/.ssh/id_ed25519
```
Im Container stehen Aliase wie `ap` (ansible-playbook), `ag` (ansible-galaxy)
und `av` (ansible-vault) zur Verfügung. Das Image wird über
`docker/Dockerfile` gebaut, die benötigten Collections werden beim Build
automatisch installiert.
## Inventories
Alle Inventories liegen unter `inventory/`. Pro Inventory existiert eine
gleichnamige Datei sowie ein Eintrag in `group_vars/`:
| Inventory | Datei | group_vars | Beschreibung |
|------------------|--------------------|------------------------|-------------------------------------------------|
| 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) |
Die `group_vars` definieren standortspezifische Werte wie Zabbix-Server,
Spiegelserver und autorisierte SSH-Keys.
## Playbooks
| Playbook | Rolle(n) | Zweck |
|---------------------------------------|-----------------------------------|--------------------------------------------------------|
| `bootstrap.yml` | bootstrap | Vollständige Ersteinrichtung eines Debian-Hosts |
| `playbooks/docker.yml` | docker | Docker Engine installieren |
| `playbooks/monitoring.yml` | monitoring | Zabbix Agent 2 installieren & registrieren |
| `playbooks/os-updates-deb.yml` | os-updates, healthcheck | Pakete aktualisieren + Healthcheck |
| `playbooks/upgrade-hawser.yml` | hawser | Hawser-Stack aktualisieren |
| `playbooks/hardening/manage-ssh-keys.yml` | manage-ssh-keys | SSH-Schlüssel hartieren |
### Aufrufbeispiele
```bash
# 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/cloudlorean.yml playbooks/os-updates-deb.yml
# Hawser aktualisieren
ansible-playbook -i inventory/cloudlorean.yml playbooks/upgrade-hawser.yml
# SSH-Keys härtene
ansible-playbook -i inventory/cloudlorean.yml playbooks/hardening/manage-ssh-keys.yml
```
## Rollen
| Rolle | Kurzbeschreibung | Dokumentation |
|---------------------|-----------------------------------------------------------------------------|-------------------------------------|
| `bootstrap` | Ersteinrichtung: Pakete, Admin-User, SSH, MOTD, sysctl, Docker, Monitoring | [roles/bootstrap/README.md](roles/bootstrap/README.md) |
| `docker` | Installation der Docker Engine über das offizielle Repository | [roles/docker/README.md](roles/docker/README.md) |
| `hawser` | Aktualisierung des Hawser Docker-Compose-Stacks | [roles/hawser/README.md](roles/hawser/README.md) |
| `healthcheck` | Quality-Gate: prüft laufende Container nach Updates | [roles/healthcheck/README.md](roles/healthcheck/README.md) |
| `manage-ssh-keys` | Verwaltung erwünschter/unerwünschter SSH-Keys (Hardening) | [roles/manage-ssh-keys/README.md](roles/manage-ssh-keys/README.md) |
| `monitoring` | Zabbix Agent 2: Installation, TLS-PSK, Docker-Plugin, API-Registrierung | [roles/monitoring/README.md](roles/monitoring/README.md) |
| `os-updates` | Debian-Paketaktualisierung mit Spiegel-Wechsel, Reboot & Logging | [roles/os-updates/README.md](roles/os-updates/README.md) |
## Secrets & Vault
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/cloudlorean.yml --vault-password-file .vault_pass bootstrap.yml
```
> **Hinweis:** Die Datei `setenv.sh` enthält Zugangsdaten für Proxmox und ist
> bewusst nicht für eine Veröffentlichung vorgesehen. `.vault_pass` und
> `setenv.sh` stehen in `.gitignore`.
## Logging
Die Rollen `os-updates` und `healthcheck` schreiben pro Host ein Update-Log
unter `logs/<inventory>/<hostname>/update.log`. Das Verzeichnis `logs/` ist
git-ignored und dient als lokales Audit-Trail.
## Konfiguration
`ansible.cfg` deaktiviert Host-Key-Checking und legt `./roles` als
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).
+4 -2
View File
@@ -2,9 +2,11 @@
host_key_checking = False
roles_path = ./roles:/etc/ansible/roles
group_vars = ./group_vars
interpreter_python = auto_silent
forks = 20
strategy = free
gathering = smart
internal_poll_interval = 0.001
[ssh_connection]
ssh_args = -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ControlMaster=auto -o ControlPersist=1800s
pipelining = True
ssh_args = -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ControlMaster=auto -o ControlPersist=60s
+3 -1
View File
@@ -1,6 +1,8 @@
- hosts: all
become: true
remote_user: "{{ user|default('admin') }}"
user: root
vars_files:
- vault.yml
tasks:
- name: Verify if system is Debian
debug:
+9 -2
View File
@@ -1,7 +1,7 @@
FROM debian:trixie-slim
ARG DEBIAN_FRONTEND=noninteractive
ENV TZ=Europe/Berlin
ENV ANSIBLE_MAJOR_VERSION=2.20
ENV ANSIBLE_VERSION=2.20.6
ENV PIP_BREAK_SYSTEM_PACKAGES=1
RUN apt update && apt install -y --no-install-recommends \
@@ -13,19 +13,26 @@ RUN apt update && apt install -y --no-install-recommends \
python3-pip \
tzdata \
openssh-client \
iputils-ping \
vim \
less \
&& apt clean \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p /etc/ssh/ssh_config.d \
&& printf 'Host *\n StrictHostKeyChecking no\n UserKnownHostsFile /dev/null\n' > /etc/ssh/ssh_config.d/99-no-hostkey-check.conf
RUN pip3 install --no-cache-dir --upgrade \
ansible-core~=${ANSIBLE_MAJOR_VERSION}
ansible-core~=${ANSIBLE_VERSION}
RUN mkdir -p /root/.bashrc.d
COPY ansible-functs.sh /root/.bashrc.d/ansible-functs.sh
RUN chmod +x /root/.bashrc.d/ansible-functs.sh \
&& printf '\n[ -f /root/.bashrc.d/ansible-functs.sh ] && . /root/.bashrc.d/ansible-functs.sh\n' >> /root/.bashrc
COPY requirements.yml /ansible/requirements.yml
RUN ansible-galaxy collection install -r /ansible/requirements.yml
WORKDIR /ansible
CMD ["ansible-playbook", "--version"]
+9
View File
@@ -0,0 +1,9 @@
collections:
- name: ansible.posix
version: 2.2.0
- name: community.general
version: 13.0.1
- name: community.docker
version: 5.2.0
- name: community.zabbix
version: 4.2.0
Regular → Executable
+15 -9
View File
@@ -1,9 +1,20 @@
#!/bin/bash
defaultimage="ansible:deb13"
ssh_key_dir="${SSH_KEY_DIR:-$HOME/.ssh}"
if [ ! -d "$ssh_key_dir" ] || [ ! -r "$ssh_key_dir" ]; then
echo "Lokales SSH-Verzeichnis '$ssh_key_dir' fehlt oder ist nicht lesbar." >&2
echo "Setze SSH_KEY_DIR auf das Verzeichnis mit deinem SSH-Key." >&2
exit 1
fi
case "$ssh_key_dir" in
/*) ;;
*) ssh_key_dir="$(pwd)/$ssh_key_dir" ;;
esac
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,15 +46,12 @@ 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 \
-v "$ssh_key_dir:/root/.ssh:ro" \
-w /ansible \
$defaultimage \
"/bin/bash"
docker exec "$container_name" sh -lc "mkdir -p /root/.ssh && chmod 700 /root/.ssh && printf 'Host *\n StrictHostKeyChecking no\n UserKnownHostsFile /dev/null\n' > /root/.ssh/config && chmod 600 /root/.ssh/config"
"$defaultimage" \
"/bin/bash" || exit "$?"
container_ipv4=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{if .IPAddress}}{{.IPAddress}}{{end}}{{end}}' "$container_name" 2>/dev/null)
container_ipv6=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{if .GlobalIPv6Address}}{{.GlobalIPv6Address}}{{end}}{{end}}' "$container_name" 2>/dev/null)
@@ -58,5 +66,3 @@ echo "-----------------------------------"
docker attach "$container_name"
+44
View File
@@ -0,0 +1,44 @@
cloudlorean:
children:
debian:
proxmox:
debian:
hosts:
rp:
ansible_host: 192.168.212.50
bitwarden:
ansible_host: 192.168.212.20
pdf:
ansible_host: 192.168.212.65
mailcow:
ansible_host: 192.168.212.70
plex:
ansible_host: 192.168.212.62
cloud:
ansible_host: 192.168.212.60
romm:
ansible_host: 192.168.212.64
gitea:
ansible_host: 192.168.212.30
ssh_service_name: ssh
ipam:
ansible_host: 192.168.212.56
ssh_service_name: ssh
teamspeck6:
ansible_host: 192.168.212.75
wiki:
ansible_host: 192.168.212.55
patchmon:
ansible_host: 192.168.212.80
einstein-dns:
ansible_host: 192.168.212.53
weddigram-devel:
ansible_host: 192.168.212.201
proxmox:
hosts:
dmc12:
ansible_host: 192.168.212.10
pbs:
ansible_host: 192.168.212.11
+14
View File
@@ -0,0 +1,14 @@
external:
children:
debian:
debian:
hosts:
healthcheck:
ansible_host: 2a01:4f8:1c1e:62dd::1
netbird:
ansible_host: 2a01:4f8:1c1c:8c8::1
teleport:
ansible_host: 2a01:4f8:1c19:c7d::1
podcast-linkstack:
ansible_host: 2a01:4f8:c17:de13::1
+19
View File
@@ -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"
+8
View File
@@ -0,0 +1,8 @@
# Standardwerte, die überschrieben werden können
os_update_auto_upgrade: true
os_also_update_mirror: false
os_update_mirrors:
# Role needs two mirros to use for the sources.list.j2 Template
- "http://deb.debian.org/debian" # Enter a main mirror here (not security)
- "http://security.debian.org/debian-security" # Enter a security mirror here
os_update_version_codename: "trixie" # KEEP UNTOUCHED!! | Used for jinja2 Template fill in as it determines the current codename of system where ansible is run on
+23
View File
@@ -0,0 +1,23 @@
---
# SSH authorized keys for admin user
# Format: key and optional comment
admin_authorized_keys:
- key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL/XwF0Na+YH7lRqGtwEcyIMVGTQZetNDrC9sZ8ofjC5 niklas@Linkman-PC"
comment: "Niklas - Linkman-PC"
- key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA+EvtGavPlXfv7b00jSYsXX2+IEsqFWupEs6Rzf5z9q root@ansible"
comment: "Generic Ansible Key"
# 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"]
+19
View File
@@ -0,0 +1,19 @@
monitoring_zabbix_passive_servers: ["10.13.37.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", "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: "gatekeeper.meyer-kempen.de"
monitoring_zabbix_proxy_address: "10.13.37.1"
+24
View File
@@ -0,0 +1,24 @@
home:
children:
debian:
proxmox:
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
+2
View File
@@ -1,6 +1,8 @@
- hosts: all
user: admin
become: true
vars_files:
- ../vault.yml
tasks:
- name: Verify if system is Debian
debug:
+5
View File
@@ -16,3 +16,8 @@
include_role:
name: os-updates
when: ansible_facts['os_family'] == "Debian"
- name: Include healthcheck role (post-update quality gate)
include_role:
name: healthcheck
when: ansible_facts['os_family'] == "Debian"
+18
View File
@@ -0,0 +1,18 @@
- hosts: all
user: admin
become: true
tasks:
- name: Verify if system is Debian
debug:
msg: "This playbook is running on a Debian system."
when: ansible_facts['os_family'] == "Debian"
- name: Stop playbook if system is not Debian
fail:
msg: "This playbook only supports Debian."
when: ansible_facts['os_family'] != "Debian"
- name: Include hawser role
import_role:
name: hawser
when: ansible_facts['os_family'] == "Debian"
+119
View File
@@ -0,0 +1,119 @@
# Rolle `bootstrap`
Ersteinrichtung (Provisioning) eines frischen Debian-Hosts. Die Rolle legt den
Admin-User an, installiert Basispakete, härtet die SSH-Konfiguration, verteilt
autorisierte SSH-Keys, konfiguriert Tastaturlayout, MOTD, Bash-Aliase und
sysctl, und bindet optional die Rollen `docker` und `monitoring` ein.
> Siehe auch das Top-Level-Playbook `bootstrap.yml` sowie die
> [Repo-README](../../README.md).
## Voraussetzungen
- Debian-Ziel-Host
- SSH-Zugang als Benutzer, der `become` darf (Playbook nutzt `user: admin`)
- `ansible.posix` Collection (für `sysctl`-Modul)
## Einbindung
Die Rolle wird normalerweise über das Top-Level-Playbook `bootstrap.yml`
aufgerufen, kann aber auch direkt per `include_role`/`import_role` eingebunden
werden:
```yaml
- hosts: all
become: true
user: admin
roles:
- role: bootstrap
```
```bash
ansible-playbook -i inventory/cloudlorean.yml --vault-password-file .vault_pass bootstrap.yml
```
## Funktionsweise
`tasks/main.yml` reiht die Subtasks nacheinander aus und bindet am Ende
bedarfsabhängig die Rollen `docker` und `monitoring` ein:
1. `install-basicpackages.yml` – Basispakete installieren
2. `create-admin-user.yml` – Admin-User anlegen + sudoers
3. `set-motd.yml` – MOTD via fastfetch (`tags: motd`)
4. `set-keyboardlayout.yml` – QWERTZ-Layout
5. `install-openssh.yml` – OpenSSH Server/Client (`tags: ssh`)
6. `configure-ssh.yml` – sshd_config härtbar (`tags: ssh`)
7. `add-ssh-keys.yml` – authorized_keys verteilen (`tags: ssh`)
8. `setup-bashrc.yml` – nützliche Aliase (`tags: bashrc`)
9. `configure-sysctl.yml` – vm.swappiness = 10 (`tags: sysctl`)
10. `import_role: docker` (sofern nicht `skip_docker`)
11. `import_role: monitoring` (sofern nicht `skip_monitoring`)
### Installierte Basispakete
`fastfetch`, `htop`, `curl`, `wget`, `git`, `sudo`, `console-setup`,
`qemu-guest-agent`, `cron`, `net-tools`, `tcpdump`, `locales-all`.
### SSH-Härtung
Die Vorlage `templates/sshd.conf.j2` deaktiviert Root-Login und
Passwort-Authentifizierung, erlaubt ausschließlich Publickey-Auth und setzt
restriktive Forwarding-/Logging-Optionen. Nach Änderung wird der sshd via
Handler neu gestartet.
## Variablen
| Variable | Typ | Default | Beschreibung |
|-------------------------|----------|-------------------|-----------------------------------------------------|
| `admin_authorized_keys` | list | siehe `defaults/` | Liste von `{ key, comment }`-Einträgen für `admin` |
| `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)
| Variable | Typ | Default | Beschreibung |
|--------------------|--------|---------|-----------------------------------------------------------|
| `skip_docker` | bool | `false` | Wenn `true`, wird die `docker`-Rolle übersprungen |
| `skip_monitoring` | bool | `false` | Wenn `true`, wird die `monitoring`-Rolle übersprungen |
| `ssh_service_name` | string | `sshd` | Name des SSH-Service (überschrieben bspw. für `ssh`) |
`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/cloudlorean.yml`).
## Templates
| Template | Ziel |
|------------------------|------------------------------------------------|
| `sshd.conf.j2` | `/etc/ssh/sshd_config` (validiert via `sshd -T`) |
| `authorized_keys.j2` | `/home/admin/.ssh/authorized_keys` |
| `sudoers-admin.j2` | `/etc/sudoers.d/10-admin` (validiert via `visudo -cf`) |
| `keyboard.j2` | `/etc/default/keyboard` |
## Handler
| Handler | Auslöser |
|-------------------------|-------------------------------------------|
| `Restart sshd` | Änderung an `sshd_config` |
| `Reload keyboard layout`| Änderung an `/etc/default/keyboard` |
## Tags
`motd`, `ssh`, `bashrc`, `sysctl` – ermöglichen das gezielte Re-Apply einzelner
Teile, z. B.:
```bash
ansible-playbook bootstrap.yml -i inventory/cloudlorean.yml -t ssh
```
## Abhängigkeiten
- `docker` (optional, via `import_role`)
- `monitoring` (optional, via `import_role`)
## Hinweise
- `configure-sysctl.yml` wird auf LXC-Containern übersprungen
(`virtualization_type != "lxc"`).
- `setup-bashrc.yml` legt Aliase für alle regulären User inkl. root an.
- `install-openssh.yml` entfernt ggf. das Meta-Paket `ssh` vor der
Installation von `openssh-server`/`openssh-client`.
+2 -2
View File
@@ -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.
+2
View File
@@ -3,7 +3,9 @@
service:
name: "{{ ssh_service_name | default('sshd') }}"
state: restarted
remote_user: admin
- name: Reload keyboard layout
shell: setupcon
ignore_errors: yes
remote_user: admin
@@ -0,0 +1,9 @@
---
- name: Configure swappiness to reduce swap usage
become: yes
ansible.posix.sysctl:
name: vm.swappiness
value: 10
state: present
reload: yes
when: ansible_facts['virtualization_type'] != "lxc"
+11
View File
@@ -19,3 +19,14 @@
- import_tasks: setup-bashrc.yml
tags: bashrc
- import_tasks: configure-sysctl.yml
tags: sysctl
- import_role:
name: docker
when: not skip_docker | default(false)
- import_role:
name: monitoring
when: not skip_monitoring | default(false)
+103
View File
@@ -0,0 +1,103 @@
# Rolle `docker`
Installiert die Docker Engine über das offizielle Docker-Repository für Debian.
Die Rolle fügt den Docker-GPG-Key hinzu, richtet eine APT-Quelle im deb822-Format
ein und installiert alle benötigten Pakete (Engine, CLI, containerd, Buildx- und
Compose-Plugin).
> Siehe auch das Playbook `playbooks/docker.yml` sowie die
> [Repo-README](../../README.md).
## Voraussetzungen
- Debian-Ziel-Host
- Internetzugang zum Docker-Repository (`download.docker.com`)
- Benutzer mit `become`-Rechten
## Einbindung
Die Rolle wird typischerweise über das Playbook `playbooks/docker.yml` oder
automatisch durch die `bootstrap`-Rolle (`import_role`) aufgerufen. Direkte
Einbindung per `import_role`:
```yaml
- hosts: all
become: true
user: admin
roles:
- role: docker
```
```bash
ansible-playbook -i inventory/cloudlorean.yml playbooks/docker.yml
```
## Funktionsweise
`tasks/main.yml` inkludiert die eigentliche Installations-Logik aus
`tasks/install-docker.yml`:
1. **apt-Cache aktualisieren** – `cache_valid_time: 3600`
2. **Voraussetzungen installieren** – `ca-certificates`, `curl`
3. **Keyring-Verzeichnis anlegen** – `/etc/apt/keyrings` (Mode `0755`)
4. **Docker GPG-Key herunterladen** – nach `/etc/apt/keyrings/docker.asc`
5. **APT-Quelle einrichten** – Template `sources.list.j2` nach
`/etc/apt/sources.list.d/docker.sources` (deb822-Format)
6. **apt-Cache aktualisieren** – nach Repository-Hinzufügung
7. **Docker-Pakete installieren** – `docker_packages` (benachrichtigt Handler
`Start Docker`)
## Variablen
| Variable | Typ | Default | Beschreibung |
|----------------------|---------|--------------------------------------|-------------------------------------------------------|
| `docker_mirror` | string | `https://download.docker.com/linux/debian` | Basis-URL des Docker-APT-Repositories |
| `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`:
```yaml
docker_packages:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
- docker-compose-plugin
```
`os_version_codename` wird standardmäßig automatisch anhand von
`ansible_facts['distribution_release']` ermittelt und kann bei Bedarf
überschrieben werden.
## Templates
| Template | Ziel |
|-------------------|-------------------------------------------------|
| `sources.list.j2` | `/etc/apt/sources.list.d/docker.sources` (deb822) |
## Handler
| Handler | Auslöser |
|------------------|-----------------------------------------------------|
| `Start Docker` | Installation/Aktualisierung der Docker-Pakete |
| `Restart Docker` | (reserviert) systemd-Neustart |
| `Reload Docker` | (reserviert) systemd-Reload |
## Tags
Die Rolle vergibt keine eigenen Tags. Bei Bedarf über das Playbook steuerbar.
## Abhängigkeiten
- Keine weiteren Rollen.
- Collections: `ansible.builtin` (Bordmittel).
## Hinweise
- Die APT-Quelle liegt im **deb822-Format** vor, was ab Debian 12+ empfohlen
wird. Das签ierte-Keyfile liegt unter `/etc/apt/keyrings/docker.asc`.
- 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.
+1 -1
View File
@@ -1,5 +1,5 @@
docker_mirror: https://download.docker.com/linux/debian
os_version_codename: "{{ ansible_facts['distribution_release'] | lower }}"
os_version_codename: "{{ ansible_facts['distribution_release'] }}"
docker_packages:
- docker-ce
- docker-ce-cli
-4
View File
@@ -1,6 +1,2 @@
- name: Include Docker installation tasks
ansible.builtin.include_tasks: install-docker.yml
when: docker_check is failed
- name: Include Docker APT migration tasks
ansible.builtin.include_tasks: migrate-apt.yml
-23
View File
@@ -1,23 +0,0 @@
- name: Remove old Docker APT repo list files (legacy deb format)
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop:
- /etc/apt/sources.list.d/docker.list
- /etc/apt/sources.list.d/docker-ce.list
- /etc/apt/sources.list.d/download_docker_com_linux_ubuntu.list
register: docker_apt_sources_cleanup
failed_when: false
- name: Remove legacy Docker repo entries from /etc/apt/sources.list
ansible.builtin.lineinfile:
path: /etc/apt/sources.list
regexp: '^deb\s+.*download\.docker\.com/linux/.*$'
state: absent
register: docker_sources_list_cleanup
failed_when: false
- name: Update apt cache after legacy Docker repo cleanup
ansible.builtin.apt:
update_cache: true
when: docker_apt_sources_cleanup is changed or docker_sources_list_cleanup is changed
+94
View File
@@ -0,0 +1,94 @@
# Rolle `hawser`
Aktualisiert den [Hawser](https://ghcr.io/finsys/hawser) Docker-Compose-Stack
auf den Ziel-Hosts. Hawser ist ein Docker-Management-Agent, der das Docker-
Socket nach außen freigibt. Die Rolle deployt die Compose-Datei aus einem
Template und zieht stets die neuesten Images (`pull: always`).
> Siehe auch das Playbook `playbooks/upgrade-hawser.yml` sowie die
> [Repo-README](../../README.md).
## Voraussetzungen
- Debian-Ziel-Host mit installierter Docker Engine (Rolle `docker`)
- Bereits vorhandene Hawser-Installation
(`/opt/hawser/docker-compose.yml` muss existieren)
- `community.docker` Collection
## Einbindung
```yaml
- hosts: all
become: true
user: admin
roles:
- role: hawser
```
```bash
ansible-playbook -i inventory/cloudlorean.yml playbooks/upgrade-hawser.yml
```
## Funktionsweise
`tasks/main.yml` importiert `tasks/update-stack.yml`:
1. **Docker-Host-Info sammeln** – prüft, ob Docker erreichbar ist
(`community.docker.docker_host_info`). Bei Fehler werden nachfolgende
Tasks übersprungen.
2. **Compose-Datei prüfen** – `stat` auf `<hawser_compose_dir>/docker-compose.yml`.
Fehlt die Datei, wird kein Update durchgeführt (Bestandsschutz).
3. **Compose-Datei ausliefern** – Template `docker-compose.yml.j2` wird nur
geschrieben, wenn Docker erreichbar UND bereits eine Compose-Datei
vorhanden ist.
4. **Stack aktualisieren** – `community.docker.docker_compose_v2` mit
`pull: always` und `state: present` zieht die neuesten Images und
erneuert die Container.
## Variablen
| Variable | Typ | Default | Beschreibung |
|--------------------------------|--------|----------------|-----------------------------------------------------------|
| `hawser_project_name` | string | `hawser` | Name des Docker-Compose-Projekts |
| `hawser_compose_dir` | string | `/opt/hawser` | Verzeichnis der Compose-Datei auf dem Zielhost |
| `hawser_port` | string | `2376` | Freigegebener Port des Hawser-Agents (Host-Port) |
| `hawser_stacks_volume` | string | `hawser_stacks`| Name des externen Docker-Volumes für Stack-Dateien |
| `hawser_allow_insecure_no_auth`| bool | `true` | `ALLOW_INSECURE_NO_AUTH` – Standard-Mode ohne Token |
> **Achtung:** `hawser_allow_insecure_no_auth: true` erlaubt den Betrieb ohne
> Token-Authentifizierung auch auf Nicht-Loopback-Adressen. Nur setzen, wenn
> das Netzwerk durch andere Maßnahmen (Firewall, VPN) abgesichert ist.
## Templates
| Template | Ziel |
|------------------------|--------------------------------------------|
| `docker-compose.yml.j2`| `<hawser_compose_dir>/docker-compose.yml` |
Das Template konfiguriert den Hawser-Container mit Socket-Mount, externem
Volume, Port-Mapping, Environment-Variablen, `image: ghcr.io/finsys/hawser:latest`
und `restart: always`.
## Handler
Keine Handler erforderlich – `docker compose up -d` ersetzt laufende Container
eigenständig.
## Tags
Die Rolle vergibt keine eigenen Tags.
## Abhängigkeiten
- `community.docker` Collection (`docker_host_info`, `docker_compose_v2`)
- Vorherige Installation der `docker`-Rolle
## Hinweise
- Die Rolle **erzeugt keine Neuinstallation** – die Compose-Datei wird nur
geschrieben, wenn bereits eine existiert. So wird verhindert, dass Hawser
versehentlich auf Hosts ausgebracht wird, auf denen es nicht vorgesehen ist.
- Das externe Volume `hawser_stacks` muss vor dem ersten Start manuell
erstellt werden (`docker volume create hawser_stacks`).
- `pull: always` sorgt für aktuelle Images, erfordert aber Internetzugang
zum ghcr.io-Registry.
+15
View File
@@ -0,0 +1,15 @@
# Name des Docker Compose Projekts
hawser_project_name: hawser
# Verzeichnis, in dem die Compose-Datei auf den Zielsystemen liegt
hawser_compose_dir: /opt/hawser
# Freigegebener Port des Hawser-Agents
hawser_port: "2376"
# Name des externen Docker Volumes für die Stack-Dateien
hawser_stacks_volume: hawser_stacks
# ALLOW_INSECURE_NO_AUTH=true erlaubt Standard-Mode ohne Token auf nicht-loopback Adressen.
# Nur setzen, wenn das Netzwerk durch andere Maßnahmen abgesichert ist.
hawser_allow_insecure_no_auth: true
+2
View File
@@ -0,0 +1,2 @@
---
# Keine Handler erforderlich – docker compose up -d ersetzt laufende Container eigenständig
+2
View File
@@ -0,0 +1,2 @@
- name: Update Hawser Docker stack
ansible.builtin.import_tasks: update-stack.yml
+31
View File
@@ -0,0 +1,31 @@
- name: Gather Docker host information
community.docker.docker_host_info:
register: hawser_docker_info
ignore_errors: true
- name: Check if Hawser compose file is present
ansible.builtin.stat:
path: "{{ hawser_compose_dir }}/docker-compose.yml"
register: hawser_compose_file
when: hawser_docker_info is not failed
- name: Deploy Hawser compose file from template
ansible.builtin.template:
src: docker-compose.yml.j2
dest: "{{ hawser_compose_dir }}/docker-compose.yml"
owner: root
group: root
mode: "0640"
when:
- hawser_docker_info is not failed
- hawser_compose_file.stat.exists
- name: Pull latest images and recreate Hawser stack
community.docker.docker_compose_v2:
project_src: "{{ hawser_compose_dir }}"
project_name: "{{ hawser_project_name }}"
state: present
pull: always
when:
- hawser_docker_info is not failed
- hawser_compose_file.stat.exists
@@ -0,0 +1,17 @@
services:
hawser:
container_name: hawser
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- {{ hawser_stacks_volume }}:/data/stacks
ports:
- "{{ hawser_port }}:2376"
environment:
- ALLOW_INSECURE_NO_AUTH={{ hawser_allow_insecure_no_auth | string | lower }}
image: ghcr.io/finsys/hawser:latest
restart: always
volumes:
{{ hawser_stacks_volume }}:
external: true
name: {{ hawser_stacks_volume }}
+97
View File
@@ -0,0 +1,97 @@
# Rolle `healthcheck`
Post-Update Quality-Gate. Die Rolle prüft nach einem OS-Update, ob alle
Docker-Container wieder laufen, und protokolliert das Ergebnis im selben
Update-Log, das auch von der `os-updates`-Rolle verwendet wird.
> Siehe auch das Playbook `playbooks/os-updates-deb.yml` sowie die
> [Repo-README](../../README.md).
## Voraussetzungen
- Debian-Ziel-Host
- Optional installiertes Docker (wird automatisch erkannt)
- Idealerweise vorheriger Lauf der `os-updates`-Rolle (für gemeinsames Log)
## Einbindung
Die Rolle ist fest in `playbooks/os-updates-deb.yml` nach der `os-updates`-
Rolle eingebunden und wird in der Regel nicht separat aufgerufen:
```yaml
- hosts: all
become: true
user: admin
roles:
- role: os-updates
- role: healthcheck
```
```bash
ansible-playbook -i inventory/cloudlorean.yml playbooks/os-updates-deb.yml
```
## Funktionsweise
`tasks/main.yml` führt nacheinander folgende Schritte aus:
1. **Docker-Binary erkennen** – `which docker` (fehlertolerant)
2. **Docker-Präsenz festhalten** – Fakt `healthcheck_docker_installed`
3. **Alle Container auflisten** – `docker ps -a --format {{.Names}}`
4. **Laufende Container auflisten** – `docker ps --filter status=running`
5. **Container-Health bewerten** – Differenz aus allen und laufenden
Containern ergibt `healthcheck_non_running_containers`
6. **Gesamtergebnis bestimmen** – `healthcheck_all_running`,
`healthcheck_passed`
7. **Default-Ergebnis ohne Docker** – falls Docker nicht installiert ist,
gilt der Healthcheck als bestanden (`passed: true`, 0 Container)
8. **Log-Verzeichnis sicherstellen** – delegiert an localhost
9. **Quality-Gate in Update-Log schreiben** – `blockinfile` mit Marker
`# {mark} ANSIBLE-HEALTHCHECK`, Abschnitt `quality_gate:`
10. **Fehlschlagen bei ungesunden Containern** – nur wenn
`healthcheck_fail_on_unhealthy` gesetzt ist
## Variablen
| Variable | Typ | Default | Beschreibung |
|------------------------------|-------|------------------------------------------------------------------|-----------------------------------------------|
| `healthcheck_logging_enabled`| bool | `{{ os_update_logging_enabled \| default(true) }}` | Logging aktivieren |
| `healthcheck_log_dir` | string| `{{ os_update_log_dir \| default('/ansible/logs') }}` | Basis-Verzeichnis für Logs |
| `healthcheck_log_inventory` | string| `{{ inventory_file \| basename \| splitext \| first }}` | Inventory-Name (abgeleitet) |
| `healthcheck_log_file` | string| `<log_dir>/<inventory>/<hostname>/update.log` | Pfad zur Update-Log-Datei |
| `healthcheck_fail_on_unhealthy`| bool| `false` | Playbook fehlschlagen lassen, wenn Container nicht laufen |
Die Variablen leiten sich standardmäßig aus den Werten der `os-updates`-Rolle
ab, sodass beide Rollen in dasselbe Log schreiben.
## Templates
Keine Templates.
## Handler
Keine Handler.
## Tags
Die Rolle vergibt keine eigenen Tags.
## Abhängigkeiten
- Keine weiteren Rollen.
- Collections: `ansible.builtin` (Bordmittel).
- Empfohlener Partner: `os-updates`-Rolle (für gemeinsames Log-Format).
## Hinweise
- Wird Docker nicht gefunden, gilt der Healthcheck als bestanden – nützlich
für Hosts, die keine Container betreiben.
- Das Log wird **delegiert auf localhost** geschrieben (auf dem Ansible-
Controller), nicht auf dem Zielhost. Das Verzeichnis `logs/` ist
git-ignored.
- Die `quality_gate`-Sektion im Log ist an denselben Eintrag gekoppelt wie
der `os-updates`-Preflight, sodass ein Update-Lauf inkl. Healthcheck
nachvollziehbar dokumentiert ist.
- `healthcheck_fail_on_unhealthy: false` (Default) bricht das Playbook nicht
ab – bewusst gewählt, damit nach Updates nicht versehentlich ganze
Host-Gruppen blockiert werden. Für kritische Hosts individuell setzen.
+11
View File
@@ -0,0 +1,11 @@
# Post-update health check (quality gate) defaults.
# The role verifies that every Docker container is running after an OS update
# and records the result in the same update log used by the os-updates role.
healthcheck_logging_enabled: "{{ os_update_logging_enabled | default(true) }}"
healthcheck_log_dir: "{{ os_update_log_dir | default('/ansible/logs') }}"
healthcheck_log_inventory: "{{ inventory_file | basename | splitext | first }}"
healthcheck_log_file: "{{ healthcheck_log_dir }}/{{ healthcheck_log_inventory }}/{{ ansible_facts['hostname'] }}/update.log"
# When true, the role fails the playbook if any container is not running.
healthcheck_fail_on_unhealthy: false
+85
View File
@@ -0,0 +1,85 @@
# Post-update health check (quality gate).
# Detects whether Docker is installed and, if so, verifies that every
# container is in the running state. Results are appended to the update
# log as a "quality_gate" section, tied to the current update run via the
# os-updates preflight start timestamp.
- name: Healthcheck - Detect Docker binary
ansible.builtin.command: which docker
register: healthcheck_docker_bin
changed_when: false
failed_when: false
- name: Healthcheck - Record Docker presence
ansible.builtin.set_fact:
healthcheck_docker_installed: "{{ healthcheck_docker_bin.rc == 0 }}"
- name: Healthcheck - List all container names
ansible.builtin.command: "docker ps -a --format {{ '{{' }}.Names{{ '}}' }}"
register: healthcheck_all_containers
changed_when: false
when: healthcheck_docker_installed | bool
- name: Healthcheck - List running container names
ansible.builtin.command: "docker ps --filter status=running --format {{ '{{' }}.Names{{ '}}' }}"
register: healthcheck_running_containers
changed_when: false
when: healthcheck_docker_installed | bool
- name: Healthcheck - Evaluate container health
ansible.builtin.set_fact:
healthcheck_total_containers: "{{ healthcheck_all_containers.stdout_lines | length }}"
healthcheck_running_count: "{{ healthcheck_running_containers.stdout_lines | length }}"
healthcheck_non_running_containers: "{{ (healthcheck_all_containers.stdout_lines | difference(healthcheck_running_containers.stdout_lines)) | list }}"
when: healthcheck_docker_installed | bool
- name: Healthcheck - Determine overall result
ansible.builtin.set_fact:
healthcheck_all_running: "{{ (healthcheck_non_running_containers | length) == 0 }}"
healthcheck_passed: "{{ (healthcheck_non_running_containers | length) == 0 }}"
when: healthcheck_docker_installed | bool
- name: Healthcheck - Default result when Docker is not installed
ansible.builtin.set_fact:
healthcheck_total_containers: 0
healthcheck_running_count: 0
healthcheck_non_running_containers: []
healthcheck_all_running: true
healthcheck_passed: true
when: not (healthcheck_docker_installed | bool)
- name: Healthcheck - Ensure log directory exists
ansible.builtin.file:
path: "{{ healthcheck_log_file | dirname }}"
state: directory
delegate_to: localhost
when: healthcheck_logging_enabled | bool
- name: Healthcheck - Write quality gate section to update log
ansible.builtin.blockinfile:
path: "{{ healthcheck_log_file }}"
create: yes
marker: "# {mark} ANSIBLE-HEALTHCHECK"
block: |
quality_gate:
docker_installed: {{ healthcheck_docker_installed }}
total_containers: {{ healthcheck_total_containers }}
running_containers: {{ healthcheck_running_count }}
all_running: {{ healthcheck_all_running }}
non_running_containers:
{% for c in healthcheck_non_running_containers %}
- {{ c }}
{% endfor %}
healthcheck_passed: {{ healthcheck_passed }}
delegate_to: localhost
when: healthcheck_logging_enabled | bool
- name: Healthcheck - Fail when containers are unhealthy (quality gate)
ansible.builtin.fail:
msg: >-
Quality gate failed: the following containers are not running:
{{ healthcheck_non_running_containers | join(', ') }}
when:
- healthcheck_fail_on_unhealthy | bool
- healthcheck_docker_installed | bool
- not (healthcheck_all_running | bool)

Some files were not shown because too many files have changed in this diff Show More