--- - name: After-upgrade verification for indoor (DEV2 via DEV1 tunnel) hosts: all gather_facts: no vars: # ---------------- BusyBox-safe path prefix ---------------- pathprefix: "PATH=/sbin:/usr/sbin:/bin:/usr/bin:$PATH; " # ---------------- DEV1 (outer device) ---------------- dev1_user: "root" dev1_pass: "wavewave" dev1_iface: "br-wan" # ---------------- DEV2 (indoor behind DEV1) ---------------- dev2_host: "192.168.1.1" dev2_port: 22 dev2_side_ip: "192.168.1.11/24" dev2_ssh_user: "root" dev2_passfiles: - "basicpass" - "basicpass2" # ---------------- RabbitMQ (same env scheme as main playbooks) ---------------- rmq_host: "{{ lookup('env','RMQ_HOST') | default('10.210.12.2', true) }}" rmq_port: "{{ lookup('env','RMQ_PORT') | default('15672', true) }}" rmq_user: "{{ lookup('env','RMQ_USER') | default('admin', true) }}" rmq_pass: "{{ lookup('env','RMQ_PASS') | default('change_me', true) }}" rmq_vhost: "{{ lookup('env','RMQ_VHOST') | default('app', true) }}" rmq_exchange: "{{ lookup('env','RMQ_EXCHANGE') | default('controls', true) }}" control_queue: "{{ lookup('env','CONTROLQUEUE') | default('queue_controls', true) }}" tasks: # ---------------- Step 1: Read DEV1 hostname and sanity ---------------- - name: Read DEV1 hostname ansible.builtin.raw: > {{ pathprefix }} (cat /proc/sys/kernel/hostname 2>/dev/null || echo "") register: dev1_host_read changed_when: false - name: Stop if DEV1 hostname mismatch ansible.builtin.meta: end_host when: (dev1_host_read.stdout | trim | length > 0) and ((dev1_host_read.stdout | trim) != (inventory_hostname | string)) # ---------------- Step 2: Setup temporary IP for reachability ---------------- - name: Add temporary IP on DEV1 (ignore if exists) ansible.builtin.raw: > {{ pathprefix }} ip a add {{ dev2_side_ip }} dev {{ dev1_iface }} register: add_ip changed_when: add_ip.rc == 0 failed_when: > add_ip.rc != 0 and ('File exists' not in (add_ip.stdout | default(''))) and ('File exists' not in (add_ip.stderr | default(''))) # ---------------- Step 3: Start SSH tunnel via DEV1 ---------------- - name: Pick random free local port delegate_to: localhost ansible.builtin.shell: | set -e for i in $(seq 1 25); do p="$(shuf -i 20000-39999 -n1)" if ! ss -ltn | awk '{print $4}' | grep -qE "(:|\.)${p}$"; then echo "$p"; exit 0; fi done exit 1 register: pick_port changed_when: false - name: Stop if no free local port ansible.builtin.meta: end_host when: (pick_port.stdout | trim | length) == 0 - name: Record chosen local port and control dir delegate_to: localhost ansible.builtin.set_fact: _local_port: "{{ pick_port.stdout | trim }}" _ctrl_dir: "{{ lookup('ansible.builtin.pipe', 'mktemp -d') }}" - name: Build tunnel control socket path delegate_to: localhost ansible.builtin.set_fact: _ctrl_sock: "{{ _ctrl_dir }}/ssh_tunnel_ctl" - name: Start SSH ControlMaster tunnel via DEV1 delegate_to: localhost ansible.builtin.shell: | set -e sshpass -p '{{ dev1_pass }}' ssh -f -N \ -M -S "{{ _ctrl_sock }}" \ -L "127.0.0.1:{{ _local_port }}:{{ dev2_host }}:{{ dev2_port }}" \ -o StrictHostKeyChecking=no -o PubkeyAuthentication=no -o ConnectTimeout=15 \ "{{ dev1_user }}@{{ ansible_host | default(inventory_hostname) }}" args: executable: /bin/bash register: start_tunnel changed_when: true # ---------------- Step 4: Determine working password for DEV2 ---------------- - name: Try both passfiles for DEV2 delegate_to: localhost ansible.builtin.shell: | for f in {{ dev2_passfiles | join(' ') }}; do if sshpass -f "$f" ssh -p {{ _local_port }} -o StrictHostKeyChecking=no -o PubkeyAuthentication=no -o ConnectTimeout=10 root@127.0.0.1 "echo OK" >/dev/null 2>&1; then echo "$f"; exit 0; fi done echo NONE register: dev2_passfile_try changed_when: false - name: Save selected DEV2 passfile delegate_to: localhost ansible.builtin.set_fact: dev2_passfile_used: "{{ (dev2_passfile_try.stdout | trim) }}" changed_when: false - name: Stop if no valid passfile found ansible.builtin.meta: end_host when: dev2_passfile_used == "NONE" # ---------------- Step 5: Read firmware version on DEV2 ---------------- - name: Read firmware version from DEV2 delegate_to: localhost ansible.builtin.shell: | sshpass -f "{{ dev2_passfile_used }}" ssh -p {{ _local_port }} \ -o StrictHostKeyChecking=no -o PubkeyAuthentication=no -o ConnectTimeout=10 \ root@127.0.0.1 "cat /usr/lib/release/firmux 2>/dev/null || grep -i rev /etc/banner 2>/dev/null || echo unknown" register: dev2_fwver changed_when: false ignore_errors: true - name: Show firmware version readout delegate_to: localhost ansible.builtin.debug: msg: "Firmware version on DEV2: {{ (dev2_fwver.stdout | default('')) | trim }}" # ---------------- Step 6: Publish result journal ---------------- - name: Build journal payload delegate_to: localhost ansible.builtin.set_fact: journal_payload: inscope_device: "{{ ansible_hostname | default(inventory_hostname) }}" task_name: "journal_add" task_result: >- afterupgrade_indoor_check: DEV2 firmware='{{ (dev2_fwver.stdout | default('unknown')) | trim }}' tunnel_port={{ _local_port }} - name: Publish journal to control queue delegate_to: localhost ansible.builtin.uri: url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost | urlencode }}/{{ rmq_exchange | urlencode }}/publish" method: POST user: "{{ rmq_user }}" password: "{{ rmq_pass }}" force_basic_auth: true status_code: 200 headers: content-type: "application/json" body_format: json body: properties: content_type: "application/json" routing_key: "{{ control_queue }}" payload: "{{ journal_payload | to_json }}" payload_encoding: "string" register: rmq_pub_fwver changed_when: (rmq_pub_fwver.json is defined) and (rmq_pub_fwver.json.routed | default(false) | bool) post_tasks: - name: Cleanup tunnel and temp IP block: - ansible.builtin.debug: msg: "Cleaning up tunnel + temp IP" changed_when: false delegate_to: localhost always: - name: Close tunnel delegate_to: localhost ansible.builtin.shell: | ssh -S "{{ _ctrl_sock | default('/dev/null') }}" -O exit 2>/dev/null || true changed_when: false ignore_errors: true - name: Remove control dir delegate_to: localhost ansible.builtin.file: path: "{{ _ctrl_dir | default('/tmp/none') }}" state: absent ignore_errors: true - name: Remove temporary IP from DEV1 ansible.builtin.raw: > {{ pathprefix }} ip a del {{ dev2_side_ip }} dev {{ dev1_iface }} register: del_ip failed_when: false changed_when: false