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.
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.
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
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
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
I can now add the template and just use one of the utility hosts to run it
hmm…. i messed something up
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.
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
Lastly, I’ll create an hourly schedule so if it changes, I’ll get notified
When saved, I can see it’s scheduled here on out
DOH.
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
I let it run on schedule and it had no issues
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.















