feat: Add OTA auto-update and guest IPv6 via Yggdrasil
OTA: build.sh writes version/profile to firmware, generates manifest.json with SHA256 per device. parahub-autoupdate script runs nightly at 3am, fetches manifest (Yggdrasil first), verifies checksum, runs sysupgrade. sysupgrade.conf preserves /etc/parahub/, yggdrasil.conf, dropbear keys. Guest IPv6: Yggdrasil 300::/64 subnet assigned to guest via SLAAC. Separate yggdrasil firewall zone (5 zones total) with guest→yggdrasil forwarding. IPv6 exempt from tc shaping — full speed to Parahub services. IPv6 to WAN blocked. Heartbeat now reads version from file, not hardcoded. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
97
files/usr/bin/parahub-autoupdate
Executable file
97
files/usr/bin/parahub-autoupdate
Executable file
@@ -0,0 +1,97 @@
|
||||
#!/bin/sh
|
||||
# Parahub Mesh — OTA Auto-Update
|
||||
# Checks manifest.json for new firmware, downloads and verifies, runs sysupgrade.
|
||||
# Runs nightly via cron. Lock file prevents concurrent runs.
|
||||
|
||||
LOCK="/tmp/parahub-autoupdate.lock"
|
||||
MANIFEST_YGG="http://[200:abb9:5810:37d3:8a4c:98a6:b82b:969a]/firmware/manifest.json"
|
||||
MANIFEST_PUBLIC="https://parahub.io/firmware/manifest.json"
|
||||
FIRMWARE_YGG="http://[200:abb9:5810:37d3:8a4c:98a6:b82b:969a]/firmware"
|
||||
FIRMWARE_PUBLIC="https://parahub.io/firmware"
|
||||
|
||||
# Lock — exit if already running
|
||||
if [ -f "$LOCK" ]; then
|
||||
LOCK_PID=$(cat "$LOCK" 2>/dev/null)
|
||||
if kill -0 "$LOCK_PID" 2>/dev/null; then
|
||||
logger -t parahub-update "Already running (pid $LOCK_PID), exiting"
|
||||
exit 0
|
||||
fi
|
||||
rm -f "$LOCK"
|
||||
fi
|
||||
echo $$ > "$LOCK"
|
||||
trap 'rm -f "$LOCK"' EXIT
|
||||
|
||||
# Read current version and device profile
|
||||
CURRENT_VERSION=$(cat /etc/parahub/version 2>/dev/null)
|
||||
DEVICE_PROFILE=$(cat /etc/parahub/profile 2>/dev/null)
|
||||
ROLE=$(cat /etc/parahub/role 2>/dev/null || echo "unknown")
|
||||
|
||||
if [ -z "$CURRENT_VERSION" ] || [ -z "$DEVICE_PROFILE" ]; then
|
||||
logger -t parahub-update "Missing /etc/parahub/version or /etc/parahub/profile, skipping"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Fetch manifest (Yggdrasil first for bumblebee, public for bee)
|
||||
MANIFEST=""
|
||||
if [ "$ROLE" != "bee" ] && ping6 -c 1 -W 3 200:abb9:5810:37d3:8a4c:98a6:b82b:969a >/dev/null 2>&1; then
|
||||
MANIFEST=$(curl -s -m 30 "$MANIFEST_YGG" 2>/dev/null)
|
||||
BASE_URL="$FIRMWARE_YGG"
|
||||
fi
|
||||
|
||||
if [ -z "$MANIFEST" ]; then
|
||||
MANIFEST=$(curl -s -m 30 "$MANIFEST_PUBLIC" 2>/dev/null)
|
||||
BASE_URL="$FIRMWARE_PUBLIC"
|
||||
fi
|
||||
|
||||
if [ -z "$MANIFEST" ]; then
|
||||
logger -t parahub-update "Failed to fetch manifest"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Parse manifest with jsonfilter
|
||||
NEW_VERSION=$(echo "$MANIFEST" | jsonfilter -e '$.version' 2>/dev/null)
|
||||
SYSUPGRADE_FILE=$(echo "$MANIFEST" | jsonfilter -e "$.devices.${DEVICE_PROFILE}.sysupgrade" 2>/dev/null)
|
||||
EXPECTED_SHA256=$(echo "$MANIFEST" | jsonfilter -e "$.devices.${DEVICE_PROFILE}.sha256" 2>/dev/null)
|
||||
|
||||
if [ -z "$NEW_VERSION" ]; then
|
||||
logger -t parahub-update "Could not parse version from manifest"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$SYSUPGRADE_FILE" ] || [ -z "$EXPECTED_SHA256" ]; then
|
||||
logger -t parahub-update "Device ${DEVICE_PROFILE} not found in manifest"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Compare versions
|
||||
if [ "$CURRENT_VERSION" = "$NEW_VERSION" ]; then
|
||||
logger -t parahub-update "Already up to date: ${CURRENT_VERSION}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
logger -t parahub-update "Update available: ${CURRENT_VERSION} → ${NEW_VERSION}"
|
||||
|
||||
# Download firmware
|
||||
FIRMWARE_PATH="/tmp/firmware.bin"
|
||||
rm -f "$FIRMWARE_PATH"
|
||||
|
||||
curl -s -m 600 -o "$FIRMWARE_PATH" "${BASE_URL}/${SYSUPGRADE_FILE}" 2>/dev/null
|
||||
|
||||
if [ ! -f "$FIRMWARE_PATH" ]; then
|
||||
logger -t parahub-update "Download failed: ${SYSUPGRADE_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify SHA256
|
||||
ACTUAL_SHA256=$(sha256sum "$FIRMWARE_PATH" | cut -d' ' -f1)
|
||||
|
||||
if [ "$ACTUAL_SHA256" != "$EXPECTED_SHA256" ]; then
|
||||
logger -t parahub-update "SHA256 mismatch! Expected: ${EXPECTED_SHA256}, Got: ${ACTUAL_SHA256}"
|
||||
rm -f "$FIRMWARE_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
logger -t parahub-update "SHA256 verified, starting sysupgrade to ${NEW_VERSION}..."
|
||||
|
||||
# Run sysupgrade (preserves /etc/config/ by default + /etc/sysupgrade.conf entries)
|
||||
sysupgrade "$FIRMWARE_PATH"
|
||||
@@ -32,7 +32,8 @@ case "$HW" in
|
||||
*) HW="${HW:-unknown}" ;;
|
||||
esac
|
||||
|
||||
PAYLOAD="{\"mac\":\"${MAC}\",\"hostname\":\"${HOSTNAME}\",\"yggdrasil_address\":\"${YGG_ADDR}\",\"firmware_version\":\"25.12.0-rc4\",\"hardware_profile\":\"${HW}\",\"uptime\":${UPTIME},\"private_ssid\":\"${SSID}\",\"firmware_role\":\"${ROLE}\",\"mesh_ip\":\"${MESH_IP}\"}"
|
||||
FW_VERSION=$(cat /etc/parahub/version 2>/dev/null || echo "unknown")
|
||||
PAYLOAD="{\"mac\":\"${MAC}\",\"hostname\":\"${HOSTNAME}\",\"yggdrasil_address\":\"${YGG_ADDR}\",\"firmware_version\":\"${FW_VERSION}\",\"hardware_profile\":\"${HW}\",\"uptime\":${UPTIME},\"private_ssid\":\"${SSID}\",\"firmware_role\":\"${ROLE}\",\"mesh_ip\":\"${MESH_IP}\"}"
|
||||
|
||||
RESPONSE=""
|
||||
|
||||
|
||||
@@ -25,12 +25,16 @@ case "$1" in
|
||||
# tc filter: packets with mark 0x20 → paid class
|
||||
tc filter add dev $IFACE parent 1: protocol ip handle 0x20 fw flowid 1:20
|
||||
|
||||
# Exempt ALL IPv6 from shaping — only Yggdrasil IPv6 reaches guests (firewall enforced)
|
||||
tc filter add dev $IFACE parent 1: protocol ipv6 prio 1 u32 match u32 0 0 flowid 1:20
|
||||
|
||||
# --- Ingress shaping (client → router, i.e. upload for client) via IFB ---
|
||||
ip link add ifb-guest type ifb 2>/dev/null
|
||||
ip link set ifb-guest up
|
||||
tc qdisc del dev $IFACE ingress 2>/dev/null
|
||||
tc qdisc add dev $IFACE ingress
|
||||
tc filter add dev $IFACE parent ffff: protocol ip u32 match u32 0 0 action mirred egress redirect dev ifb-guest
|
||||
tc filter add dev $IFACE parent ffff: protocol ipv6 u32 match u32 0 0 action mirred egress redirect dev ifb-guest
|
||||
|
||||
tc qdisc del dev ifb-guest root 2>/dev/null
|
||||
tc qdisc add dev ifb-guest root handle 1: htb default 10
|
||||
@@ -41,6 +45,9 @@ case "$1" in
|
||||
tc qdisc add dev ifb-guest parent 1:20 fq_codel
|
||||
tc filter add dev ifb-guest parent 1: protocol ip handle 0x20 fw flowid 1:20
|
||||
|
||||
# Exempt ALL IPv6 from ingress shaping
|
||||
tc filter add dev ifb-guest parent 1: protocol ipv6 prio 1 u32 match u32 0 0 flowid 1:20
|
||||
|
||||
# --- nftables: paid_clients set + mark rules ---
|
||||
nft add table inet parahub 2>/dev/null
|
||||
nft flush table inet parahub 2>/dev/null
|
||||
|
||||
Reference in New Issue
Block a user