Last active
May 8, 2025 16:20
-
-
Save Geofferey/e3c49d195a01229e61b878f4530bd052 to your computer and use it in GitHub Desktop.
A script for changing IPs of nodes in headscale v0.23.0
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
## Put me in your PATH to quickly change headscale node IPs | |
## Written by: | |
### Engineer: Geofferey | |
TESTED_VER="v0.23.0" | |
TS_CIDR="100.64.0.0/10" | |
HEADSCALE_DB=/var/lib/headscale/db.sqlite | |
if ! [ $(headscale version) ]; then | |
echo | |
echo "headscale not found, or not imediately in $PATH" | |
echo | |
echo "Is it actually installed?" | |
echo | |
echo "...quitting!..." | |
echo | |
exit | |
fi | |
if ! [ $(headscale version) = ${TESTED_VER} ]; then | |
echo | |
echo "This script has not been tested with your current version of headscale, be careful!" | |
echo "Modify the 'TESTED_VER=' variable to match `headscale version` to continue..." | |
echo | |
echo "...quitting!..." | |
echo | |
exit | |
fi | |
SQLITE3_CHK=$(sqlite3 --version) | |
#echo $SQLITE3_CHK | |
if [ -z "${SQLITE3_CHK}" ]; then | |
echo | |
echo "sqlite3 not found, or not imediately in $PATH" | |
echo | |
echo "Is it actually installed?" | |
echo | |
echo "...quitting!..." | |
echo | |
exit | |
fi | |
if [ -z ${1} ] || [ -z ${2} ] && [[ ${1} != "list" ]]; then | |
echo | |
echo " quick use: ${0} hostname newip" | |
echo | |
echo " e.g. ${0} Win10-Wrksta 100.64.0.2 (case censitive)" | |
echo " e.g. ${0} list" | |
echo | |
fi | |
if [ -z ${1} ]; then | |
read -p "Enter the hostname of the headscale node you wish to change IP for: " HOST_NAME | |
echo | |
else | |
HOST_NAME=${1} | |
fi | |
if [ ${1} = list ]; then | |
echo | |
sqlite3 /var/lib/headscale/db.sqlite "SELECT Hostname,Ipv4 FROM nodes" | |
echo | |
exit | |
fi | |
CHK_NODE=$(sqlite3 /var/lib/headscale/db.sqlite "SELECT Hostname FROM nodes where Hostname = '${HOST_NAME}'") | |
if ! [ ${CHK_NODE} ]; then | |
echo | |
echo "Node ${HOST_NAME} not found in table..." | |
echo | |
exit | |
fi | |
if [ -z ${2} ]; then | |
read -p "Enter the IP you wish to assign to your headscale node: " NEW_IP | |
else | |
NEW_IP=${2} | |
fi | |
LANG=C | |
## Stolen from https://stackoverflow.com/questions/13777387/check-for-ip-validity | |
TMP="${NEW_IP}" # <-- your string in input | |
IP="$TMP" | |
CHECK="" | |
I=4 | |
while [ $I -ne 0 ]; do | |
I=$(( $I - 1 )) | |
J="${TMP%%.*}" | |
K="${J#[0-9]}" | |
K="${K#[0-9]}" | |
K="${K#[0-9]}" | |
if [ "$J" != "$K" ] && [ -z "$K" ] && [ "$J" -ge 0 ] && [ "$J" -le 255 ]; then | |
CHECK="$CHECK.$(( $J ))" | |
fi | |
TMP="${TMP#*.}" | |
done | |
CHECK="${CHECK#.}" | |
echo | |
if [ -n "$IP" ] && [ "$IP" != "$CHECK" ]; then | |
echo "'$IP' appears to be an invalid IP" # input is from 0.0.0.0 to 255.255.255.255 | |
echo | |
echo "...quitting!..." | |
echo | |
exit | |
fi | |
## Lifted from https://serverfault.com/a/1165239/475457 | |
function is_ip_in_cidr() | |
{ | |
local ip=$1 | |
local cidr=$2 | |
#Process the CIDR first | |
local network=$(echo $cidr | cut -d/ -f1) | |
local mask=$(echo $cidr | cut -d/ -f2) | |
#Quad dot notation has 4 fields. Shift and add to give decimal number | |
local network_dec=$(echo $network | awk -F. '{printf("%d\n", (($1 * 256 +$2) * 256 + $3) * 256 + $4) }') | |
#TEST echo "network_dec: $network_dec" | |
#Shift bitmask correct number of places for given mask | |
local mask_dec=$((0xffffffff << (32 - $mask))) | |
#TEST local mask_dec=$((0x0000000f << (32 - $mask))) | |
#TEST printf "mask_dec: %x\n", $mask_dec | |
#But limit bitmask to 32 bits or 8 hexidecimal places. | |
local mask_dec2=$((0xffffffff & $mask_dec)) | |
#TEST printf "mask_dec2: %x\n", $mask_dec2 | |
#Apply mask to network address to get the bits to check | |
local net1=$(( $mask_dec2 & $network_dec )) | |
#TEST printf "net1: %x\n", $net1 | |
#Process the IP address. Again Quad dot notation, shift and add. | |
local ip_dec=$(echo $ip | awk -F. '{printf("%d\n", (($1 * 256 +$2) * 256 + $3) * 256 + $4) }') | |
#TEST echo "IP DEC: $ip_dec" | |
#Apply the same mask to IP address | |
local net2=$(( $mask_dec2 & $ip_dec )) | |
#TEST printf "net2: %x\n", $net2 | |
#Now the two network components can be compared | |
if [[ $net1 == $net2 ]]; then | |
return 0 | |
else | |
return 1 | |
fi | |
} | |
# Abuse the function with a sample IP address and CIDR | |
ip=${IP} | |
cidr=${TS_CIDR} | |
if ! $(is_ip_in_cidr $ip $cidr); then | |
echo "IP is not in ${TS_CIDR} subnet" | |
echo | |
echo "...quitting!..." | |
echo | |
exit | |
fi | |
CHK_NODE_IP=$(sqlite3 /var/lib/headscale/db.sqlite "SELECT ipv4 FROM nodes where ipv4 = '${NEW_IP}'") | |
if ! [ -z ${CHK_NODE_IP} ]; then | |
IP_HOLDER=$(sqlite3 /var/lib/headscale/db.sqlite "SELECT Hostname FROM nodes where ipv4 = '${NEW_IP}'") | |
echo "Chosen IP is currently in use by ${IP_HOLDER}, try with a different IP or fandangle em around..." | |
echo | |
exit 0 | |
fi | |
if $(sqlite3 ${HEADSCALE_DB} "update nodes set ipv4 = '$NEW_IP' where Hostname = '${HOST_NAME}'"); then | |
echo "Node IP update successful..." | |
fi | |
echo |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Btw, you are setting the HEADSCALE_DB variable, but then are still using the hard-coded path. Also for docker uses headscale is not installed on the system (and the db is in a different location), so they can't use your script without modifying it