Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 169 additions & 0 deletions bin/omarchy-network-onboard
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
#!/bin/bash

# omarchy:summary=Show this machine's MAC addresses and how to join a network from the terminal
# omarchy:group=network
# omarchy:args=[--macs-only]
# omarchy:examples=omarchy network onboard | omarchy network onboard --macs-only

set -euo pipefail

macs_only=0
case "${1:-}" in
"")
;;
--macs-only)
macs_only=1
;;
-h|--help)
cat <<USAGE
Usage: omarchy-network-onboard [--macs-only]

Print the MAC address of every physical network interface, then the nmcli
recipes for joining a network from the terminal.

Networks that gate access on a registered MAC have to be told the address
before the machine can associate, so the addresses come first and the join
instructions second.

--macs-only prints just the addresses, one "interface<TAB>mac" per line.
USAGE
exit 0
;;
*)
echo "Usage: omarchy-network-onboard [--macs-only]" >&2
exit 2
;;
esac

# Overridable so the interface layout can be faked in tests, following the
# root-override pattern omarchy-hyprland-reload-guard uses.
net_root="${OMARCHY_NETWORK_SYSFS_ROOT:-/sys/class/net}"

# Physical interfaces only. Everything the kernel synthesizes -- lo, docker0,
# veth pairs, bridges, wireguard -- links back under /sys/devices/virtual, and
# registering one of those addresses with a network administrator accomplishes
# nothing.
physical_interfaces() {
local path name
for path in "$net_root"/*; do
name=${path##*/}
[[ -e $path/address ]] || continue
[[ $(readlink -f "$path") == */virtual/* ]] && continue
printf '%s\n' "$name"
done
}

interface_kind() {
local name=$1
if [[ -d $net_root/$name/wireless ]]; then
printf 'Wi-Fi'
else
printf 'Ethernet'
fi
}

print_macs() {
local name
for name in $(physical_interfaces); do
printf '%s\t%s\n' "$name" "$(<"$net_root/$name/address")"
done
}

if (( macs_only )); then
print_macs
exit 0
fi

bold=$'\033[1m'
green=$'\033[32m'
dim=$'\033[2m'
reset=$'\033[0m'

heading() {
printf '\n%s%s%s\n\n' "$bold" "$1" "$reset"
}

# The first Wi-Fi interface stands in for the ifname argument in the enterprise
# recipe, so the line can be pasted as-is on the common single-radio laptop.
# Not a pipeline: breaking out of a `while read` on the reading side of one
# leaves the producer writing into a closed pipe, and pipefail turns that
# SIGPIPE into a failure that set -e takes the whole guide down with.
wifi_interface=""
for name in $(physical_interfaces); do
if [[ -d $net_root/$name/wireless ]]; then
wifi_interface=$name
break
fi
done
wifi_interface=${wifi_interface:-wlan0}

heading "1. Register this machine, if the network asks you to"

cat <<'EXPLAIN'
Campus, office, hotel and guest networks often admit only devices whose
MAC address has been registered in advance. Hand these addresses over and
wait for confirmation before trying to connect -- associating first just
drops you on a quarantine VLAN that looks like broken Wi-Fi.

EXPLAIN

found_interface=0
while read -r name; do
(( found_interface = 1 ))
printf ' %-10s %-16s %s%s%s\n' \
"$(interface_kind "$name")" "$name" "$green" "$(<"$net_root/$name/address")" "$reset"
done < <(physical_interfaces)

if (( found_interface == 0 )); then
printf ' %sNo physical network interface found.%s\n' "$dim" "$reset"
printf ' %sCheck that the adapter is not disabled in firmware.%s\n' "$dim" "$reset"
fi

printf '\n %sCopy one to the clipboard:%s omarchy network onboard --macs-only | wl-copy\n' "$dim" "$reset"

heading "2. Join a Wi-Fi network from the terminal"

cat <<EXPLAIN
nmcli radio wifi on
nmcli device wifi list
nmcli device wifi connect "SSID" --ask

${dim}--ask prompts for the password instead of taking it as an argument,
which keeps it out of your shell history and the process list.${reset}

${bold}Hidden network${reset}
nmcli device wifi connect "SSID" --ask hidden yes

${bold}WPA2/WPA3-Enterprise${reset} ${dim}(username + password; the usual pairing with MAC registration)${reset}
nmcli connection add type wifi con-name "NAME" ifname $wifi_interface ssid "SSID" \\
wifi-sec.key-mgmt wpa-eap 802-1x.eap peap 802-1x.phase2-auth mschapv2 \\
802-1x.identity "USERNAME"
nmcli connection up "NAME" --ask
EXPLAIN

heading "3. Check, reconnect, clean up"

cat <<EXPLAIN
nmcli device status ${dim}what each interface is doing${reset}
nmcli connection show ${dim}saved networks${reset}
nmcli connection up "NAME" ${dim}rejoin a saved network${reset}
nmcli connection delete "NAME" ${dim}forget one${reset}
omarchy network status ${dim}the active link, as the bar sees it${reset}

${bold}Captive portal${reset} ${dim}(hotel, airport: you associate, but nothing loads)${reset}
omarchy launch browser http://neverssl.com
EXPLAIN

echo

# The toast that opens this guide carries a single click action --
# omarchy-notification-send sends an empty D-Bus actions array and takes one
# --exec -- so the choice between staying here and using the picker belongs at
# the end of the guide, once the address above has been read and handed over.
# Only when a terminal is actually attached: piped or redirected runs (the
# --macs-only path, a script) have nobody to answer.
if : 2>/dev/null <>/dev/tty; then
if gum confirm --default=false "Open the Wi-Fi panel instead?"; then
omarchy-shell shell toggle omarchy.network
fi
fi
1 change: 1 addition & 0 deletions default/omarchy/omarchy-menu.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@
"setup.network.dns.google": {"icon":"󰊭","label":"Google","checked":"[[ \"$(omarchy-dns)\" == \"Google\" ]]","action":"omarchy-dns Google"},
"setup.network.dns.custom": {"icon":"","label":"Custom","checked":"[[ \"$(omarchy-dns)\" == \"Custom\" ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-dns Custom'"},
"setup.network.qr": {"icon":"󰐲","label":"QR Code","aliases":["wifi-qr"],"when":"[[ $(omarchy-network-status) == wifi* ]]","action":"omarchy-shell shell summon omarchy.wifiqr"},
"setup.network.onboard": {"icon":"󰈀","label":"Connection Guide","action":"omarchy-launch-floating-terminal-with-presentation omarchy-network-onboard"},
"setup.default": {"icon":"","label":"Defaults","aliases":["default","defaults"]},
"setup.default.agent": {"icon":"󰚩","label":"Agent","title":"Default Agent"},
"setup.default.agent.agy": {"icon":"󰫢","label":"Antigravity","checked":"[[ \"$(omarchy-default-agent)\" == \"agy\" ]]","action":"omarchy-default-agent agy"},
Expand Down
3 changes: 1 addition & 2 deletions docs/file-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,8 +289,7 @@ and/or a working user systemd instance:
first login and opens the cheatsheet when clicked. The caller runs
`omarchy-notification-wait` once before this and the Wi-Fi step, so both
toasts land on a live notification server.
- `install/user/first-run/wifi.sh` — Wi-Fi/update toasts (waits detached on
`nm-online` so the update prompt only lands once there is a connection).
- `install/user/first-run/wifi.sh` — network/update prompts (waits detached on `nm-online` so the update prompt only lands once there is a connection). When the session comes up with no link it opens `omarchy-network-onboard` in a floating terminal and leaves a toast that reopens it, rather than the network panel: a network that gates access on a registered MAC has to be handed that address before it can be joined at all.

The entire sequence has one idempotency marker:
`~/.local/state/omarchy/done/first-run-user`, managed by `omarchy-done`.
Expand Down
20 changes: 16 additions & 4 deletions install/user/first-run/wifi.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@ notify_update() {
--exec omarchy-launch-floating-terminal-with-presentation omarchy-update
}

notify_wifi() {
omarchy-notification-send -u critical -g 󰖩 "Setup Wi-Fi" "Click to configure the wireless network." \
--exec omarchy-shell shell toggle omarchy.network
# Backgrounded: the launcher hands off through uwsm-app and does not
# necessarily return until the terminal closes, and the update prompt below
# must not wait on the user reading this.
show_network_onboarding() {
omarchy-launch-floating-terminal-with-presentation omarchy-network-onboard &
}

notify_network() {
omarchy-notification-send -u critical -g 󰖩 "Set Up Network" "Click for this machine's MAC address and the terminal instructions." \
--exec omarchy-launch-floating-terminal-with-presentation omarchy-network-onboard
}

announce_network() {
Expand All @@ -18,7 +25,12 @@ announce_network() {
# -x takes that answer as it stands rather than waiting out the timeout, so
# a laptop with nothing to connect to gets prompted immediately.
if ! nm-online -q -x -t 30; then
notify_wifi
# Open the guide rather than the network panel. A network that gates on a
# registered MAC cannot be joined from the picker at all: the address has
# to be handed over and cleared first, and the picker has nowhere to show
# it. The toast stays as the way back in once the terminal is closed.
show_network_onboarding
notify_network
# Nothing to update against until a link lands, so hold that prompt.
nm-online -q -t 3600 || return
fi
Expand Down
6 changes: 6 additions & 0 deletions manual/35-networking.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ Networking in Omarchy is handled by NetworkManager, and you drive it from the ne

That panel scans for Wi-Fi networks, shows signal strength, and connects. Ethernet needs nothing at all — plug it in and it works. If you'd rather stay in the terminal, `nmtui` gives you the same controls, and there's an `omarchy network` command group too.

## Networks that have to know you first

Campus, office, and some guest networks only admit devices whose MAC address has been registered with whoever runs the network. There is nothing to pick in the Wi-Fi panel until that is done, so the first login on a machine with no link opens a terminal guide instead of the panel. You can bring it back any time from _Setup > Network > Connection Guide_, or with `omarchy network onboard`.

It prints the MAC address of every physical interface — `omarchy network onboard --macs-only | wl-copy` puts one on the clipboard to paste into a registration form — followed by the `nmcli` recipes for joining from the terminal, including hidden networks and the WPA2-Enterprise username-and-password case that usually accompanies MAC registration. It ends by offering the Wi-Fi panel, so nothing here traps you in the terminal if you would rather point and click; `Super + Ctrl + W` opens that panel at any time regardless.

## Sharing your Wi-Fi

Rather than reading a long password out loud, run _Setup > Network > QR Code_ while you're on Wi-Fi. That puts a QR code on screen that any phone camera can scan to join. It's one of those things you'll use more than you'd expect once you know it's there.
Expand Down
110 changes: 110 additions & 0 deletions test/shell.d/network-onboard-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#!/bin/bash

set -euo pipefail

source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"

tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT

# A stand-in for /sys/class/net. Physical interfaces are plain directories;
# kernel-synthesized ones resolve through a "virtual" path the way sysfs links
# lo, docker0, and veth pairs back to /sys/devices/virtual/net.
net_root="$tmp/net"
devices="$tmp/devices"
mkdir -p "$net_root" "$devices/virtual/net/docker0" "$devices/pci/net"

mkdir -p "$net_root/wlan0/wireless"
echo "aa:bb:cc:dd:ee:ff" >"$net_root/wlan0/address"

mkdir -p "$net_root/eth0"
echo "11:22:33:44:55:66" >"$net_root/eth0/address"

echo "de:ad:be:ef:00:01" >"$devices/virtual/net/docker0/address"
ln -s "$devices/virtual/net/docker0" "$net_root/docker0"

# No address file at all: sysfs exposes such entries, and reading one would
# print an empty MAC rather than skipping the interface.
mkdir -p "$net_root/bond0"

run_onboard() {
OMARCHY_NETWORK_SYSFS_ROOT="$net_root" "$ROOT/bin/omarchy-network-onboard" "$@"
}

macs=$(run_onboard --macs-only)

[[ $macs == *$'wlan0\taa:bb:cc:dd:ee:ff'* ]] ||
fail "onboard lists physical wireless interfaces" "missing wlan0 in:\n$macs"
pass "onboard lists physical wireless interfaces"

[[ $macs == *$'eth0\t11:22:33:44:55:66'* ]] ||
fail "onboard lists physical wired interfaces" "missing eth0 in:\n$macs"
pass "onboard lists physical wired interfaces"

# The whole point of the command is an address a network administrator will
# accept. A bridge MAC registered against a port grants nothing.
[[ $macs != *docker0* ]] ||
fail "onboard omits kernel-synthesized interfaces" "docker0 leaked into:\n$macs"
pass "onboard omits kernel-synthesized interfaces"

[[ $macs != *bond0* ]] ||
fail "onboard omits interfaces with no address" "bond0 leaked into:\n$macs"
pass "onboard omits interfaces with no address"

guide=$(run_onboard)

[[ $guide == *"aa:bb:cc:dd:ee:ff"* ]] ||
fail "guide prints the address before the join instructions" "address missing from guide"
pass "guide prints the address before the join instructions"

# The enterprise recipe is meant to be pasted, so it has to name a real radio
# rather than a placeholder when one is present.
[[ $guide == *"ifname wlan0"* ]] ||
fail "guide names the detected Wi-Fi interface" "expected 'ifname wlan0' in the enterprise recipe"
pass "guide names the detected Wi-Fi interface"

[[ $guide == *"Wi-Fi"*"wlan0"* && $guide == *"Ethernet"*"eth0"* ]] ||
fail "guide labels each interface by kind" "kind labels missing"
pass "guide labels each interface by kind"

# Breaking out of a `while read` on the reading side of a pipeline leaves the
# producer writing into a closed pipe, and pipefail turns that SIGPIPE (141)
# into a failure. The break only reaches the producer once the pipe is full, so
# the listing has to exceed the 64KB buffer: padded names get there in a few
# hundred entries, where realistic ones would need thousands.
many="$tmp/many"
mkdir -p "$many"
pad=$(printf 'x%.0s' {1..200})
for i in $(seq 1 400); do
mkdir -p "$many/wlan$i$pad/wireless"
echo "aa:bb:cc:00:00:01" >"$many/wlan$i$pad/address"
done
# The full guide, not --macs-only: the interface the enterprise recipe names is
# resolved after the --macs-only path has already returned.
set +e
timeout 60 setsid env OMARCHY_NETWORK_SYSFS_ROOT="$many" \
"$ROOT/bin/omarchy-network-onboard" >/dev/null 2>&1
status=$?
set -e
(( status == 0 )) ||
fail "onboard survives many interfaces without SIGPIPE" "exited $status"
pass "onboard survives many interfaces without SIGPIPE"

# first-run opens this guide with no terminal attached; a prompt that blocks
# there would hang the login sequence.
set +e
timeout 20 setsid env OMARCHY_NETWORK_SYSFS_ROOT="$net_root" \
"$ROOT/bin/omarchy-network-onboard" >/dev/null 2>&1
status=$?
set -e
(( status == 0 )) ||
fail "guide completes with no controlling terminal" "exited $status"
pass "guide completes with no controlling terminal"

set +e
run_onboard --nonsense >/dev/null 2>&1
status=$?
set -e
(( status == 2 )) ||
fail "onboard rejects unknown options" "expected exit 2, got $status"
pass "onboard rejects unknown options"