Compare commits
2 commits
6203513220
...
67f2aba9d8
| Author | SHA1 | Date | |
|---|---|---|---|
| 67f2aba9d8 | |||
| aea4f8c3d6 |
6 changed files with 86 additions and 19 deletions
12
STATUS.md
12
STATUS.md
|
|
@ -26,19 +26,23 @@ _Last reviewed: 2026-06-11._
|
|||
| ADR-002 security strategy + `docs/security/{accepted-risks,service-checklist}.md` | Present — threat model, principles, governance frame; checklist + risk register are docs, enforced manually in review |
|
||||
| Service-role standard + per-service `SECURITY.md` convention | Defined (ADR-004 + `docs/security/service-security-template.md`); not yet applied — no service roles exist |
|
||||
| Tag standard + enforcement (ADR-019) | Works — `tests/tags.yml` (closed vocabulary) + `scripts/check-tags.py` (run by `make lint`, unit-tested): enforces the tag vocabulary and that each role import in a play's `roles:` block carries its role-name tag. Governs mostly-unbuilt roles, but the linter is live now. Proxmox VM tag convention (`<env>`, group, `managed-by=terraform`) is in the Terraform HCL but unprovisioned. |
|
||||
| `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. **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); OPNsense DHCP reservation for 10.20.10.151 (MAC `88:a4:c2:e0:ee:da`); Terraform state backup (no TF state yet). |
|
||||
| `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). |
|
||||
| `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). |
|
||||
|
||||
## Scaffolded but empty — NOT implemented
|
||||
|
||||
| Thing | State |
|
||||
|---|---|
|
||||
| `roles/base/` | **Partially built.** The `firewall` concern is implemented (nftables: catalog-driven default-deny + east-west allowlist + auto-rollback apply; ADR-020) with pytest + Molecule render/syntax tests. Other concerns (SSH hardening, fail2ban, auditd, packages, users) are **not** built yet, so `make deploy PLAYBOOK=site` is still incomplete. |
|
||||
| `roles/base/` | **Partially built.** The `firewall` concern is implemented (nftables: catalog-driven default-deny + east-west allowlist + auto-rollback apply; ADR-020) with pytest + Molecule render/syntax tests. Other concerns (SSH hardening, fail2ban, auditd, packages, users) are **not** built yet, so `make deploy PLAYBOOK=site` has no real content to apply (the make target itself now works — see "Real and working today"). |
|
||||
| `roles/docker_host/` | Not in git. Same. |
|
||||
| `inventories/*/hosts.yml` | Structured stubs with empty host maps (`hosts: {}`); regenerated by `make tf-inventory` once Terraform has hosts |
|
||||
| `inventories/production/group_vars/{docker_hosts,proxmox_hosts}/` | Empty dirs |
|
||||
|
||||
So `make deploy PLAYBOOK=site` is still incomplete — `base` is only partially built (its
|
||||
`firewall` concern only) and the `docker_host` role does not exist yet.
|
||||
So `make deploy PLAYBOOK=site` has no real content to apply — `base` is only partially
|
||||
built (its `firewall` concern only) and the `docker_host` role does not exist yet. (The
|
||||
`make check`/`deploy` machinery itself now works — first proven by applying `dev_env` via
|
||||
`playbooks/workstation.yml`.)
|
||||
|
||||
## Designed but not built
|
||||
|
||||
|
|
|
|||
|
|
@ -10,11 +10,12 @@ or service VMs.
|
|||
|
||||
## What it does
|
||||
|
||||
- Installs packages: `zsh, tmux, git, stow, build-essential, curl, ca-certificates,
|
||||
fzf, ripgrep, direnv, nodejs, npm` (`dev_env__packages`).
|
||||
- Installs **pinned** neovim (`dev_env__nvim_version`) and oh-my-posh
|
||||
(`dev_env__omp_version`) from GitHub releases, and the system-wide oh-my-posh theme
|
||||
`/etc/oh-my-posh/zen.toml`.
|
||||
- Installs packages: `zsh, tmux, git, stow, acl, build-essential, curl,
|
||||
ca-certificates, fzf, ripgrep, direnv` (`dev_env__packages`).
|
||||
- Installs **pinned** neovim (`dev_env__nvim_version`), oh-my-posh
|
||||
(`dev_env__omp_version`) and Node.js (`dev_env__node_version`) from upstream releases
|
||||
(Node from the nodejs.org tarball — not Debian's `npm`, which pulls a ~400-package
|
||||
tree), plus the system-wide oh-my-posh theme `/etc/oh-my-posh/zen.toml`.
|
||||
- For each user in `dev_env__users`: sets the login shell to zsh, clones oh-my-zsh +
|
||||
custom plugins and the tmux/TPM plugins, and **stows** the dotfiles into `~`.
|
||||
|
||||
|
|
@ -32,6 +33,7 @@ LSPs/formatters self-install via mason (no system LSP packages needed).
|
|||
| `dev_env__users` | `[]` | Users to configure. Set per group, e.g. `group_vars/control → [sjat, claude]`. Empty = no per-user work. |
|
||||
| `dev_env__nvim_version` | `v0.12.2` | Pinned neovim release. |
|
||||
| `dev_env__omp_version` | `29.0.1` | Pinned oh-my-posh release. |
|
||||
| `dev_env__node_version` | `v20.19.2` | Pinned Node.js release (nodejs.org tarball; npm bundled). |
|
||||
| `dev_env__packages` | see defaults | APT packages. |
|
||||
| `dev_env__omz_custom_plugins` | autosuggestions, syntax-highlighting | Cloned into `~/.oh-my-zsh/custom/plugins`. |
|
||||
| `dev_env__tmux_plugins` | tpm, tmux-sensible, vim-tmux-navigator, catppuccin@v1.0.3 | Cloned into `~/.tmux/plugins`. |
|
||||
|
|
|
|||
|
|
@ -6,27 +6,28 @@
|
|||
# workstation-class group (e.g. group_vars/control → [sjat, claude]).
|
||||
dev_env__users: []
|
||||
|
||||
# APT packages. nvim + oh-my-posh are installed separately from pinned releases.
|
||||
# (nvim uses mason internally for LSPs, so no system LSP packages are needed; node is
|
||||
# present so mason's node-based servers work. direnv is referenced by the .zshrc.)
|
||||
# APT packages. nvim, oh-my-posh and Node.js are installed separately from pinned
|
||||
# releases — Debian's `npm` pulls a ~400-package node-* tree, so we use the upstream
|
||||
# Node tarball instead (npm bundled). nvim uses mason internally for LSPs; node is
|
||||
# present so mason's node-based servers work. direnv is referenced by the .zshrc;
|
||||
# acl lets Ansible become_user an unprivileged user (sjat -> claude) for file copies.
|
||||
dev_env__packages:
|
||||
- zsh
|
||||
- tmux
|
||||
- git
|
||||
- stow
|
||||
- acl # lets Ansible become_user an unprivileged user (sjat -> claude) for file copies
|
||||
- acl
|
||||
- build-essential
|
||||
- curl
|
||||
- ca-certificates
|
||||
- fzf
|
||||
- ripgrep
|
||||
- direnv
|
||||
- nodejs
|
||||
- npm
|
||||
|
||||
# Pinned tool versions (ADR-014 — pin, don't track "latest").
|
||||
dev_env__nvim_version: "v0.12.2"
|
||||
dev_env__omp_version: "29.0.1"
|
||||
dev_env__node_version: "v20.19.2"
|
||||
|
||||
# oh-my-zsh custom plugins (cloned per user into ~/.oh-my-zsh/custom/plugins).
|
||||
dev_env__omz_custom_plugins:
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@
|
|||
loop:
|
||||
- /usr/local/bin/nvim
|
||||
- /usr/local/bin/oh-my-posh
|
||||
- /usr/local/bin/node
|
||||
- /usr/local/bin/npm
|
||||
- /etc/oh-my-posh/zen.toml
|
||||
register: dev_env__sys
|
||||
loop_control:
|
||||
|
|
@ -31,10 +33,8 @@
|
|||
- name: Assert system tools are installed
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- dev_env__sys.results[0].stat.exists
|
||||
- dev_env__sys.results[1].stat.exists
|
||||
- dev_env__sys.results[2].stat.exists
|
||||
fail_msg: nvim/oh-my-posh/zen.toml missing
|
||||
- dev_env__sys.results | map(attribute='stat.exists') | min
|
||||
fail_msg: a system tool (nvim/oh-my-posh/node/npm/zen.toml) is missing
|
||||
|
||||
- name: Look up the test user
|
||||
ansible.builtin.getent:
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@
|
|||
ansible.builtin.include_tasks: oh_my_posh.yml
|
||||
tags: [packages]
|
||||
|
||||
- name: Install Node.js (pinned release)
|
||||
ansible.builtin.include_tasks: nodejs.yml
|
||||
tags: [packages]
|
||||
|
||||
- name: Configure each developer user
|
||||
ansible.builtin.include_tasks: per_user.yml
|
||||
loop: "{{ dev_env__users }}"
|
||||
|
|
|
|||
56
roles/dev_env/tasks/nodejs.yml
Normal file
56
roles/dev_env/tasks/nodejs.yml
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
- name: Node.js | Read installed-version sentinel
|
||||
ansible.builtin.slurp:
|
||||
src: /etc/node_installed_version
|
||||
register: dev_env__node_sentinel
|
||||
failed_when: false
|
||||
|
||||
- name: Node.js | Determine installed version
|
||||
ansible.builtin.set_fact:
|
||||
dev_env__node_installed: >-
|
||||
{{ (dev_env__node_sentinel.content | b64decode | trim)
|
||||
if dev_env__node_sentinel.content is defined else '' }}
|
||||
|
||||
- name: Node.js | Install pinned release
|
||||
when: dev_env__node_installed != dev_env__node_version
|
||||
block:
|
||||
- name: Node.js | Download release tarball
|
||||
ansible.builtin.get_url:
|
||||
url: "https://nodejs.org/dist/{{ dev_env__node_version }}/node-{{ dev_env__node_version }}-linux-x64.tar.gz"
|
||||
dest: "/tmp/node-{{ dev_env__node_version }}.tar.gz"
|
||||
mode: "0644"
|
||||
|
||||
- name: Node.js | Create versioned install directory
|
||||
ansible.builtin.file:
|
||||
path: "/opt/node-{{ dev_env__node_version }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Node.js | Extract tarball
|
||||
ansible.builtin.unarchive:
|
||||
src: "/tmp/node-{{ dev_env__node_version }}.tar.gz"
|
||||
dest: "/opt/node-{{ dev_env__node_version }}"
|
||||
remote_src: true
|
||||
extra_opts: ["--strip-components=1"]
|
||||
|
||||
- name: Node.js | Symlink node, npm, npx into PATH
|
||||
ansible.builtin.file:
|
||||
src: "/opt/node-{{ dev_env__node_version }}/bin/{{ item }}"
|
||||
dest: "/usr/local/bin/{{ item }}"
|
||||
state: link
|
||||
force: true
|
||||
loop:
|
||||
- node
|
||||
- npm
|
||||
- npx
|
||||
|
||||
- name: Node.js | Write version sentinel
|
||||
ansible.builtin.copy:
|
||||
content: "{{ dev_env__node_version }}"
|
||||
dest: /etc/node_installed_version
|
||||
mode: "0644"
|
||||
|
||||
- name: Node.js | Remove downloaded tarball
|
||||
ansible.builtin.file:
|
||||
path: "/tmp/node-{{ dev_env__node_version }}.tar.gz"
|
||||
state: absent
|
||||
Loading…
Add table
Reference in a new issue