Homelab tweaks: Kubeconfigs and ext IPs

Published: Mar 24, 2026 by Isaac Johnson

One of my challenges is that with rapid experimentation, especially in blasting and redoing my varied Kubernetes clusters, the management of my Kubernetes config files on all my laptops was getting a bit challenging.

A while back I had created a perl script that used to work but frankly it fell down long ago and mangled the shared kubeconfig file.

I thought now might be a good time to both come up with a scalable solution to my hosts (using Ansible) and also notify me when my egress IP changes (as then I have to update my configs for a new IP).

A Better K8s Config

One of my challenges is that over time, my big kubeconfig gets to be a bit much.

My original design was to store a base64’ed version in AKV then pull it down on any laptop I might be using to access all my Kubernetes.

Something like

$ getpass.sh k3sremoteconfig idjakv | base64 --decode | tee ~/.kube/config

which really just does

$ az keyvault secret show --vault-name idjakv --name k3sremoteconfig --subscription Pay-As-You-Go | jq -r .value

note: you can find those scripts (setpass.sh, getpass.sh and listpass.sh) here

However, the combined config got too big for AKV and in the end, it’s mangled and gone

builder@DESKTOP-QADGF36:~/Workspaces$ getpass.sh k3sremoteconfig idjakv


Storing a different way

So I rethought it - really I just need the configs from primary and secondary.

This seems like a good job for a playbook with a script

First, I make a working script - this will find the hostid (4th part of an IPv4 address) then pull the k3s config and store it in AKV

Here is setk8s.sh

#!/bin/bash

# find my local IP
export MYIP=`ifconfig | grep 192.168.1 | head -n1 | sed 's/^.*inet \(.*\)  netmask.*/\1/' | tr -d '\n'`
export MYHOST=`ifconfig | grep 192.168.1 | head -n1 | sed 's/^.*inet 192.168.1.\(.*\)  netmask.*/\1/' |
 tr -d '\n'`

# create local int
cat /etc/rancher/k3s/k3s.yaml | sed "s/127.0.0.1/$MYIP/g" | tee /tmp/k8s-int

# save in AKV
az keyvault secret set --vault-name idjakv --name int$MYHOST-int --file /tmp/k8s-int

Then I need the playbook that will call it for my hosts

- name: Save AKV K8s Int
  hosts: all

  tasks:
  - name: Transfer the script
    copy: src=setk8s.sh dest=/tmp mode=0755

  - name: SaveThisK8sInt
    command: sh /tmp/setk8s.sh
    args:
      chdir: /tmp

Now I could just onboard this job to AWX and call it for each host (primary and secondary control-plane hosts), however it would be best as a general turnkey playbook so I made an inventory of just Kubernetes control-plane (master) hosts.

/content/images/2026/03/ansible-01.png

This might require re-authing to Azure

I could do better with my naming, but i know what it means. I can now see the two creds in AKV

$ listpass.sh idjakv | grep int
int247-int                                 https://idjakv.vault.azure.net/secrets/int247-int                                                                                                                                                                                                                      True
int77-int                                  https://idjakv.vault.azure.net/secrets/int77-int                                                                                                                                                                                                                       True

My old perl script worked for a while, but had hard-coded ports and servers - not really ideal.

This new setup doesn’t b64 the k8s configs, rather stores them as files as it’s just the single ones.

/content/images/2026/03/ansible-03.png

Not AI

So I don’t want to let my brain atrophy further by just letting some LLM write the code. I want to make this. I’ll use an LLM just if I get really stuck.

My first thought is to use yq to replace values.

I can fetch the old certificate authority data for both the “int” (internal URL) and “ext” (external URL) using ‘mac77’ and ‘ext77’.

$ cat ~/.kube/config | yq '.clusters[] | select (.name == "mac77") | .cluster."certificate-authority
-data"'
"TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwgc2VkIGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWduYSBhbGlxdWEuIFV0IGVuaW0gYWQgbWluaW0gdmVuaWFtLCBxdWlzIG5vc3RydWQgZXhlcmNpdGF0aW9uIHVsbGFtY28gbGFib3JpcyBuaXNpIHV0IGFsaXF1aXAgZXggZWEgY29tbW9kbyBjb25zZXF1YXQuIER1aXMgYXV0ZSBpcnVyZSBkb2xvciBpbiByZXByZWhlbmRlcml0IGluIHZvbHVwdGF0ZSB2ZWxpdCBlc3NlIGNpbGx1bSBkb2xvcmUgZXUgZnVnaWF0IG51bGxhIHBhcmlhdHVyLiBFeGNlcHRldXIgc2ludCBvY2NhZWNhdCBjdXBpZGF0YXQgbm9uIHByb2lkZW50LCBzdW50IGluIGN1bHBhIHF1aSBvZmZpY2lhIGRlc2VydW50IG1vbGxpdCBhbmltIGlkIGVzdCBsYWJvcnVtCg=="

This might be even easier than I assumed.

I’ll set a variable to the old value

$ export OLDINT=`cat ~/.kube/config | yq -r '.clusters[] | select (.name == "ext77") | .cluster."certificate-authority-data"'`

Then one to my new value

$ export NEWINT=`getpass.sh int77-int idjakv | yq  -r '.clusters[0].cluster."certificate-authority-data"'`

I can now whip up a quick bash script to see if they are a match or different

$ cat ./test.sh
#!/bin/bash

OLDCERTAUTHDATA=`cat ~/.kube/config | yq -r '.clusters[] | select (.name == "mac77") | .cluster."certificate-authority-data"' | tr -d '\n'`
NEWCERTAUTHDATA=`az keyvault secret show --vault-name idjakv --name int77-int --subscription Pay-As-You-Go | jq -r .value | yq  -r '.clusters[0].cluster."certificate-authority-data"' | tr -d '\n'`

if [[ "$OLDCERTAUTHDATA" == "$NEWCERTAUTHDATA" ]]
then
  echo match
else
  echo different
fi

$ ./test.sh
different

And if it’s been updated

$ ./test.sh
match

Let’s now expand it to cover the user data as well

$ cat ./test.sh
#!/bin/bash

# Current Mapping
SECONDARYCPIP=77

OLDCERTAUTHDATA=`cat ~/.kube/config | yq -r '.clusters[] | select (.name == "mac77") | .cluster."certificate-authority-data"' | tr -d '\n'`
NEWCERTAUTHDATA=`az keyvault secret show --vault-name idjakv --name int$SECONDARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq  -r '.clusters[0].cluster."certificate-authority-data"' | tr -d '\n'`

if [[ "$OLDCERTAUTHDATA" == "$NEWCERTAUTHDATA" ]]
then
  echo "cert auth data match"
else
  echo "cert auth data different"
fi

# Users

OLDCLIENTCERTDATA=`cat ~/.kube/config | yq -r '.users[] | select (.name == "mac77") | .user."client-certificate-data"' | tr -d '\n'`
NEWCLIENTCERTDATA=`az keyvault secret show --vault-name idjakv --name int77-int --subscription Pay-As-You-Go | jq -r .value | yq -r '.users[0].user."client-certificate-data"' | tr -d '\n'`

OLDCLIENTKEYDATA=`cat ~/.kube/config | yq -r '.users[] | select (.name == "mac77") | .user."client-key-data"' | tr -d '\n'`
NEWCLIENTKEYDATA=`az keyvault secret show --vault-name idjakv --name int77-int --subscription Pay-As-You-Go | jq -r .value | yq -r '.users[0].user."client-key-data"' | tr -d '\n'`


if [[ "$OLDCLIENTCERTDATA" == "$NEWCLIENTCERTDATA" ]]
then
  echo "client cert data match"
else
  echo "client cert data different"
fi

if [[ "$OLDCLIENTKEYDATA" == "$NEWCLIENTKEYDATA" ]]
then
  echo "client key data match"
else
  echo "client key data different"
fi

Since I know my latest local config is up to date, I expect matches

$ ./test.sh
cert auth data match
client cert data match
client key data match

My next step is to check both Internal and External blocks. I also fixed some hard-coded variables above

$ cat ./test.sh
#!/bin/bash

# Current Mapping
SECONDARYCPIP=77
SECONDARYINTNAME=mac77
SECONDARYEXTNAME=ext77

#####
# Secondary Int

echo "==========================="
echo " Checking $SECONDARYINTNAME "
echo "==========================="

OLDCERTAUTHDATA=`cat ~/.kube/config | yq -r ".clusters[] | select (.name == \"$SECONDARYINTNAME\") | .cluster.\"certificate-authority-data\"" | tr -d '\n'`
NEWCERTAUTHDATA=`az keyvault secret show --vault-name idjakv --name int$SECONDARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq  -r '.clusters[0].cluster."certificate-authority-data"' | tr -d '\n'`

if [[ "$OLDCERTAUTHDATA" == "$NEWCERTAUTHDATA" ]]
then
  echo "cert auth data match"
else
  echo "cert auth data different"
fi

# Users

OLDCLIENTCERTDATA=`cat ~/.kube/config | yq -r ".users[] | select (.name == \"$SECONDARYINTNAME\") | .user.\"client-certificate-data\"" | tr -d '\n'`
NEWCLIENTCERTDATA=`az keyvault secret show --vault-name idjakv --name int$SECONDARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq -r '.users[0].user."client-certificate-data"' | tr -d '\n'`

OLDCLIENTKEYDATA=`cat ~/.kube/config | yq -r ".users[] | select (.name == \"$SECONDARYINTNAME\") | .user.\"client-key-data\"" | tr -d '\n'`
NEWCLIENTKEYDATA=`az keyvault secret show --vault-name idjakv --name int$SECONDARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq -r '.users[0].user."client-key-data"' | tr -d '\n'`


if [[ "$OLDCLIENTCERTDATA" == "$NEWCLIENTCERTDATA" ]]
then
  echo "client cert data match"
else
  echo "client cert data different"
fi

if [[ "$OLDCLIENTKEYDATA" == "$NEWCLIENTKEYDATA" ]]
then
  echo "client key data match"
else
  echo "client key data different"
fi

#####
# Secondary Int

echo "==========================="
echo " Checking $SECONDARYEXTNAME "
echo "==========================="

OLDCERTAUTHDATA=`cat ~/.kube/config | yq -r ".clusters[] | select (.name == \"$SECONDARYEXTNAME\") | .cluster.\"certificate-authority-data\"" | tr -d '\n'`
NEWCERTAUTHDATA=`az keyvault secret show --vault-name idjakv --name int$SECONDARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq  -r '.clusters[0].cluster."certificate-authority-data"' | tr -d '\n'`

if [[ "$OLDCERTAUTHDATA" == "$NEWCERTAUTHDATA" ]]
then
  echo "cert auth data match"
else
  echo "cert auth data different"
fi

# Users

OLDCLIENTCERTDATA=`cat ~/.kube/config | yq -r ".users[] | select (.name == \"$SECONDARYEXTNAME\") | .user.\"client-certificate-data\"" | tr -d '\n'`
NEWCLIENTCERTDATA=`az keyvault secret show --vault-name idjakv --name int$SECONDARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq -r '.users[0].user."client-certificate-data"' | tr -d '\n'`

OLDCLIENTKEYDATA=`cat ~/.kube/config | yq -r ".users[] | select (.name == \"$SECONDARYEXTNAME\") | .user.\"client-key-data\"" | tr -d '\n'`
NEWCLIENTKEYDATA=`az keyvault secret show --vault-name idjakv --name int$SECONDARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq -r '.users[0].user."client-key-data"' | tr -d '\n'`


if [[ "$OLDCLIENTCERTDATA" == "$NEWCLIENTCERTDATA" ]]
then
  echo "client cert data match"
else
  echo "client cert data different"
fi

if [[ "$OLDCLIENTKEYDATA" == "$NEWCLIENTKEYDATA" ]]
then
  echo "client key data match"
else
  echo "client key data different"
fi

We can see our “ext” (external) block is in need of TLC

$ ./test.sh
===========================
 Checking mac77
===========================
cert auth data match
client cert data match
client key data match
===========================
 Checking ext77
===========================
cert auth data different
client cert data different
client key data different

Next I added updates with a backup. However, all the bash if else blocks are getting a bit long. I might need to make some functions soon

$ cat ./test.sh
#!/bin/bash

# Current Mapping
SECONDARYCPIP=77
SECONDARYINTNAME=mac77
SECONDARYEXTNAME=ext77

ACTIONIT=1

if [ "$ACTIONIT" -gt "0" ]; then
  echo "we going to action it"
  # backup current file
  export BKUPNAME="config.`date +%s`"
  cp ~/.kube/config ~/.kube/$BKUPNAME
  echo "current config backed up to ~/.kube/$BKUPNAME"
else
  echo "check only"
fi

#####
# Secondary Int

echo "==========================="
echo " Checking $SECONDARYINTNAME "
echo "==========================="

OLDCERTAUTHDATA=`cat ~/.kube/config | yq -r ".clusters[] | select (.name == \"$SECONDARYINTNAME\") | .cluster.\"certificate-authority-data\"" | tr -d '\n'`
NEWCERTAUTHDATA=`az keyvault secret show --vault-name idjakv --name int$SECONDARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq  -r '.clusters[0].cluster."certificate-authority-data"' | tr -d '\n'`

if [[ "$OLDCERTAUTHDATA" == "$NEWCERTAUTHDATA" ]]
then
  echo "cert auth data match"
else
  if [ "$ACTIONIT" -gt "0" ]; then
    echo "cert auth data different. updating"
    sed -i "s/$OLDCERTAUTHDATA/$NEWCERTAUTHDATA/g" ~/.kube/config
  else
    echo "cert auth data different"
  fi
fi

# Users

OLDCLIENTCERTDATA=`cat ~/.kube/config | yq -r ".users[] | select (.name == \"$SECONDARYINTNAME\") | .user.\"client-certificate-data\"" | tr -d '\n'`
NEWCLIENTCERTDATA=`az keyvault secret show --vault-name idjakv --name int$SECONDARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq -r '.users[0].user."client-certificate-data"' | tr -d '\n'`

OLDCLIENTKEYDATA=`cat ~/.kube/config | yq -r ".users[] | select (.name == \"$SECONDARYINTNAME\") | .user.\"client-key-data\"" | tr -d '\n'`
NEWCLIENTKEYDATA=`az keyvault secret show --vault-name idjakv --name int$SECONDARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq -r '.users[0].user."client-key-data"' | tr -d '\n'`

if [[ "$OLDCLIENTCERTDATA" == "" ]]; then
  echo "No client cert data found for $SECONDARYINTNAME. skipping"
else
  if [[ "$OLDCLIENTCERTDATA" == "$NEWCLIENTCERTDATA" ]]
  then
    echo "client cert data match"
  else
    if [ "$ACTIONIT" -gt "0" ]; then
      echo "client cert data different. updating"
      sed -i "s/$OLDCLIENTCERTDATA/$NEWCLIENTCERTDATA/g" ~/.kube/config
    else
      echo "client cert data different."
    fi
  fi
fi

if [[ "$OLDCLIENTKEYDATA" == "" ]]; then
  echo "No client key data found for $SECONDARYINTNAME. skipping"
else
  if [[ "$OLDCLIENTKEYDATA" == "$NEWCLIENTKEYDATA" ]]
  then
    echo "client key data match"
  else
    if [ "$ACTIONIT" -gt "0" ]; then
      echo "client key data different. updating"
      sed -i "s/$OLDCLIENTKEYDATA/$NEWCLIENTKEYDATA/g" ~/.kube/config
    else
      echo "client key data different."
    fi
  fi
fi

#####
# Secondary Int

echo "==========================="
echo " Checking $SECONDARYEXTNAME "
echo "==========================="

OLDCERTAUTHDATA=`cat ~/.kube/config | yq -r ".clusters[] | select (.name == \"$SECONDARYEXTNAME\") | .cluster.\"certificate-authority-data\"" | tr -d '\n'`
NEWCERTAUTHDATA=`az keyvault secret show --vault-name idjakv --name int$SECONDARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq  -r '.clusters[0].cluster."certificate-authority-data"' | tr -d '\n'`

if [[ "$OLDCERTAUTHDATA" == "$NEWCERTAUTHDATA" ]]
then
  echo "cert auth data match"
else
  if [ "$ACTIONIT" -gt "0" ]; then
    echo "cert auth data different. updating"
    sed -i "s/$OLDCERTAUTHDATA/$NEWCERTAUTHDATA/g" ~/.kube/config
  else
    echo "cert auth data different"
  fi
fi

# Users

OLDCLIENTCERTDATA=`cat ~/.kube/config | yq -r ".users[] | select (.name == \"$SECONDARYEXTNAME\") | .user.\"client-certificate-data\"" | tr -d '\n'`
NEWCLIENTCERTDATA=`az keyvault secret show --vault-name idjakv --name int$SECONDARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq -r '.users[0].user."client-certificate-data"' | tr -d '\n'`

OLDCLIENTKEYDATA=`cat ~/.kube/config | yq -r ".users[] | select (.name == \"$SECONDARYEXTNAME\") | .user.\"client-key-data\"" | tr -d '\n'`
NEWCLIENTKEYDATA=`az keyvault secret show --vault-name idjakv --name int$SECONDARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq -r '.users[0].user."client-key-data"' | tr -d '\n'`

if [[ "$OLDCLIENTCERTDATA" == "" ]]; then
  echo "No client cert data found for $SECONDARYEXTNAME. skipping"
else
  if [[ "$OLDCLIENTCERTDATA" == "$NEWCLIENTCERTDATA" ]]
  then
    echo "client cert data match"
  else
    if [ "$ACTIONIT" -gt "0" ]; then
      echo "client cert data different. updating"
      sed -i "s/$OLDCLIENTCERTDATA/$NEWCLIENTCERTDATA/g" ~/.kube/config
    else
      echo "client cert data different."
    fi
  fi
fi

if [[ "$OLDCLIENTKEYDATA" == "" ]]; then
  echo "No client key data found for $SECONDARYEXTNAME. skipping"
else
  if [[ "$OLDCLIENTKEYDATA" == "$NEWCLIENTKEYDATA" ]]
  then
    echo "client key data match"
  else
    if [ "$ACTIONIT" -gt "0" ]; then
      echo "client key data different. updating"
      sed -i "s/$OLDCLIENTKEYDATA/$NEWCLIENTKEYDATA/g" ~/.kube/config
    else
      echo "client key data different."
    fi
  fi
fi

This works just fine to update

$ ./test.sh
we going to action it
current config backed up to ~/.kube/config.1774349638
===========================
 Checking mac77
===========================
cert auth data match
client cert data match
client key data match
===========================
 Checking ext77
===========================
cert auth data match
No client cert data found for ext77. skipping
No client key data found for ext77. skipping

I realized there was one more piece I neglected - the external IP, which does change from time to time

$ kubectx ext77
Switched to context "ext77".
$ kubectl get nodes
E0324 05:56:49.548034   96004 memcache.go:265] "Unhandled Error" err="couldn't get current server API group list: Get \"https://75.72.233.202:22222/api?timeout=32s\": dial tcp 75.72.233.202:22222: i/o timeout"

I’ll add a block to test that, knowing that I always keep my harbor.freshbrewed.science updated (as without it, my blogging system falls down)

$ cat test.sh
#!/bin/bash

# Current Mapping
SECONDARYCPIP=77
SECONDARYINTNAME=mac77
SECONDARYEXTNAME=ext77

ACTIONIT=1

if [ "$ACTIONIT" -gt "0" ]; then
  echo "we going to action it"
  # backup current file
  export BKUPNAME="config.`date +%s`"
  cp ~/.kube/config ~/.kube/$BKUPNAME
  echo "current config backed up to ~/.kube/$BKUPNAME"
else
  echo "check only"
fi

#####
# Secondary Int

echo "==========================="
echo " Checking $SECONDARYINTNAME "
echo "==========================="

OLDCERTAUTHDATA=`cat ~/.kube/config | yq -r ".clusters[] | select (.name == \"$SECONDARYINTNAME\") | .cluster.\"certificate-authority-data\"" | tr -d '\n'`
NEWCERTAUTHDATA=`az keyvault secret show --vault-name idjakv --name int$SECONDARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq  -r '.clusters[0].cluster."certificate-authority-data"' | tr -d '\n'`

if [[ "$OLDCERTAUTHDATA" == "$NEWCERTAUTHDATA" ]]
then
  echo "cert auth data match"
else
  if [ "$ACTIONIT" -gt "0" ]; then
    echo "cert auth data different. updating"
    sed -i "s/$OLDCERTAUTHDATA/$NEWCERTAUTHDATA/g" ~/.kube/config
  else
    echo "cert auth data different"
  fi
fi

# Users

OLDCLIENTCERTDATA=`cat ~/.kube/config | yq -r ".users[] | select (.name == \"$SECONDARYINTNAME\") | .user.\"client-certificate-data\"" | tr -d '\n'`
NEWCLIENTCERTDATA=`az keyvault secret show --vault-name idjakv --name int$SECONDARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq -r '.users[0].user."client-certificate-data"' | tr -d '\n'`

OLDCLIENTKEYDATA=`cat ~/.kube/config | yq -r ".users[] | select (.name == \"$SECONDARYINTNAME\") | .user.\"client-key-data\"" | tr -d '\n'`
NEWCLIENTKEYDATA=`az keyvault secret show --vault-name idjakv --name int$SECONDARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq -r '.users[0].user."client-key-data"' | tr -d '\n'`

if [[ "$OLDCLIENTCERTDATA" == "" ]]; then
  echo "No client cert data found for $SECONDARYINTNAME. skipping"
