Skip to content

Instantly share code, notes, and snippets.

@Fail-Safe
Last active September 25, 2025 20:53
Show Gist options
  • Select an option

  • Save Fail-Safe/6f9bac241191da60529ea755b8a17242 to your computer and use it in GitHub Desktop.

Select an option

Save Fail-Safe/6f9bac241191da60529ea755b8a17242 to your computer and use it in GitHub Desktop.
AP PPSK (per-PSK) conversion script for OpenWrt
#!/bin/sh
# Local AP PPSK (per-PSK) conversion script – run directly ON the WAP.
# Converts existing multi-SSID setup to use central wpa_psk_file + dynamic VLANs.
set -e
BACKUP_DIR=${BACKUP_DIR:-/root/backups}
PPSK_MAIN_SRC=${PPSK_MAIN_SRC:-./ppsk-vlans}
PPSK_MAIN_DIR=/etc/hostapd
PPSK_MAIN_DST=$PPSK_MAIN_DIR/ppsk-vlans
WPAD_CAP_DST=/etc/capabilities/wpad.json
echo "[1/7] Creating backup..."
mkdir -p "$BACKUP_DIR"
HOSTNAME=$(uci get system.@system[0].hostname 2>/dev/null || echo unknown)
STAMP=$(date +%F-%H%M)
BACKUP_TMP=/tmp/backup-${HOSTNAME}-${STAMP}.tar.gz
sysupgrade -b "$BACKUP_TMP"
mv "$BACKUP_TMP" "$BACKUP_DIR"/
echo " Backup stored at $BACKUP_DIR/$(basename "$BACKUP_TMP")"
echo "[2/7] Create PPSK directory..."
mkdir -p "$PPSK_MAIN_DIR"
chown network:network "$PPSK_MAIN_DIR" 2>/dev/null || true
chmod 640 "$PPSK_MAIN_DIR" 2>/dev/null || true
echo "[3/7] Generating PPSK file from wireless config..."
# Desired format lines:
# vlanid=<VID> 00:00:00:00:00:00 <PASSWD>
# If you later want MAC-specific PPSKs replace the all-zero MAC with the target.
# Source can be overridden with WIRELESS_SRC (defaults /etc/config/wireless).
WIRELESS_SRC=${WIRELESS_SRC:-/etc/config/wireless}
TMP_PPSK=$(mktemp)
if [ ! -f "$WIRELESS_SRC" ]; then
echo " Wireless source $WIRELESS_SRC not found; skipping generation." >&2
else
awk -v OUTFILE="$TMP_PPSK" '
function unq(s){ gsub(/^["\047]|["\047]$/,"",s); return s }
function flush_station(){
if(in_station && st_key!="" && st_vid!="") {
st_key=unq(st_key); st_vid=unq(st_vid);
print "vlanid=" st_vid " 00:00:00:00:00:00 " st_key > OUTFILE;
}
in_station=0; st_key=""; st_vid="";
}
function flush_iface(){
if(in_iface && if_key!="") {
if_key=unq(if_key);
print "vlanid=0 00:00:00:00:00:00 " if_key > OUTFILE;
}
in_iface=0; if_key="";
}
BEGIN{in_station=0; in_iface=0; st_key=""; st_vid=""; if_key=""}
/^config[[:space:]]+wifi-station/ { flush_station(); flush_iface(); in_station=1; next }
/^config[[:space:]]+wifi-iface/ { flush_station(); flush_iface(); in_iface=1; next }
/^config[[:space:]]+/ { flush_station(); flush_iface(); next }
in_station && /option[[:space:]]+key[[:space:]]+/ { st_key=$3 }
in_station && /option[[:space:]]+vid[[:space:]]+/ { st_vid=$3 }
in_iface && /option[[:space:]]+key[[:space:]]+/ { if_key=$3 }
END{ flush_station(); flush_iface() }
' "$WIRELESS_SRC"
# Deduplicate in case same key appears multiple times
sort -u "$TMP_PPSK" -o "$TMP_PPSK"
# If we produced anything, install/merge.
if [ -s "$TMP_PPSK" ]; then
# Backup existing file if present.
if [ -f "$PPSK_MAIN_DST" ]; then
cp "$PPSK_MAIN_DST" "$PPSK_MAIN_DST.bak.$(date +%s)"
fi
mv "$TMP_PPSK" "$PPSK_MAIN_DST"
echo " Generated $(wc -l < "$PPSK_MAIN_DST") PPSK entries into $PPSK_MAIN_DST"
else
echo " No wifi-station entries with vid+key found; leaving existing PPSK file intact." >&2
rm -f "$TMP_PPSK"
fi
fi
[ -f "$PPSK_MAIN_DST" ] && chown network:network "$PPSK_MAIN_DST" && chmod 640 "$PPSK_MAIN_DST"
echo "[4/7] Applying UCI wireless edits..."
# Assumptions: iface[0] + iface[1] are main dual-band SSID.
# Adjust indices via env if needed: IFACE_MAIN_24, IFACE_MAIN_5
IM24=${IFACE_MAIN_24:-0}
IM5=${IFACE_MAIN_5:-1}
# Set both main SSIDs to use the new PPSK file and dynamic VLANs.
uci set wireless.@wifi-iface[$IM24].wpa_psk_file="$PPSK_MAIN_DST"
uci set wireless.@wifi-iface[$IM5].wpa_psk_file="$PPSK_MAIN_DST"
uci set wireless.@wifi-iface[$IM24].dynamic_vlan='2'
uci set wireless.@wifi-iface[$IM5].dynamic_vlan='2'
# Remove legacy single PSKs (ignore errors if already gone)
uci delete wireless.@wifi-iface[$IM24].key 2>/dev/null || true
uci delete wireless.@wifi-iface[$IM5].key 2>/dev/null || true
# Loop over the PPSK file and remove the corresponding `wifi-station` sections
# for each VLAN that we created in the PPSK file.
# A `wifi-station` section will look like this:
# config wifi-station
# option iface 'default_radio0'
# option vid '178'
# option key 'redacted'
while read -r line; do
vlanid=$(echo "$line" | sed -n "s/^vlanid=\([0-9][0-9]*\).*/\1/p")
echo " Processing VLAN $vlanid..."
# We must loop over all the `wifi-station` sections in the wireless config
# and remove the ones that match the VLAN ID. `option vid '<vlanid>'`
uci_section_num=$(uci show wireless \
| grep -E "wireless\.@wifi-station\[[0-9]+\]\.vid='${vlanid}'" \
| sed -n "s/.*@wifi-station\[\([0-9][0-9]*\)\].*/\1/p")
if [ -z "$uci_section_num" ]; then
echo " No matching wifi-station section found for VLAN $vlanid; skipping."
continue
fi
if [ -n "$uci_section_num" ] && [ "$uci_section_num" -ge 0 ]; then
echo " Deleting wifi-station section number $uci_section_num for VLAN $vlanid..."
uci delete wireless.@wifi-station["$uci_section_num"] 2>/dev/null || true
fi
done < "$PPSK_MAIN_DST"
uci commit wireless
echo " Wireless config updated."
echo "[5/7] Ensuring /etc/hostapd filesystem entry exists in wpad.json..."
# This entry is required for hostapd/wpad to access the
# new PPSK file in the new /etc/hostapd path.
# {
# "path": "/etc/hostapd",
# "rw": false
# },
if [ -f "$WPAD_CAP_DST" ]; then
if grep -q '"path" *: *"/etc/hostapd"' "$WPAD_CAP_DST"; then
echo " Entry already present."
else
tmpf=$(mktemp)
awk '
BEGIN{in_fs=0; inserted=0}
/"filesystem"[[:space:]]*:[[:space:]]*\[/ {in_fs=1}
in_fs && /{/ && inserted==0 {
print " {";
print " \"path\": \"/etc/hostapd\",";
print " \"rw\": false";
print " },";
inserted=1
}
{print}
in_fs && /]/ {in_fs=0}
END{if(inserted==0) fprintf(stderr,"Note: could not insert /etc/hostapd entry (no filesystem array objects found)\n")}
' "$WPAD_CAP_DST" > "$tmpf" && mv "$tmpf" "$WPAD_CAP_DST"
echo " Added /etc/hostapd entry."
fi
chmod 644 "$WPAD_CAP_DST" 2>/dev/null || true
else
echo " No existing $WPAD_CAP_DST found; skipping (create manually if needed)."
fi
echo "[6/7] Adding new files/dirs to sysupgrade configuration file..."
echo "$$WPAD_CAP_DST" >> /etc/sysupgrade.conf
echo "$$PPSK_MAIN_DIR" >> /etc/sysupgrade.conf
echo "[7/7] Restarting wpad and checking logs..."
/etc/init.d/wpad restart
sleep 3
logread | grep -m1 "Assigned VLAN ID" || echo "No VLAN assignment observed yet (connect a PPSK client to trigger)."
echo "Done."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment