Store zone brightness in model/data flow, apply it in the zones UI, and record updated led-driver, led-simulator, and led-tool submodule pointers. Co-authored-by: Cursor <cursoragent@cursor.com>
254 lines
6.8 KiB
Bash
Executable File
254 lines
6.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# Configure Raspberry Pi OS: Wi-Fi client on IF_WAN (default wlan0), Ethernet IF_LAN
|
||
# (default eth0) toward an external AP. Static LAN IP, DHCP via dnsmasq, NAT masquerade.
|
||
#
|
||
# Usage:
|
||
# sudo ./pi-eth-lan-router.sh install
|
||
# sudo ./pi-eth-lan-router.sh remove
|
||
#
|
||
# Environment overrides (optional):
|
||
# IF_WAN=wlan0 IF_LAN=eth0 LAN_IP=192.168.4.1 LAN_PREFIX=24 \
|
||
# DHCP_START=192.168.4.100 DHCP_END=192.168.4.200 \
|
||
# DNSMASQ_DNS=1.1.1.1,8.8.8.8 \
|
||
# sudo ./pi-eth-lan-router.sh install
|
||
|
||
set -euo pipefail
|
||
|
||
IF_WAN="${IF_WAN:-wlan0}"
|
||
IF_LAN="${IF_LAN:-eth0}"
|
||
LAN_IP="${LAN_IP:-192.168.4.1}"
|
||
LAN_PREFIX="${LAN_PREFIX:-24}"
|
||
DHCP_START="${DHCP_START:-192.168.4.100}"
|
||
DHCP_END="${DHCP_END:-192.168.4.200}"
|
||
# Comma-separated DNS for DHCP clients (Pi does not need to run a resolver).
|
||
DNSMASQ_DNS="${DNSMASQ_DNS:-1.1.1.1,8.8.8.8}"
|
||
|
||
NM_CON_NAME="pi-eth-lan-router"
|
||
MARK_BEGIN="# BEGIN pi-eth-lan-router (scripts/pi-eth-lan-router.sh)"
|
||
MARK_END="# END pi-eth-lan-router"
|
||
SYSCTL_FILE="/etc/sysctl.d/99-pi-eth-lan-router.conf"
|
||
DNSMASQ_SNIPPET="/etc/dnsmasq.d/pi-eth-lan-router.conf"
|
||
NFT_SNIPPET="/etc/nftables.d/50-pi-eth-lan-router.nft"
|
||
NFT_INCLUDE='include "/etc/nftables.d/50-pi-eth-lan-router.nft"'
|
||
NFTABLES_CONF="/etc/nftables.conf"
|
||
DHCPCD_CONF="/etc/dhcpcd.conf"
|
||
|
||
die() { echo "error: $*" >&2; exit 1; }
|
||
log() { echo "$*"; }
|
||
|
||
need_root() {
|
||
[[ "${EUID:-0}" -eq 0 ]] || die "run as root (sudo)"
|
||
}
|
||
|
||
have_cmd() { command -v "$1" >/dev/null 2>&1; }
|
||
|
||
apt_install() {
|
||
export DEBIAN_FRONTEND=noninteractive
|
||
apt-get update -qq
|
||
apt-get install -y -qq dnsmasq nftables
|
||
}
|
||
|
||
write_sysctl() {
|
||
cat >"$SYSCTL_FILE" <<EOF
|
||
# Managed by scripts/pi-eth-lan-router.sh
|
||
net.ipv4.ip_forward=1
|
||
EOF
|
||
sysctl --system -q 2>/dev/null || sysctl -p "$SYSCTL_FILE" || true
|
||
}
|
||
|
||
remove_sysctl() {
|
||
rm -f "$SYSCTL_FILE"
|
||
sysctl --system -q 2>/dev/null || true
|
||
}
|
||
|
||
write_dnsmasq() {
|
||
local mask="255.255.255.0"
|
||
if [[ "$LAN_PREFIX" != "24" ]]; then
|
||
die "only LAN_PREFIX=24 is supported by this script (extend dnsmasq netmask manually)"
|
||
fi
|
||
cat >"$DNSMASQ_SNIPPET" <<EOF
|
||
# Managed by scripts/pi-eth-lan-router.sh
|
||
interface=$IF_LAN
|
||
bind-interfaces
|
||
dhcp-range=$DHCP_START,$DHCP_END,$mask,24h
|
||
dhcp-option=option:router,$LAN_IP
|
||
dhcp-option=option:dns-server,$DNSMASQ_DNS
|
||
EOF
|
||
}
|
||
|
||
remove_dnsmasq() {
|
||
rm -f "$DNSMASQ_SNIPPET"
|
||
}
|
||
|
||
write_nft() {
|
||
mkdir -p /etc/nftables.d
|
||
cat >"$NFT_SNIPPET" <<EOF
|
||
# Managed by scripts/pi-eth-lan-router.sh
|
||
table ip pi_eth_wlan_nat {
|
||
chain postrouting {
|
||
type nat hook postrouting priority 100; policy accept;
|
||
oifname "$IF_WAN" masquerade
|
||
}
|
||
}
|
||
EOF
|
||
if [[ -f "$NFTABLES_CONF" ]] && ! grep -qF '50-pi-eth-lan-router.nft' "$NFTABLES_CONF" 2>/dev/null; then
|
||
printf '\n# pi-eth-lan-router\n%s\n' "$NFT_INCLUDE" >>"$NFTABLES_CONF"
|
||
elif [[ ! -f "$NFTABLES_CONF" ]]; then
|
||
log "warning: $NFTABLES_CONF missing; NAT was not added for boot persistence. Install/configure nftables, or add: $NFT_INCLUDE"
|
||
fi
|
||
}
|
||
|
||
remove_nft() {
|
||
rm -f "$NFT_SNIPPET"
|
||
if [[ -f "$NFTABLES_CONF" ]]; then
|
||
sed -i '/# pi-eth-lan-router/d;/50-pi-eth-lan-router\.nft/d' "$NFTABLES_CONF" || true
|
||
fi
|
||
nft delete table ip pi_eth_wlan_nat 2>/dev/null || true
|
||
}
|
||
|
||
apply_nft() {
|
||
if have_cmd nft; then
|
||
nft delete table ip pi_eth_wlan_nat 2>/dev/null || true
|
||
nft -f "$NFT_SNIPPET"
|
||
fi
|
||
}
|
||
|
||
configure_nm_eth() {
|
||
have_cmd nmcli || return 1
|
||
systemctl is-active --quiet NetworkManager 2>/dev/null || return 1
|
||
|
||
if nmcli -t -f NAME con show --active 2>/dev/null | grep -qxF "$NM_CON_NAME"; then
|
||
nmcli con down "$NM_CON_NAME" || true
|
||
fi
|
||
if nmcli -t -f NAME con show 2>/dev/null | grep -qxF "$NM_CON_NAME"; then
|
||
nmcli con mod "$NM_CON_NAME" \
|
||
connection.interface-name "$IF_LAN" \
|
||
ipv4.method manual \
|
||
ipv4.addresses "${LAN_IP}/${LAN_PREFIX}" \
|
||
ipv4.gateway "" \
|
||
ipv4.dns "" \
|
||
ipv4.never-default yes \
|
||
ipv6.method ignore
|
||
else
|
||
nmcli con add type ethernet con-name "$NM_CON_NAME" ifname "$IF_LAN" \
|
||
ipv4.method manual \
|
||
ipv4.addresses "${LAN_IP}/${LAN_PREFIX}" \
|
||
ipv4.gateway "" \
|
||
ipv4.dns "" \
|
||
ipv4.never-default yes \
|
||
ipv6.method ignore
|
||
fi
|
||
if ! nmcli con up "$NM_CON_NAME"; then
|
||
log "warning: could not activate '$NM_CON_NAME' (is $IF_LAN connected?); profile saved for next boot."
|
||
fi
|
||
return 0
|
||
}
|
||
|
||
remove_nm_eth() {
|
||
have_cmd nmcli || return 0
|
||
if nmcli -t -f NAME con show 2>/dev/null | grep -qxF "$NM_CON_NAME"; then
|
||
nmcli con delete "$NM_CON_NAME" || true
|
||
fi
|
||
}
|
||
|
||
configure_dhcpcd_eth() {
|
||
[[ -f "$DHCPCD_CONF" ]] || return 1
|
||
if grep -qF "$MARK_BEGIN" "$DHCPCD_CONF" 2>/dev/null; then
|
||
sed -i "/$MARK_BEGIN/,/$MARK_END/d" "$DHCPCD_CONF" || true
|
||
fi
|
||
{
|
||
echo "$MARK_BEGIN"
|
||
echo "interface $IF_LAN"
|
||
echo "static ip_address=${LAN_IP}/${LAN_PREFIX}"
|
||
echo "nohook wpa_supplicant"
|
||
echo "$MARK_END"
|
||
} >>"$DHCPCD_CONF"
|
||
systemctl restart dhcpcd 2>/dev/null || true
|
||
return 0
|
||
}
|
||
|
||
remove_dhcpcd_block() {
|
||
[[ -f "$DHCPCD_CONF" ]] || return 0
|
||
if grep -qF "$MARK_BEGIN" "$DHCPCD_CONF" 2>/dev/null; then
|
||
sed -i "/$MARK_BEGIN/,/$MARK_END/d" "$DHCPCD_CONF" || true
|
||
systemctl restart dhcpcd 2>/dev/null || true
|
||
fi
|
||
}
|
||
|
||
configure_eth_static() {
|
||
if configure_nm_eth; then
|
||
log "configured $IF_LAN via NetworkManager profile '$NM_CON_NAME'"
|
||
return 0
|
||
fi
|
||
if configure_dhcpcd_eth; then
|
||
log "configured $IF_LAN via dhcpcd ($DHCPCD_CONF)"
|
||
return 0
|
||
fi
|
||
die "neither NetworkManager (active) nor $DHCPCD_CONF found; set $IF_LAN to ${LAN_IP}/${LAN_PREFIX} manually"
|
||
}
|
||
|
||
remove_eth_static() {
|
||
remove_nm_eth
|
||
remove_dhcpcd_block
|
||
}
|
||
|
||
do_install() {
|
||
need_root
|
||
log "installing packages (dnsmasq, nftables)…"
|
||
apt_install
|
||
|
||
log "writing sysctl, dnsmasq, nftables snippets…"
|
||
write_sysctl
|
||
write_dnsmasq
|
||
write_nft
|
||
|
||
log "setting static IP on $IF_LAN…"
|
||
configure_eth_static
|
||
|
||
log "restarting dnsmasq…"
|
||
systemctl enable dnsmasq
|
||
systemctl restart dnsmasq
|
||
|
||
log "loading NAT rules and enabling nftables…"
|
||
apply_nft
|
||
systemctl enable nftables 2>/dev/null || true
|
||
systemctl restart nftables 2>/dev/null || true
|
||
|
||
log "done. Connect $IF_LAN to the external AP (DHCP off on the AP)."
|
||
log "Join Wi-Fi on $IF_WAN to the uplink network and complete any captive portal on the Pi."
|
||
}
|
||
|
||
do_remove() {
|
||
need_root
|
||
remove_eth_static
|
||
remove_dnsmasq
|
||
systemctl restart dnsmasq 2>/dev/null || true
|
||
|
||
remove_nft
|
||
systemctl restart nftables 2>/dev/null || true
|
||
|
||
remove_sysctl
|
||
sysctl -w net.ipv4.ip_forward=0 2>/dev/null || true
|
||
|
||
log "removed pi-eth-lan-router configuration snippets and NM profile '$NM_CON_NAME' (if present)."
|
||
}
|
||
|
||
usage() {
|
||
cat <<EOF
|
||
Usage: sudo $0 install|remove
|
||
|
||
WAN (Wi-Fi client): $IF_WAN
|
||
LAN (Ethernet to AP): $IF_LAN
|
||
LAN address: ${LAN_IP}/${LAN_PREFIX}
|
||
DHCP range: $DHCP_START – $DHCP_END
|
||
|
||
Override with environment variables (see script header).
|
||
EOF
|
||
}
|
||
|
||
case "${1:-}" in
|
||
install) do_install ;;
|
||
remove) do_remove ;;
|
||
*) usage; exit 1 ;;
|
||
esac
|