From 5931542473ae08032f32714161b9962ba2424d1a Mon Sep 17 00:00:00 2001 From: sjat Date: Mon, 8 Jun 2026 19:42:56 +0200 Subject: [PATCH] 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) --- play_bootstrap.yml | 52 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 play_bootstrap.yml diff --git a/play_bootstrap.yml b/play_bootstrap.yml new file mode 100644 index 0000000..10895cc --- /dev/null +++ b/play_bootstrap.yml @@ -0,0 +1,52 @@ +--- +# FIRST-CONTACT bootstrap (run once, password auth): +# ansible-playbook play_bootstrap.yml -e ansible_user=admin --ask-pass +# Creates the named admin user, imports the operator SSH public key over SCP, and +# enables SSH so day-2 runs (play_switch.yml) can use key auth as that user. +# Keep a WinBox MAC / serial recovery channel open while running this. +# +# vault_switch_admin_password is decrypted automatically from +# group_vars/mikrotik.vault.yml via the `makerfloss` vault id in ansible.cfg. +# All device-touching tasks are :if [find] guarded, so the play is safe to re-run. +- name: Bootstrap MikroTik switch (first contact, password auth) + hosts: mikrotik + gather_facts: false + # The vaulted admin password is NOT auto-loaded: group_vars/mikrotik.vault.yml + # doesn't match the group-name convention (only mikrotik.yml or group_vars/mikrotik/ + # auto-load), so load it explicitly here. Day-2 (play_switch.yml) is key auth and + # needs no secret. Decrypted automatically via the makerfloss vault id in ansible.cfg. + vars_files: + - group_vars/mikrotik.vault.yml + vars: + pubkey_local: "{{ switch_admin_ssh_pubkey_file | default('~/.ssh/id_ed25519.pub') }}" + pubkey_remote: "id_ansible.pub" + tasks: + - name: Create named admin user (idempotent) + community.routeros.command: + commands: + - >- + :if ([:len [/user find name="{{ switch_admin_user }}"]] = 0) + do={ /user add name="{{ switch_admin_user }}" + group="{{ switch_admin_group | default('full') }}" + password="{{ vault_switch_admin_password }}" } + changed_when: false + + - name: Copy operator public key to the switch + ansible.netcommon.net_put: + src: "{{ pubkey_local }}" + dest: "{{ pubkey_remote }}" + + - name: Import the SSH public key for the admin user (only if none yet) + community.routeros.command: + commands: + - >- + :if ([:len [/user/ssh-keys/find user="{{ switch_admin_user }}"]] = 0) + do={ /user/ssh-keys/import public-key-file="{{ pubkey_remote }}" + user="{{ switch_admin_user }}" } + changed_when: false + + - name: Ensure SSH service is enabled + community.routeros.command: + commands: + - /ip/service/set ssh disabled=no port={{ switch_ssh_port | default(22) }} + changed_when: false