master1_a_cable.sh
#!/bin/bash
# Pasa master-1 (master, dueño de la VIP) de la WiFi al cable, con la misma IP 192.0.2.112.
#
# Uso, como root EN EL master-1, con el cable ya enchufado, y lanzado con systemd-run para que
# termine aunque se corte la sesión durante el cambio de red:
#
# systemd-run --unit master1-a-cable --collect bash /root/k3s/scripts/master1_a_cable.sh
# tail -f /var/log/master1-a-cable.log
#
# Pasos: 1) comprueba el cable, el clúster y que master-2/master-3 pueden tomar la VIP; 2) para
# keepalived (la VIP pasa a master-2); 3) .112 fija en el cable y WiFi desactivada; 4) k3s con
# --flannel-iface del cable; 5) keepalived por el cable (la VIP vuelve al master-1).
# Si falla algo desde el paso 2, vuelve solo a la WiFi y deja la configuración anterior.
set -uo pipefail
CABLE=enp4s0f0
WIFI=wlp3s0
IP=192.0.2.112
GW=192.0.2.1
DNS="192.0.2.241 1.1.1.1"
CON_CABLE="Conexión cableada 1"
CONS_WIFI=(Pluton12 Luna12)
K3S=/etc/systemd/system/k3s.service
KA=/etc/keepalived/keepalived.conf
LOG=/var/log/master1-a-cable.log
COPIA=/root/master1-a-cable-$(date +%F-%H%M)
VIP=192.0.2.250
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
exec >>"$LOG" 2>&1
log() { echo "$(date +%T) $*"; }
fallo() { log "✘ $*"; exit 1; }
esperar() { # esperar <segundos> <orden...>
local t=$1; shift
for _ in $(seq "$t"); do "$@" >/dev/null 2>&1 && return 0; sleep 1; done
return 1
}
nodo_ready() { kubectl get node master-1 --no-headers | awk '{exit !($2=="Ready")}'; }
volver_a_wifi() {
trap - ERR; set +e # la vuelta atrás llega hasta el final aunque falle algún paso
log "↩ VUELTA ATRÁS: dejo todo como estaba (WiFi)"
[ -f "$COPIA/k3s.service" ] && cp -a "$COPIA/k3s.service" "$K3S"
[ -f "$COPIA/keepalived.conf" ] && cp -a "$COPIA/keepalived.conf" "$KA"
nmcli connection modify "$CON_CABLE" ipv4.method auto ipv4.addresses "" ipv4.gateway "" \
ipv4.dns "" connection.autoconnect no
nmcli connection down "$CON_CABLE" || true
for c in "${CONS_WIFI[@]}"; do nmcli connection modify "$c" connection.autoconnect yes || true; done
nmcli connection up "${CONS_WIFI[0]}" ifname "$WIFI" || true
esperar 60 ping -c1 -W1 "$GW" && log " WiFi de nuevo con $IP"
systemctl daemon-reload
systemctl restart k3s
esperar 300 nodo_ready && log " k3s Ready por WiFi"
systemctl start keepalived
log "↩ Vuelta atrás terminada: el master-1 sigue por WiFi, como antes. Avisa a Claude."
}
log "===== master-1: WiFi → cable ($CABLE) ====="
log "1/5 Comprobaciones previas"
[ "$(id -u)" -eq 0 ] || fallo "hay que lanzarlo como root"
[ "$(cat /sys/class/net/$CABLE/carrier 2>/dev/null)" = 1 ] || fallo "el cable no tiene enlace: enchúfalo antes"
log " enlace del cable: $(cat /sys/class/net/$CABLE/speed) Mb/s"
[ "$(kubectl get nodes --no-headers | awk '$2!="Ready"' | wc -l)" -eq 0 ] || fallo "hay nodos que no están Ready: no toco nada"
for m in 192.0.2.139 192.0.2.129; do
e=$(ssh -o BatchMode=yes -o ConnectTimeout=8 admin@$m \
"journalctl -u keepalived -n 100 --no-pager | grep -oiE 'entering (master|backup|fault) state' | tail -1" 2>/dev/null)
case "${e,,}" in *backup*) log " $m: keepalived BACKUP (puede tomar la VIP)" ;;
*) fallo "$m no está en BACKUP ($e): la VIP no tendría relevo" ;; esac
done
mkdir -p "$COPIA"
cp -a "$K3S" "$KA" "$COPIA/"
nmcli connection show "$CON_CABLE" > "$COPIA/nm-cable.txt"
log " copias en $COPIA"
trap 'volver_a_wifi' ERR
set -e
log "2/5 Parar keepalived: la VIP pasa a master-2"
systemctl stop keepalived
esperar 30 bash -c "! ip -4 addr show dev dummy0 | grep -q '$VIP/'"
esperar 30 curl -sk -o /dev/null --max-time 2 "https://$VIP:6443/healthz"
log " la API responde por la VIP (ahora en otro master)"
log "3/5 $IP fija en el cable y WiFi desactivada"
for c in "${CONS_WIFI[@]}"; do nmcli connection modify "$c" connection.autoconnect no; done
nmcli connection modify "$CON_CABLE" ipv4.method manual ipv4.addresses "$IP/24" \
ipv4.gateway "$GW" ipv4.dns "$DNS" ipv4.route-metric 100 connection.autoconnect yes
nmcli connection down "${CONS_WIFI[0]}" || true
nmcli connection up "$CON_CABLE" ifname "$CABLE"
esperar 30 ping -c1 -W1 "$GW"
esperar 30 ping -c1 -W1 192.0.2.139
ip -4 addr show dev "$CABLE" | grep -q "$IP/" || false
log " $IP en $CABLE; router y master-2 responden"
log "4/5 k3s con --flannel-iface=$CABLE"
sed -i "s/--flannel-iface=$WIFI/--flannel-iface=$CABLE/" "$K3S"
grep -q -- "--flannel-iface=$CABLE" "$K3S"
systemctl daemon-reload
systemctl restart k3s
esperar 300 nodo_ready
log " master-1 Ready"
log "5/5 keepalived por $CABLE: la VIP vuelve al master-1"
sed -i -E "s/^(\s*interface\s+)$WIFI\b/\1$CABLE/" "$KA"
grep -qE "^\s*interface\s+$CABLE\b" "$KA"
salida=$(keepalived -t -f "$KA" 2>&1 || true)
if grep -v 'Truncating auth_pass' <<<"$salida" | grep -q .; then echo "$salida"; false; fi
systemctl start keepalived
esperar 30 bash -c "ip -4 addr show dev dummy0 | grep -q '$VIP/'"
esperar 30 curl -sk -o /dev/null --max-time 2 "https://$VIP:6443/healthz"
trap - ERR
log "✓ Terminado: master-1 por cable ($CABLE, $(cat /sys/class/net/$CABLE/speed) Mb/s), con la VIP y k3s Ready."
log " La WiFi queda configurada pero sin conectarse sola (autoconnect=no)."