Commit graph

26 commits

Author SHA1 Message Date
4dc335321d feat(users): add lars as a switch operator
RSA key lars@hrossen.dk, taken from his VPS authorized_keys and confirmed
identical to the key on his forgejo.makerfloss.eu account, so this is the
same identity he already uses across MakerFLOSS. Group full, vaulted
password like the other operators.

NOT yet applied to the device: mamba's wired link currently lands on
10.0.0.118/24 (gateway 10.0.0.1), not the CRS310 mgmt VLAN, so 192.168.88.1
is unreachable. Lint, syntax-check and the operator list are verified; the
run-twice check is owed once the cable is back in ether8.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EyYJWABgSEHzbjsZGrkxVE
2026-09-01 21:35:41 +02:00
e6c1651da1 feat(users): model operators as accounts; add claus
Replace the single `switch_admin_user` day-2 task with a `switch_operators`
list in host_vars: one RouterOS user per person, each with the public keys
that may log in as it. Logins become attributable and revocable per person
instead of accreting as extra keys on `sjat`.

Adds claus@stjerno.dk as user `claus` (group full), and codifies the two
keys already on the device (sjat's mamba seat, the claude@ubongo automation
seat that was hand-imported earlier today).

Two things the mechanism has to get right:
- Idempotency: query /user/ssh-keys first and upload only missing keys.
  An unconditional net_put reports changed on every run.
- No passwordless accounts: WinBox/console accept an empty password and
  WinBox is deliberately left on for recovery, so each operator gets a
  vaulted password. users.yml asserts one exists before touching anything.

Verified: yamllint, ansible-lint (production), syntax-check, and the play
run twice against crs310-maker — second run changed=0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EyYJWABgSEHzbjsZGrkxVE
2026-09-01 20:29:18 +02:00
d96ec189d4 fix: repair stdout callback + refresh the access recipe
community.general 12 removed the `yaml` stdout callback, so every ansible
invocation died with a [DEPRECATED] error. Use the core default callback
with `callback_result_format = yaml` — identical output, no collection dep.

