Compare commits

...

5 Commits

Author SHA1 Message Date
5c0984b97d feat: Rename guest SSID from Parahub_Free to parahub.io/free
SSID as call-to-action URL — guests see where to go directly in WiFi list.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 18:07:51 +00:00
29070e95ef fix(vpn): Wait for WiFi interface before adding guest route to table 100
Guest WiFi interface (phy0-ap0) may not be up when init script runs at
boot. Move guest route addition to a background retry loop (up to 60s).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 17:21:38 +00:00
3771521d59 fix(vpn): Replace UCI grev6 with manual GRE6 tunnel init script
OpenWrt 25.x lacks the netifd grev6 protocol handler, so the UCI
vpn_tunnel interface never came up (NO_DEVICE). Now using a dedicated
init script (parahub-vpn-tunnel, START=96) that:

- Creates ip6gre tunnel with encaplimit none (critical: Yggdrasil
  drops IPv6 packets with DSTOPT extension headers from encaplimit)
- Waits for Yggdrasil address before creating tunnel
- Adds guest subnet direct route to table 100 (fixes DNS/reply
  routing — without it, router replies with source in guest subnet
  get routed through GRE instead of back to the client)
- Reloads firewall so vpn_tunnel zone picks up gre6-vpn device

Also updated VPS mesh-gateway.sh with encaplimit none.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 17:02:49 +00:00
e84d578ce4 fix(firmware): Three critical bugs found during MT3000 testing
1. Add yggdrasil init script (procd) — OpenWrt 25.x package doesn't
   include one, causing uci-defaults to fail on `/etc/init.d/yggdrasil
   enable` with set -e, leaving script in /etc/uci-defaults/ to re-run
   every boot and overwrite all config changes.

2. Delete default br-lan/lan before creating br-private — both bridges
   competing for eth0/eth1 port, preventing LAN cable access.

3. Per-device port mapping via /etc/parahub/port_map — filogic devices
   (MT3000, MT6000, WR3000) have eth0=WAN, eth1=LAN (opposite of
   qualcommax/ath79). Build.sh writes PORT_MAP, uci-defaults reads it.

Also: remove `set -e` from uci-defaults (too fragile for first-boot),
add SSH/HTTP firewall rules on yggdrasil zone for remote management.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 15:06:47 +00:00
44e6e616cf refactor: Remove AX6S (Xiaomi Redmi AX6S) target
Incomplete sysupgrade (.itb format), not published on download page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:18:28 +00:00
5 changed files with 180 additions and 32 deletions

View File

