diff --git a/src/provision.py b/src/provision.py index 57056db..cb393b7 100644 --- a/src/provision.py +++ b/src/provision.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -import ipaddress, os, re, subprocess, tempfile, json, datetime +import ipaddress, os, re, subprocess, tempfile, json, datetime, shlex from pathlib import Path from dotenv import load_dotenv @@ -111,13 +111,66 @@ def step_wireguard_provision(iot_host: str, client_name: str): return {"wg_name": name, "wg_iface": iface} def step_set_hostname(iot_host: str, hostname: str | None): - """Placeholder: set hostname on the device via custom script under /home/caster/bumble-auracast/src/server. + """Set hostname on the device by running the project's provision script over SSH. - Intention: SSH into device and run a script, e.g. /home/caster/bumble-auracast/src/server/set-hostname . - Currently does nothing. + Executes: /home/caster/bumble-auracast/src/auracast/server/provision_domain_hostname.sh + Domain is always 'local'. Returns a dict including whether the hostname appears changed. """ - print("⏭️ [placeholder] Skipping hostname change (no-op). Intended hostname:", hostname) - return {"hostname": hostname} + if not hostname: + print("⏭️ hostname: no hostname provided, skipping") + return {"hostname": None, "changed": False} + + # Known locations where the script might live on the device + candidates = [ + "/home/caster/bumble-auracast/src/auracast/server/provision_domain_hostname.sh", + "/home/caster/bumble-auracast/src/server/provision_domain_hostname.sh", + ] + domain = "local" + + # Build remote command. Use sudo since hostname changes typically require elevated privileges. + quoted_name = shlex.quote(hostname) + quoted_domain = shlex.quote(domain) + # Build a remote snippet that picks the first existing candidate and runs it via bash + # Using bash avoids executable-bit/shebang issues. + remote_candidates = " ".join(shlex.quote(c) for c in candidates) + remote = ( + "set -e\n" + f"for p in {remote_candidates}; do\n" + " if [ -f \"$p\" ]; then sp=\"$p\"; break; fi\n" + "done\n" + "if [ -z \"${sp:-}\" ]; then echo 'script not found in any candidate path' >&2; exit 1; fi\n" + f"sudo bash \"$sp\" {quoted_name} {quoted_domain}\n" + "hostname 2>/dev/null || true\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() + + # After running the script, re-check the hostname from the device + facts_post = get_device_facts(iot_host) + new_hostname = facts_post.get("hostname") + + changed = bool(new_hostname) and (new_hostname == hostname) + if proc.returncode != 0: + print(f"❌ hostname: remote script failed rc={proc.returncode}: {stderr}") + else: + print(f"✅ hostname: script executed. device hostname now '{new_hostname}' (rc={proc.returncode})") + + return { + "hostname": hostname, + "domain": domain, + "device_hostname": new_hostname, + "changed": changed, + "rc": proc.returncode, + "out": stdout[-500:], # tail to keep logs compact + "err": stderr[-500:], + } def step_update_app(iot_host: str, services: list[str] | None = None): """Placeholder: start/enable required system services on the device.