202 lines
7.4 KiB
Plaintext
202 lines
7.4 KiB
Plaintext
---
|
|
- 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
|