Skip to content

Instantly share code, notes, and snippets.

@lixin9311
Last active March 17, 2026 13:49
Show Gist options
  • Select an option

  • Save lixin9311/1c692470e189eb615f3e2524d2e66ca5 to your computer and use it in GitHub Desktop.

Select an option

Save lixin9311/1c692470e189eb615f3e2524d2e66ca5 to your computer and use it in GitHub Desktop.
Enable V6 Plus Static IP on UDM.
#!/bin/sh
# Configure JPIX "v6 Plus" Fixed IP service (IPIP).
# Intended for unattended use (e.g., crontab).
#
# Use --dry-run to preview the changes
#
# Prerequisites: System must already have MAP-E JPIX configured.
#
# Note: Weekly scheduled backup could revert the network settings.
# Disable the backup to allow the change to persist.
# Or use the provided systemd service to persist the settings.
set -eu
# ── Parameters ────────────────────────────────────────────────────────
USERNAME="kotei0123456"
PASSWORD="p1a2s3s4"
IPV4_CIDR="8.8.8.8/32" # static IPv4 in CIDR notation
IPV6_IID="::1:2:3:4" # IPv6 Interface Identifier (lower 64 bits)
IPV6_REMOTE="2404:9200:225:100::65" # tunnel remote endpoint (IPv6 or hostname)
STATE_FILE="/data/udapi-config/ubios-udapi-server/ubios-udapi-server.state"
WORKING_FILE="${STATE_FILE}.modified"
# ──────────────────────────────────────────────────────────────────────
DRY_RUN=false
case "${1:-}" in --dry-run) DRY_RUN=true ;; esac
# Split CIDR into address and prefix length.
IPV4_ADDR="${IPV4_CIDR%/*}"
IPV4_PREFIX="${IPV4_CIDR#*/}"
# Create working copy.
cp -p "$STATE_FILE" "$WORKING_FILE"
# Build the serverName payload: IID|IPv6_Remote|IPv4_Address|IPv4_Prefix
SERVER_NAME="${IPV6_IID}|${IPV6_REMOTE}|${IPV4_ADDR}|${IPV4_PREFIX}"
# Apply modifications with jq:
# 1. Change capability from map_e_jpix → ipip_jpix
# 2. Add authentication block with serverName encoding
# 3. Remove 192.0.0.x addresses from all interfaces
jq --indent 1 \
--arg user "$USERNAME" \
--arg pass "$PASSWORD" \
--arg sname "$SERVER_NAME" \
'
.interfaces |= [
.[] |
# Update hb46pp capability and add authentication
if .ipv6.hb46pp.capability == "map_e_jpix" then
.ipv6.hb46pp.capability = "ipip_jpix"
| .ipv6.hb46pp.authentication = {
username: $user,
password: $pass,
serverName: $sname
}
else . end
|
# Remove 192.0.0.x addresses
if .addresses then
.addresses |= [ .[] | select(.cidr == null or (.cidr | startswith("192.0.0.") | not)) ]
else . end
]
' "$WORKING_FILE" > "${WORKING_FILE}.tmp" && mv "${WORKING_FILE}.tmp" "$WORKING_FILE"
# Dry run: show diff and exit without applying.
if [ "$DRY_RUN" = true ]; then
diff -u "$STATE_FILE" "$WORKING_FILE" || true
rm -f "$WORKING_FILE"
exit 0
fi
# Apply changes.
ubios-udapi-client put /system/ubios/udm/configuration "@${WORKING_FILE}"
#!/bin/bash
# install.sh
# Run this on your UniFi router to deploy the network watcher service.
# It will copy network scripts to /data/scripts.
# Network watcher service will watch for network configuration changes.
# On changes, it will run on-network-change.sh, which will run the configure.sh to restore static IP settings.
# Usage:
# 1. download all scripts into the same folder
# 2. grant executable permission: chmod +x *.sh
# 3. fill in your IPv4 connections details inside the Parameters section of configure.sh
# 4. run configure.sh once to apply the settings for the first time
# 5. run install.sh to install the watcher service
# 6. Change some settings (assign a static ip to a client device etc.)
# 7. Check if the static IP setting is persisted, check the logs: journalctl -u network-watcher
set -euo pipefail
SCRIPT_DIR="/data/scripts"
SERVICE_FILE="/etc/systemd/system/network-watcher.service"
echo "=== Network Watcher Installer ==="
# 1. Install inotify-tools if missing
if ! command -v inotifywait &> /dev/null; then
echo "[1/4] Installing inotify-tools..."
apt-get update -qq && apt-get install -y -qq inotify-tools
else
echo "[1/4] inotify-tools already installed"
fi
# 2. Deploy scripts
echo "[2/4] Deploying scripts to ${SCRIPT_DIR}/"
mkdir -p "$SCRIPT_DIR"
cp network-watcher.sh "$SCRIPT_DIR/"
cp on-network-change.sh "$SCRIPT_DIR/"
cp configure.sh "$SCRIPT_DIR/"
chmod +x "$SCRIPT_DIR"/*.sh
# 3. Install systemd service
echo "[3/4] Installing systemd service..."
cp network-watcher.service "$SERVICE_FILE"
systemctl daemon-reload
systemctl enable network-watcher.service
# 4. Start the service
echo "[4/4] Starting network-watcher..."
systemctl restart network-watcher.service
echo ""
echo "=== Done ==="
echo ""
echo "Useful commands:"
echo " systemctl status network-watcher # Check service status"
echo " journalctl -fu network-watcher # Follow logs live"
echo " journalctl -u network-watcher --since '1 hour ago' # Recent logs"
echo ""
echo "Edit your custom logic in: ${SCRIPT_DIR}/on-network-change.sh"
[Unit]
Description=Watch ubios-udapi-server state for network changes
After=network.target unifi-core.service udapi-server.service ubntconf.service
Wants=network.target
[Service]
Type=simple
ExecStartPre=/bin/sh -c 'command -v inotifywait >/dev/null 2>&1 || { echo "inotifywait not found. Install with: apt-get install -y inotify-tools"; exit 1; }'
ExecStart=/data/scripts/network-watcher.sh
Restart=on-failure
RestartSec=10
StartLimitIntervalSec=120
StartLimitBurst=3
StandardOutput=journal
StandardError=journal
SyslogIdentifier=network-watcher
[Install]
WantedBy=multi-user.target
#!/bin/bash
# /data/scripts/network-watcher.sh
# Watches the ubios-udapi-server state file for changes and triggers
# the on-network-change.sh script when modifications are detected.
set -euo pipefail
STATE_FILE_ORIG="/data/udapi-config/ubios-udapi-server/ubios-udapi-server.state"
STATE_FILE="$(readlink -f "$STATE_FILE_ORIG")"
CHANGE_SCRIPT="/data/scripts/on-network-change.sh"
log() {
logger -t "network-watcher" "$1"
echo "$(date '+%Y-%m-%d %H:%M:%S') [network-watcher] $1"
}
log "Starting network watcher (PID: $$)"
log "Original path: ${STATE_FILE_ORIG}"
log "Resolved path: ${STATE_FILE}"
# Wait for the state file to exist (may not be ready immediately after boot)
if [ ! -f "$STATE_FILE" ]; then
log "State file not found, waiting..."
while [ ! -f "$STATE_FILE" ]; do
sleep 5
done
log "State file found, proceeding"
fi
# Verify inotifywait is available
if ! command -v inotifywait &> /dev/null; then
log "ERROR: inotifywait not found. Install with: apt-get install -y inotify-tools"
exit 1
fi
# Verify change script exists and is executable
if [ ! -x "$CHANGE_SCRIPT" ]; then
log "ERROR: ${CHANGE_SCRIPT} not found or not executable"
exit 1
fi
STATE_DIR="$(dirname "$STATE_FILE")"
STATE_BASENAME="$(basename "$STATE_FILE")"
log "Entering watch loop (watching directory: ${STATE_DIR})"
inotifywait -m -e moved_to,create,modify,close_write --format '%T %e %f' --timefmt '%Y-%m-%dT%H:%M:%S' "$STATE_DIR" |
while read timestamp event filename; do
if [ "$filename" = "$STATE_BASENAME" ]; then
log "State file changed: event=${event} file=${filename} at ${timestamp}"
"$CHANGE_SCRIPT" &
fi
done
# If inotifywait exits unexpectedly
log "ERROR: inotifywait exited unexpectedly"
exit 1
#!/bin/bash
# /data/scripts/on-network-change.sh
# Called by network-watcher.sh when the ubios state file changes.
# Includes debounce logic to avoid rapid-fire execution.
set -euo pipefail
LOCKFILE="/tmp/network-change.lock"
DEBOUNCE_SECONDS=5
log() {
logger -t "network-change" "$1"
echo "$(date '+%Y-%m-%d %H:%M:%S') [network-change] $1"
}
# Debounce: skip if another instance is already running
if [ -f "$LOCKFILE" ]; then
log "Debounce: skipping (another instance running)"
exit 0
fi
cleanup() {
rm -f "$LOCKFILE"
}
trap cleanup EXIT
touch "$LOCKFILE"
log "Network change detected, waiting ${DEBOUNCE_SECONDS}s debounce..."
sleep "$DEBOUNCE_SECONDS"
log "Executing network change actions"
# ============================================================
# ADD YOUR CUSTOM LOGIC BELOW
# ============================================================
STATE_FILE="/data/udapi-config/ubios-udapi-server/ubios-udapi-server.state"
if grep -q "map_e_jpix" "$STATE_FILE"; then
log "Network config reverted to map_e_jpix in state file, running configure.sh"
/data/scripts/configure.sh && \
log "configure.sh completed successfully" || \
log "configure.sh failed with exit code $?"
else
log "Network config did not revert to map_e_jpix in state file, skipping"
fi
# ============================================================
# END CUSTOM LOGIC
# ============================================================
log "Network change actions completed"
#!/bin/bash
# uninstall.sh
# Removes the network watcher service and scripts from the router.
# Usage: bash uninstall.sh
set -euo pipefail
SERVICE_NAME="network-watcher"
SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
SCRIPT_DIR="/data/scripts"
echo "=== Network Watcher Uninstaller ==="
# 1. Stop and disable the service
if systemctl is-active --quiet "$SERVICE_NAME" 2>/dev/null; then
echo "[1/3] Stopping ${SERVICE_NAME}..."
systemctl stop "$SERVICE_NAME"
else
echo "[1/3] Service not running, skipping stop"
fi
if systemctl is-enabled --quiet "$SERVICE_NAME" 2>/dev/null; then
echo " Disabling ${SERVICE_NAME}..."
systemctl disable "$SERVICE_NAME"
fi
# 2. Remove service file
if [ -f "$SERVICE_FILE" ]; then
echo "[2/3] Removing ${SERVICE_FILE}"
rm -f "$SERVICE_FILE"
systemctl daemon-reload
systemctl reset-failed "$SERVICE_NAME" 2>/dev/null || true
else
echo "[2/3] Service file not found, skipping"
fi
# 3. Remove scripts (but leave configure.sh alone)
echo "[3/3] Removing scripts..."
rm -f "${SCRIPT_DIR}/network-watcher.sh"
rm -f "${SCRIPT_DIR}/on-network-change.sh"
rm -f "${SCRIPT_DIR}/configure.sh"
rm -f "/tmp/network-change.lock"
# Clean up empty dir, but only if nothing else is in there
if [ -d "$SCRIPT_DIR" ] && [ -z "$(ls -A "$SCRIPT_DIR")" ]; then
rmdir "$SCRIPT_DIR"
echo " Removed empty ${SCRIPT_DIR}/"
else
echo " Kept ${SCRIPT_DIR}/ (other files present)"
fi
echo ""
echo "=== Uninstall complete ==="
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment