feat: add MAC address provisioning for secondary ethernet port

- Added step_set_eth1_mac() to generate and configure random locally administered MAC addresses
- Made --name argument optional and conditional based on selected provisioning steps
- Updated documentation to include MAC configuration in production deployment checklist
This commit is contained in:
2025-11-11 14:23:57 +01:00
parent da411e1ee6
commit d343ec2583
2 changed files with 65 additions and 8 deletions

View File

@@ -50,4 +50,5 @@ For production, the devices need to be provisoned uniquely
- set channel name etc. in bumble-auracast/src/auracast/.env
- execute the update service scripts
- start the application (script if custom device, server and frontend if ui version)
- set mac add of secondary eth port in /etc/systemd/network/10-eth1-mac.link
- activate overlayfs (?) -probably not because we need persistent storage for stream states

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env python3
import ipaddress, os, re, subprocess, tempfile, json, datetime, shlex
import secrets
from pathlib import Path
from dotenv import load_dotenv
@@ -101,6 +102,48 @@ def step_wireguard_provision(iot_host: str, client_name: str):
scp_and_enable(iot_host, cfg)
return {"wg_name": name, "wg_iface": iface}
def step_set_eth1_mac(iot_host: str):
"""Generate a random locally administered MAC (02:xx:xx:xx:xx:xx) for eth1 and
configure it via systemd .link on the remote device. Prints that a reboot is required.
Returns a dict with the chosen mac.
"""
mac = "02:" + ":".join(f"{secrets.randbelow(256):02x}" for _ in range(5))
remote = (
"set -e\n"
"sudo mkdir -p /etc/systemd/network\n"
"sudo tee /etc/systemd/network/10-eth1-mac.link >/dev/null <<'EOF'\n"
"[Match]\n"
"Driver=smsc95xx\n"
"\n"
"[Link]\n"
f"MACAddress={mac}\n"
"EOF\n"
)
ssh_cmd = ["ssh", "-p", str(SSH_PORT)]
if SSH_KEY:
ssh_cmd += ["-i", SSH_KEY]
ssh_cmd += [f"{SSH_USER}@{iot_host}", remote]
proc = subprocess.run(ssh_cmd, check=False, capture_output=True, text=True)
stdout = (proc.stdout or "").strip()
stderr = (proc.stderr or "").strip()
if proc.returncode != 0:
print(f"❌ set eth1 mac: failed rc={proc.returncode}: {stderr}")
else:
print(f"✅ set eth1 mac: configured {mac} at /etc/systemd/network/10-eth1-mac.link")
print(" Reboot is required for the MAC change to take effect.")
return {
"rc": proc.returncode,
"mac": mac,
"out": stdout[-500:],
"err": stderr[-500:],
}
def step_set_hostname(iot_host: str, hostname: str | None):
"""Set hostname on the device by running the project's provision script over SSH.
@@ -326,11 +369,11 @@ def main():
)
)
ap.add_argument("iot_host", help="Local/LAN IP or hostname of the IoT device reachable via SSH")
ap.add_argument("-n", "--name", required=True, help="Device name to use for both hostname and WireGuard client")
ap.add_argument("-n", "--name", required=False, help="Device name to use for both hostname and WireGuard client")
ap.add_argument(
"--steps",
nargs="+",
choices=["pull", "wg", "hostname", "update_app", "start_app", "finish", "all"],
choices=["pull", "wg", "hostname", "mac", "update_app", "start_app", "finish", "all"],
default=["all"],
help="Which steps to run. Default: all",
)
@@ -343,15 +386,20 @@ def main():
# Validate wg-easy env
get_env_auth()
# Derive device name: if numeric, prefix with 'summitwave-beacon'
name = args.name
if re.fullmatch(r"\d+", name):
name = f"summitwave-beacon{name}"
# Normalize steps
steps = args.steps
if "all" in steps:
steps = ["pull", "hostname", "wg", "update_app", "start_app", "finish"]
steps = ["pull", "hostname", "mac", "wg", "update_app", "start_app", "finish"]
# Validate required args per step
name = args.name
if any(s in steps for s in ("wg", "hostname")) and not name:
print("error: --name is required for steps: wg, hostname")
raise SystemExit(2)
# Derive device name: if numeric, prefix with 'summitwave-beacon'
if name and re.fullmatch(r"\d+", name):
name = f"summitwave-beacon{name}"
# Gather device facts once (may change after hostname step, but we at least log the initial state)
facts = get_device_facts(args.iot_host)
@@ -382,6 +430,14 @@ def main():
**wg_info,
})
if "mac" in steps:
mac_info = step_set_eth1_mac(args.iot_host)
write_provision_log({
"action": "mac",
**get_device_facts(args.iot_host),
**mac_info,
})
if "update_app" in steps:
upd_info = step_update_app(args.iot_host)
write_provision_log({