else
  if [[ "$OLDCLIENTCERTDATA" == "$NEWCLIENTCERTDATA" ]]
  then
    echo "client cert data match"
  else
    if [ "$ACTIONIT" -gt "0" ]; then
      echo "client cert data different. updating"
      sed -i "s/$OLDCLIENTCERTDATA/$NEWCLIENTCERTDATA/g" ~/.kube/config
    else
      echo "client cert data different."
    fi
  fi
fi

if [[ "$OLDCLIENTKEYDATA" == "" ]]; then
  echo "No client key data found for $SECONDARYINTNAME. skipping"
else
  if [[ "$OLDCLIENTKEYDATA" == "$NEWCLIENTKEYDATA" ]]
  then
    echo "client key data match"
  else
    if [ "$ACTIONIT" -gt "0" ]; then
      echo "client key data different. updating"
      sed -i "s/$OLDCLIENTKEYDATA/$NEWCLIENTKEYDATA/g" ~/.kube/config
    else
      echo "client key data different."
    fi
  fi
fi

#####
# Secondary Ext

echo "==========================="
echo " Checking $SECONDARYEXTNAME "
echo "==========================="

MYEXTIP=`dig +short A harbor.freshbrewed.science | tr -d '\n'`
OLDEXTIP=`cat ~/.kube/config | yq -r ".clusters[] | select (.name == \"$SECONDARYEXTNAME\") | .cluster.server" | sed 's/^.*:\/\///' | sed 's/:.*//' | tr -d '\n'`

if [[ "$OLDEXTIP" == "$MYEXTIP" ]]
then
  echo "ext IPs are a match"
else
  if [ "$ACTIONIT" -gt "0" ]; then
    echo "ext IPs are different: Old $OLDEXTIP and new $MYEXTIP. updating"
    sed -i "s/$OLDEXTIP/$MYEXTIP/g" ~/.kube/config
  else
    echo "ext IPs are different: Old $OLDEXTIP and new $MYEXTIP"
  fi
fi

OLDCERTAUTHDATA=`cat ~/.kube/config | yq -r ".clusters[] | select (.name == \"$SECONDARYEXTNAME\") | .cluster.\"certificate-authority-data\"" | tr -d '\n'`
NEWCERTAUTHDATA=`az keyvault secret show --vault-name idjakv --name int$SECONDARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq  -r '.clusters[0].cluster."certificate-authority-data"' | tr -d '\n'`

if [[ "$OLDCERTAUTHDATA" == "$NEWCERTAUTHDATA" ]]
then
  echo "cert auth data match"
else
  if [ "$ACTIONIT" -gt "0" ]; then
    echo "cert auth data different. updating"
    sed -i "s/$OLDCERTAUTHDATA/$NEWCERTAUTHDATA/g" ~/.kube/config
  else
    echo "cert auth data different"
  fi
fi

# Users

OLDCLIENTCERTDATA=`cat ~/.kube/config | yq -r ".users[] | select (.name == \"$SECONDARYEXTNAME\") | .user.\"client-certificate-data\"" | tr -d '\n'`
NEWCLIENTCERTDATA=`az keyvault secret show --vault-name idjakv --name int$SECONDARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq -r '.users[0].user."client-certificate-data"' | tr -d '\n'`

OLDCLIENTKEYDATA=`cat ~/.kube/config | yq -r ".users[] | select (.name == \"$SECONDARYEXTNAME\") | .user.\"client-key-data\"" | tr -d '\n'`
NEWCLIENTKEYDATA=`az keyvault secret show --vault-name idjakv --name int$SECONDARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq -r '.users[0].user."client-key-data"' | tr -d '\n'`

if [[ "$OLDCLIENTCERTDATA" == "" ]]; then
  echo "No client cert data found for $SECONDARYEXTNAME. skipping"
else
  if [[ "$OLDCLIENTCERTDATA" == "$NEWCLIENTCERTDATA" ]]
  then
    echo "client cert data match"
  else
    if [ "$ACTIONIT" -gt "0" ]; then
      echo "client cert data different. updating"
      sed -i "s/$OLDCLIENTCERTDATA/$NEWCLIENTCERTDATA/g" ~/.kube/config
    else
      echo "client cert data different."
    fi
  fi
fi

if [[ "$OLDCLIENTKEYDATA" == "" ]]; then
  echo "No client key data found for $SECONDARYEXTNAME. skipping"
else
  if [[ "$OLDCLIENTKEYDATA" == "$NEWCLIENTKEYDATA" ]]
  then
    echo "client key data match"
  else
    if [ "$ACTIONIT" -gt "0" ]; then
      echo "client key data different. updating"
      sed -i "s/$OLDCLIENTKEYDATA/$NEWCLIENTKEYDATA/g" ~/.kube/config
    else
      echo "client key data different."
    fi
  fi
fi

Let’s test

$ ./test.sh
we going to action it
current config backed up to ~/.kube/config.1774350306
===========================
 Checking mac77
===========================
cert auth data match
client cert data match
client key data match
===========================
 Checking ext77
===========================
ext IPs are different: Old 75.72.233.202 and new 76.156.69.232. updating
cert auth data match
No client cert data found for ext77. skipping
No client key data found for ext77. skipping
$ kubectx ext77
Switched to context "ext77".
$ kubectl get nodes
NAME                    STATUS   ROLES           AGE     VERSION
builder-macbookpro8-1   Ready    <none>          5d21h   v1.35.1+k3s1
builder-macbookpro8-2   Ready    <none>          2d15h   v1.35.1+k3s1
isaac-macbookair        Ready    control-plane   6d10h   v1.35.1+k3s1

My next issue is I need to repeat all this for int247-int which is really int33 and ext33 in my kubeconfig, again, for historical reasons.

Now, this next block, which is just too damn long, does work

$ cat ./test.sh
#!/bin/bash

# Current Mapping
SECONDARYCPIP=77
SECONDARYINTNAME=mac77
SECONDARYEXTNAME=ext77

ACTIONIT=1

if [ "$ACTIONIT" -gt "0" ]; then
  echo "we going to action it"
  # backup current file
  export BKUPNAME="config.`date +%s`"
  cp ~/.kube/config ~/.kube/$BKUPNAME
  echo "current config backed up to ~/.kube/$BKUPNAME"
else
  echo "check only"
fi

#####
# Secondary Int

echo "==========================="
echo " Checking $SECONDARYINTNAME "
echo "==========================="

OLDCERTAUTHDATA=`cat ~/.kube/config | yq -r ".clusters[] | select (.name == \"$SECONDARYINTNAME\") | .cluster.\"certificate-authority-data\"" | tr -d '\n'`
NEWCERTAUTHDATA=`az keyvault secret show --vault-name idjakv --name int$SECONDARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq  -r '.clusters[0].cluster."certificate-authority-data"' | tr -d '\n'`

if [[ "$OLDCERTAUTHDATA" == "$NEWCERTAUTHDATA" ]]
then
  echo "cert auth data match"
else
  if [ "$ACTIONIT" -gt "0" ]; then
    echo "cert auth data different. updating"
    sed -i "s/$OLDCERTAUTHDATA/$NEWCERTAUTHDATA/g" ~/.kube/config
  else
    echo "cert auth data different"
  fi
fi

# Users

