294 lines
10 KiB
YAML
294 lines
10 KiB
YAML
---
|
|
# sot-updater.yml — Read fw on Dev1 + Dev2, publish to NetBox via Rabbit (telemetry mode)
|
|
|
|
- name: Read fw on Dev1 + Dev2, publish NetBox custom fields (full base, AIRPINGs, soft-fail telemetry)
|
|
hosts: all
|
|
gather_facts: no
|
|
|
|
vars:
|
|
pathprefix: "PATH=/sbin:/usr/sbin:/bin:/usr/bin:$PATH; "
|
|
dev1_user: "root"
|
|
dev1_pass: "wavewave"
|
|
ssh_timeout: 30
|
|
|
|
dev2_host: "192.168.1.1"
|
|
dev2_port: 22
|
|
dev2_ssh_user: "root"
|
|
dev2_passfiles: [ "basicpass", "basicpass2" ]
|
|
|
|
dev2_side_ip: "192.168.1.11/24"
|
|
dev1_iface: "br-wan"
|
|
arping_iface: "eth0"
|
|
dev2_side_ip_addr: "{{ dev2_side_ip.split('/')[0] }}"
|
|
|
|
ssh_opts_common: >-
|
|
-o PreferredAuthentications=password
|
|
-o PubkeyAuthentication=no
|
|
-o StrictHostKeyChecking=no
|
|
-o UserKnownHostsFile=/dev/null
|
|
-o NumberOfPasswordPrompts=1
|
|
-o ConnectTimeout=30
|
|
|
|
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:
|
|
- name: "⚙️ Start | Dev1 banner → tunnel/auth → Dev2 firmux (telemetry mode)"
|
|
debug:
|
|
msg:
|
|
- "Device: {{ inventory_hostname }}"
|
|
- "Mode: report-only (soft-fail; publish journals on failures)"
|
|
|
|
# ----------------------- Temp IP on DEV1 -----------------------
|
|
- name: Add temporary IP on DEV1
|
|
raw: "{{ pathprefix }} ip a add {{ dev2_side_ip }} dev {{ dev1_iface }}"
|
|
register: add_ip
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Refresh ARP #1
|
|
raw: "{{ pathprefix }} arping -U -I {{ arping_iface }} {{ dev2_side_ip_addr }} -c 3"
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
# ----------------------- Local tunnel prep -----------------------
|
|
- name: Pick a free local TCP port for the tunnel
|
|
delegate_to: localhost
|
|
shell: |
|
|
for i in $(seq 1 50); do
|
|
p="$(shuf -i 20000-39999 -n 1)"
|
|
ss -ltn | awk '{print $4}' | grep -qE "(:|\.)${p}$" || { echo $p; exit 0; }
|
|
done
|
|
exit 1
|
|
register: pick_port
|
|
failed_when: false
|
|
|
|
- name: Record chosen port and create control dir
|
|
delegate_to: localhost
|
|
shell: "mktemp -d"
|
|
register: mktemp_dir
|
|
failed_when: false
|
|
|
|
- name: Set facts for tunnel paths
|
|
delegate_to: localhost
|
|
set_fact:
|
|
_local_port: "{{ (pick_port.stdout | default('') | trim) }}"
|
|
_ctrl_dir: "{{ (mktemp_dir.stdout | default('') | trim) }}"
|
|
_ctrl_sock: "{{ (mktemp_dir.stdout | default('') | trim) }}/ssh_tunnel_ctl"
|
|
|
|
# ----------------------- AIRPING #2 -----------------------
|
|
- name: Refresh ARP #2
|
|
raw: "{{ pathprefix }} arping -U -I {{ arping_iface }} {{ dev2_side_ip_addr }} -c 3"
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
# ----------------------- Start tunnel -----------------------
|
|
- name: Start SSH tunnel via DEV1
|
|
delegate_to: localhost
|
|
shell: |
|
|
set -e
|
|
USER="{{ dev1_user }}"
|
|
HOST="{{ ansible_host | default(inventory_hostname) }}"
|
|
sshpass -p '{{ dev1_pass }}' ssh -f -N {{ ssh_opts_common }} \
|
|
-M -S "{{ _ctrl_sock }}" \
|
|
-L "127.0.0.1:{{ _local_port }}:{{ dev2_host }}:{{ dev2_port }}" \
|
|
"${USER}@${HOST}"
|
|
args: { executable: /bin/bash }
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Probe tunnel
|
|
delegate_to: localhost
|
|
shell: "nc -z -w5 127.0.0.1 {{ _local_port }}"
|
|
register: nc_probe
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Set tunnel_ok
|
|
delegate_to: localhost
|
|
set_fact:
|
|
tunnel_ok: "{{ nc_probe.rc == 0 }}"
|
|
|
|
# ----------------------- DEV1 banner -----------------------
|
|
- name: Dev1 | Probe banner
|
|
delegate_to: localhost
|
|
shell: |
|
|
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
|
|
"{{ dev1_user }}@{{ ansible_host | default(inventory_hostname) }}" \
|
|
"cat /etc/banner | grep -i rev | head -n1"
|
|
register: dev1_banner
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Dev1 | Normalize banner (convert 'rev' → '-r' and extract)
|
|
delegate_to: localhost
|
|
run_once: true
|
|
set_fact:
|
|
dev1_fw_clean: >-
|
|
{{
|
|
(
|
|
(dev1_banner.stdout | default('') | trim)
|
|
| regex_replace('[Rr][Ee][Vv]\\.?\\s*([0-9]+)', '-r\\1')
|
|
| regex_search('[0-9]+\\.[0-9]+\\.[0-9]+-r[0-9]+')
|
|
)
|
|
| default('', true)
|
|
| trim
|
|
}}
|
|
|
|
- name: Publish Dev1 fw_version
|
|
delegate_to: localhost
|
|
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: "{{ {
|
|
'inscope_device': (ansible_hostname | default(inventory_hostname)),
|
|
'task_name': 'custom_field_set',
|
|
'task_add1': 'fw_version',
|
|
'task_result': (dev1_fw_clean if (dev1_fw_clean|length>0) else 'unavailable')
|
|
} | to_json }}"
|
|
payload_encoding: "string"
|
|
changed_when: false
|
|
|
|
# ----------------------- DEV2 auth -----------------------
|
|
- name: Try DEV2 login
|
|
when: tunnel_ok | default(false)
|
|
delegate_to: localhost
|
|
shell: |
|
|
for f in basicpass basicpass2; do
|
|
PORT="{{ _local_port }}"
|
|
if sshpass -f "$f" ssh -o StrictHostKeyChecking=no -o PubkeyAuthentication=no \
|
|
-p "$PORT" root@127.0.0.1 "echo ok" >/dev/null 2>&1; then
|
|
echo "$f"; exit 0
|
|
fi
|
|
done
|
|
exit 1
|
|
register: dev2_auth
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Set dev2_passfile_used
|
|
delegate_to: localhost
|
|
set_fact:
|
|
dev2_passfile_used: "{{ (dev2_auth.rc == 0) | ternary(dev2_auth.stdout | trim, 'NONE') }}"
|
|
|
|
# ----------------------- AIRPING #3 -----------------------
|
|
- name: Refresh ARP #3
|
|
raw: "{{ pathprefix }} arping -U -I {{ arping_iface }} {{ dev2_side_ip_addr }} -c 3"
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
|
|
# ----------------------- DEV2 firmux (simplified, literal) -----------------------
|
|
- name: Dev2 | Read /usr/lib/release/firmux
|
|
when: tunnel_ok | default(false) and dev2_passfile_used != 'NONE'
|
|
delegate_to: localhost
|
|
shell: |
|
|
PORT="{{ _local_port }}"
|
|
sshpass -f "{{ dev2_passfile_used }}" ssh \
|
|
-o StrictHostKeyChecking=no -o PubkeyAuthentication=no \
|
|
-o ConnectTimeout=15 \
|
|
-p "$PORT" "{{ dev2_ssh_user }}@127.0.0.1" \
|
|
"cat /usr/lib/release/firmux 2>/dev/null || true"
|
|
register: dev2_firmux
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Dev2 | Extract firmware version line (prefer 'rev', else first non-empty)
|
|
delegate_to: localhost
|
|
run_once: true
|
|
set_fact:
|
|
indoor_fw_norm: >-
|
|
{{
|
|
(
|
|
(
|
|
dev2_firmux.stdout | default('') | regex_replace('\r','')
|
|
) | split('\n')
|
|
| map('trim')
|
|
| select('truthy')
|
|
| list
|
|
) | select('match', '(?i).*\\brev\\s*[0-9]+.*')
|
|
| list
|
|
| first
|
|
| default(
|
|
((dev2_firmux.stdout | default('') | regex_replace('\r',''))
|
|
| split('\n')
|
|
| map('trim')
|
|
| select('truthy')
|
|
| list
|
|
| first
|
|
| default(''))
|
|
)
|
|
| trim
|
|
}}
|
|
|
|
- name: Publish Dev2 indoor_fwver
|
|
delegate_to: localhost
|
|
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: "{{ {
|
|
'inscope_device': (ansible_hostname | default(inventory_hostname)),
|
|
'task_name': 'custom_field_set',
|
|
'task_add1': 'indoor_fwver',
|
|
'task_result': (indoor_fw_norm if indoor_fw_norm|length>0 else 'unavailable')
|
|
} | to_json }}"
|
|
payload_encoding: "string"
|
|
changed_when: false
|
|
|
|
post_tasks:
|
|
- name: "✅ Cleanup start"
|
|
debug:
|
|
msg: "Closing tunnel and removing temporary IP"
|
|
|
|
- name: Refresh ARP #4
|
|
raw: "{{ pathprefix }} arping -U -I {{ arping_iface }} {{ dev2_side_ip_addr }} -c 3"
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Close SSH ControlMaster
|
|
delegate_to: localhost
|
|
shell: "ssh -S '{{ _ctrl_sock | default('/dev/null') }}' -O exit 2>/dev/null || true"
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: Remove tunnel control dir
|
|
delegate_to: localhost
|
|
file:
|
|
path: "{{ _ctrl_dir | default('/tmp/none') }}"
|
|
state: absent
|
|
ignore_errors: true
|
|
|
|
- name: Remove temporary IP
|
|
raw: "{{ pathprefix }} ip a del {{ dev2_side_ip }} dev {{ dev1_iface }}"
|
|
failed_when: false
|
|
ignore_errors: true
|
|
|
|
- name: "✅ Completed | Device processed"
|
|
debug:
|
|
msg:
|
|
- "Device: {{ inventory_hostname }}"
|
|
- "Status: DONE"
|