CLAUDE.md's access recipe still described the retired `-J kuku … 10.8.0.4`
hop; `mamba` is now an ssh alias over the wg overlay. Also record two things
that cost time on the 2026-09-01 bench session: the vaulted admin password
cannot authenticate over SSH at all (RouterOS refuses password auth for a
user holding an SSH key while always-allow-password-login=no), so each
operator seat needs its own key imported; and the checked-in .venv carries
shebangs from the original /home/sjat path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EyYJWABgSEHzbjsZGrkxVE
2026-09-01 20:20:46 +02:00
sjat
2796616d05 docs: capture topology + operational learnings in CLAUDE.md/README
Bring the everyday guides up to the live state (flat data VLAN 30 + isolated mgmt
VLAN 99 on ether8, DHCP + web UI experiment) and record the gotchas that cost time:
the bench tunnel (paramiko ignores ProxyJump), mamba NM-profile stickiness on cable
flap, the RouterOS find-by-address quirk, and the commit-confirmed detached-flip
pattern for lockout-prone changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 13:04:35 +02:00
sjat
18de750507 feat(mgmt): DHCP server + web UI on the isolated mgmt VLAN
Makerspace experiment: plug into ether8 and get a 192.168.88.x lease, reach the
admin at http://192.168.88.1 (web UI re-enabled) / WinBox / SSH. Login still
required; default admin stays disabled. mamba keeps static .2 (outside the pool).
New flags switch_web_enabled + switch_mgmt_dhcp_enabled/pool/network (off by
default). Verified: www HTTP 200, lease handed out + bound, run-twice idempotent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 12:55:03 +02:00
sjat
199edf85ad fix(vlans): robust bridge-IP removal; record cutover + gotchas
RouterOS 'find ... address=<prefix>' never matches an ip/address value, so the
legacy-bridge-IP removal is now a :foreach get-and-compare. Refresh the committed
export.rsc to the post-cutover config (flat VLAN 30 + isolated mgmt VLAN 99 on
ether8, vlan-filtering on). Spec updated with execution notes (NM autoconnect flap,
the find-address quirk, and the commit-confirmed detached-flip technique used).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 12:38:04 +02:00
sjat
ebd21623ef feat: real flat+mgmt-VLAN topology in host_vars; role tweaks
host_vars: DATA VLAN 30 (ether1 uplink + ether2-7 + sfp1/2), isolated MGMT VLAN 99
on ether8, mgmt 192.168.88.1/24, no gateway, NTP disabled. Role: switch_ntp_enabled
flag (enable/disable NTP), conditional default route (skip when no gateway), and a
guarded removal of the legacy defconf bridge IP so the mgmt IP lives only on vlan-mgmt.
Membership Jinja re-validated; lint+syntax clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 12:15:23 +02:00
sjat
8a42f5482f docs(spec): flat data path + isolated mgmt VLAN topology
ether1 copper uplink (SFP+ deferred), flat 10.2.30.0/24 data VLAN 30, isolated
mgmt VLAN 99 on ether8 with switch mgmt 192.168.88.1/24, no gateway/NTP/DNS.
Includes the lockout-safe on-site cutover runbook.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 12:12:22 +02:00
sjat
67554c0b38 docs: mark domain tasks implemented; note deferred vlans device run
Implements Task 10 doc updates. README/CLAUDE/role-README now reflect that all
task files + play_bootstrap/play_backup are implemented and idempotency-verified,
that vlans is built+validated but its device run is deferred (placeholder topology,
on-site recovery needed), and that the bootstrap/backup plays exist. Corrects the
bootstrap invocation example (-e ansible_user=admin --ask-pass).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 19:45:36 +02:00
sjat
5931542473 feat: first-contact bootstrap play (named admin + SSH key import)
Implements Task 4 (the play was run on-site but never committed). Creates the
named admin user, imports the operator pubkey over SCP (net_put), enables SSH.
Improvements over the plan: the key import is :if [find] guarded so re-runs don't
create duplicate keys, and the vaulted password is loaded via vars_files (it is
not auto-loaded because group_vars/mikrotik.vault.yml doesn't match the group-name
convention). Verified idempotent (changed=0) against crs310-maker; no duplicate key.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 19:42:56 +02:00
sjat
5a5a194437 feat(firmware): opt-in RouterOS + RouterBOOT upgrade to pinned target
Implements Task 9. Version-guarded (no-op when already >= switch_firmware_target,
as crs310-maker is at 7.19.6). Upgrade steps grouped in a block; reboot uses
ignore_unreachable + wait_for_connection instead of ignore_errors so it stays
lint-clean under the production profile. Syntax + lint only; not run (opt-in).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 19:40:24 +02:00
sjat
33dc378c3c feat(vlans): VLAN-aware bridge, ports, mgmt interface (mechanism)
Implements Task 7. Deliberate lockout-safe ordering (vlan-filtering LAST) with
:if [find] guards that adopt the existing defconf bridge/ports rather than
recreating them. Membership Jinja: trunk ports tagged per tagged_vlans, access
ports untagged per pvid, bridge/CPU tagged only on the mgmt VLAN; else={set} makes
membership declarative. Jinja render validated offline against the placeholder
topology. Device run DEFERRED to an on-site session with a recovery channel
(remote bench has no serial/WinBox-MAC fallback). Topology stays placeholder.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 19:39:04 +02:00
sjat
39a12ae23b feat(backup): export + binary backup, fetch into repo
Implements Task 8. play_backup.yml ensures the local dir then includes backup.yml,
which runs /export + /system backup save and pulls both over SCP (net_get).
Binary .backup is gitignored (may contain secrets); export.rsc is committed.
Verified against crs310-maker on the bench: both artifacts fetched non-empty.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 19:36:14 +02:00
sjat
ea7cf5ec03 feat(users): ensure named admin, disable default admin
Implements Task 6. Guards user creation with :if [find]; disables the built-in
admin (switch_disable_default_admin) now that sjat key login is proven. Verified
run-twice idempotent (changed=0); admin disabled=true, sjat reachable on bench.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 19:34:58 +02:00
sjat
cfc6ec9721 feat(identity): identity, DNS, NTP, service hardening
Implements Task 5. Disables telnet/ftp/www/www-ssl/api/api-ssl (winbox kept
for recovery), sets DNS + NTP client, ensures SSH on the configured port.
Verified run-twice idempotent (changed=0) against crs310-maker on the bench.
Also sets ansible_user=sjat in host_vars for day-2 key auth.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 19:33:48 +02:00
sjat
12001abac6 docs: README, role README, CLAUDE.md
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 19:22:43 +02:00
sjat
3fef7ba9e5 feat: bootstrap CRS310 on-site (sjat user + key + vaulted password)
Recorded device facts (CRS310-8G+2S+IN, serial HM40B8TDNDD, RouterOS 7.19.6,
pinned firmware_target 7.19.6). Added encrypted makerfloss-vault admin password
and excluded *.vault.yml from linters. Device bootstrapped over SSH: identity
set to crs310-maker, named user sjat (full) with operator ed25519 key + vaulted
password; key-based login verified from the controller. Default admin still
enabled (hardening + VLANs deferred to the day-2 task files).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 19:13:53 +02:00
sjat
bdfde1644c Merge: initial scaffolding + field guide + role skeleton (Tasks 1-3)
No-device tasks complete and two-stage reviewed:
- repo scaffolding (direnv, ansible.cfg, lint, requirements)
- makerfloss vault identity, inventory, connection group_vars
- role skeleton makerfloss.mikrotik_switch (stubbed domains), host_vars, play_switch.yml
- on-site makerspace field guide; plan carry-over notes

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 18:26:08 +02:00
sjat
0721ecc34c docs(plan): carry-over notes from skeleton code review
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 08:38:23 +02:00
sjat
ad2c00f84a feat: role skeleton, host_vars, day-2 play (stubbed domains)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 08:34:13 +02:00
sjat
5c04b3405b docs(group_vars): clarify ansible_user=admin is a bootstrap default
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 08:31:34 +02:00
sjat
3abb937a03 feat: inventory, connection group_vars, makerfloss vault identity
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 08:29:57 +02:00
sjat
be9ac7f78b chore: repo scaffolding (direnv, ansible.cfg, lint, requirements) 2026-06-07 08:26:09 +02:00
sjat
66a1aaad69 docs: on-site makerspace field guide for CRS310 prep
Standalone printable checklist: bring-list, access via WinBox MAC,
confirm RouterOS, upgrade+pin firmware, record facts, reset to
no-defaults, temp IP + SSH, addressing decisions, physical finish.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 08:24:10 +02:00
sjat
7731f98f15 docs: CRS310 Ansible implementation plan
Bite-sized, idempotency-verified plan: scaffolding -> vault/inventory ->
role skeleton -> bootstrap (key import) -> domain tasks (identity, users,
vlans, backup, firmware) -> docs/publish. Phase 0 gates device-dependent
work on physical switch prep + forgejo repo creation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 08:12:15 +02:00
sjat
f1d7b3059c docs: CRS310 Ansible management design (brainstorming spec)
Initial design doc for managing the makerspace MikroTik CRS310-8G+2S+IN
switch as IaC over SSH with community.routeros. Single-switch scope,
fresh repo in AnsibleBaobabV4 conventions, separate makerfloss vault.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 08:04:56 +02:00