OLDCLIENTCERTDATA=`cat ~/.kube/config | yq -r ".users[] | select (.name == \"$SECONDARYINTNAME\") | .user.\"client-certificate-data\"" | tr -d '\n'`
NEWCLIENTCERTDATA=`az keyvault secret show --vault-name idjakv --name int$SECONDARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq -r '.users[0].user."client-certificate-data"' | tr -d '\n'`

OLDCLIENTKEYDATA=`cat ~/.kube/config | yq -r ".users[] | select (.name == \"$SECONDARYINTNAME\") | .user.\"client-key-data\"" | tr -d '\n'`
NEWCLIENTKEYDATA=`az keyvault secret show --vault-name idjakv --name int$SECONDARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq -r '.users[0].user."client-key-data"' | tr -d '\n'`

if [[ "$OLDCLIENTCERTDATA" == "" ]]; then
  echo "No client cert data found for $SECONDARYINTNAME. skipping"
else
  if [[ "$OLDCLIENTCERTDATA" == "$NEWCLIENTCERTDATA" ]]
  then
    echo "client cert data match"
  else
    if [ "$ACTIONIT" -gt "0" ]; then
      echo "client cert data different. updating"
      sed -i "s/$OLDCLIENTCERTDATA/$NEWCLIENTCERTDATA/g" ~/.kube/config
    else
      echo "client cert data different."
    fi
  fi
fi

if [[ "$OLDCLIENTKEYDATA" == "" ]]; then
  echo "No client key data found for $SECONDARYINTNAME. skipping"
else
  if [[ "$OLDCLIENTKEYDATA" == "$NEWCLIENTKEYDATA" ]]
  then
    echo "client key data match"
  else
    if [ "$ACTIONIT" -gt "0" ]; then
      echo "client key data different. updating"
      sed -i "s/$OLDCLIENTKEYDATA/$NEWCLIENTKEYDATA/g" ~/.kube/config
    else
      echo "client key data different."
    fi
  fi
fi

#####
# Secondary Ext

echo "==========================="
echo " Checking $SECONDARYEXTNAME "
echo "==========================="

MYEXTIP=`dig +short A harbor.freshbrewed.science | tr -d '\n'`
OLDEXTIP=`cat ~/.kube/config | yq -r ".clusters[] | select (.name == \"$SECONDARYEXTNAME\") | .cluster.server" | sed 's/^.*:\/\///' | sed 's/:.*//' | tr -d '\n'`

if [[ "$OLDEXTIP" == "$MYEXTIP" ]]
then
  echo "ext IPs are a match"
else
  if [ "$ACTIONIT" -gt "0" ]; then
    echo "ext IPs are different: Old $OLDEXTIP and new $MYEXTIP. updating"
    sed -i "s/$OLDEXTIP/$MYEXTIP/g" ~/.kube/config
  else
    echo "ext IPs are different: Old $OLDEXTIP and new $MYEXTIP"
  fi
fi

OLDCERTAUTHDATA=`cat ~/.kube/config | yq -r ".clusters[] | select (.name == \"$SECONDARYEXTNAME\") | .cluster.\"certificate-authority-data\"" | tr -d '\n'`
NEWCERTAUTHDATA=`az keyvault secret show --vault-name idjakv --name int$SECONDARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq  -r '.clusters[0].cluster."certificate-authority-data"' | tr -d '\n'`

if [[ "$OLDCERTAUTHDATA" == "$NEWCERTAUTHDATA" ]]
then
  echo "cert auth data match"
else
  if [ "$ACTIONIT" -gt "0" ]; then
    echo "cert auth data different. updating"
    sed -i "s/$OLDCERTAUTHDATA/$NEWCERTAUTHDATA/g" ~/.kube/config
  else
    echo "cert auth data different"
  fi
fi

# Users

OLDCLIENTCERTDATA=`cat ~/.kube/config | yq -r ".users[] | select (.name == \"$SECONDARYEXTNAME\") | .user.\"client-certificate-data\"" | tr -d '\n'`
NEWCLIENTCERTDATA=`az keyvault secret show --vault-name idjakv --name int$SECONDARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq -r '.users[0].user."client-certificate-data"' | tr -d '\n'`

OLDCLIENTKEYDATA=`cat ~/.kube/config | yq -r ".users[] | select (.name == \"$SECONDARYEXTNAME\") | .user.\"client-key-data\"" | tr -d '\n'`
NEWCLIENTKEYDATA=`az keyvault secret show --vault-name idjakv --name int$SECONDARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq -r '.users[0].user."client-key-data"' | tr -d '\n'`

if [[ "$OLDCLIENTCERTDATA" == "" ]]; then
  echo "No client cert data found for $SECONDARYEXTNAME. skipping"
else
  if [[ "$OLDCLIENTCERTDATA" == "$NEWCLIENTCERTDATA" ]]
  then
    echo "client cert data match"
  else
    if [ "$ACTIONIT" -gt "0" ]; then
      echo "client cert data different. updating"
      sed -i "s/$OLDCLIENTCERTDATA/$NEWCLIENTCERTDATA/g" ~/.kube/config
    else
      echo "client cert data different."
    fi
  fi
fi

if [[ "$OLDCLIENTKEYDATA" == "" ]]; then
  echo "No client key data found for $SECONDARYEXTNAME. skipping"
else
  if [[ "$OLDCLIENTKEYDATA" == "$NEWCLIENTKEYDATA" ]]
  then
    echo "client key data match"
  else
    if [ "$ACTIONIT" -gt "0" ]; then
      echo "client key data different. updating"
      sed -i "s/$OLDCLIENTKEYDATA/$NEWCLIENTKEYDATA/g" ~/.kube/config
    else
      echo "client key data different."
    fi
  fi
fi

# Current Mapping
PRIMARYCPIP=247
PRIMARYINTNAME=int33
PRIMARYEXTNAME=ext33

#####
# Primary Int

echo "==========================="
echo " Checking $PRIMARYINTNAME "
echo "==========================="

OLDCERTAUTHDATA=`cat ~/.kube/config | yq -r ".clusters[] | select (.name == \"$PRIMARYINTNAME\") | .cluster.\"certificate-authority-data\"" | tr -d '\n'`
NEWCERTAUTHDATA=`az keyvault secret show --vault-name idjakv --name int$PRIMARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq  -r '.clusters[0].cluster."certificate-authority-data"' | tr -d '\n'`

if [[ "$OLDCERTAUTHDATA" == "$NEWCERTAUTHDATA" ]]
then
  echo "cert auth data match"
else
  if [ "$ACTIONIT" -gt "0" ]; then
    echo "cert auth data different. updating"
    sed -i "s/$OLDCERTAUTHDATA/$NEWCERTAUTHDATA/g" ~/.kube/config
  else
    echo "cert auth data different"
  fi
fi

# Users

OLDCLIENTCERTDATA=`cat ~/.kube/config | yq -r ".users[] | select (.name == \"$PRIMARYINTNAME\") | .user.\"client-certificate-data\"" | tr -d '\n'`
NEWCLIENTCERTDATA=`az keyvault secret show --vault-name idjakv --name int$PRIMARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq -r '.users[0].user."client-certificate-data"' | tr -d '\n'`

OLDCLIENTKEYDATA=`cat ~/.kube/config | yq -r ".users[] | select (.name == \"$PRIMARYINTNAME\") | .user.\"client-key-data\"" | tr -d '\n'`
NEWCLIENTKEYDATA=`az keyvault secret show --vault-name idjakv --name int$PRIMARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq -r '.users[0].user."client-key-data"' | tr -d '\n'`

