Add initial project structure and documentation
This commit is contained in:
33
README.md
Normal file
33
README.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# homelab-ansible
|
||||||
|
|
||||||
|
Ansible playbook to align my homelab VMs — Alpine & Debian.
|
||||||
|
|
||||||
|
## Roles
|
||||||
|
|
||||||
|
| # | Role | Purpose |
|
||||||
|
| --- | --------- | ------------------------------------------------------------- |
|
||||||
|
| 1 | `ufw` | Install UFW, deny incoming by default, allow only Tailscale |
|
||||||
|
| 2 | `repos` | Set Alpine repos to `edge` |
|
||||||
|
| 3 | `upgrade` | Upgrade all packages (handles Tailscale SSH drops gracefully) |
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Full playbook
|
||||||
|
ansible-playbook homelab.yml
|
||||||
|
|
||||||
|
# Single role
|
||||||
|
ansible-playbook homelab.yml --tags ufw
|
||||||
|
|
||||||
|
# Dry-run
|
||||||
|
ansible-playbook homelab.yml --check --diff
|
||||||
|
```
|
||||||
|
|
||||||
|
## Inventory
|
||||||
|
|
||||||
|
- `inventory/tailscale_inventory.py` — dynamic inventory from local `tailscale status`, filtered by `tag:homelab`
|
||||||
|
- `inventory/static_groups.yml` — static group assignments (Alpine vs Debian)
|
||||||
|
|
||||||
|
## Vars
|
||||||
|
|
||||||
|
Per-role defaults live in `roles/*/defaults/main.yml`. Per-group overrides in `group_vars/`.
|
||||||
55
roles/upgrade/README.md
Normal file
55
roles/upgrade/README.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# Upgrade role
|
||||||
|
|
||||||
|
Upgrades all packages using the native package manager (`apk` or `apt`).
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
Tailscale may be updated during the upgrade. When its service restarts, the SSH tunnel drops and Ansible loses connectivity, causing a fatal `UNREACHABLE`.
|
||||||
|
|
||||||
|
## Solution — async + poll
|
||||||
|
|
||||||
|
Instead of a blocking upgrade, we fire the job in the background and poll for completion, tolerating temporary unreachability.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant A as Ansible
|
||||||
|
participant T as Target
|
||||||
|
|
||||||
|
A->>T: apk upgrade (async, poll: 0)
|
||||||
|
T-->>A: job ID: 42
|
||||||
|
Note over T: 🔄 upgrade en cours...
|
||||||
|
A->>T: async_status jid=42
|
||||||
|
T-->>A: finished: false
|
||||||
|
Note over A: FAILED-RETRYING (normal)
|
||||||
|
A-->>A: sleep 10s
|
||||||
|
A->>T: async_status jid=42
|
||||||
|
T-->>A: finished: false
|
||||||
|
Note over A: FAILED-RETRYING (normal)
|
||||||
|
A-->>A: sleep 10s
|
||||||
|
A->>T: async_status jid=42
|
||||||
|
T-->>A: finished: true ✅
|
||||||
|
Note over A: ok
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`async: 3600` + `poll: 0`** — fire and forget. Ansible gets a job ID and moves on.
|
||||||
|
- **`async_status` with `until: finished`** — polls every 10s for up to 60 retries (~10 min).
|
||||||
|
- **`ignore_unreachable: true`** — if Tailscale dropped SSH, Ansible doesn't crash. It retries until the host is back.
|
||||||
|
- The `FAILED - RETRYING` messages are normal — they just mean the `until` condition isn't met yet.
|
||||||
|
|
||||||
|
## Idempotency
|
||||||
|
|
||||||
|
A task with `async` + `poll: 0` always reports `changed: true` by default, even when nothing happened. To fix this:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
A["Fire: changed_when: false"] --> B["Package manager runs in background"]
|
||||||
|
B --> C["Wait: polls every 10s"]
|
||||||
|
C -->|finished| D["async_status reads real changed value"]
|
||||||
|
D -->|"packages updated"| E["changed ✅"]
|
||||||
|
D -->|"nothing to do"| F["ok"]
|
||||||
|
```
|
||||||
|
|
||||||
|
- The **fire** task has `changed_when: false` — it never claims change.
|
||||||
|
- The **wait** task reads `changed_when: async_result.changed` — it propagates the real status from the package manager once the job finishes.
|
||||||
|
|
||||||
|
Second run with nothing to upgrade → `changed=0` on all hosts.
|
||||||
Reference in New Issue
Block a user