@@ -0,0 +1,79 @@
#!/bin/sh /etc/rc.common
# Parahub VPN Tunnel — Creates GRE6 tunnel over Yggdrasil for guest traffic
# Runs after yggdrasil (START=95). OpenWrt 25.x lacks the netifd grev6 protocol
# handler, so we create the tunnel manually with ip6gre.
#
# IMPORTANT: encaplimit must be "none" — Yggdrasil drops IPv6 packets with
# Destination Options extension headers (added by default encaplimit 4).
START=96
STOP=10
VPS_YGG="200:39f1:6a26:328a:d901:fbd2:d30d:faef"
GRE_LOCAL_IP="172.16.0.2"
GRE_GATEWAY="172.16.0.1"
start() {
# Only for Bumblebee role
[ "$(cat /etc/parahub/role 2>/dev/null)" = "bumblebee" ] || return 0
# Wait for Yggdrasil address (up to 60s)
local ygg_addr="" attempts=0
while [ $attempts -lt 30 ]; do
ygg_addr=$(ip -6 addr show dev ygg0 scope global 2>/dev/null | awk '/inet6/{print $2}' | cut -d/ -f1 | head -1)
[ -n "$ygg_addr" ] && break
attempts=$((attempts + 1))
sleep 2
done
if [ -z "$ygg_addr" ]; then
logger -t parahub-vpn "No Yggdrasil address after 60s, skipping GRE6 tunnel"
return 1
fi
# Create GRE6 tunnel (encaplimit none — critical for Yggdrasil compatibility)
ip -6 tunnel add gre6-vpn mode ip6gre \
remote "$VPS_YGG" \
local "$ygg_addr" \
encaplimit none
ip addr add ${GRE_LOCAL_IP}/24 dev gre6-vpn
ip link set gre6-vpn mtu 1400 up
# Default route through GRE (table 100 — used by guest policy routing)
ip route add default via "$GRE_GATEWAY" table 100
# Guest subnet direct route in table 100 (background — WiFi may not be up yet)
# Without this, router's own replies (source IP in guest subnet) go through
# GRE instead of back to the guest client (ip rule matches source-based)
local guest_subnet
guest_subnet=$(awk -F= '/GUEST_SUBNET/{print $2}' /etc/parahub/keys 2>/dev/null)
if [ -n "$guest_subnet" ]; then
# WiFi interfaces take time to come up at boot — retry in background
(
local guest_dev="" ga=0
while [ $ga -lt 30 ]; do
guest_dev=$(ip route | grep "^${guest_subnet} " | awk '{print $3}')
[ -n "$guest_dev" ] && break
ga=$((ga + 1))
sleep 2
done
if [ -n "$guest_dev" ]; then
ip route add "$guest_subnet" dev "$guest_dev" table 100 2>/dev/null
logger -t parahub-vpn "Guest route: $guest_subnet via $guest_dev in table 100"
else
logger -t parahub-vpn "Warning: guest device not found for $guest_subnet after 60s"
fi
) &
fi
# Reload firewall so vpn_tunnel zone picks up gre6-vpn device
/etc/init.d/firewall reload 2>/dev/null &
logger -t parahub-vpn "GRE6 tunnel up: ${GRE_LOCAL_IP} → ${VPS_YGG} via $ygg_addr (encaplimit none)"
}
stop() {
ip route flush table 100 2>/dev/null
ip -6 tunnel del gre6-vpn 2>/dev/null
logger -t parahub-vpn "GRE6 tunnel down"
}

17
files/etc/init.d/yggdrasil Executable file
View File

@@ -0,0 +1,17 @@
#!/bin/sh /etc/rc.common
START=95
STOP=10
USE_PROCD=1
CONF_FILE="/etc/yggdrasil.conf"
start_service() {
[ -f "$CONF_FILE" ] || return 1
procd_open_instance
procd_set_param command /usr/sbin/yggdrasil -useconffile "$CONF_FILE"
procd_set_param respawn
procd_set_param stdout 1
procd_set_param stderr 1
procd_close_instance
}

View File

