I am trying to update my external IPs when changed with a script, I have a cronjob that does that great for IPv4 once a minute. I would like to do that with IPv6, and have made a great looking script, but I cannot find a command to do that, similar to
Is there such a command? I would like to drop the old unused IPv6 and replace it with the proper one when my provider(rarely) changes my Prefix delegation.
Sure, give me a sec to try and remove my info from it…
I will post my working ipv4 script, and my attempts to create an IPv6 script, I had assumed the command supported both, I never checked first… whoops
I made this script by hand, it worked well, then I ran it through Grok3 and it introduced fallback checks, commented everything and generally cleaned it up, that was a nice touch, and my first use of Grok3 for this… I am a fan.
#!/bin/bash
# File to store the last-used new IP
CONFIG_FILE="/home/XXXXXXXXX/.virtualmin_ip_last_used"
LOG_FILE="/home/XXXXXXXXX/Virtualmin_Auto_Update_Log"
LOG_DIR="/home/XXXXXXXXX/logs"
# Maximum log file size in bytes (5MB = 5,242,880 bytes)
MAX_SIZE=5242880
# Number of rotated logs to keep
MAX_ROTATIONS=10
# Ensure log file exists and is writable
touch "$LOG_FILE" 2>/dev/null || { echo "Cannot write to log file"; exit 1; }
chmod 644 "$LOG_FILE"
# Ensure log directory exists and is writable (assuming you've created it)
if [ ! -d "$LOG_DIR" ]; then
echo "Log directory $LOG_DIR does not exist. Please create it."
exit 1
fi
touch "$LOG_DIR/test" 2>/dev/null && rm "$LOG_DIR/test" || { echo "Cannot write to $LOG_DIR"; exit 1; }
# Function to rotate logs if size exceeds limit
rotate_logs() {
if [ -f "$LOG_FILE" ]; then
LOG_SIZE=$(stat -c %s "$LOG_FILE" 2>/dev/null || wc -c < "$LOG_FILE")
if [ "$LOG_SIZE" -gt "$MAX_SIZE" ]; then
echo "$(date): Log file exceeds 5MB ($LOG_SIZE bytes), rotating logs..." >> "$LOG_FILE"
# Remove oldest log if we’ve hit the max rotations
if [ -f "$LOG_DIR/Virtualmin_Auto_Update_Log.$MAX_ROTATIONS" ]; then
rm "$LOG_DIR/Virtualmin_Auto_Update_Log.$MAX_ROTATIONS"
fi
# Rotate existing logs (e.g., .9 -> .10, .8 -> .9, etc.)
for i in $(seq $((MAX_ROTATIONS - 1)) -1 0); do
[ -f "$LOG_DIR/Virtualmin_Auto_Update_Log.$i" ] && mv "$LOG_DIR/Virtualmin_Auto_Update_Log.$i" "$LOG_DIR/Virtualmin_Auto_Update_Log.$((i + 1))"
done
# Move current log to .0 in log directory and start fresh
mv "$LOG_FILE" "$LOG_DIR/Virtualmin_Auto_Update_Log.0"
touch "$LOG_FILE"
chmod 644 "$LOG_FILE"
echo "$(date): Log file rotated to $LOG_DIR." >> "$LOG_FILE"
fi
fi
}
# Rotate logs before any new entries
rotate_logs
# Fetch the old IP addresses from Virtualmin
OLD_IPS=$(virtualmin list-domains --multiline | grep "External IP address" | awk '{print $NF}' | sort -u)
# Check if we got any IPs
#if [ -z "$OLD_IPS" ]; then
# echo "$(date): No external IP addresses found in Virtualmin domains." >> "$LOG_FILE"
# exit 1
#fi
# Count unique IPs and log them
echo "$(date): Found the following old IP addresses:" >> "$LOG_FILE"
echo "$OLD_IPS" >> "$LOG_FILE"
NUM_IPS=$(echo "$OLD_IPS" | /usr/bin/wc -l)
echo "$(date): Number of unique IPs: $NUM_IPS" >> "$LOG_FILE"
# Function to validate an IP address
validate_ip() {
local ip=$1
# Basic IPv4 format check
if ! [[ "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
return 1
fi
# Check each octet is 0-255
IFS='.' read -r -a octets <<< "$ip"
for octet in "${octets[@]}"; do
if [ "$octet" -gt 255 ] || [ "$octet" -lt 0 ]; then
return 1
fi
done
return 0
}
# Fetch the current external IP address (primary source)
NEW_IP=$(curl -4 ifconfig.me 2>/dev/null)
if [ -z "$NEW_IP" ] || ! validate_ip "$NEW_IP"; then
echo "$(date): Primary IP fetch from ifconfig.me failed or returned invalid IP: '$NEW_IP'. Trying fallback..." >> "$LOG_FILE"
# Fallback source
NEW_IP=$(curl -4 icanhazip.com 2>/dev/null)
if [ -z "$NEW_IP" ] || ! validate_ip "$NEW_IP"; then
echo "$(date): Fallback IP fetch from icanhazip.com also failed or returned invalid IP: '$NEW_IP'. Aborting." >> "$LOG_FILE"
exit 1
fi
echo "$(date): Successfully fetched new IP from fallback (icanhazip.com): $NEW_IP" >> "$LOG_FILE"
else
echo "$(date): Fetched and validated new external IP from primary (ifconfig.me): $NEW_IP" >> "$LOG_FILE"
fi
# Save the new IP to the config file for reference
echo "LAST_NEW_IP=$NEW_IP" > "$CONFIG_FILE"
# Loop through each unique old IP and run the Virtualmin command if needed
echo "$(date): Updating IP addresses..." >> "$LOG_FILE"
while IFS= read -r OLD_IP; do
if [ "$OLD_IP" = "$NEW_IP" ]; then
echo "$(date): Skipping $OLD_IP - it’s already set to $NEW_IP." >> "$LOG_FILE"
else
COMMAND="virtualmin modify-all-ips --old-ip $OLD_IP --new-ip $NEW_IP"
echo "$(date): Running: $COMMAND" >> "$LOG_FILE"
$COMMAND >> "$LOG_FILE" 2>&1
if [ $? -eq 0 ]; then
echo "$(date): Successfully updated $OLD_IP to $NEW_IP." >> "$LOG_FILE"
else
echo "$(date): Error updating $OLD_IP to $NEW_IP. Check Virtualmin logs." >> "$LOG_FILE"
fi
fi
done <<< "$OLD_IPS"
echo "$(date): IP modification process completed." >> "$LOG_FILE"
This was intended as a root cron job, and so does not give feedback with echo while running, but logs it. I had one I ran manually, and had it echoing the responses.
Here is the IPv6 update made between me and Grok3, it took more work, particularly Grok3 could not solve the IPv6 validation, but I got it sorted, and when I finally tested it running, found that the virtualmin update ips does not actually support IPv6, or at least I do not know how.
#!/bin/bash
# File to store the last-used new IP
CONFIG_FILE="/home/XXXXXXXXX/.virtualmin_ip_last_used"
LOG_FILE="/home/XXXXXXXXX/Virtualmin_Auto_Update_Log"
LOG_DIR="/home/XXXXXXXXX/logs"
# Maximum log file size in bytes (5MB = 5,242,880 bytes)
MAX_SIZE=524
# Number of rotated logs to keep
MAX_ROTATIONS=10
# Ensure log file exists and is writable
touch "$LOG_FILE" 2>/dev/null || { echo "Cannot write to log file"; exit 1; }
chmod 644 "$LOG_FILE"
# Ensure log directory exists and is writable (assuming you've created it)
if [ ! -d "$LOG_DIR" ]; then
echo "Log directory $LOG_DIR does not exist. Please create it."
exit 1
fi
touch "$LOG_DIR/test" 2>/dev/null && rm "$LOG_DIR/test" || { echo "Cannot write to $LOG_DIR"; exit 1; }
# Function to rotate logs if size exceeds limit
rotate_logs() {
if [ -f "$LOG_FILE" ]; then
LOG_SIZE=$(stat -c %s "$LOG_FILE" 2>/dev/null || wc -c < "$LOG_FILE")
if [ "$LOG_SIZE" -gt "$MAX_SIZE" ]; then
echo "$(date): Log file exceeds 5MB ($LOG_SIZE bytes), rotating logs..." >> "$LOG_FILE"
# Remove oldest log if we’ve hit the max rotations
if [ -f "$LOG_DIR/Virtualmin_Auto_Update_Log.$MAX_ROTATIONS" ]; then
rm "$LOG_DIR/Virtualmin_Auto_Update_Log.$MAX_ROTATIONS"
fi
# Rotate existing logs (e.g., .9 -> .10, .8 -> .9, etc.)
for i in $(seq $((MAX_ROTATIONS - 1)) -1 0); do
[ -f "$LOG_DIR/Virtualmin_Auto_Update_Log.$i" ] && mv "$LOG_DIR/Virtualmin_Auto_Update_Log.$i" "$LOG_DIR/Virtualmin_Auto_Update_Log.$((i + 1))"
done
# Move current log to .0 in log directory and start fresh
mv "$LOG_FILE" "$LOG_DIR/Virtualmin_Auto_Update_Log.0"
touch "$LOG_FILE"
chmod 644 "$LOG_FILE"
echo "$(date): Log file rotated to $LOG_DIR." >> "$LOG_FILE"
fi
fi
}
# Rotate logs before any new entries
rotate_logs
# Fetch the old IP addresses from Virtualmin
OLD_IPS=$(virtualmin list-domains --multiline | grep "External IP address" | awk '{print $NF}' | sort -u)
# Check if we got any IPs
#if [ -z "$OLD_IPS" ]; then
# echo "$(date): No external IP addresses found in Virtualmin domains." >> "$LOG_FILE"
# exit 1
#fi
# Count unique IPs and log them
echo "$(date): Found the following old IP addresses:" >> "$LOG_FILE"
echo "$OLD_IPS" >> "$LOG_FILE"
NUM_IPS=$(echo "$OLD_IPS" | /usr/bin/wc -l)
echo "$(date): Number of unique IPs: $NUM_IPS" >> "$LOG_FILE"
# Function to validate an IP address
validate_ip() {
local ip=$1
# Basic IPv4 format check
if ! [[ "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
return 1
fi
# Check each octet is 0-255
IFS='.' read -r -a octets <<< "$ip"
for octet in "${octets[@]}"; do
if [ "$octet" -gt 255 ] || [ "$octet" -lt 0 ]; then
return 1
fi
done
return 0
}
# Fetch the current external IP address (primary source)
NEW_IP=$(curl -4 ifconfig.me 2>/dev/null)
if [ -z "$NEW_IP" ] || ! validate_ip "$NEW_IP"; then
echo "$(date): Primary IP fetch from ifconfig.me failed or returned invalid IP: '$NEW_IP'. Trying fallback..." >> "$LOG_FILE"
# Fallback source
NEW_IP=$(curl -4 icanhazip.com 2>/dev/null)
if [ -z "$NEW_IP" ] || ! validate_ip "$NEW_IP"; then
echo "$(date): Fallback IP fetch from icanhazip.com also failed or returned invalid IP: '$NEW_IP'. Aborting." >> "$LOG_FILE"
exit 1
fi
echo "$(date): Successfully fetched new IP from fallback (icanhazip.com): $NEW_IP" >> "$LOG_FILE"
else
echo "$(date): Fetched and validated new external IP from primary (ifconfig.me): $NEW_IP" >> "$LOG_FILE"
fi
# Save the new IP to the config file for reference
echo "LAST_NEW_IP=$NEW_IP" > "$CONFIG_FILE"
# Loop through each unique old IP and run the Virtualmin command if needed
echo "$(date): Updating IP addresses..." >> "$LOG_FILE"
while IFS= read -r OLD_IP; do
if [ "$OLD_IP" = "$NEW_IP" ]; then
echo "$(date): Skipping $OLD_IP - it’s already set to $NEW_IP." >> "$LOG_FILE"
else
COMMAND="virtualmin modify-all-ips --old-ip $OLD_IP --new-ip $NEW_IP"
echo "$(date): Running: $COMMAND" >> "$LOG_FILE"
$COMMAND >> "$LOG_FILE" 2>&1
if [ $? -eq 0 ]; then
echo "$(date): Successfully updated $OLD_IP to $NEW_IP." >> "$LOG_FILE"
else
echo "$(date): Error updating $OLD_IP to $NEW_IP. Check Virtualmin logs." >> "$LOG_FILE"
fi
fi
done <<< "$OLD_IPS"
echo "$(date): IP modification process completed." >> "$LOG_FILE"
Also, this is still half-baked, as I think I would need to restart the system, or restart the networking to get the new IPv6, it gets changed very occasionally when I restart the main router. IPv4 gets changed every time, which is what led to this originally, but IPv6 does not. So it may not work reliably until I address that.
The issue is not me retrieving the external IPv6, I can do that with IPv6, the issue is I can’t find a virtualmin command on the CLI that updates the IPv6.
Basically I have the script for ipv4 and ipv6, but I have no way to update the IPv6 via the command line
There’s no other command than virtualmin modify-all-ips documented, hence I assumed it does for both, IPv4 and IPv6… Sorry, then I won’t be of any help for you.