#!/usr/bin/env bash # # omatether — iPhone USB tethering for Omarchy / Arch (systemd-networkd + iwd) # # Plug an iPhone in over USB and use its internet connection (the phone's # Wi-Fi, or cellular if it isn't on Wi-Fi). Works headless — no GUI needed. # # Usage: omatether.sh # set -euo pipefail NETFILE="/etc/systemd/network/10-iphone-tether.network" # Route metrics: Omarchy ships ethernet=100, wifi=600, wwan=700. METRIC_FALLBACK=750 # tether used only when nothing better is up (default) METRIC_PREFERRED=50 # tether beats ethernet/wifi while plugged in RED=$'\e[31m'; GRN=$'\e[32m'; YLW=$'\e[33m'; RST=$'\e[0m' say() { printf '%s\n' "$*"; } ok() { printf '%s\n' "${GRN}✓${RST} $*"; } warn() { printf '%s\n' "${YLW}!${RST} $*"; } die() { printf '%s\n' "${RED}✗${RST} $*" >&2; exit 1; } as_root() { if [[ $EUID -eq 0 ]]; then "$@"; else sudo "$@"; fi } # Find the network interface backed by the ipheth driver, if any. tether_iface() { local dev drv for dev in /sys/class/net/*; do drv=$(readlink -f "$dev/device/driver" 2>/dev/null) || continue [[ ${drv##*/} == ipheth ]] && { basename "$dev"; return 0; } done return 1 } write_netfile() { local metric=$1 as_root tee "$NETFILE" >/dev/null </dev/null || true say "" say "Next: unlock the iPhone, then run: $0 pair" } cmd_pair() { command -v idevicepair >/dev/null || die "libimobiledevice not installed — run: $0 install" idevice_id -l 2>/dev/null | grep -q . || die "No iPhone detected over USB. Plug it in and try again." say "Pairing — unlock the iPhone and tap ${GRN}Trust${RST} when prompted…" local out if out=$(idevicepair pair 2>&1); then ok "$out" else case $out in *passcode*) die "Phone is locked. Unlock it, then re-run: $0 pair" ;; *user\ denied*|*denied\ the\ trust*) die "Trust was declined on the phone. Re-run and tap Trust." ;; *Please\ accept*) warn "Trust dialog is showing on the phone — tap Trust, then re-run: $0 pair"; exit 1 ;; *) die "$out" ;; esac fi say "" say "Now enable the hotspot on the phone:" say " Settings → Personal Hotspot → Allow Others to Join: ON" say "The connection comes up automatically. Check with: $0 status" } cmd_unpair() { idevicepair unpair && ok "Unpaired." } cmd_status() { local iface udid name if udid=$(idevice_id -l 2>/dev/null | head -1) && [[ -n $udid ]]; then name=$(ideviceinfo -k DeviceName 2>/dev/null || echo "unknown") ok "iPhone connected over USB: $name" if idevicepair validate >/dev/null 2>&1; then ok "Paired (trusted)" else warn "Not paired — run: $0 pair" fi else warn "No iPhone detected over USB" fi if iface=$(tether_iface); then local state addr state=$(networkctl status "$iface" 2>/dev/null | awk '/State:/{print $2; exit}') ok "Tether interface: $iface ($state)" addr=$(ip -4 -br addr show "$iface" | awk '{print $3}') if [[ -n $addr ]]; then ok "IPv4: $addr" ip route show default dev "$iface" | sed 's/^/ /' if curl -sf --interface "$iface" --max-time 5 -o /dev/null https://www.google.com; then ok "Internet via $iface works" else warn "No internet through $iface yet (is Personal Hotspot on?)" fi else warn "No IP yet — enable Personal Hotspot on the phone (Allow Others to Join)" fi else warn "No tether interface — phone unplugged, untrusted, or hotspot off" fi } cmd_priority() { [[ -f $NETFILE ]] || die "Not installed — run: $0 install" case ${1:-} in high) write_netfile "$METRIC_PREFERRED" reload_networkd ok "Tether now PREFERRED (metric $METRIC_PREFERRED) — traffic routes via the phone when plugged in" ;; low) write_netfile "$METRIC_FALLBACK" reload_networkd ok "Tether now FALLBACK (metric $METRIC_FALLBACK) — ethernet/wifi win when available" ;; *) local cur cur=$(awk -F= '/^RouteMetric/{print $2; exit}' "$NETFILE") say "Current metric: $cur (ethernet=100, wifi=600)" say "Usage: $0 priority " ;; esac } cmd_uninstall() { as_root rm -f "$NETFILE" as_root networkctl reload ok "Removed $NETFILE (usbmuxd/libimobiledevice left installed)" } case ${1:-help} in install) cmd_install ;; pair) cmd_pair ;; unpair) cmd_unpair ;; status) cmd_status ;; priority) shift; cmd_priority "${1:-}" ;; uninstall) cmd_uninstall ;; help|-h|--help) sed -n '2,10p' "$0" | sed 's/^# \{0,1\}//' ;; *) die "Unknown command: $1 (try: $0 help)" ;; esac