Add phone hotspot uplink
Prefer a configured phone hotspot for outbound synchronization, preserve secure write-only credentials, and recover through home WiFi or the local Pi hotspot. Co-authored-by: Copilot <[email protected]>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
[ "$1" = "wlan0" ] || exit 0
|
||||
|
||||
case "$2" in
|
||||
up|down|connectivity-change|dhcp4-change)
|
||||
/usr/local/bin/yaris-uplink record >/dev/null 2>&1 || true
|
||||
;;
|
||||
esac
|
||||
@@ -1,19 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
# One-time install of the yaris-obd WiFi hotspot <-> home-WiFi auto-switch
|
||||
# One-time install of the yaris-obd phone/home/local-hotspot auto-switch
|
||||
# watcher on the Pi. Run this ON THE PI as the `yaris` user (needs sudo):
|
||||
# cd /home/yaris/app/yaris-obd && bash deploy/install_netwatch.sh
|
||||
#
|
||||
# Prerequisite: the "home-wifi" (home WiFi) and "Hotspot" (AP mode) NetworkManager
|
||||
# connection profiles must already exist (see PLAN.md Phase 6 for the nmcli
|
||||
# commands used to create them) with home-wifi having a higher
|
||||
# connection.autoconnect-priority than Hotspot.
|
||||
# Prerequisite: the "home-wifi" and "Hotspot" NetworkManager profiles must
|
||||
# already exist (see PLAN.md Phase 6). The optional "phone-hotspot" profile is
|
||||
# created through the Settings page.
|
||||
set -euo pipefail
|
||||
|
||||
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
sudo install -m 755 "$REPO_DIR/deploy/yaris-netwatch.sh" /usr/local/bin/yaris-netwatch.sh
|
||||
sudo systemctl stop yaris-uplink-wait.service 2>/dev/null || true
|
||||
sudo systemctl stop yaris-netwatch.timer yaris-netwatch.service 2>/dev/null || true
|
||||
sudo rm -f /usr/local/bin/yaris-netwatch.sh
|
||||
sudo install -m 755 "$REPO_DIR/deploy/yaris_uplink.py" /usr/local/bin/yaris-uplink
|
||||
sudo install -m 440 "$REPO_DIR/deploy/yaris-uplink.sudoers" /etc/sudoers.d/yaris-uplink
|
||||
sudo visudo -cf /etc/sudoers.d/yaris-uplink
|
||||
sudo install -m 644 "$REPO_DIR/deploy/yaris-hotspot-dns.conf" \
|
||||
/etc/NetworkManager/dnsmasq-shared.d/yaris-hotspot-dns.conf
|
||||
sudo install -m 755 "$REPO_DIR/deploy/90-yaris-uplink" \
|
||||
/etc/NetworkManager/dispatcher.d/90-yaris-uplink
|
||||
sudo nmcli connection modify Hotspot ipv4.method shared ipv4.addresses 10.42.0.1/24
|
||||
if nmcli connection show phone-hotspot >/dev/null 2>&1; then
|
||||
sudo nmcli connection modify phone-hotspot \
|
||||
ipv4.dhcp-send-hostname yes ipv4.dhcp-hostname yaris \
|
||||
802-11-wireless.mtu 1400
|
||||
fi
|
||||
sudo cp "$REPO_DIR/deploy/yaris-netwatch.service" /etc/systemd/system/yaris-netwatch.service
|
||||
sudo cp "$REPO_DIR/deploy/yaris-netwatch.timer" /etc/systemd/system/yaris-netwatch.timer
|
||||
sudo cp "$REPO_DIR/deploy/yaris-uplink-wait.service" /etc/systemd/system/yaris-uplink-wait.service
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now yaris-netwatch.timer
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# Unicast fallback for phones/browsers that do not resolve mDNS on a WiFi
|
||||
# network without internet. Avahi still publishes the same name over mDNS.
|
||||
address=/yaris.local/10.42.0.1
|
||||
address=/yaris/10.42.0.1
|
||||
@@ -1,11 +1,9 @@
|
||||
[Unit]
|
||||
Description=yaris-obd network watcher (hotspot -> home WiFi reconnect check)
|
||||
Description=yaris-obd phone/home/local hotspot uplink watcher
|
||||
After=NetworkManager.service
|
||||
Wants=NetworkManager.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
# Retry loop (down AP, rescan, retry connect up to 2x) can take ~80s worst
|
||||
# case - default 90s systemd start timeout was too tight a margin.
|
||||
TimeoutStartSec=180
|
||||
ExecStart=/usr/local/bin/yaris-netwatch.sh
|
||||
TimeoutStartSec=120
|
||||
ExecStart=/usr/local/bin/yaris-uplink check
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
#!/bin/bash
|
||||
# yaris-netwatch.sh
|
||||
#
|
||||
# Runs periodically via yaris-netwatch.timer. If wlan0 is currently in
|
||||
# AP/hotspot mode, briefly attempts to reconnect to the known home WiFi.
|
||||
# If that succeeds, we're back on home WiFi (used for syncing itinerary/
|
||||
# dashcam data to the remote server). If it fails (still out of range),
|
||||
# reactivates the hotspot so the web UI stays reachable locally in the car.
|
||||
#
|
||||
# The Pi's onboard WiFi radio (BCM43438) cannot act as an AP and scan for
|
||||
# client networks at the same time, so this brief switch-and-check is the
|
||||
# only way to detect "home WiFi is back in range" while sitting in AP mode.
|
||||
# The reverse direction (home WiFi -> hotspot, i.e. leaving range) is handled
|
||||
# automatically by NetworkManager's own autoconnect-priority mechanism and
|
||||
# needs no watcher: the "home-wifi" profile has a higher
|
||||
# connection.autoconnect-priority than Hotspot, so NM tries it first whenever
|
||||
# the device needs to reconnect, and falls through to Hotspot on its own once
|
||||
# home WiFi drops out of range.
|
||||
|
||||
set -u
|
||||
|
||||
LOG_TAG="yaris-netwatch"
|
||||
HOME_CON="home-wifi"
|
||||
AP_CON="Hotspot"
|
||||
IFACE="wlan0"
|
||||
LAST_LOG="/tmp/yaris-netwatch-last.log"
|
||||
|
||||
active_con() {
|
||||
nmcli -t -f DEVICE,CONNECTION device status | awk -F: -v d="$IFACE" '$1==d {print $2}'
|
||||
}
|
||||
|
||||
current="$(active_con)"
|
||||
|
||||
if [ "$current" != "$AP_CON" ]; then
|
||||
# Already on home WiFi (or something else) - nothing to do.
|
||||
logger -t "$LOG_TAG" "wlan0 is on '$current', no action needed"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
logger -t "$LOG_TAG" "wlan0 is on hotspot, checking for home WiFi ($HOME_CON)..."
|
||||
|
||||
# Real-hardware finding: a plain "nmcli con up home-wifi" straight out of AP
|
||||
# mode reliably reports "not found" even when actually in range, repeatedly
|
||||
# (confirmed: 4 failures in a row over 13min while sitting right next to the
|
||||
# router). NM's scan cache for wlan0 is stale/empty coming out of AP mode
|
||||
# (the radio literally can't scan while acting as an AP - see comment above),
|
||||
# and a single con-up attempt doesn't wait for a fresh scan to land before
|
||||
# giving up. Fix: explicitly drop the AP connection first to free the radio,
|
||||
# force a fresh scan and give it a few seconds to populate, then retry the
|
||||
# connect attempt with a longer timeout up to 2 times before conceding and
|
||||
# restoring the hotspot.
|
||||
nmcli con down "$AP_CON" >"$LAST_LOG" 2>&1
|
||||
|
||||
connected=false
|
||||
for attempt in 1 2; do
|
||||
nmcli device wifi rescan ifname "$IFACE" >>"$LAST_LOG" 2>&1
|
||||
sleep 5
|
||||
if nmcli --wait 30 con up "$HOME_CON" >>"$LAST_LOG" 2>&1; then
|
||||
connected=true
|
||||
break
|
||||
fi
|
||||
logger -t "$LOG_TAG" "home WiFi attempt $attempt failed, retrying..."
|
||||
done
|
||||
|
||||
if [ "$connected" = true ]; then
|
||||
logger -t "$LOG_TAG" "home WiFi found, switched to '$HOME_CON'"
|
||||
else
|
||||
logger -t "$LOG_TAG" "home WiFi not found after retries, restoring hotspot"
|
||||
nmcli con up "$AP_CON" >>"$LAST_LOG" 2>&1
|
||||
fi
|
||||
@@ -1,5 +1,5 @@
|
||||
[Unit]
|
||||
Description=Periodically check if home WiFi is back in range while in hotspot mode
|
||||
Description=Periodically select the preferred Yaris internet uplink
|
||||
|
||||
[Timer]
|
||||
OnBootSec=1min
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
[Unit]
|
||||
Description=Wait for the configured phone hotspot
|
||||
After=NetworkManager.service
|
||||
Wants=NetworkManager.service
|
||||
Conflicts=yaris-netwatch.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
TimeoutStartSec=180
|
||||
ExecStartPre=-/usr/bin/systemctl stop yaris-netwatch.timer
|
||||
ExecStart=/usr/local/bin/yaris-uplink wait
|
||||
ExecStopPost=/usr/bin/systemctl start yaris-netwatch.timer
|
||||
@@ -0,0 +1,3 @@
|
||||
yaris ALL=(root) NOPASSWD: /usr/local/bin/yaris-uplink configure
|
||||
yaris ALL=(root) NOPASSWD: /usr/local/bin/yaris-uplink remove
|
||||
yaris ALL=(root) NOPASSWD: /usr/bin/systemctl start --no-block yaris-uplink-wait.service
|
||||
@@ -0,0 +1,451 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Manage the Pi's phone-hotspot, home-WiFi, and local-AP priority."""
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import uuid
|
||||
|
||||
|
||||
PHONE_CONNECTION = "phone-hotspot"
|
||||
HOME_CONNECTION = "home-wifi"
|
||||
AP_CONNECTION = "Hotspot"
|
||||
INTERFACE = "wlan0"
|
||||
STATUS_PATH = "/var/lib/yaris-obd/uplink-status.json"
|
||||
PHONE_KEYFILE = (
|
||||
"/etc/NetworkManager/system-connections/"
|
||||
"phone-hotspot.nmconnection"
|
||||
)
|
||||
|
||||
|
||||
class UplinkError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def run_nmcli(*args, timeout=45, check=True):
|
||||
process = subprocess.run(
|
||||
["nmcli", *args],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout,
|
||||
)
|
||||
if check and process.returncode != 0:
|
||||
detail = (process.stderr or process.stdout).strip()
|
||||
raise UplinkError(detail or f"nmcli exited {process.returncode}")
|
||||
return process
|
||||
|
||||
|
||||
def connection_exists(name):
|
||||
return run_nmcli(
|
||||
"-g",
|
||||
"connection.id",
|
||||
"connection",
|
||||
"show",
|
||||
name,
|
||||
check=False,
|
||||
).returncode == 0
|
||||
|
||||
|
||||
def active_connection():
|
||||
process = run_nmcli(
|
||||
"-g",
|
||||
"GENERAL.CONNECTION",
|
||||
"device",
|
||||
"show",
|
||||
INTERFACE,
|
||||
check=False,
|
||||
)
|
||||
return process.stdout.strip() if process.returncode == 0 else ""
|
||||
|
||||
|
||||
def ipv4_address():
|
||||
process = run_nmcli(
|
||||
"-g",
|
||||
"IP4.ADDRESS",
|
||||
"device",
|
||||
"show",
|
||||
INTERFACE,
|
||||
check=False,
|
||||
)
|
||||
return (
|
||||
process.stdout.strip().split("/", 1)[0]
|
||||
if process.returncode == 0
|
||||
else ""
|
||||
)
|
||||
|
||||
|
||||
def internet_reachable():
|
||||
process = run_nmcli(
|
||||
"-g",
|
||||
"GENERAL.IP4-CONNECTIVITY",
|
||||
"device",
|
||||
"show",
|
||||
INTERFACE,
|
||||
check=False,
|
||||
)
|
||||
return process.returncode == 0 and process.stdout.strip() == "4 (full)"
|
||||
|
||||
|
||||
def phone_ssid():
|
||||
process = run_nmcli(
|
||||
"-g",
|
||||
"802-11-wireless.ssid",
|
||||
"connection",
|
||||
"show",
|
||||
PHONE_CONNECTION,
|
||||
check=False,
|
||||
)
|
||||
return process.stdout.strip() if process.returncode == 0 else ""
|
||||
|
||||
|
||||
def phone_is_visible():
|
||||
ssid = phone_ssid()
|
||||
if not ssid:
|
||||
return False
|
||||
process = run_nmcli(
|
||||
"-t",
|
||||
"--escape",
|
||||
"no",
|
||||
"-f",
|
||||
"SSID",
|
||||
"device",
|
||||
"wifi",
|
||||
"list",
|
||||
"ifname",
|
||||
INTERFACE,
|
||||
check=False,
|
||||
)
|
||||
return process.returncode == 0 and ssid in process.stdout.splitlines()
|
||||
|
||||
|
||||
def activate(name, wait=30):
|
||||
if not connection_exists(name):
|
||||
return False
|
||||
process = run_nmcli(
|
||||
"--wait",
|
||||
str(wait),
|
||||
"connection",
|
||||
"up",
|
||||
name,
|
||||
timeout=wait + 5,
|
||||
check=False,
|
||||
)
|
||||
return process.returncode == 0
|
||||
|
||||
|
||||
def write_status(action, error=None):
|
||||
previous = {}
|
||||
try:
|
||||
with open(STATUS_PATH, encoding="utf-8") as handle:
|
||||
previous = json.load(handle)
|
||||
except (FileNotFoundError, OSError, json.JSONDecodeError):
|
||||
pass
|
||||
current = active_connection()
|
||||
last_phone_ip = previous.get("last_phone_ip")
|
||||
if current == PHONE_CONNECTION:
|
||||
last_phone_ip = ipv4_address() or last_phone_ip
|
||||
status = {
|
||||
"action": action,
|
||||
"active_connection": current,
|
||||
"error": error,
|
||||
"last_phone_ip": last_phone_ip,
|
||||
"updated_at": time.time(),
|
||||
}
|
||||
os.makedirs(os.path.dirname(STATUS_PATH), mode=0o755, exist_ok=True)
|
||||
temporary_path = None
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(
|
||||
"w",
|
||||
encoding="utf-8",
|
||||
dir=os.path.dirname(STATUS_PATH),
|
||||
prefix=".uplink-status-",
|
||||
delete=False,
|
||||
) as handle:
|
||||
temporary_path = handle.name
|
||||
os.fchmod(handle.fileno(), 0o644)
|
||||
json.dump(status, handle)
|
||||
handle.flush()
|
||||
os.fsync(handle.fileno())
|
||||
os.replace(temporary_path, STATUS_PATH)
|
||||
except OSError:
|
||||
if temporary_path is not None:
|
||||
try:
|
||||
os.remove(temporary_path)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
raise
|
||||
|
||||
|
||||
def restore_fallback():
|
||||
if activate(HOME_CONNECTION):
|
||||
write_status("connected_home")
|
||||
return True
|
||||
if activate(AP_CONNECTION):
|
||||
write_status("restored_local_hotspot")
|
||||
return True
|
||||
write_status("recovery_failed", "Could not activate home WiFi or local hotspot.")
|
||||
return False
|
||||
|
||||
|
||||
def check_uplink():
|
||||
current = active_connection()
|
||||
phone_configured = connection_exists(PHONE_CONNECTION)
|
||||
if current == PHONE_CONNECTION:
|
||||
if internet_reachable():
|
||||
write_status("phone_connected")
|
||||
return True
|
||||
if activate(HOME_CONNECTION):
|
||||
write_status("phone_without_internet_connected_home")
|
||||
return True
|
||||
if active_connection() == PHONE_CONNECTION or activate(PHONE_CONNECTION):
|
||||
write_status(
|
||||
"phone_connected_without_internet",
|
||||
"Phone hotspot is connected but internet is not reachable.",
|
||||
)
|
||||
return True
|
||||
return restore_fallback()
|
||||
|
||||
if phone_configured:
|
||||
if current == AP_CONNECTION:
|
||||
run_nmcli("connection", "down", AP_CONNECTION, check=False)
|
||||
run_nmcli("device", "wifi", "rescan", "ifname", INTERFACE, check=False)
|
||||
time.sleep(5)
|
||||
if not phone_is_visible():
|
||||
if current != AP_CONNECTION and active_connection():
|
||||
write_status("phone_not_in_range_current_connection_preserved")
|
||||
return True
|
||||
return restore_fallback()
|
||||
if activate(PHONE_CONNECTION):
|
||||
time.sleep(3)
|
||||
if internet_reachable():
|
||||
write_status("connected_phone")
|
||||
return True
|
||||
if activate(HOME_CONNECTION):
|
||||
write_status("phone_without_internet_connected_home")
|
||||
return True
|
||||
if active_connection() == PHONE_CONNECTION:
|
||||
write_status(
|
||||
"phone_connected_without_internet",
|
||||
"Phone hotspot is connected but internet is not reachable.",
|
||||
)
|
||||
return True
|
||||
return restore_fallback()
|
||||
if active_connection() == HOME_CONNECTION:
|
||||
write_status("phone_unavailable_home_preserved")
|
||||
return True
|
||||
return restore_fallback()
|
||||
|
||||
if current != AP_CONNECTION:
|
||||
write_status("phone_not_configured")
|
||||
return True
|
||||
|
||||
run_nmcli("connection", "down", AP_CONNECTION, check=False)
|
||||
run_nmcli("device", "wifi", "rescan", "ifname", INTERFACE, check=False)
|
||||
time.sleep(5)
|
||||
return restore_fallback()
|
||||
|
||||
|
||||
def wait_for_phone(timeout=120):
|
||||
if not connection_exists(PHONE_CONNECTION):
|
||||
raise UplinkError("No phone hotspot is configured.")
|
||||
if active_connection() == PHONE_CONNECTION:
|
||||
write_status("phone_connected")
|
||||
return True
|
||||
|
||||
original_connection = active_connection()
|
||||
write_status("waiting_for_phone")
|
||||
time.sleep(10)
|
||||
if original_connection == AP_CONNECTION:
|
||||
run_nmcli("connection", "down", AP_CONNECTION, check=False)
|
||||
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
run_nmcli("device", "wifi", "rescan", "ifname", INTERFACE, check=False)
|
||||
time.sleep(5)
|
||||
if phone_is_visible():
|
||||
return check_uplink()
|
||||
|
||||
if original_connection != AP_CONNECTION and active_connection():
|
||||
write_status(
|
||||
"phone_wait_timed_out_current_connection_preserved",
|
||||
"Phone hotspot did not appear within two minutes.",
|
||||
)
|
||||
return True
|
||||
restored = restore_fallback()
|
||||
if restored:
|
||||
write_status(
|
||||
"phone_wait_timed_out_fallback_restored",
|
||||
"Phone hotspot did not appear within two minutes.",
|
||||
)
|
||||
return restored
|
||||
|
||||
|
||||
def validate_credentials(payload):
|
||||
if not isinstance(payload, dict):
|
||||
raise UplinkError("Expected a JSON object.")
|
||||
ssid = payload.get("ssid")
|
||||
password = payload.get("password")
|
||||
if not isinstance(ssid, str) or not ssid or len(ssid.encode("utf-8")) > 32:
|
||||
raise UplinkError("SSID must be between 1 and 32 bytes.")
|
||||
if "\x00" in ssid or "\n" in ssid or "\r" in ssid:
|
||||
raise UplinkError("SSID contains unsupported characters.")
|
||||
if ";" in ssid:
|
||||
raise UplinkError("SSID cannot contain a semicolon.")
|
||||
if not isinstance(password, str) or not 8 <= len(password.encode("utf-8")) <= 63:
|
||||
raise UplinkError("Password must be between 8 and 63 bytes.")
|
||||
if "\x00" in password or "\n" in password or "\r" in password:
|
||||
raise UplinkError("Password contains unsupported characters.")
|
||||
return ssid, password
|
||||
|
||||
|
||||
def keyfile_escape(value):
|
||||
leading_spaces = len(value) - len(value.lstrip(" "))
|
||||
trailing_spaces = len(value) - len(value.rstrip(" "))
|
||||
end = len(value) - trailing_spaces if trailing_spaces else len(value)
|
||||
middle = value[leading_spaces:end].replace("\\", "\\\\")
|
||||
return "\\s" * leading_spaces + middle + "\\s" * trailing_spaces
|
||||
|
||||
|
||||
def configure():
|
||||
raw = sys.stdin.buffer.read(2049)
|
||||
if len(raw) > 2048:
|
||||
raise UplinkError("Configuration request is too large.")
|
||||
try:
|
||||
payload = json.loads(raw)
|
||||
except (UnicodeDecodeError, json.JSONDecodeError) as error:
|
||||
raise UplinkError("Invalid configuration JSON.") from error
|
||||
ssid, password = validate_credentials(payload)
|
||||
|
||||
was_active = active_connection() == PHONE_CONNECTION
|
||||
run_nmcli("connection", "delete", PHONE_CONNECTION, check=False)
|
||||
keyfile = f"""[connection]
|
||||
id={PHONE_CONNECTION}
|
||||
uuid={uuid.uuid4()}
|
||||
type=wifi
|
||||
interface-name={INTERFACE}
|
||||
autoconnect=true
|
||||
autoconnect-priority=20
|
||||
|
||||
[wifi]
|
||||
mode=infrastructure
|
||||
ssid={keyfile_escape(ssid)}
|
||||
mtu=1400
|
||||
|
||||
[wifi-security]
|
||||
key-mgmt=wpa-psk
|
||||
psk={keyfile_escape(password)}
|
||||
|
||||
[ipv4]
|
||||
method=auto
|
||||
dhcp-send-hostname=true
|
||||
dhcp-hostname=yaris
|
||||
|
||||
[ipv6]
|
||||
method=auto
|
||||
"""
|
||||
temporary_path = None
|
||||
try:
|
||||
os.makedirs(os.path.dirname(PHONE_KEYFILE), mode=0o700, exist_ok=True)
|
||||
with tempfile.NamedTemporaryFile(
|
||||
"w",
|
||||
encoding="utf-8",
|
||||
dir=os.path.dirname(PHONE_KEYFILE),
|
||||
prefix=".phone-hotspot-",
|
||||
delete=False,
|
||||
) as handle:
|
||||
temporary_path = handle.name
|
||||
os.fchmod(handle.fileno(), 0o600)
|
||||
handle.write(keyfile)
|
||||
handle.flush()
|
||||
os.fsync(handle.fileno())
|
||||
os.replace(temporary_path, PHONE_KEYFILE)
|
||||
temporary_path = None
|
||||
run_nmcli("connection", "reload")
|
||||
if not connection_exists(PHONE_CONNECTION):
|
||||
raise UplinkError("NetworkManager did not load the phone hotspot.")
|
||||
except (OSError, UplinkError, subprocess.TimeoutExpired):
|
||||
if temporary_path is not None:
|
||||
try:
|
||||
os.remove(temporary_path)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
try:
|
||||
os.remove(PHONE_KEYFILE)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
run_nmcli("connection", "reload", check=False)
|
||||
run_nmcli("connection", "delete", PHONE_CONNECTION, check=False)
|
||||
if was_active:
|
||||
try:
|
||||
restore_fallback()
|
||||
except (OSError, subprocess.TimeoutExpired, UplinkError):
|
||||
pass
|
||||
raise
|
||||
write_status("phone_configured")
|
||||
if was_active:
|
||||
check_uplink()
|
||||
|
||||
|
||||
def remove():
|
||||
was_active = active_connection() == PHONE_CONNECTION
|
||||
run_nmcli("connection", "delete", PHONE_CONNECTION, check=False)
|
||||
try:
|
||||
os.remove(PHONE_KEYFILE)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
if was_active:
|
||||
restore_fallback()
|
||||
else:
|
||||
write_status("phone_removed")
|
||||
|
||||
|
||||
def record_current_connection():
|
||||
current = active_connection()
|
||||
actions = {
|
||||
PHONE_CONNECTION: "network_phone_connected",
|
||||
HOME_CONNECTION: "network_home_connected",
|
||||
AP_CONNECTION: "network_local_hotspot_active",
|
||||
}
|
||||
write_status(actions.get(current, "network_disconnected"))
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2 or sys.argv[1] not in {
|
||||
"check",
|
||||
"configure",
|
||||
"connect",
|
||||
"record",
|
||||
"wait",
|
||||
"remove",
|
||||
}:
|
||||
print(
|
||||
"Usage: yaris-uplink {check|configure|connect|record|wait|remove}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 2
|
||||
action = sys.argv[1]
|
||||
try:
|
||||
if action == "configure":
|
||||
configure()
|
||||
elif action == "remove":
|
||||
remove()
|
||||
elif action == "wait":
|
||||
wait_for_phone()
|
||||
elif action == "record":
|
||||
record_current_connection()
|
||||
else:
|
||||
check_uplink()
|
||||
except (OSError, subprocess.TimeoutExpired, UplinkError) as error:
|
||||
try:
|
||||
write_status(f"{action}_failed", str(error))
|
||||
except OSError:
|
||||
pass
|
||||
print(str(error), file=sys.stderr)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user