if [[ "$OLDCLIENTCERTDATA" == "" ]]; then
  echo "No client cert data found for $PRIMARYINTNAME. skipping"
else
  if [[ "$OLDCLIENTCERTDATA" == "$NEWCLIENTCERTDATA" ]]
  then
    echo "client cert data match"
  else
    if [ "$ACTIONIT" -gt "0" ]; then
      echo "client cert data different. updating"
      sed -i "s/$OLDCLIENTCERTDATA/$NEWCLIENTCERTDATA/g" ~/.kube/config
    else
      echo "client cert data different."
    fi
  fi
fi

if [[ "$OLDCLIENTKEYDATA" == "" ]]; then
  echo "No client key data found for $PRIMARYINTNAME. skipping"
else
  if [[ "$OLDCLIENTKEYDATA" == "$NEWCLIENTKEYDATA" ]]
  then
    echo "client key data match"
  else
    if [ "$ACTIONIT" -gt "0" ]; then
      echo "client key data different. updating"
      sed -i "s/$OLDCLIENTKEYDATA/$NEWCLIENTKEYDATA/g" ~/.kube/config
    else
      echo "client key data different."
    fi
  fi
fi

#####
# Primary Ext

echo "==========================="
echo " Checking $PRIMARYEXTNAME "
echo "==========================="

MYEXTIP=`dig +short A harbor.freshbrewed.science | tr -d '\n'`
OLDEXTIP=`cat ~/.kube/config | yq -r ".clusters[] | select (.name == \"$PRIMARYEXTNAME\") | .cluster.server" | sed 's/^.*:\/\///' | sed 's/:.*//' | tr -d '\n'`

if [[ "$OLDEXTIP" == "$MYEXTIP" ]]
then
  echo "ext IPs are a match"
else
  if [ "$ACTIONIT" -gt "0" ]; then
    echo "ext IPs are different: Old $OLDEXTIP and new $MYEXTIP. updating"
    sed -i "s/$OLDEXTIP/$MYEXTIP/g" ~/.kube/config
  else
    echo "ext IPs are different: Old $OLDEXTIP and new $MYEXTIP"
  fi
fi

OLDCERTAUTHDATA=`cat ~/.kube/config | yq -r ".clusters[] | select (.name == \"$PRIMARYEXTNAME\") | .cluster.\"certificate-authority-data\"" | tr -d '\n'`
NEWCERTAUTHDATA=`az keyvault secret show --vault-name idjakv --name int$PRIMARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq  -r '.clusters[0].cluster."certificate-authority-data"' | tr -d '\n'`

if [[ "$OLDCERTAUTHDATA" == "$NEWCERTAUTHDATA" ]]
then
  echo "cert auth data match"
else
  if [ "$ACTIONIT" -gt "0" ]; then
    echo "cert auth data different. updating"
    sed -i "s/$OLDCERTAUTHDATA/$NEWCERTAUTHDATA/g" ~/.kube/config
  else
    echo "cert auth data different"
  fi
fi

# Users

OLDCLIENTCERTDATA=`cat ~/.kube/config | yq -r ".users[] | select (.name == \"$PRIMARYEXTNAME\") | .user.\"client-certificate-data\"" | tr -d '\n'`
NEWCLIENTCERTDATA=`az keyvault secret show --vault-name idjakv --name int$PRIMARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq -r '.users[0].user."client-certificate-data"' | tr -d '\n'`

OLDCLIENTKEYDATA=`cat ~/.kube/config | yq -r ".users[] | select (.name == \"$PRIMARYEXTNAME\") | .user.\"client-key-data\"" | tr -d '\n'`
NEWCLIENTKEYDATA=`az keyvault secret show --vault-name idjakv --name int$PRIMARYCPIP-int --subscription Pay-As-You-Go | jq -r .value | yq -r '.users[0].user."client-key-data"' | tr -d '\n'`

if [[ "$OLDCLIENTCERTDATA" == "" ]]; then
  echo "No client cert data found for $PRIMARYEXTNAME. skipping"
else
  if [[ "$OLDCLIENTCERTDATA" == "$NEWCLIENTCERTDATA" ]]
  then
    echo "client cert data match"
  else
    if [ "$ACTIONIT" -gt "0" ]; then
      echo "client cert data different. updating"
      sed -i "s/$OLDCLIENTCERTDATA/$NEWCLIENTCERTDATA/g" ~/.kube/config
    else
      echo "client cert data different."
    fi
  fi
fi

if [[ "$OLDCLIENTKEYDATA" == "" ]]; then
  echo "No client key data found for $PRIMARYEXTNAME. skipping"
else
  if [[ "$OLDCLIENTKEYDATA" == "$NEWCLIENTKEYDATA" ]]
  then
    echo "client key data match"
  else
    if [ "$ACTIONIT" -gt "0" ]; then
      echo "client key data different. updating"
      sed -i "s/$OLDCLIENTKEYDATA/$NEWCLIENTKEYDATA/g" ~/.kube/config
    else
      echo "client key data different."
    fi
  fi
fi

We can see it does the job

$ ./test.sh
we going to action it
current config backed up to ~/.kube/config.1774350712
===========================
 Checking mac77
===========================
cert auth data match
client cert data match
client key data match
===========================
 Checking ext77
===========================
ext IPs are a match
cert auth data match
No client cert data found for ext77. skipping
No client key data found for ext77. skipping
===========================
 Checking int33
===========================
cert auth data match
client cert data match
client key data match
===========================
 Checking ext33
===========================
ext IPs are a match
cert auth data match
No client cert data found for ext33. skipping
No client key data found for ext33. skipping

But seriously, that is a gnarly long unoptimized script.

A little touch of LLM help

I copied over the ++verbose script to a new folder

builder@DESKTOP-QADGF36:~/Workspaces/jekyll-blog$ cd ..
builder@DESKTOP-QADGF36:~/Workspaces$ mkdir k8scheck
builder@DESKTOP-QADGF36:~/Workspaces$ cd k8scheck/
builder@DESKTOP-QADGF36:~/Workspaces/k8scheck$ cp ../jekyll-blog/test.sh
cp: missing destination file operand after '../jekyll-blog/test.sh'
Try 'cp --help' for more information.
builder@DESKTOP-QADGF36:~/Workspaces/k8scheck$ cp ../jekyll-blog/test.sh ./checkandfix.sh
builder@DESKTOP-QADGF36:~/Workspaces/k8scheck$ ls
checkandfix.sh

I asked Gemini CLI for help

My @checkandfix.sh works, however it is very unoptimized with lots of repeated blocks.  Please clean this script up with a focus on optimizing
   and reuse.  Add comments to any created functions or subroutines.

What is usually really quite fast took nearly 2 minutes

It also failed to update the file. So i had to grab the output from the console and do it myself.

$ cat ./checkandfix.sh
#!/bin/bash

# Configuration
VAULT_NAME="idjakv"
SUBSCRIPTION="Pay-As-You-Go"
KUBECONFIG="$HOME/.kube/config"
ACTIONIT=1

# --- Functions ---

# Backs up the current kubeconfig if ACTIONIT is set
backup_config() {
    if [ "$ACTIONIT" -gt "0" ]; then
        local backup_name="config.$(date +%s)"
        cp "$KUBECONFIG" "$HOME/.kube/$backup_name"
        echo "Current config backed up to ~/.kube/$backup_name"
    else
        echo "Check-only mode. No backup created."
    fi
}

