diff --git a/ansible/README.md b/ansible/README.md index 1c8333b..153f742 100644 --- a/ansible/README.md +++ b/ansible/README.md @@ -39,7 +39,6 @@ Secret layout expected in OpenBao (KVv2, mount `secret`): |---|---| | `routeros_api` | `username`, `password` | | `wan_pppoe` | `username`, `password` | -| `router_tailscale` | `container_password` | ## OpenWrt dlink AP diff --git a/ansible/playbooks/routeros.yml b/ansible/playbooks/routeros.yml index 8d577c0..c2d54af 100644 --- a/ansible/playbooks/routeros.yml +++ b/ansible/playbooks/routeros.yml @@ -39,15 +39,10 @@ engine_mount_point=openbao_kv_mount ).secret[openbao_fields.wan_pppoe.password_key] }} - routeros_tailscale_container_password: >- - {{ - lookup( - 'community.hashi_vault.vault_kv2_get', - openbao_fields.routeros_tailscale_container.path, - engine_mount_point=openbao_kv_mount - ).secret[openbao_fields.routeros_tailscale_container.container_password_key] - }} + no_log: true + tags: + - tailscale-script module_defaults: group/community.routeros.api: diff --git a/ansible/roles/routeros/tasks/addressing.yml b/ansible/roles/routeros/tasks/addressing.yml index 7cdc6b3..18a2ced 100644 --- a/ansible/roles/routeros/tasks/addressing.yml +++ b/ansible/roles/routeros/tasks/addressing.yml @@ -3,9 +3,9 @@ community.routeros.api_modify: path: ip address data: - - address: 172.17.0.1/16 - interface: dockers - network: 172.17.0.0 + - address: 172.20.0.1/24 + interface: containers + network: 172.20.0.0 - address: 192.168.4.1/24 interface: lo network: 192.168.4.0 @@ -44,7 +44,7 @@ from-pool: pool1 interface: vlan2 - address: 2001:470:61a3:500:ffff:ffff:ffff:ffff/64 - interface: dockers + interface: containers - address: 2001:470:61a3:100::1/64 advertise: false interface: vlan4 diff --git a/ansible/roles/routeros/tasks/base.yml b/ansible/roles/routeros/tasks/base.yml index ac85427..a67fe19 100644 --- a/ansible/roles/routeros/tasks/base.yml +++ b/ansible/roles/routeros/tasks/base.yml @@ -5,7 +5,7 @@ data: - name: bridge1 vlan-filtering: true - - name: dockers + - name: containers handle_absent_entries: remove handle_entries_content: remove_as_much_as_possible @@ -62,8 +62,8 @@ community.routeros.api_modify: path: interface bridge port data: - - bridge: dockers - interface: veth1 + - bridge: containers + interface: veth-tailscale comment: Tailscale container interface - bridge: bridge1 interface: ether1 @@ -190,6 +190,18 @@ cache-size: 20480 servers: 1.1.1.1,1.0.0.1,2606:4700:4700::1111,2606:4700:4700::1001 +- name: Configure DNS static entries + community.routeros.api_modify: + path: ip dns static + data: + - name: ts.net + type: FWD + forward-to: 100.100.100.100 + match-subdomain: true + comment: Tailscale MagicDNS + handle_absent_entries: remove + handle_entries_content: remove_as_much_as_possible + - name: Configure NAT-PMP global settings community.routeros.api_find_and_modify: ignore_dynamic: false @@ -202,7 +214,7 @@ community.routeros.api_modify: path: ip nat-pmp interfaces data: - - interface: dockers + - interface: containers type: internal - interface: pppoe-gpon type: external @@ -223,7 +235,7 @@ community.routeros.api_modify: path: ip upnp interfaces data: - - interface: dockers + - interface: containers type: internal - interface: pppoe-gpon type: external diff --git a/ansible/roles/routeros/tasks/containers.yml b/ansible/roles/routeros/tasks/containers.yml index 965e3d4..ca63958 100644 --- a/ansible/roles/routeros/tasks/containers.yml +++ b/ansible/roles/routeros/tasks/containers.yml @@ -5,28 +5,12 @@ path: container config find: {} values: - registry-url: https://ghcr.io - tmpdir: /tmp1/pull + tmpdir: tmp - name: Configure container env lists community.routeros.api_modify: path: container envs - data: - - key: ADVERTISE_ROUTES - list: tailscale - value: 192.168.0.0/24,192.168.1.0/24,192.168.4.1/32,192.168.100.1/32,192.168.255.0/24,10.42.0.0/16,10.43.0.0/16,10.44.0.0/16,2001:470:61a3::/48 - - key: CONTAINER_GATEWAY - list: tailscale - value: 172.17.0.1 - - key: PASSWORD - list: tailscale - value: "{{ routeros_tailscale_container_password }}" - - key: TAILSCALE_ARGS - list: tailscale - value: --accept-routes --advertise-exit-node --snat-subnet-routes=false - - key: UPDATE_TAILSCALE - list: tailscale - value: y + data: [] handle_absent_entries: remove handle_entries_content: remove_as_much_as_possible @@ -35,11 +19,8 @@ path: container mounts data: - dst: /var/lib/tailscale - list: tailscale - src: /usb1/tailscale - - dst: /root - list: tailscale-root - src: /tmp1/tailscale-root + list: tailscale_state + src: tailscale/state handle_absent_entries: remove handle_entries_content: remove_as_much_as_possible @@ -48,16 +29,12 @@ path: container data: - dns: 172.17.0.1 - envlists: tailscale - hostname: mikrotik - interface: veth1 - layer-dir: "" - mountlists: tailscale - name: tailscale-mikrotik:latest - remote-image: fluent-networks/tailscale-mikrotik:latest - root-dir: /usb1/containers/tailscale + interface: veth-tailscale + logging: true + mountlists: tailscale_state + name: tailscale + remote-image: gitea.lumpiasty.xyz/lumpiasty/mikrotik-tailscale:stable + root-dir: tailscale/root start-on-boot: true - tmpfs: /tmp:67108864:01777 - workdir: / handle_absent_entries: remove handle_entries_content: remove_as_much_as_possible diff --git a/ansible/roles/routeros/tasks/firewall.yml b/ansible/roles/routeros/tasks/firewall.yml index a7c6e9d..58f9da5 100644 --- a/ansible/roles/routeros/tasks/firewall.yml +++ b/ansible/roles/routeros/tasks/firewall.yml @@ -65,8 +65,8 @@ out-interface-list: wan - action: accept chain: forward - comment: Allow from dockers to everywhere - in-interface: dockers + comment: Allow from containers to everywhere + in-interface: containers - action: jump chain: forward comment: Allow port forwards @@ -137,14 +137,14 @@ protocol: tcp - action: accept chain: input - comment: Allow DNS from dockers + comment: Allow DNS from containers dst-port: 53 - in-interface: dockers + in-interface: containers protocol: udp - action: accept chain: input dst-port: 53 - in-interface: dockers + in-interface: containers protocol: tcp - action: accept chain: input @@ -188,9 +188,9 @@ protocol: udp - action: accept chain: input - comment: NAT-PMP from dockers (for tailscale) + comment: NAT-PMP from containers (for tailscale) dst-port: 5351 - in-interface: dockers + in-interface: containers protocol: udp - action: reject chain: input @@ -229,8 +229,8 @@ - action: accept chain: allow-ports comment: Allow anything udp to Tailscale - dst-address: 172.17.0.2 - out-interface: dockers + dst-address: 172.20.0.2 + out-interface: containers protocol: udp - action: accept chain: allow-ports @@ -419,14 +419,14 @@ out-interface-list: wan - action: accept chain: forward - comment: Allow from dockers to everywhere - in-interface: dockers + comment: Allow from containers to everywhere + in-interface: containers - action: accept chain: forward - comment: Allow from internet to dockers + comment: Allow from internet to containers dst-address: 2001:470:61a3:500::/64 in-interface-list: wan - out-interface: dockers + out-interface: containers - action: accept chain: forward comment: Allow tcp transmission port to LAN @@ -485,14 +485,14 @@ protocol: tcp - action: accept chain: input - comment: Allow DNS from dockers + comment: Allow DNS from containers dst-port: 53 - in-interface: dockers + in-interface: containers protocol: udp - action: accept chain: input dst-port: 53 - in-interface: dockers + in-interface: containers protocol: tcp - action: accept chain: input diff --git a/ansible/roles/routeros/tasks/hardware.yml b/ansible/roles/routeros/tasks/hardware.yml index d72a155..e86257a 100644 --- a/ansible/roles/routeros/tasks/hardware.yml +++ b/ansible/roles/routeros/tasks/hardware.yml @@ -39,19 +39,43 @@ loop_control: label: "{{ item.default_name }}" -- name: Configure temporary disk for containers - community.routeros.api_modify: +# community.routeros.api_modify can't remove hardware disks +# but it tries to do so with handle_absent_entries: remove +# Working around by manually deleting other ones + +- name: Read current disk entries + community.routeros.api_info: path: disk - data: - - slot: tmp1 - type: tmpfs - # This is not ideal, there's no unique identifier for usb disk, - # after reinstall it might be assigned to another slot - # Just adding disk with slot usb1 and not specifying anything else - # so ansible doesn't touch it - - slot: usb1 - handle_absent_entries: remove - handle_entries_content: remove_as_much_as_possible + register: routeros_disks + check_mode: false + +- name: Remove stale software-defined disk entries + community.routeros.api: + path: disk + remove: "{{ item['.id'] }}" + loop: >- + {{ + routeros_disks.result + | rejectattr('type', 'in', ['hardware', 'partition']) + | rejectattr('slot', 'equalto', 'tmp') + }} + loop_control: + label: "{{ item.slot }}" + +- name: Create temporary disk for containers if absent + community.routeros.api: + path: disk + add: "slot=tmp type=tmpfs" + when: routeros_disks.result | selectattr('slot', 'equalto', 'tmp') | list | length == 0 + +- name: Configure temporary disk for containers + community.routeros.api_find_and_modify: + ignore_dynamic: false + path: disk + find: + slot: tmp + values: + type: tmpfs - name: Configure switch settings community.routeros.api_find_and_modify: diff --git a/ansible/roles/routeros/tasks/main.yml b/ansible/roles/routeros/tasks/main.yml index 7505254..489e265 100644 --- a/ansible/roles/routeros/tasks/main.yml +++ b/ansible/roles/routeros/tasks/main.yml @@ -2,12 +2,12 @@ - name: Preflight checks ansible.builtin.import_tasks: preflight.yml -- name: Base network configuration - ansible.builtin.import_tasks: base.yml - - name: WAN and tunnel interfaces ansible.builtin.import_tasks: wan.yml +- name: Base network configuration + ansible.builtin.import_tasks: base.yml + - name: Hardware and platform tuning ansible.builtin.import_tasks: hardware.yml diff --git a/ansible/roles/routeros/tasks/preflight.yml b/ansible/roles/routeros/tasks/preflight.yml index 8f6e2e2..03d1c50 100644 --- a/ansible/roles/routeros/tasks/preflight.yml +++ b/ansible/roles/routeros/tasks/preflight.yml @@ -32,15 +32,4 @@ fail_msg: "RouterOS device-mode does not report container as enabled. Payload: {{ routeros_device_mode | to_nice_json }}" success_msg: "RouterOS device-mode confirms container=yes" -- name: Read configured disks - community.routeros.api_info: - path: disk - register: routeros_disks - check_mode: false -- name: Assert usb1 disk is present - ansible.builtin.assert: - that: - - (routeros_disks.result | selectattr('slot', 'equalto', 'usb1') | list | length) > 0 - fail_msg: "Required disk slot usb1 is not present on router." - success_msg: "Required disk usb1 is present" diff --git a/ansible/roles/routeros/tasks/routing.yml b/ansible/roles/routeros/tasks/routing.yml index 446105f..3281c6c 100644 --- a/ansible/roles/routeros/tasks/routing.yml +++ b/ansible/roles/routeros/tasks/routing.yml @@ -7,7 +7,7 @@ disabled: false distance: 1 dst-address: 100.64.0.0/10 - gateway: 172.17.0.2 + gateway: 172.20.0.2 routing-table: main scope: 30 suppress-hw-offload: false diff --git a/ansible/roles/routeros/tasks/system.yml b/ansible/roles/routeros/tasks/system.yml index 491dda9..cfd21f3 100644 --- a/ansible/roles/routeros/tasks/system.yml +++ b/ansible/roles/routeros/tasks/system.yml @@ -19,6 +19,101 @@ handle_absent_entries: ignore handle_entries_content: remove_as_much_as_possible +# The RouterOS API can neither store multi-line script source (newlines +# collapse into one line) nor evaluate the [/file/get ...] expression itself. +# So we fetch the update logic as a .rsc file onto the router's flash, then run +# a single-line bootstrap script (which the API CAN store) whose body RouterOS +# evaluates natively: it builds the real, browsable, multi-line named script +# from the file via [/file get ... contents]. The scheduler then runs that +# named script by name (the upstream-intended design). The update logic stays +# out of this repo entirely. +- name: Download tailscale auto-update script to router + community.routeros.api: + path: tool + cmd: >- + fetch + url=https://gitea.lumpiasty.xyz/Lumpiasty/mikrotik-tailscale/raw/branch/main/routeros/update-tailscale.rsc + dst-path=update-tailscale.rsc + mode=https + changed_when: true + tags: + - tailscale-script + +- name: Build the named auto-update script from the fetched file + community.routeros.api: + path: system script + cmd: >- + add name=update-tailscale-bootstrap + source=":do { /system script remove update-tailscale } on-error={}; + /system script add name=update-tailscale + comment=\"Check for mikrotik-tailscale image updates\" + source=[/file get update-tailscale.rsc contents]" + changed_when: true + tags: + - tailscale-script + +- name: Find bootstrap script id + community.routeros.api: + path: system script + extended_query: + attributes: [.id, name] + where: + - attribute: name + is: "==" + value: update-tailscale-bootstrap + register: routeros_bootstrap + changed_when: false + tags: + - tailscale-script + +- name: Run bootstrap to create the named auto-update script + community.routeros.api: + path: system script + cmd: "run .id={{ routeros_bootstrap.msg[0]['.id'] }}" + register: routeros_bootstrap_run + failed_when: + - routeros_bootstrap_run is failed + - "'interrupted' not in (routeros_bootstrap_run.msg | string)" + changed_when: true + tags: + - tailscale-script + +- name: Verify named auto-update script exists + community.routeros.api: + path: system script + extended_query: + attributes: [.id, name] + where: + - attribute: name + is: "==" + value: update-tailscale + register: routeros_named_script + failed_when: (routeros_named_script.msg | length) == 0 + changed_when: false + tags: + - tailscale-script + +- name: Remove bootstrap script + community.routeros.api: + path: system script + remove: "{{ routeros_bootstrap.msg[0]['.id'] }}" + changed_when: true + tags: + - tailscale-script + +- name: Configure tailscale auto-update scheduler + community.routeros.api_modify: + path: system scheduler + data: + - name: update-tailscale + interval: 1d + on-event: /system script run update-tailscale + comment: Check for mikrotik-tailscale image updates + handle_absent_entries: remove + handle_entries_content: remove_as_much_as_possible + tags: + - tailscale-script + - name: Configure service ports and service enablement community.routeros.api_find_and_modify: ignore_dynamic: false diff --git a/ansible/roles/routeros/tasks/wan.yml b/ansible/roles/routeros/tasks/wan.yml index ebe4783..3776174 100644 --- a/ansible/roles/routeros/tasks/wan.yml +++ b/ansible/roles/routeros/tasks/wan.yml @@ -29,13 +29,13 @@ community.routeros.api_modify: path: interface veth data: - - address: 172.17.0.2/16,2001:470:61a3:500::1/64 + - address: 172.20.0.2/24,2001:470:61a3:500::1/64 container-mac-address: 7E:7E:A1:B1:2A:7C dhcp: false - gateway: 172.17.0.1 + gateway: 172.20.0.1 gateway6: 2001:470:61a3:500:ffff:ffff:ffff:ffff mac-address: 7E:7E:A1:B1:2A:7B - name: veth1 + name: veth-tailscale comment: Tailscale container handle_absent_entries: remove handle_entries_content: remove_as_much_as_possible diff --git a/ansible/roles/routeros/vars/main.yml b/ansible/roles/routeros/vars/main.yml index bfb66e2..0ee3cc8 100644 --- a/ansible/roles/routeros/vars/main.yml +++ b/ansible/roles/routeros/vars/main.yml @@ -14,6 +14,4 @@ openbao_fields: path: wan_pppoe username_key: username password_key: password - routeros_tailscale_container: - path: router_tailscale - container_password_key: container_password +