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
|
- set channel name etc. in bumble-auracast/src/auracast/.env
|
||||||
- execute the update service scripts
|
- execute the update service scripts
|
||||||
- start the application (script if custom device, server and frontend if ui version)
|
- 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
|
- activate overlayfs (?) -probably not because we need persistent storage for stream states
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import ipaddress, os, re, subprocess, tempfile, json, datetime, shlex
|
import ipaddress, os, re, subprocess, tempfile, json, datetime, shlex
|
||||||
|
import secrets
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
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)
|
scp_and_enable(iot_host, cfg)
|
||||||
return {"wg_name": name, "wg_iface": iface}
|
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):
|
def step_set_hostname(iot_host: str, hostname: str | None):
|
||||||
"""Set hostname on the device by running the project's provision script over SSH.
|
"""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("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(
|
ap.add_argument(
|
||||||
"--steps",
|
"--steps",
|
||||||
nargs="+",
|
nargs="+",
|
||||||
choices=["pull", "wg", "hostname", "update_app", "start_app", "finish", "all"],
|
choices=["pull", "wg", "hostname", "mac", "update_app", "start_app", "finish", "all"],
|
||||||
default=["all"],
|
default=["all"],
|
||||||
help="Which steps to run. Default: all",
|
help="Which steps to run. Default: all",
|
||||||
)
|
)
|
||||||
@@ -343,15 +386,20 @@ def main():
|
|||||||
# Validate wg-easy env
|
# Validate wg-easy env
|
||||||
get_env_auth()
|
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
|
# Normalize steps
|
||||||
steps = args.steps
|
steps = args.steps
|
||||||
if "all" in 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)
|
# Gather device facts once (may change after hostname step, but we at least log the initial state)
|
||||||
facts = get_device_facts(args.iot_host)
|
facts = get_device_facts(args.iot_host)
|
||||||
@@ -382,6 +430,14 @@ def main():
|
|||||||
**wg_info,
|
**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:
|
if "update_app" in steps:
|
||||||
upd_info = step_update_app(args.iot_host)
|
upd_info = step_update_app(args.iot_host)
|
||||||
write_provision_log({
|
write_provision_log({
|
||||||
|
|||||||
Reference in New Issue
Block a user