@@ -7,8 +7,6 @@
# bumblebee (L3 Gateway) — full stack: yggdrasil, GRE6, VPN, guest isolation, SQM, DoH # bumblebee (L3 Gateway) — full stack: yggdrasil, GRE6, VPN, guest isolation, SQM, DoH
# bee (L2 Transport) — minimal: batman-adv mesh relay, heartbeat # bee (L2 Transport) — minimal: batman-adv mesh relay, heartbeat
set -e
# Read firmware role (written by build.sh) # Read firmware role (written by build.sh)
ROLE=$(cat /etc/parahub/role 2>/dev/null || echo "bumblebee") ROLE=$(cat /etc/parahub/role 2>/dev/null || echo "bumblebee")
@@ -25,7 +23,7 @@ NODE_SUFFIX=$(echo "$BASE_MAC" | awk -F: '{print toupper($5$6)}')
HOSTNAME="Parahub-${NODE_SUFFIX}" HOSTNAME="Parahub-${NODE_SUFFIX}"
PRIVATE_SSID="Parahub_${NODE_SUFFIX}" PRIVATE_SSID="Parahub_${NODE_SUFFIX}"
MESH_ID="parahub-mesh" MESH_ID="parahub-mesh"
PUBLIC_SSID="Parahub_Free" PUBLIC_SSID="parahub.io/free"
# ============================================================================ # ============================================================================
# 2. SUBNET GENERATION (collision avoidance from MAC octets) # 2. SUBNET GENERATION (collision avoidance from MAC octets)
@@ -77,6 +75,40 @@ chmod 600 /etc/parahub/keys
# 4. NETWORK CONFIGURATION # 4. NETWORK CONFIGURATION
# ============================================================================ # ============================================================================
# --- Port mapping (written by build.sh per device) ---
PORT_MAP=$(cat /etc/parahub/port_map 2>/dev/null || echo "eth0:lan eth1:wan")
if [ "$PORT_MAP" = "dsa" ]; then
# DSA switch (ax53u) — individual ports
LAN_PORTS="lan1 lan2 lan3 lan4"
WAN_DEV="wan"
else
# Parse port map: "eth0:wan eth1:lan" → extract LAN and WAN
LAN_PORTS=""
WAN_DEV=""
for mapping in $PORT_MAP; do
port="${mapping%%:*}"
role="${mapping##*:}"
if [ "$role" = "lan" ]; then
LAN_PORTS="$port"
elif [ "$role" = "wan" ]; then
WAN_DEV="$port"
fi
done
[ -z "$LAN_PORTS" ] && LAN_PORTS="eth0"
[ -z "$WAN_DEV" ] && WAN_DEV="eth1"
fi
# Delete default OpenWrt br-lan/lan (conflicts with our br-private)
uci -q delete network.lan 2>/dev/null || true
# Find and delete br-lan device
for i in 0 1 2 3 4; do
name=$(uci -q get "network.@device[$i].name" 2>/dev/null)
if [ "$name" = "br-lan" ]; then
uci delete "network.@device[$i]"
break
fi
done
if [ "$ROLE" = "bee" ]; then if [ "$ROLE" = "bee" ]; then
# --- Bee: minimal network (bat0 gw_mode=client, no GRE6, no guest, no policy routing) --- # --- Bee: minimal network (bat0 gw_mode=client, no GRE6, no guest, no policy routing) ---
uci batch <<-NET_EOF uci batch <<-NET_EOF
@@ -114,7 +146,6 @@ set network.private_dev.type='bridge'
set network.private_dev.name='br-private' set network.private_dev.name='br-private'
delete network.private_dev.ports delete network.private_dev.ports
add_list network.private_dev.ports='bat0' add_list network.private_dev.ports='bat0'
add_list network.private_dev.ports='eth0'
set network.private=interface set network.private=interface
set network.private.device='br-private' set network.private.device='br-private'
@@ -124,10 +155,15 @@ set network.private.netmask='255.255.255.0'
# --- wan (DHCP, create if missing) --- # --- wan (DHCP, create if missing) ---
set network.wan=interface set network.wan=interface
set network.wan.device='eth1' set network.wan.device='${WAN_DEV}'
set network.wan.proto='dhcp' set network.wan.proto='dhcp'
NET_EOF NET_EOF
# Add LAN ports to private bridge (DSA: lan1-4, non-DSA: eth0)
for port in $LAN_PORTS; do
uci add_list network.private_dev.ports="$port"
done
else else
# --- Bumblebee: full network (gw_mode=server, GRE6, guest, policy routing) --- # --- Bumblebee: full network (gw_mode=server, GRE6, guest, policy routing) ---
uci batch <<-NET_EOF uci batch <<-NET_EOF
@@ -165,7 +201,6 @@ set network.private_dev.type='bridge'
set network.private_dev.name='br-private' set network.private_dev.name='br-private'
delete network.private_dev.ports delete network.private_dev.ports
add_list network.private_dev.ports='bat0' add_list network.private_dev.ports='bat0'
add_list network.private_dev.ports='eth0'
set network.private=interface set network.private=interface
set network.private.device='br-private' set network.private.device='br-private'
@@ -181,18 +216,13 @@ set network.guest.netmask='255.255.255.0'
# --- wan (DHCP, create if missing) --- # --- wan (DHCP, create if missing) ---
set network.wan=interface set network.wan=interface
set network.wan.device='eth1' set network.wan.device='${WAN_DEV}'
set network.wan.proto='dhcp' set network.wan.proto='dhcp'
# --- GRE6 tunnel (guest traffic → VPS gateway via Yggdrasil) --- # --- VPN tunnel interface (device created by parahub-vpn-tunnel init script) ---
set network.vpn_tunnel=interface set network.vpn_tunnel=interface
set network.vpn_tunnel.proto='grev6' set network.vpn_tunnel.device='gre6-vpn'
set network.vpn_tunnel.peeraddr='200:39f1:6a26:328a:d901:fbd2:d30d:faef' set network.vpn_tunnel.proto='none'
set network.vpn_tunnel.ipaddr='172.16.0.2'
set network.vpn_tunnel.netmask='255.255.255.0'
set network.vpn_tunnel.gateway='172.16.0.1'
set network.vpn_tunnel.mtu='1400'
set network.vpn_tunnel.ip4table='100'
# --- Policy routing: guest traffic → VPN table 100 --- # --- Policy routing: guest traffic → VPN table 100 ---
add network rule add network rule
@@ -200,6 +230,11 @@ set network.@rule[-1].src='${GUEST_SUBNET}/24'
set network.@rule[-1].lookup='100' set network.@rule[-1].lookup='100'
set network.@rule[-1].priority='100' set network.@rule[-1].priority='100'
NET_EOF NET_EOF
# Add LAN ports to private bridge (DSA: lan1-4, non-DSA: eth0)
for port in $LAN_PORTS; do
uci add_list network.private_dev.ports="$port"
done
fi fi
uci commit network uci commit network
@@ -250,7 +285,7 @@ for radio in $RADIOS; do
uci -q delete "wireless.${radio}.disabled" 2>/dev/null || true uci -q delete "wireless.${radio}.disabled" 2>/dev/null || true
done done
# --- 2.4GHz radio: mesh backhaul + Parahub_Free AP --- # --- 2.4GHz radio: mesh backhaul + parahub.io/free AP ---
if [ -n "$RADIO_2G" ]; then if [ -n "$RADIO_2G" ]; then
# Mesh interface on 2.4GHz # Mesh interface on 2.4GHz
uci batch <<-WIFI_2G_MESH uci batch <<-WIFI_2G_MESH
@@ -265,25 +300,23 @@ set wireless.mesh_2g.network='bat0_hardif_mesh0'
WIFI_2G_MESH WIFI_2G_MESH
if [ "$ROLE" = "bee" ]; then if [ "$ROLE" = "bee" ]; then
# Bee: Parahub_Free on private bridge (no guest isolation) # Bee: parahub.io/free on private bridge (no guest isolation)
uci batch <<-WIFI_2G_PUB uci batch <<-WIFI_2G_PUB
set wireless.public_2g=wifi-iface set wireless.public_2g=wifi-iface
set wireless.public_2g.device='${RADIO_2G}' set wireless.public_2g.device='${RADIO_2G}'
set wireless.public_2g.mode='ap' set wireless.public_2g.mode='ap'
set wireless.public_2g.ssid='${PUBLIC_SSID}' set wireless.public_2g.ssid='${PUBLIC_SSID}'
set wireless.public_2g.encryption='owe' set wireless.public_2g.encryption='none'
set wireless.public_2g.owe_transition='1'
set wireless.public_2g.network='private' set wireless.public_2g.network='private'
WIFI_2G_PUB WIFI_2G_PUB
else else
# Bumblebee: Parahub_Free on guest network (isolated) # Bumblebee: parahub.io/free on guest network (isolated)
uci batch <<-WIFI_2G_PUB uci batch <<-WIFI_2G_PUB
set wireless.public_2g=wifi-iface set wireless.public_2g=wifi-iface
set wireless.public_2g.device='${RADIO_2G}' set wireless.public_2g.device='${RADIO_2G}'
set wireless.public_2g.mode='ap' set wireless.public_2g.mode='ap'
set wireless.public_2g.ssid='${PUBLIC_SSID}' set wireless.public_2g.ssid='${PUBLIC_SSID}'
set wireless.public_2g.encryption='owe' set wireless.public_2g.encryption='none'
set wireless.public_2g.owe_transition='1'
set wireless.public_2g.isolate='1' set wireless.public_2g.isolate='1'
set wireless.public_2g.network='guest' set wireless.public_2g.network='guest'
WIFI_2G_PUB WIFI_2G_PUB
@@ -625,6 +658,22 @@ set firewall.@zone[-1].output='ACCEPT'
set firewall.@zone[-1].forward='REJECT' set firewall.@zone[-1].forward='REJECT'
add_list firewall.@zone[-1].network='yggdrasil' add_list firewall.@zone[-1].network='yggdrasil'
# Allow SSH on Yggdrasil (remote management)
add firewall rule
set firewall.@rule[-1].name='Allow SSH Ygg'
set firewall.@rule[-1].src='yggdrasil'
set firewall.@rule[-1].proto='tcp'
set firewall.@rule[-1].dest_port='22'
set firewall.@rule[-1].target='ACCEPT'
# Allow HTTP on Yggdrasil (LuCI)
add firewall rule
set firewall.@rule[-1].name='Allow HTTP Ygg'
set firewall.@rule[-1].src='yggdrasil'
set firewall.@rule[-1].proto='tcp'
set firewall.@rule[-1].dest_port='80'
set firewall.@rule[-1].target='ACCEPT'
# Allow GRE6 protocol input (tunnel endpoint) # Allow GRE6 protocol input (tunnel endpoint)
add firewall rule add firewall rule
set firewall.@rule[-1].name='Allow GRE6 input' set firewall.@rule[-1].name='Allow GRE6 input'
@@ -656,8 +705,9 @@ set firewall.@forwarding[-1].dest='yggdrasil'
YGG_FW_EOF YGG_FW_EOF
uci commit firewall uci commit firewall
# Enable yggdrasil service # Enable yggdrasil service + VPN tunnel (creates GRE6 after yggdrasil starts)
/etc/init.d/yggdrasil enable /etc/init.d/yggdrasil enable 2>/dev/null || true
/etc/init.d/parahub-vpn-tunnel enable 2>/dev/null || true
# Save yggdrasil address to node keys file # Save yggdrasil address to node keys file
YGG_ADDR=$(yggdrasil -address -useconffile /etc/yggdrasil.conf 2>/dev/null || echo "unknown") YGG_ADDR=$(yggdrasil -address -useconffile /etc/yggdrasil.conf 2>/dev/null || echo "unknown")
@@ -720,7 +770,7 @@ logger -t parahub-mesh "Role: ${ROLE}"
logger -t parahub-mesh "Private: ${PRIVATE_SSID} @ ${PRIV_IP}/24" logger -t parahub-mesh "Private: ${PRIVATE_SSID} @ ${PRIV_IP}/24"
if [ "$ROLE" = "bee" ]; then if [ "$ROLE" = "bee" ]; then
logger -t parahub-mesh "Parahub_Free: bridged to private (no guest isolation)" logger -t parahub-mesh "parahub.io/free: bridged to private (no guest isolation)"
logger -t parahub-mesh "bat0 gw_mode: client (uses Bumblebee as gateway)" logger -t parahub-mesh "bat0 gw_mode: client (uses Bumblebee as gateway)"
else else
logger -t parahub-mesh "Guest: ${PUBLIC_SSID} @ ${GUEST_IP}/24" logger -t parahub-mesh "Guest: ${PUBLIC_SSID} @ ${GUEST_IP}/24"