# Compares old and new data and updates the config using sed if ACTIONIT is set
# Usage: check_and_update <label> <old_value> <new_value>
check_and_update() {
    local label="$1"
    local old_val="$2"
    local new_val="$3"

    if [[ -z "$old_val" ]]; then
        echo "No $label found. Skipping."
        return
    fi

    if [[ "$old_val" == "$new_val" ]]; then
        echo "$label matches."
    else
        if [ "$ACTIONIT" -gt "0" ]; then
            echo "$label different. Updating..."
            # Using | as delimiter in case values contain /
            sed -i "s|$old_val|$new_val|g" "$KUBECONFIG"
        else
            echo "$label different."
        fi
    fi
}

# Processes a specific cluster context (Cluster and User entries)
# Usage: process_context <context_name> <secret_data_yaml> <is_external_flag>
process_context() {
    local name="$1"
    local secret_data="$2"
    local is_ext="$3"

    echo "==========================="
    echo " Checking $name "
    echo "==========================="

    # 1. Handle IP check for external endpoints
    if [[ "$is_ext" == "true" ]]; then
        local my_ext_ip=$(dig +short A harbor.freshbrewed.science | tr -d '\n')
        local old_ext_ip=$(yq -r ".clusters[] | select (.name == \"$name\") | .cluster.server" "$KUBECONFIG" | sed 's|^.*://||;s|:.*||' | tr -d '\n')

        if [[ "$old_ext_ip" == "$my_ext_ip" ]]; then
            echo "Ext IPs are a match."
        else
            if [ "$ACTIONIT" -gt "0" ]; then
                echo "Ext IPs differ: Old $old_ext_ip, New $my_ext_ip. Updating..."
                sed -i "s|$old_ext_ip|$my_ext_ip|g" "$KUBECONFIG"
            else
                echo "Ext IPs differ: Old $old_ext_ip, New $my_ext_ip."
            fi
        fi
    fi

    # 2. Extract current data from kubeconfig
    local old_ca=$(yq -r ".clusters[] | select (.name == \"$name\") | .cluster.\"certificate-authority-data\"" "$KUBECONFIG" | tr -d '\n')
    local old_cert=$(yq -r ".users[] | select (.name == \"$name\") | .user.\"client-certificate-data\"" "$KUBECONFIG" | tr -d '\n')
    local old_key=$(yq -r ".users[] | select (.name == \"$name\") | .user.\"client-key-data\"" "$KUBECONFIG" | tr -d '\n')

    # 3. Extract new data from the secret (passed as a string)
    local new_ca=$(echo "$secret_data" | yq -r '.clusters[0].cluster."certificate-authority-data"' | tr -d '\n')
    local new_cert=$(echo "$secret_data" | yq -r '.users[0].user."client-certificate-data"' | tr -d '\n')
    local new_key=$(echo "$secret_data" | yq -r '.users[0].user."client-key-data"' | tr -d '\n')

    # 4. Perform comparisons and updates
    check_and_update "Cert auth data" "$old_ca" "$new_ca"
    check_and_update "Client cert data" "$old_cert" "$new_cert"
    check_and_update "Client key data" "$old_key" "$new_key"
}

# --- Main Logic ---

backup_config

# Targets array format: "CPIP_SUFFIX:INT_NAME:EXT_NAME"
targets=(
    "77:mac77:ext77"
    "247:int33:ext33"
)

for target in "${targets[@]}"; do
    IFS=":" read -r cpip int_name ext_name <<< "$target"

    echo "Fetching secret for IP suffix $cpip..."
    # Fetch the secret once per cluster set to optimize API calls
    secret_json=$(az keyvault secret show --vault-name "$VAULT_NAME" --name "int$cpip-int" --subscription "$SUBSCRIPTION" | jq -r .value)

    if [[ -z "$secret_json" ]]; then
        echo "Error: Could not retrieve secret for $cpip. Skipping."
        continue
    fi

    process_context "$int_name" "$secret_json" "false"
    process_context "$ext_name" "$secret_json" "true"
done

This works

$ ./checkandfix.sh
Current config backed up to ~/.kube/config.1774351669
Fetching secret for IP suffix 77...
===========================
 Checking mac77
===========================
Cert auth data matches.
Client cert data matches.
Client key data matches.
===========================
 Checking ext77
===========================
Ext IPs are a match.
Cert auth data matches.
No Client cert data found. Skipping.
No Client key data found. Skipping.
Fetching secret for IP suffix 247...
===========================
 Checking int33
===========================
Cert auth data matches.
Client cert data matches.
Client key data matches.
===========================
 Checking ext33
===========================
Ext IPs are a match.
Cert auth data matches.
No Client cert data found. Skipping.
No Client key data found. Skipping.

I’m just a little bit disappointed in Gemini CLI for being slow and not writing files.

Knowing when IP shifts happen

I have a script for updating Route53, but not Google DNS (steeped.icu) nor Azure DNS (tpk.pw)

I also have a script for just testing if my IP changed and turning on a light bulb in my office

#!/bin/bash 

MYIP="`host -4 myip.opendns.com resolver1.opendns.com | tail -n1 | sed 's/.* //' | tr -d '\n'`"
HARBOR="`host -4 harbor.freshbrewed.science resolver1.opendns.com | tail -n1 | sed 's/.* //' | tr -d '\n'`"

if [ "$MYIP" = "$HARBOR" ]; then
   echo "Strings are Equal . $MYIP matches $HARBOR"
else
   echo "IP CHANGED!  Harbor $HARBOR does not match local $MYIP"
   wget "https://kasarest.freshbrewed.science/on?devip=192.168.1.24&apikey=$1"
   exit 1
fi

I want to start by using Gotify for this.

I’ll make a new app for AWX

/content/images/2026/03/ansible-05.png

