1010
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
# ptsd-migration.yml — PPPoE to super DHCP | Phase 0 connectivity + Phase 1 commands (initially uptime)
|
# ptsd-migration.yml — PPPoE to super DHCP
|
||||||
|
# Phase 0: connectivity + safe identity guard
|
||||||
|
# Phase 1: wrapper (no device changes yet; uptime will be introduced later)
|
||||||
|
|
||||||
- name: "PPPoE to super DHCP | Phase 0 | Prove reliable DEV2 connectivity (no device commands)"
|
- name: "PPPoE to super DHCP | Phase 0+1 | Reliable DEV2 connectivity + wrapper"
|
||||||
hosts: all
|
hosts: all
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
|
|
||||||
@@ -31,6 +33,7 @@
|
|||||||
- "basicpass2"
|
- "basicpass2"
|
||||||
|
|
||||||
# SSH options used from controller
|
# SSH options used from controller
|
||||||
|
# LogLevel=ERROR is critical: suppresses hostkey "Permanently added..." lines that poison parsing.
|
||||||
ssh_opts_common: >-
|
ssh_opts_common: >-
|
||||||
-o PreferredAuthentications=password
|
-o PreferredAuthentications=password
|
||||||
-o PubkeyAuthentication=no
|
-o PubkeyAuthentication=no
|
||||||
@@ -38,6 +41,8 @@
|
|||||||
-o UserKnownHostsFile=/dev/null
|
-o UserKnownHostsFile=/dev/null
|
||||||
-o NumberOfPasswordPrompts=1
|
-o NumberOfPasswordPrompts=1
|
||||||
-o ConnectTimeout=30
|
-o ConnectTimeout=30
|
||||||
|
-o ConnectionAttempts=1
|
||||||
|
-o LogLevel=ERROR
|
||||||
|
|
||||||
# Debugging toggle
|
# Debugging toggle
|
||||||
debugging: true
|
debugging: true
|
||||||
@@ -120,7 +125,6 @@
|
|||||||
- "LLDP IPv6 candidate={{ lldp_dev2_ip6 | default('<none>') }}"
|
- "LLDP IPv6 candidate={{ lldp_dev2_ip6 | default('<none>') }}"
|
||||||
|
|
||||||
# ============================ PRIMARY PATH DECISION (same as indoor-restart-cloudagent) ============================
|
# ============================ PRIMARY PATH DECISION (same as indoor-restart-cloudagent) ============================
|
||||||
# If LLDP gives 192.168.* we use it as dev2_host for the tunnel target
|
|
||||||
- name: Override dev2_host from LLDP when candidate is 192.168.x.x
|
- name: Override dev2_host from LLDP when candidate is 192.168.x.x
|
||||||
when: (lldp_ip_class | trim) == "192_168" and (lldp_dev2_ip | trim | length > 0)
|
when: (lldp_ip_class | trim) == "192_168" and (lldp_dev2_ip | trim | length > 0)
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
@@ -128,7 +132,6 @@
|
|||||||
dev2_host: "{{ lldp_dev2_ip | trim }}"
|
dev2_host: "{{ lldp_dev2_ip | trim }}"
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
|
||||||
# Direct LLDP only if 10.x
|
|
||||||
- name: Set connection method initial (direct_lldp if 10.x else tunnel)
|
- name: Set connection method initial (direct_lldp if 10.x else tunnel)
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
@@ -144,7 +147,6 @@
|
|||||||
HOST="{{ lldp_dev2_ip }}"
|
HOST="{{ lldp_dev2_ip }}"
|
||||||
sshpass -f basicpass ssh {{ ssh_opts_common }} \
|
sshpass -f basicpass ssh {{ ssh_opts_common }} \
|
||||||
-o AddressFamily=inet \
|
-o AddressFamily=inet \
|
||||||
-o ConnectionAttempts=1 \
|
|
||||||
"{{ dev2_ssh_user }}@${HOST}" echo OK >/dev/null 2>&1
|
"{{ dev2_ssh_user }}@${HOST}" echo OK >/dev/null 2>&1
|
||||||
args: { executable: /bin/bash }
|
args: { executable: /bin/bash }
|
||||||
register: dev2_lldp_try_basicpass
|
register: dev2_lldp_try_basicpass
|
||||||
@@ -166,7 +168,6 @@
|
|||||||
HOST="{{ lldp_dev2_ip }}"
|
HOST="{{ lldp_dev2_ip }}"
|
||||||
sshpass -f basicpass2 ssh {{ ssh_opts_common }} \
|
sshpass -f basicpass2 ssh {{ ssh_opts_common }} \
|
||||||
-o AddressFamily=inet \
|
-o AddressFamily=inet \
|
||||||
-o ConnectionAttempts=1 \
|
|
||||||
"{{ dev2_ssh_user }}@${HOST}" echo OK >/dev/null 2>&1
|
"{{ dev2_ssh_user }}@${HOST}" echo OK >/dev/null 2>&1
|
||||||
args: { executable: /bin/bash }
|
args: { executable: /bin/bash }
|
||||||
register: dev2_lldp_try_basicpass2
|
register: dev2_lldp_try_basicpass2
|
||||||
@@ -327,7 +328,6 @@
|
|||||||
PORT="{{ _local_port }}"
|
PORT="{{ _local_port }}"
|
||||||
sshpass -f basicpass ssh {{ ssh_opts_common }} \
|
sshpass -f basicpass ssh {{ ssh_opts_common }} \
|
||||||
-o AddressFamily=inet \
|
-o AddressFamily=inet \
|
||||||
-o ConnectionAttempts=1 \
|
|
||||||
-p "$PORT" root@127.0.0.1 echo OK >/dev/null 2>&1
|
-p "$PORT" root@127.0.0.1 echo OK >/dev/null 2>&1
|
||||||
args: { executable: /bin/bash }
|
args: { executable: /bin/bash }
|
||||||
register: dev2_try_basicpass
|
register: dev2_try_basicpass
|
||||||
@@ -349,7 +349,6 @@
|
|||||||
PORT="{{ _local_port }}"
|
PORT="{{ _local_port }}"
|
||||||
sshpass -f basicpass2 ssh {{ ssh_opts_common }} \
|
sshpass -f basicpass2 ssh {{ ssh_opts_common }} \
|
||||||
-o AddressFamily=inet \
|
-o AddressFamily=inet \
|
||||||
-o ConnectionAttempts=1 \
|
|
||||||
-p "$PORT" root@127.0.0.1 echo OK >/dev/null 2>&1
|
-p "$PORT" root@127.0.0.1 echo OK >/dev/null 2>&1
|
||||||
args: { executable: /bin/bash }
|
args: { executable: /bin/bash }
|
||||||
register: dev2_try_basicpass2
|
register: dev2_try_basicpass2
|
||||||
@@ -370,33 +369,56 @@
|
|||||||
dev2_passfile_used_tunnel: "NONE"
|
dev2_passfile_used_tunnel: "NONE"
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
|
||||||
# Safety guard (only if we discovered MAC)
|
# ============================ SAFETY GUARD: MAC CHECK (only if tunnel auth succeeded) ============================
|
||||||
- name: Read remote eth0 MAC via tunnel (guard)
|
- name: Read remote eth0 MAC via tunnel (guard, sanitized)
|
||||||
when: dev2_conn_method == "tunnel" and dev2_passfile_used_tunnel != "NONE" and (dev2_mac | default('') | trim | length > 0)
|
when:
|
||||||
|
- dev2_conn_method == "tunnel"
|
||||||
|
- (dev2_passfile_used_tunnel | default('NONE')) != "NONE"
|
||||||
|
- (dev2_mac | default('') | length) > 0
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
ansible.builtin.shell: |
|
ansible.builtin.shell: |
|
||||||
set -e
|
|
||||||
PORT="{{ _local_port }}"
|
PORT="{{ _local_port }}"
|
||||||
sshpass -f "{{ dev2_passfile_used_tunnel }}" ssh {{ ssh_opts_common }} \
|
sshpass -f "{{ dev2_passfile_used_tunnel }}" ssh {{ ssh_opts_common }} \
|
||||||
-o AddressFamily=inet \
|
|
||||||
-o ConnectionAttempts=1 \
|
|
||||||
-p "$PORT" "{{ dev2_ssh_user }}@127.0.0.1" \
|
-p "$PORT" "{{ dev2_ssh_user }}@127.0.0.1" \
|
||||||
"cat /sys/class/net/eth0/address 2>/dev/null || echo" 2>&1
|
"cat /sys/class/net/eth0/address 2>/dev/null || ip link show eth0 2>/dev/null" 2>&1
|
||||||
args: { executable: /bin/bash }
|
args: { executable: /bin/bash }
|
||||||
register: dev2_eth0_mac_read
|
register: dev2_eth0_mac_raw
|
||||||
changed_when: false
|
changed_when: false
|
||||||
failed_when: false
|
failed_when: false
|
||||||
|
|
||||||
|
- name: Normalize remote eth0 MAC (extract last MAC-like token)
|
||||||
|
when:
|
||||||
|
- dev2_conn_method == "tunnel"
|
||||||
|
- (dev2_passfile_used_tunnel | default('NONE')) != "NONE"
|
||||||
|
- (dev2_mac | default('') | length) > 0
|
||||||
|
delegate_to: localhost
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
remote_eth0_mac: >-
|
||||||
|
{{
|
||||||
|
(
|
||||||
|
(dev2_eth0_mac_raw.stdout | default('') | regex_replace('\r','')) ~ "\n" ~
|
||||||
|
(dev2_eth0_mac_raw.stderr | default('') | regex_replace('\r',''))
|
||||||
|
)
|
||||||
|
| regex_findall('([0-9a-fA-F]{2}(?::[0-9a-fA-F]{2}){5})')
|
||||||
|
| last
|
||||||
|
| default('')
|
||||||
|
| lower
|
||||||
|
}}
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
- name: Abort if remote eth0 MAC != discovered DEV2 MAC
|
- name: Abort if remote eth0 MAC != discovered DEV2 MAC
|
||||||
when: dev2_conn_method == "tunnel" and dev2_passfile_used_tunnel != "NONE" and (dev2_mac | default('') | trim | length > 0) and ((dev2_eth0_mac_read.stdout | default('') | trim | lower) != (dev2_mac | trim | lower))
|
when:
|
||||||
|
- dev2_conn_method == "tunnel"
|
||||||
|
- (dev2_passfile_used_tunnel | default('NONE')) != "NONE"
|
||||||
|
- (dev2_mac | default('') | length) > 0
|
||||||
|
- (remote_eth0_mac | default('') | length) > 0
|
||||||
|
- (remote_eth0_mac | lower) != (dev2_mac | lower)
|
||||||
ansible.builtin.fail:
|
ansible.builtin.fail:
|
||||||
msg: >
|
msg: >-
|
||||||
Safety stop: tunnel reached wrong device.
|
Safety stop: tunnel reached wrong device.
|
||||||
expected_dev2_mac={{ dev2_mac | trim }},
|
expected_dev2_mac={{ dev2_mac }}, remote_eth0_mac={{ remote_eth0_mac }}
|
||||||
remote_eth0_mac={{ dev2_eth0_mac_read.stdout | default('') | trim }}.
|
|
||||||
|
|
||||||
# ============================ FALLBACKS (only if tunnel/direct failed) ============================
|
# ============================ FALLBACKS (only if tunnel/direct failed) ============================
|
||||||
# LLDP IPv4 fallback is only for 10.x in sot-updater; keep that behavior:
|
|
||||||
- name: Try DEV2 login via LLDP IPv4 10.x (fallback if tunnel auth failed)
|
- name: Try DEV2 login via LLDP IPv4 10.x (fallback if tunnel auth failed)
|
||||||
when:
|
when:
|
||||||
- dev2_conn_method == "tunnel"
|
- dev2_conn_method == "tunnel"
|
||||||
@@ -409,7 +431,7 @@
|
|||||||
for f in {{ dev2_passfiles | join(' ') }}; do
|
for f in {{ dev2_passfiles | join(' ') }}; do
|
||||||
if timeout 20s sshpass -f "$f" ssh {{ ssh_opts_common }} \
|
if timeout 20s sshpass -f "$f" ssh {{ ssh_opts_common }} \
|
||||||
-o AddressFamily=inet \
|
-o AddressFamily=inet \
|
||||||
-o ConnectionAttempts=1 -o ConnectTimeout=10 \
|
-o ConnectTimeout=10 \
|
||||||
"{{ dev2_ssh_user }}@${HOST}" echo OK >/dev/null 2>&1; then
|
"{{ dev2_ssh_user }}@${HOST}" echo OK >/dev/null 2>&1; then
|
||||||
echo "$f"; exit 0
|
echo "$f"; exit 0
|
||||||
fi
|
fi
|
||||||
@@ -431,7 +453,6 @@
|
|||||||
dev2_passfile_used_lldp4: "{{ (dev2_auth_lldp4.rc == 0) | ternary((dev2_auth_lldp4.stdout | trim), 'NONE') }}"
|
dev2_passfile_used_lldp4: "{{ (dev2_auth_lldp4.rc == 0) | ternary((dev2_auth_lldp4.stdout | trim), 'NONE') }}"
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
|
||||||
# LLDP IPv6 nested fallback (last resort): only if tunnel and lldp4 failed
|
|
||||||
- name: Copy DEV2 passfiles to DEV1 for IPv6 nested SSH (last resort)
|
- name: Copy DEV2 passfiles to DEV1 for IPv6 nested SSH (last resort)
|
||||||
when:
|
when:
|
||||||
- dev2_conn_method == "tunnel"
|
- dev2_conn_method == "tunnel"
|
||||||
@@ -457,10 +478,10 @@
|
|||||||
IP6="{{ lldp_dev2_ip6 }}"
|
IP6="{{ lldp_dev2_ip6 }}"
|
||||||
for f in {{ dev2_passfiles | join(' ') }}; do
|
for f in {{ dev2_passfiles | join(' ') }}; do
|
||||||
if sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
|
if sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
|
||||||
-o ConnectionAttempts=1 \
|
|
||||||
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
|
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
|
||||||
"timeout {{ ssh_timeout }}s sshpass -f '/tmp/${f}' ssh \
|
"timeout {{ ssh_timeout }}s sshpass -f '/tmp/${f}' ssh \
|
||||||
-o StrictHostKeyChecking=no -o PubkeyAuthentication=no \
|
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR \
|
||||||
|
-o PubkeyAuthentication=no \
|
||||||
-o PreferredAuthentications=password -o NumberOfPasswordPrompts=1 \
|
-o PreferredAuthentications=password -o NumberOfPasswordPrompts=1 \
|
||||||
-o ConnectTimeout={{ ssh_timeout }} \
|
-o ConnectTimeout={{ ssh_timeout }} \
|
||||||
'{{ dev2_ssh_user }}@\[${IP6}%{{ dev1_iface }}\]' 'echo OK' " \
|
'{{ dev2_ssh_user }}@\[${IP6}%{{ dev1_iface }}\]' 'echo OK' " \
|
||||||
@@ -527,14 +548,13 @@
|
|||||||
- "lldp ipv4={{ lldp_dev2_ip | default('') }}"
|
- "lldp ipv4={{ lldp_dev2_ip | default('') }}"
|
||||||
- "lldp ipv6={{ lldp_dev2_ip6 | default('') }}"
|
- "lldp ipv6={{ lldp_dev2_ip6 | default('') }}"
|
||||||
|
|
||||||
# ---- Wrapper: produce a single executable snippet in dev2_exec_cmd ----
|
# ---- Wrapper: produce a single executable snippet in dev2_exec_cmd (for Phase 1 commands later) ----
|
||||||
- name: Build DEV2 exec wrapper (controller-side) for Phase 1 commands
|
- name: Build DEV2 exec wrapper (controller-side) for Phase 1 commands
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
dev2_exec_cmd: |
|
dev2_exec_cmd: |
|
||||||
set -e
|
set -e
|
||||||
MODE="{{ dev2_conn_final }}"
|
MODE="{{ dev2_conn_final }}"
|
||||||
# DEV2_CMD is injected by Phase 1 tasks
|
|
||||||
if [ -z "${DEV2_CMD:-}" ]; then
|
if [ -z "${DEV2_CMD:-}" ]; then
|
||||||
echo "ERROR: DEV2_CMD is empty" >&2
|
echo "ERROR: DEV2_CMD is empty" >&2
|
||||||
exit 2
|
exit 2
|
||||||
@@ -546,7 +566,6 @@
|
|||||||
PASS="{{ dev2_passfile_used_tunnel }}"
|
PASS="{{ dev2_passfile_used_tunnel }}"
|
||||||
sshpass -f "$PASS" ssh {{ ssh_opts_common }} \
|
sshpass -f "$PASS" ssh {{ ssh_opts_common }} \
|
||||||
-o AddressFamily=inet \
|
-o AddressFamily=inet \
|
||||||
-o ConnectionAttempts=1 \
|
|
||||||
-p "$PORT" "{{ dev2_ssh_user }}@127.0.0.1" \
|
-p "$PORT" "{{ dev2_ssh_user }}@127.0.0.1" \
|
||||||
"{{ pathprefix }} ${DEV2_CMD}" 2>&1
|
"{{ pathprefix }} ${DEV2_CMD}" 2>&1
|
||||||
;;
|
;;
|
||||||
@@ -555,7 +574,6 @@
|
|||||||
PASS="{{ dev2_passfile_used_direct }}"
|
PASS="{{ dev2_passfile_used_direct }}"
|
||||||
sshpass -f "$PASS" ssh {{ ssh_opts_common }} \
|
sshpass -f "$PASS" ssh {{ ssh_opts_common }} \
|
||||||
-o AddressFamily=inet \
|
-o AddressFamily=inet \
|
||||||
-o ConnectionAttempts=1 \
|
|
||||||
"{{ dev2_ssh_user }}@${HOST}" \
|
"{{ dev2_ssh_user }}@${HOST}" \
|
||||||
"{{ pathprefix }} ${DEV2_CMD}" 2>&1
|
"{{ pathprefix }} ${DEV2_CMD}" 2>&1
|
||||||
;;
|
;;
|
||||||
@@ -564,7 +582,6 @@
|
|||||||
PASS="{{ dev2_passfile_used_lldp4 }}"
|
PASS="{{ dev2_passfile_used_lldp4 }}"
|
||||||
sshpass -f "$PASS" ssh {{ ssh_opts_common }} \
|
sshpass -f "$PASS" ssh {{ ssh_opts_common }} \
|
||||||
-o AddressFamily=inet \
|
-o AddressFamily=inet \
|
||||||
-o ConnectionAttempts=1 \
|
|
||||||
"{{ dev2_ssh_user }}@${HOST}" \
|
"{{ dev2_ssh_user }}@${HOST}" \
|
||||||
"{{ pathprefix }} ${DEV2_CMD}" 2>&1
|
"{{ pathprefix }} ${DEV2_CMD}" 2>&1
|
||||||
;;
|
;;
|
||||||
@@ -572,10 +589,10 @@
|
|||||||
IP6="{{ lldp_dev2_ip6 }}"
|
IP6="{{ lldp_dev2_ip6 }}"
|
||||||
F="{{ dev2_passfile_used_lldp6 }}"
|
F="{{ dev2_passfile_used_lldp6 }}"
|
||||||
sshpass -p "{{ dev1_pass }}" ssh {{ ssh_opts_common }} \
|
sshpass -p "{{ dev1_pass }}" ssh {{ ssh_opts_common }} \
|
||||||
-o ConnectionAttempts=1 \
|
|
||||||
"{{ dev1_user }}@{{ ansible_host | default(inventory_hostname) }}" \
|
"{{ dev1_user }}@{{ ansible_host | default(inventory_hostname) }}" \
|
||||||
"sshpass -f '/tmp/${F}' ssh \
|
"sshpass -f '/tmp/${F}' ssh \
|
||||||
-o StrictHostKeyChecking=no -o PubkeyAuthentication=no \
|
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR \
|
||||||
|
-o PubkeyAuthentication=no \
|
||||||
-o PreferredAuthentications=password -o NumberOfPasswordPrompts=1 \
|
-o PreferredAuthentications=password -o NumberOfPasswordPrompts=1 \
|
||||||
-o ConnectTimeout={{ ssh_timeout }} \
|
-o ConnectTimeout={{ ssh_timeout }} \
|
||||||
'{{ dev2_ssh_user }}@\[${IP6}%{{ dev1_iface }}\]' \
|
'{{ dev2_ssh_user }}@\[${IP6}%{{ dev1_iface }}\]' \
|
||||||
@@ -588,31 +605,6 @@
|
|||||||
esac
|
esac
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
|
||||||
|
|
||||||
- name: "PPPoE to super DHCP | Phase 1 | On-device actions (initially uptime)"
|
|
||||||
hosts: all
|
|
||||||
gather_facts: no
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
# NOTE: Phase 1 assumes Phase 0 facts are still available in-play execution.
|
|
||||||
# If your runner executes plays independently, merge into one play.
|
|
||||||
- name: DEV2 | exec wrapper run (Phase 1)
|
|
||||||
delegate_to: localhost
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
export DEV2_CMD="uptime"
|
|
||||||
{{ dev2_exec_cmd }}
|
|
||||||
args: { executable: /bin/bash }
|
|
||||||
register: dev2_exec
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: DEV2 | uptime (stdout)
|
|
||||||
delegate_to: localhost
|
|
||||||
ansible.builtin.debug:
|
|
||||||
msg:
|
|
||||||
- "DEV2 uptime via {{ dev2_conn_final }}:"
|
|
||||||
- "{{ dev2_exec.stdout | default('') | regex_replace('\r','') }}"
|
|
||||||
|
|
||||||
post_tasks:
|
post_tasks:
|
||||||
- name: Cleanup note
|
- name: Cleanup note
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|||||||
Reference in New Issue
Block a user