View File

@@ -196,7 +196,7 @@ ACCT_EOF
echo "" echo ""
echo "Done! Guest traffic now routes directly through Mullvad." echo "Done! Guest traffic now routes directly through Mullvad."
echo "Server: $SERVER_HOST ($COUNTRY)" echo "Server: $SERVER_HOST ($COUNTRY)"
echo "Test: connect to Parahub_Free, visit https://am.i.mullvad.net" echo "Test: connect to parahub.io/free, visit https://am.i.mullvad.net"
} }
# ============================================================================ # ============================================================================

View File

@@ -23,41 +23,43 @@ device_config() {
OPENWRT_TARGET="qualcommax/ipq60xx" OPENWRT_TARGET="qualcommax/ipq60xx"
PROFILE="glinet_gl-axt1800" PROFILE="glinet_gl-axt1800"
FIRMWARE_ROLE="bumblebee" FIRMWARE_ROLE="bumblebee"
PORT_MAP="eth0:lan eth1:wan"
;; ;;
mt3000) mt3000)
OPENWRT_TARGET="mediatek/filogic" OPENWRT_TARGET="mediatek/filogic"
PROFILE="glinet_gl-mt3000" PROFILE="glinet_gl-mt3000"
FIRMWARE_ROLE="bumblebee" FIRMWARE_ROLE="bumblebee"
PORT_MAP="eth0:wan eth1:lan"
;; ;;
mt6000) mt6000)
OPENWRT_TARGET="mediatek/filogic" OPENWRT_TARGET="mediatek/filogic"
PROFILE="glinet_gl-mt6000" PROFILE="glinet_gl-mt6000"
FIRMWARE_ROLE="bumblebee" FIRMWARE_ROLE="bumblebee"
;; PORT_MAP="eth0:wan eth1:lan"
ax6s)
OPENWRT_TARGET="mediatek/mt7622"
PROFILE="xiaomi_redmi-router-ax6s"
FIRMWARE_ROLE="bumblebee"
;; ;;
ax53u) ax53u)
OPENWRT_TARGET="ramips/mt7621" OPENWRT_TARGET="ramips/mt7621"
PROFILE="asus_rt-ax53u" PROFILE="asus_rt-ax53u"
FIRMWARE_ROLE="bumblebee" FIRMWARE_ROLE="bumblebee"
PORT_MAP="dsa"
;; ;;
ar300m16) ar300m16)
OPENWRT_TARGET="ath79/generic" OPENWRT_TARGET="ath79/generic"
PROFILE="glinet_gl-ar300m16" PROFILE="glinet_gl-ar300m16"
FIRMWARE_ROLE="bee" FIRMWARE_ROLE="bee"
PORT_MAP="eth0:lan eth1:wan"
;; ;;
wr3000) wr3000)
OPENWRT_TARGET="mediatek/filogic" OPENWRT_TARGET="mediatek/filogic"
PROFILE="cudy_wr3000-v1" PROFILE="cudy_wr3000-v1"
FIRMWARE_ROLE="bee" FIRMWARE_ROLE="bee"
PORT_MAP="eth0:wan eth1:lan"
;; ;;
cpe710) cpe710)
OPENWRT_TARGET="ath79/generic" OPENWRT_TARGET="ath79/generic"
PROFILE="tplink_cpe710-v1" PROFILE="tplink_cpe710-v1"
FIRMWARE_ROLE="bee" FIRMWARE_ROLE="bee"
PORT_MAP="eth0:lan eth1:wan"
;; ;;
*) *)
return 1 return 1
@@ -112,7 +114,6 @@ usage() {
echo " axt1800 GL.iNet GL-AXT1800 (Slate AX) qualcommax/ipq60xx Bumblebee" echo " axt1800 GL.iNet GL-AXT1800 (Slate AX) qualcommax/ipq60xx Bumblebee"
echo " mt3000 GL.iNet GL-MT3000 (Beryl AX) mediatek/filogic Bumblebee" echo " mt3000 GL.iNet GL-MT3000 (Beryl AX) mediatek/filogic Bumblebee"
echo " mt6000 GL.iNet GL-MT6000 (Flint 2) mediatek/filogic Bumblebee" echo " mt6000 GL.iNet GL-MT6000 (Flint 2) mediatek/filogic Bumblebee"
echo " ax6s Xiaomi Redmi AX6S mediatek/mt7622 Bumblebee"
echo " ax53u Asus RT-AX53U ramips/mt7621 Bumblebee" echo " ax53u Asus RT-AX53U ramips/mt7621 Bumblebee"
echo " ar300m16 GL.iNet GL-AR300M16-EXT (16MB) ath79/generic Bee" echo " ar300m16 GL.iNet GL-AR300M16-EXT (16MB) ath79/generic Bee"
echo " wr3000 Cudy AX3000 (WR3000) mediatek/filogic Bee" echo " wr3000 Cudy AX3000 (WR3000) mediatek/filogic Bee"
@@ -175,6 +176,7 @@ build_firmware() {
echo "$FIRMWARE_ROLE" > "$tmpfiles/etc/parahub/role" echo "$FIRMWARE_ROLE" > "$tmpfiles/etc/parahub/role"
echo "$OPENWRT_VERSION" > "$tmpfiles/etc/parahub/version" echo "$OPENWRT_VERSION" > "$tmpfiles/etc/parahub/version"
echo "$PROFILE" > "$tmpfiles/etc/parahub/profile" echo "$PROFILE" > "$tmpfiles/etc/parahub/profile"
echo "$PORT_MAP" > "$tmpfiles/etc/parahub/port_map"
echo "Building firmware for profile: ${PROFILE}" echo "Building firmware for profile: ${PROFILE}"
echo "Role: ${FIRMWARE_ROLE}" echo "Role: ${FIRMWARE_ROLE}"