Back in February, we wrote about GCP OpenTelemetry and in that article, to prove the idea, i created a “GotifyMe” app which we can now use. (exposed as https://notify.tpk.pw/)

Because this is a FastAPI based app, we can check redoc for it and see the POST should be easy

$ curl -X POST -H 'Content-Type: application/json' https://notify.tpk.pw/
notify -d '{"title": "AWX: homelab IP change", "message": "IP changed from x.x.x.x to y.y.y.y", "priority": 5, "password": "xxxx"}'
{"status":"success","data":{"id":59,"appid":2,"message":"IP changed from x.x.x.x to y.y.y.y","title":"AWX: homelab IP change","priority":5,"date":"2026-03-24T11:57:01.32987876Z"}}

And i can see that fired through

/content/images/2026/03/ansible-06.png

I’ll update the quick test playbook which really hasn’t been used in a long time

builder@DESKTOP-QADGF36:~/Workspaces/ansible-playbooks$ cat testNewIp.sh
#!/bin/bash

export NOTIFYPASS=$1

MYIP="`host -4 myip.opendns.com resolver1.opendns.com | tail -n1 | sed 's/.* //' | tr -d '\n'`"
HARBOR="`host -4 harbor.freshbrewed.science resolver1.opendns.com | tail -n1 | sed 's/.* //' | tr -d '\n'`"

if [ "$MYIP" = "$HARBOR" ]; then
   echo "Strings are Equal . $MYIP matches $HARBOR"

   # Notify
   curl -X POST -H 'Content-Type: application/json' https://notify.tpk.pw/notify -d "{\"title\": \"AWX: homelab IP STILL GOOD\", \\"message\": \"IP of h.f.s is $HARBOR and egress is still $MYIP\", \"priority\": 5, \"password\": \"$NOTIFYPASS\"}"
else
   echo "IP CHANGED!  Harbor $HARBOR does not match local $MYIP"

   # Notify
   curl -X POST -H 'Content-Type: application/json' https://notify.tpk.pw/notify -d "{\"title\": \"AWX: homelab IP change\", \\"message\": \"IP changed from $HARBOR to $MYIP\", \"priority\": 5, \"password\": \"$NOTIFYPASS\"}"

   # light
   wget "https://kasarest.freshbrewed.science/on?devip=192.168.1.24&apikey=$1"
   exit 1
fi

builder@DESKTOP-QADGF36:~/Workspaces/ansible-playbooks$ cat testNewIp.yaml
- name: Check if IP Changed
  hosts: all

  tasks:
  - name: Transfer the script
    copy: src=testNewIp.sh dest=/tmp mode=0755

  - name: Run Script
    command: sh /tmp/testNewIp.sh 

I’ll sync the project for the latest playbooks

/content/images/2026/03/ansible-07.png

I can now add the template and just use one of the utility hosts to run it

/content/images/2026/03/ansible-08.png

hmm…. i messed something up

/content/images/2026/03/ansible-09.png

testing locally shows i definately goofed

 ./testNewIp.sh sadfsadfasdfsadf
Strings are Equal . 76.156.69.232 matches 76.156.69.232
{"detail":[{"type":"json_invalid","loc":["body",40],"msg":"JSON decode error","input":{},"ctx":{"error":"Expecting property name enclosed in double quotes"}}]}curl: (6) Could not resolve host: "IP


curl: (6) Could not resolve host: of
curl: (6) Could not resolve host: h.f.s

I found and fixed an errant double backslash \\

$ ./testNewIp.sh asdfsadfsafda
Strings are Equal . 76.156.69.232 matches 76.156.69.232
{"status":"success","data":{"id":60,"appid":2,"message":"IP of h.f.s is 76.156.69.232 and egress is still 76.156.69.232","title":"AWX: homelab IP STILL GOOD","priority":5,"date":"2026-03-24T12:32:32.466743299Z"}}

which showed up in Gotify.

/content/images/2026/03/ansible-10.png

I pushed the fix, synced the project in AWX

Then tried again, which worked.

Lastly, I commented out the notify line as that would get really old to see every hour

/content/images/2026/03/ansible-11.png

Lastly, I’ll create an hourly schedule so if it changes, I’ll get notified

/content/images/2026/03/ansible-12.png

When saved, I can see it’s scheduled here on out

/content/images/2026/03/ansible-13.png

DOH.

https://www.etsy.com/listing/794654622/homer-simpson-doh-the-simpsons-cross

I was all set to write the summary and close this out when it dawned on me - if my IP changes, there is no way notify.tpk.pw would resolve, let alone gotify.

I updated the script and playbook to use email instead (and tested locally)

(base) builder@LuiGi:~/Workspaces/ansible-playbooks$ cat testNewIp.sh
#!/bin/bash

export NOTIFYPASS=$1
export RESENDAPI=$2
export EMAILSENDER=$3

MYIP="`host -4 myip.opendns.com resolver1.opendns.com | tail -n1 | sed 's/.* //' | tr -d '\n'`"
HARBOR="`host -4 harbor.freshbrewed.science resolver1.opendns.com | tail -n1 | sed 's/.* //' | tr -d '\n'`"

if [ "$MYIP" = "$HARBOR" ]; then
   echo "Strings are Equal . $MYIP matches $HARBOR"

   # Notify
   # curl -X POST -H 'Content-Type: application/json' https://notify.tpk.pw/notify -d "{\"title\": \"AWX: homelab IP STILL GOOD\", \"message\": \"IP of h.f.s is $HARBOR and egress is still $MYIP\", \"priority\": 5, \"password\": \"$NOTIFYPASS\"}"
   # curl -X POST 'https://api.resend.com/emails' -H 'Authorization: Bearer xxxxxxxxxxxxxxxxxxxxxxxx' -H 'Content-Type: application/json' -d "{ \"from\": \"$EMAILSENDER\", \"to\": \"isaac.johnson@gmail.com\", \"subject\": \"AWX - Egress IP stable\", \"html\": \"<h1>hello me</h1><p>The IP of $HARBOR is still $MYIP egress</p>\"}"
else
   echo "IP CHANGED!  Harbor $HARBOR does not match local $MYIP"

   # Notify
   curl -X POST 'https://api.resend.com/emails' -H "Authorization: Bearer $RESENDAPI" -H 'Content-Type: application/json' -d "{ \"from\": \"$EMAILSENDER\", \"to\": \"isaac.johnson@gmail.com\", \"subject\": \"AWX - Egress IP changed\", \"html\": \"<h1>hello me</h1><p>The IP of $HARBOR (harbor.freshbrewed.science) does not match your current egress IP of $MYIP</p>\"}"

   # IF THE IP Changed, this really wont work till its fixed!
   curl -X POST -H 'Content-Type: application/json' https://notify.tpk.pw/notify -d "{\"title\": \"AWX: homelab IP change\", \"message\": \"IP changed from $HARBOR to $MYIP\", \"priority\": 5, \"password\": \"$NOTIFYPASS\"}"

   # light
   wget "https://kasarest.freshbrewed.science/on?devip=192.168.1.24&apikey=$1"
   exit 1
fi

(base) builder@LuiGi:~/Workspaces/ansible-playbooks$ cat testNewIp.yaml
- name: Check if IP Changed
  hosts: all

  tasks:
  - name: Transfer the script
    copy: src=testNewIp.sh dest=/tmp mode=0755

  - name: Run Script
    command: sh /tmp/testNewIp.sh   

I then added the keys to the Template in AWX

/content/images/2026/03/ansible-15.png

I let it run on schedule and it had no issues

/content/images/2026/03/ansible-16.png

Summary

Today we created a new script and Ansible playbook to go to the two main control planes in my home lab (I’ll add the Pi clusters later) and update Azure Key Vault (AKV) with updated local configs. Once that was sorted, we then moved on to creating a bash script with yq to find and parse the old entries.

I absconded LLMs for that work until the very end and then asked Gemini to do some cleanup on the script. My last piece of work in all this was to detect with the egress IP changes and notify myself. That involved upating an old script that just turned on a lightbulb to instead using my notify (Gotify) app to send a push notification to my phone. Once working, we set that playbook to run hourly so any egress change should notify my phone within the hour.

Lastly, right before posting, I realized that notifications that depend on ingressing to my cluster from DNS names, which neccessarily would be out of date, would be a bad idea and added some email notifications with Resend.

Hopefully something in here helps others, maybe some ideas on monitors or maintenance. Home labs can be fun, but do require a bit of occasional TLC.

Gotify Homelab Ansible

Have something to add? Feedback? You can use the feedback form

Isaac Johnson

Isaac Johnson

Cloud Solutions Architect

Isaac is a CSA and DevOps engineer who focuses on cloud migrations and devops processes. He also is a dad to three wonderful daughters (hence the references to Princess King sprinkled throughout the blog).

Theme built by C.S. Rhymes