Compare commits
2 commits
e83c777b44
...
a1c0f4814b
| Author | SHA1 | Date | |
|---|---|---|---|
| a1c0f4814b | |||
| 917005174a |
4 changed files with 24 additions and 6 deletions
|
|
@ -29,7 +29,8 @@ _Last reviewed: 2026-06-14._
|
|||
| `roles/dev_env/` — interactive developer environment | **Built + applied.** zsh + oh-my-zsh + oh-my-posh, tmux + TPM plugins, neovim; dotfiles deployed via GNU stow (re-derived from V4/fisi per ADR-013). Node.js from a pinned upstream tarball (not Debian's npm). Lint + Molecule (idempotent) green. **Applied to `ubongo`** for users `sjat` + `claude` (verified: zsh login shells, stow-symlinked `.zshrc`/`.tmux.conf` + nvim config, oh-my-zsh, tmux plugins; nvim v0.12.2, oh-my-posh 29.0.1). Run via `playbooks/workstation.yml` against the `control` group (no dedicated `workstations` group yet). |
|
||||
| `make check` / `make deploy PLAYBOOK=<name>` | **Works.** First end-to-end run (applying `dev_env`) surfaced + fixed latent bugs: Makefile `PLAYBOOK` var collision (binary path vs playbook-name arg) meant the targets never ran; `ansible.cfg` referenced uninstalled community.general callbacks (now built-in `default` + `ansible.posix.profile_tasks`); `acl` package added so Ansible can `become_user` an unprivileged user. The make targets now function — though `site`/`base`/`docker_host` content is still incomplete (see below). |
|
||||
| `roles/public_dns/` + `playbooks/dns.yml` | **Built + applied.** Manages wingu.me at Gandi LiveDNS as code (`community.general.gandi_livedns`, PAT from `vault.gandi.pat`); record data, anti-spoof baseline (SPF `-all` + DMARC reject), and the Gandi-defaults purge are defined + unit-tested (`tests/test_public_dns.py`). **Applied to wingu.me (2026-06-14):** purged Gandi's 13 seeded defaults; zone now holds only the SPF + DMARC TXT records; idempotent re-run clean. No null-MX (Gandi rejects `0 .`) — the MX is removed, so no MX + no apex A = no mail. M1 of the roadmap. |
|
||||
| `ubongo` — physical control / AI-worker host (ADR-015) | **Built (partial).** Debian 13.5 on a Lenovo M70q (i3-10100T, 16 GB, 256 GB SSD; no disk encryption — accepted risk). Full toolchain installed + pinned to `fisi` (Docker 29.5.3, rbw 1.15.0, Claude Code 2.1.173, ansible-core 2.17.14 + molecule via `make setup`/`make collections`). Repo cloned under a dedicated `claude` user (docker group, no sudo). Vault works via rbw (offline-cache decryption verified). SSH key-only (password + root login disabled). In the production inventory `control` group at 10.20.10.151. **`dev_env` now applied here** (zsh/tmux/nvim for `sjat` + `claude`, via `playbooks/workstation.yml`). Managed as the operator account `sjat` (`group_vars/control` sets `ansible_user: sjat`), not the `ansible` service user `group_vars/all` assumes — ubongo has no bootstrapped `ansible` user. **Pending:** NetBird mesh enrollment (so SSH is LAN-only); full `base` hardening (only the `firewall` concern exists, and it is NOT applied here — applying default-deny with no mesh would lock out inbound SSH on the physical NIC); proper `ansible`-user bootstrap (currently managed as `sjat`); OPNsense DHCP reservation for 10.20.10.151 (MAC `88:a4:c2:e0:ee:da`); Terraform state backup (no TF state yet). |
|
||||
| `ubongo` — physical control / AI-worker host (ADR-015) | **Built (partial).** Debian 13.5 on a Lenovo M70q (i3-10100T, 16 GB, 256 GB SSD; no disk encryption — accepted risk). Full toolchain installed + pinned to `fisi` (Docker 29.5.3, rbw 1.15.0, Claude Code 2.1.173, ansible-core 2.17.14 + molecule via `make setup`/`make collections`). Repo cloned under a dedicated `claude` user (docker group, no sudo). Vault works via rbw (offline-cache decryption verified). SSH key-only (password + root login disabled). In the production inventory `control` group at 10.20.10.151. **`dev_env` now applied here** (zsh/tmux/nvim for `sjat` + `claude`, via `playbooks/workstation.yml`). Managed as the operator account `sjat` (`group_vars/control` sets `ansible_user: sjat`), not the `ansible` service user `group_vars/all` assumes — ubongo has no bootstrapped `ansible` user. **Pending:** NetBird mesh enrollment (so SSH is LAN-only); full `base` hardening (only the `firewall` concern exists, and it is NOT applied here — applying default-deny with no mesh would lock out inbound SSH on the physical NIC); proper `ansible`-user bootstrap (currently managed as `sjat`); OPNsense DHCP reservation for 10.20.10.151 (MAC `88:a4:c2:e0:ee:da`); Terraform state backup (now relevant — the offsite tfstate exists). |
|
||||
| `askari` — off-site Hetzner VPS (ADR-007/016, M2) | **Built + applied.** Provisioned by Terraform (`environments/offsite`, `hetznercloud/hcloud`) as **cx23 / hel1 / Debian 13.5** (CAX11/ARM was out of stock EU-wide on 2026-06-14 → cx23 is same-spec x86, cheaper). cloud-init created the `ansible` user + passwordless sudo; a TF-managed Hetzner Cloud Firewall allows SSH only from ubongo's WAN (`91.226.145.80`). Reachable from ubongo (`ansible offsite_hosts -m ping` ✓), in the `offsite_hosts` inventory (generated `offsite.yml`), published at `askari.wingu.me` → `77.42.120.136`. **Pending:** `base` hardening (M3), NetBird coordinator (M4), offsite tfstate backup (ADR-022). |
|
||||
|
||||
## Scaffolded but empty — NOT implemented
|
||||
|
||||
|
|
@ -51,7 +52,6 @@ applying `dev_env` via `playbooks/workstation.yml`.)
|
|||
|---|---|---|
|
||||
| `dns` role (renders the internal zone) | ADR-007 / ADR-009 | Does not exist. Internal DNS ownership is assigned to it by design. |
|
||||
| Terraform actually provisioning (Proxmox) | ADR-006 / ADR-009 | Never `terraform init`ed: no `.terraform.lock.hcl`, no state, no real `local.vms` entries |
|
||||
| `terraform/{modules/hetzner_vm, environments/offsite}` (askari) | ADR-006 (amended) | **Written, not yet applied.** Terraform owns askari's existence (hcloud provider, CAX11/hel1/debian-13, cloud-init `ansible` user, Hetzner Cloud Firewall SSH-from-ubongo). Makefile token-injection + directory inventory + `tf-inventory-offsite` handoff wired; offsite-handoff pytest green. **Pending:** `terraform init/plan/apply` (run on ubongo — creates a billed VPS) + bootstrap. M2 of the roadmap. |
|
||||
| CI (Forgejo Actions) | ADR-003 / ADR-008 | Pipeline described; not implemented |
|
||||
| Level 2 / 3 testing (staging, `askari` smoke) | ADR-008 | Depends on real VMs / `askari`, which don't exist yet |
|
||||
| Per-service roles | ADR-004 | Model defined; no service roles built |
|
||||
|
|
|
|||
|
|
@ -10,8 +10,9 @@ public_dns__domain: wingu.me
|
|||
public_dns__records:
|
||||
- {record: "@", type: TXT, values: ['"v=spf1 -all"'], ttl: 3600}
|
||||
- {record: _dmarc, type: TXT, values: ['"v=DMARC1; p=reject;"'], ttl: 3600}
|
||||
# Service records appear as public-tier needs arise (askari A in M4).
|
||||
# Mesh/LAN-only services never appear here.
|
||||
# askari (off-site host, TF-provisioned M2) — public A so it's reachable by name +
|
||||
# for future ACME on *.askari.wingu.me. Mesh/LAN-only home services never appear here.
|
||||
- {record: askari, type: A, values: ["77.42.120.136"], ttl: 1800}
|
||||
|
||||
# Absent — Gandi's auto-seeded defaults we don't want (purged once, idempotent thereafter).
|
||||
public_dns__absent:
|
||||
|
|
|
|||
16
inventories/production/offsite.yml
Normal file
16
inventories/production/offsite.yml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
# Generated by scripts/tf_to_inventory.py — do not edit manually.
|
||||
# Regenerate with: make tf-inventory TF_ENV=<env>
|
||||
|
||||
all:
|
||||
children:
|
||||
control:
|
||||
hosts: {}
|
||||
docker_hosts:
|
||||
hosts: {}
|
||||
offsite_hosts:
|
||||
hosts:
|
||||
askari:
|
||||
ansible_host: 77.42.120.136
|
||||
proxmox_hosts:
|
||||
hosts: {}
|
||||
|
|
@ -6,8 +6,9 @@ module "askari" {
|
|||
source = "../../modules/hetzner_vm"
|
||||
|
||||
name = "askari"
|
||||
server_type = "cax11" # ARM, 2 vCPU / 4 GB
|
||||
location = "hel1" # Helsinki
|
||||
server_type = "cx23" # x86, 2 vCPU / 4 GB / 40 GB (CAX11/ARM was out of stock in
|
||||
# every EU location 2026-06-14; cx23 is same-spec + cheaper)
|
||||
location = "hel1" # Helsinki
|
||||
image = "debian-13"
|
||||
ansible_ssh_pubkey = var.ansible_ssh_pubkey
|
||||
ssh_admin_cidrs = var.ssh_admin_cidrs
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue