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:
@@ -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
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user