chore: init scripts
bd014d0a
23 file(s) · +1008 −0
| 1 | + | #!/bin/sh |
|
| 2 | + | # Requires: wireplumber (wpctl), gum |
|
| 3 | + | # One menu over the vol-* / audio-output scripts. |
|
| 4 | + | set -u |
|
| 5 | + | ||
| 6 | + | DIR=$(dirname "$0") |
|
| 7 | + | ||
| 8 | + | CHOICE=$(gum choose \ |
|
| 9 | + | "Volume up" \ |
|
| 10 | + | "Volume down" \ |
|
| 11 | + | "Set volume" \ |
|
| 12 | + | "Toggle mute" \ |
|
| 13 | + | "Toggle mic mute" \ |
|
| 14 | + | "Select output" \ |
|
| 15 | + | "Show status" \ |
|
| 16 | + | "Quit") || exit 0 |
|
| 17 | + | ||
| 18 | + | case "$CHOICE" in |
|
| 19 | + | "Volume up") "$DIR/vol-up" ;; |
|
| 20 | + | "Volume down") "$DIR/vol-down" ;; |
|
| 21 | + | "Set volume") |
|
| 22 | + | PCT=$(gum input --placeholder "0-100" --prompt "Volume % ") || exit 0 |
|
| 23 | + | case "$PCT" in |
|
| 24 | + | ""|*[!0-9]*) gum style --foreground 214 "Not a number: $PCT"; exit 1 ;; |
|
| 25 | + | esac |
|
| 26 | + | [ "$PCT" -gt 100 ] && PCT=100 |
|
| 27 | + | wpctl set-volume @DEFAULT_AUDIO_SINK@ "${PCT}%" && "$DIR/vol-status" -n |
|
| 28 | + | ;; |
|
| 29 | + | "Toggle mute") "$DIR/vol-mute" ;; |
|
| 30 | + | "Toggle mic mute") "$DIR/vol-mute" -s ;; |
|
| 31 | + | "Select output") "$DIR/audio-output" ;; |
|
| 32 | + | "Show status") |
|
| 33 | + | gum style --border normal --padding "1 2" --border-foreground 212 \ |
|
| 34 | + | "Volume: $("$DIR/vol-status")" \ |
|
| 35 | + | "$("$DIR/audio-output" --list)" |
|
| 36 | + | ;; |
|
| 37 | + | *) exit 0 ;; |
|
| 38 | + | esac |
| 1 | + | #!/bin/sh |
|
| 2 | + | # Requires: wireplumber (wpctl), gum |
|
| 3 | + | # Pick which sink audio plays out of. |
|
| 4 | + | # audio-output interactive picker |
|
| 5 | + | # audio-output --list print sinks (* marks the current default) |
|
| 6 | + | # audio-output --auto clear the pin, let wireplumber pick by priority |
|
| 7 | + | # audio-output ID|NAME select by wpctl id, or by a substring of the name |
|
| 8 | + | set -u |
|
| 9 | + | ||
| 10 | + | # wpctl has no machine-readable listing, so carve the Sinks block out of `status`. |
|
| 11 | + | # Output: "<*|space>\t<id>\t<name>" |
|
| 12 | + | sinks() { |
|
| 13 | + | wpctl status 2>/dev/null | awk ' |
|
| 14 | + | /^Audio/ { audio = 1; next } |
|
| 15 | + | /^[A-Z]/ { audio = 0 } |
|
| 16 | + | audio && /Sinks:/ && !/endpoints:/ { insec = 1; next } |
|
| 17 | + | audio && /Devices:|Sources:|Streams:|endpoints:|Filters:/ { insec = 0; next } |
|
| 18 | + | insec { |
|
| 19 | + | line = $0 |
|
| 20 | + | prefix = line; sub(/[0-9].*$/, "", prefix) |
|
| 21 | + | def = (prefix ~ /\*/) ? "*" : " " |
|
| 22 | + | sub(/^[^0-9]*/, "", line) |
|
| 23 | + | if (line !~ /^[0-9]+\./) next |
|
| 24 | + | id = line; sub(/\..*$/, "", id) |
|
| 25 | + | name = line; sub(/^[0-9]+\.[[:space:]]*/, "", name) |
|
| 26 | + | sub(/[[:space:]]*\[vol:.*$/, "", name) |
|
| 27 | + | sub(/[[:space:]]+$/, "", name) |
|
| 28 | + | print def "\t" id "\t" name |
|
| 29 | + | } |
|
| 30 | + | ' |
|
| 31 | + | } |
|
| 32 | + | ||
| 33 | + | name_of() { |
|
| 34 | + | printf '%s\n' "$SINKS" | awk -F'\t' -v id="$1" '$2 == id { print $3; exit }' |
|
| 35 | + | } |
|
| 36 | + | ||
| 37 | + | SINKS=$(sinks) |
|
| 38 | + | if [ -z "$SINKS" ]; then |
|
| 39 | + | echo "audio-output: no sinks found (is pipewire/wireplumber running?)" >&2 |
|
| 40 | + | exit 1 |
|
| 41 | + | fi |
|
| 42 | + | ||
| 43 | + | use_auto() { |
|
| 44 | + | wpctl clear-default || exit 1 |
|
| 45 | + | gum style --foreground 212 "Output: automatic (wireplumber picks)" |
|
| 46 | + | exit 0 |
|
| 47 | + | } |
|
| 48 | + | ||
| 49 | + | use_sink() { |
|
| 50 | + | wpctl set-default "$1" || exit 1 |
|
| 51 | + | gum style --foreground 212 "Output: $(name_of "$1")" |
|
| 52 | + | exit 0 |
|
| 53 | + | } |
|
| 54 | + | ||
| 55 | + | case "${1:-}" in |
|
| 56 | + | --list|-l) |
|
| 57 | + | printf '%s\n' "$SINKS" | awk -F'\t' '{ printf "%s %-6s %s\n", $1, $2, $3 }' |
|
| 58 | + | exit 0 |
|
| 59 | + | ;; |
|
| 60 | + | --auto|-a) use_auto ;; |
|
| 61 | + | "") ;; |
|
| 62 | + | *) |
|
| 63 | + | ID=$(printf '%s\n' "$SINKS" | awk -F'\t' -v q="$1" ' |
|
| 64 | + | $2 == q { print $2; found = 1; exit } |
|
| 65 | + | index(tolower($3), tolower(q)) { print $2; found = 1; exit }') |
|
| 66 | + | if [ -z "$ID" ]; then |
|
| 67 | + | echo "audio-output: no sink matching '$1'" >&2 |
|
| 68 | + | exit 1 |
|
| 69 | + | fi |
|
| 70 | + | use_sink "$ID" |
|
| 71 | + | ;; |
|
| 72 | + | esac |
|
| 73 | + | ||
| 74 | + | CHOICE=$(printf '%s\n' "$SINKS" \ |
|
| 75 | + | | awk -F'\t' '{ printf "%s %-6s %s\n", $1, $2, $3 }' \ |
|
| 76 | + | | { cat; echo " auto (let wireplumber choose)"; } \ |
|
| 77 | + | | gum choose --header "Select audio output") || exit 0 |
|
| 78 | + | ||
| 79 | + | [ -n "$CHOICE" ] || exit 0 |
|
| 80 | + | ||
| 81 | + | ID=$(printf '%s\n' "$CHOICE" | awk '{ print $2 }') |
|
| 82 | + | [ "$ID" = "auto" ] && use_auto |
|
| 83 | + | use_sink "$ID" |
| 1 | + | #!/bin/sh |
|
| 2 | + | # Requires: dmenu, libnotify, ifupdown-ng, openrc, doas |
|
| 3 | + | # dmenu counterpart of eth0-connect. The confirm prompt runs as the calling |
|
| 4 | + | # user; only the switch itself is handed to doas. |
|
| 5 | + | set -u |
|
| 6 | + | ||
| 7 | + | IFACE="${1:-eth0}" |
|
| 8 | + | WIFI="${2:-wlan0}" |
|
| 9 | + | NOTIFY_ID=7303 |
|
| 10 | + | ||
| 11 | + | notify() { |
|
| 12 | + | if command -v dunstify >/dev/null 2>&1; then |
|
| 13 | + | dunstify -r "$NOTIFY_ID" ${2:+-u "$2"} "Network" "$1" |
|
| 14 | + | else |
|
| 15 | + | notify-send ${2:+-u "$2"} "Network" "$1" |
|
| 16 | + | fi |
|
| 17 | + | } |
|
| 18 | + | ||
| 19 | + | fail() { notify "$1" critical; exit 1; } |
|
| 20 | + | ||
| 21 | + | doas -n true 2>/dev/null || fail "doas needs a password; add a nopass rule for this user." |
|
| 22 | + | ||
| 23 | + | [ "$(printf 'No\nYes\n' | dmenu -c -i -l 2 -p "Switch to $IFACE and disable $WIFI?")" = "Yes" ] || exit 0 |
|
| 24 | + | ||
| 25 | + | notify "Switching to wired..." |
|
| 26 | + | ||
| 27 | + | doas sh -c ' |
|
| 28 | + | ifdown "$2" 2>/dev/null |
|
| 29 | + | ip link set "$2" down 2>/dev/null |
|
| 30 | + | # Same reason as dm-wifi-disable: the supervisor respawns the daemon |
|
| 31 | + | # after a bare kill, so stop the service instead of pkill-ing it. |
|
| 32 | + | rc-service -s -q wpa_supplicant stop 2>/dev/null |
|
| 33 | + | ifup "$1" |
|
| 34 | + | ' _ "$IFACE" "$WIFI" |
|
| 35 | + | ||
| 36 | + | if ip addr show "$IFACE" | grep -q "inet "; then |
|
| 37 | + | IP=$(ip -4 addr show "$IFACE" | awk '/inet /{print $2; exit}') |
|
| 38 | + | notify "✓ $IFACE up — $IP" |
|
| 39 | + | else |
|
| 40 | + | fail "✗ $IFACE failed to come up" |
|
| 41 | + | fi |
| 1 | + | #!/bin/sh |
|
| 2 | + | # Requires: dmenu, libnotify, ifupdown-ng, doas |
|
| 3 | + | # dmenu counterpart of eth0-disable. |
|
| 4 | + | set -u |
|
| 5 | + | ||
| 6 | + | IFACE="${1:-eth0}" |
|
| 7 | + | NOTIFY_ID=7304 |
|
| 8 | + | ||
| 9 | + | notify() { |
|
| 10 | + | if command -v dunstify >/dev/null 2>&1; then |
|
| 11 | + | dunstify -r "$NOTIFY_ID" ${2:+-u "$2"} "Network" "$1" |
|
| 12 | + | else |
|
| 13 | + | notify-send ${2:+-u "$2"} "Network" "$1" |
|
| 14 | + | fi |
|
| 15 | + | } |
|
| 16 | + | ||
| 17 | + | fail() { notify "$1" critical; exit 1; } |
|
| 18 | + | ||
| 19 | + | doas -n true 2>/dev/null || fail "doas needs a password; add a nopass rule for this user." |
|
| 20 | + | ||
| 21 | + | [ "$(printf 'No\nYes\n' | dmenu -c -i -l 2 -p "Disable $IFACE?")" = "Yes" ] || exit 0 |
|
| 22 | + | ||
| 23 | + | notify "Disabling $IFACE..." |
|
| 24 | + | ||
| 25 | + | doas sh -c ' |
|
| 26 | + | ifdown "$1" 2>/dev/null |
|
| 27 | + | ip link set "$1" down |
|
| 28 | + | ' _ "$IFACE" |
|
| 29 | + | ||
| 30 | + | notify "$IFACE disabled" |
| 1 | + | #!/bin/sh |
|
| 2 | + | # Requires: dmenu, libnotify |
|
| 3 | + | # dmenu counterpart of net-menu. Everything here runs as the calling user; |
|
| 4 | + | # the individual scripts escalate with doas only for the parts that need it. |
|
| 5 | + | set -u |
|
| 6 | + | ||
| 7 | + | # Launched from a keybinding, PATH may not include this directory — resolve |
|
| 8 | + | # the sibling scripts relative to this one instead. |
|
| 9 | + | DIR=$(dirname "$0") |
|
| 10 | + | ||
| 11 | + | menu() { dmenu -c -i "$@"; } |
|
| 12 | + | ||
| 13 | + | # BusyBox's `ip` has no `-br`, so build the brief view by hand. |
|
| 14 | + | net_status() { |
|
| 15 | + | ip -4 addr show | awk ' |
|
| 16 | + | function flush() { |
|
| 17 | + | if (iface != "") printf "%-16s %-8s %s\n", iface, state, addrs |
|
| 18 | + | } |
|
| 19 | + | /^[0-9]+: / { |
|
| 20 | + | flush() |
|
| 21 | + | iface = $2; sub(/:$/, "", iface) |
|
| 22 | + | addrs = "" |
|
| 23 | + | state = "DOWN" |
|
| 24 | + | if ($0 ~ /state UP/) state = "UP" |
|
| 25 | + | else if ($0 ~ /state UNKNOWN/) state = "UNKNOWN" |
|
| 26 | + | } |
|
| 27 | + | /^[[:space:]]*inet / { addrs = addrs (addrs ? " " : "") $2 } |
|
| 28 | + | END { flush() } |
|
| 29 | + | ' |
|
| 30 | + | } |
|
| 31 | + | ||
| 32 | + | CHOICE=$(printf '%s\n' \ |
|
| 33 | + | "Connect to WiFi" \ |
|
| 34 | + | "Disable WiFi" \ |
|
| 35 | + | "Connect eth0" \ |
|
| 36 | + | "Disable eth0" \ |
|
| 37 | + | "Show status" \ |
|
| 38 | + | "Quit" | menu -l 6 -p "Network:") |
|
| 39 | + | ||
| 40 | + | case "$CHOICE" in |
|
| 41 | + | "Connect to WiFi") "$DIR/dm-wifi-connect" ;; |
|
| 42 | + | "Disable WiFi") "$DIR/dm-wifi-disable" ;; |
|
| 43 | + | "Connect eth0") "$DIR/dm-eth0-connect" ;; |
|
| 44 | + | "Disable eth0") "$DIR/dm-eth0-disable" ;; |
|
| 45 | + | "Show status") |
|
| 46 | + | # dmenu is the only display surface we have; the selection is ignored. |
|
| 47 | + | STATUS=$(net_status) |
|
| 48 | + | LINES=$(printf '%s\n' "$STATUS" | grep -c .) |
|
| 49 | + | printf '%s\n' "$STATUS" | menu -l "$LINES" -p "Status:" >/dev/null |
|
| 50 | + | ;; |
|
| 51 | + | *) exit 0 ;; |
|
| 52 | + | esac |
| 1 | + | #!/bin/sh |
|
| 2 | + | # Requires: dmenu, libnotify, iw, wpa_supplicant, udhcpc, openrc, doas |
|
| 3 | + | # dmenu counterpart of wifi-connect. All prompting happens as the calling |
|
| 4 | + | # user so dmenu keeps the X session and notifications keep the user's bus; |
|
| 5 | + | # only scanning, the config write and the bring-up are handed to doas. |
|
| 6 | + | set -u |
|
| 7 | + | ||
| 8 | + | CONF=/etc/wpa_supplicant/wpa_supplicant.conf |
|
| 9 | + | NOTIFY_ID=7301 |
|
| 10 | + | ||
| 11 | + | menu() { dmenu -c -i "$@"; } |
|
| 12 | + | ||
| 13 | + | # Typed text with no dmenu-visible input still has to be hidden, and this |
|
| 14 | + | # build has no password patch — paint the prompt's foreground onto its |
|
| 15 | + | # background so the passphrase never renders. |
|
| 16 | + | menu_secret() { |
|
| 17 | + | dmenu -c -p "$1" -nb '#000000' -nf '#000000' -sb '#000000' -sf '#000000' </dev/null |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | notify() { |
|
| 21 | + | # dunstify can replace an earlier notification in place, so the progress |
|
| 22 | + | # line becomes the result line instead of stacking up. |
|
| 23 | + | if command -v dunstify >/dev/null 2>&1; then |
|
| 24 | + | dunstify -r "$NOTIFY_ID" ${2:+-u "$2"} "WiFi" "$1" |
|
| 25 | + | else |
|
| 26 | + | notify-send ${2:+-u "$2"} "WiFi" "$1" |
|
| 27 | + | fi |
|
| 28 | + | } |
|
| 29 | + | ||
| 30 | + | fail() { notify "$1" critical; exit 1; } |
|
| 31 | + | ||
| 32 | + | # doas asks for a passphrase on a tty, and launched from a keybinding there |
|
| 33 | + | # is none — fail with something readable instead of hanging invisibly. |
|
| 34 | + | doas -n true 2>/dev/null || fail "doas needs a password; add a nopass rule for this user." |
|
| 35 | + | ||
| 36 | + | # Ask the kernel which interfaces are wireless rather than assuming wlan0. |
|
| 37 | + | IFACES=$(iw dev | awk '$1 == "Interface" { print $2 }') |
|
| 38 | + | ||
| 39 | + | case $(printf '%s' "$IFACES" | grep -c .) in |
|
| 40 | + | 0) fail "No wireless interfaces found." ;; |
|
| 41 | + | 1) IFACE=$IFACES ;; |
|
| 42 | + | *) IFACE=$(printf '%s\n' "$IFACES" | menu -l 5 -p "Interface:") ;; |
|
| 43 | + | esac |
|
| 44 | + | ||
| 45 | + | [ -n "$IFACE" ] || exit 1 |
|
| 46 | + | ||
| 47 | + | doas ip link set "$IFACE" up 2>/dev/null |
|
| 48 | + | ||
| 49 | + | notify "Scanning for networks on $IFACE..." |
|
| 50 | + | ||
| 51 | + | # `scan dump` reuses cached results if the card is busy (already associated), |
|
| 52 | + | # which is when a live scan tends to fail. |
|
| 53 | + | SCAN=$(doas iw dev "$IFACE" scan 2>/dev/null || doas iw dev "$IFACE" scan dump 2>/dev/null) |
|
| 54 | + | ||
| 55 | + | # Only real SSID lines: bare "SSID: " is a hidden network, and "SSID List" |
|
| 56 | + | # under extended capabilities is not a network at all. |
|
| 57 | + | SSIDS=$(printf '%s\n' "$SCAN" | sed -n 's/^[[:space:]]*SSID: \(..*\)$/\1/p' | sort -u) |
|
| 58 | + | ||
| 59 | + | if [ -n "$SSIDS" ]; then |
|
| 60 | + | # dmenu returns whatever was typed when nothing matches, so a hidden or |
|
| 61 | + | # out-of-range network can just be typed into the same prompt. |
|
| 62 | + | SSID=$(printf '%s\n' "$SSIDS" | menu -l 10 -p "Network:") |
|
| 63 | + | else |
|
| 64 | + | notify "No networks found on $IFACE — type one in." |
|
| 65 | + | SSID=$(menu -p "Network:" </dev/null) |
|
| 66 | + | fi |
|
| 67 | + | ||
| 68 | + | [ -n "$SSID" ] || exit 1 |
|
| 69 | + | ||
| 70 | + | PASS=$(menu_secret "Password (empty = open):") |
|
| 71 | + | ||
| 72 | + | # Build the new config before touching anything: a rejected passphrase must |
|
| 73 | + | # not blank out the config that is currently working. The PSK never lands on |
|
| 74 | + | # disk unprivileged — it goes to the root shell over a pipe. |
|
| 75 | + | if [ -z "$PASS" ]; then |
|
| 76 | + | NEW=$(printf 'network={\n\tssid="%s"\n\tkey_mgmt=NONE\n}\n' "$SSID") |
|
| 77 | + | else |
|
| 78 | + | NEW=$(wpa_passphrase "$SSID" "$PASS" 2>/dev/null | grep -v '^[[:space:]]*#psk=') || |
|
| 79 | + | fail "Rejected by wpa_passphrase — password must be 8-63 characters." |
|
| 80 | + | fi |
|
| 81 | + | ||
| 82 | + | [ -n "$NEW" ] || fail "Could not generate a config for $SSID." |
|
| 83 | + | ||
| 84 | + | printf '%s\n' "$NEW" | doas sh -c ' |
|
| 85 | + | [ -f "$1" ] && cp "$1" "$1.bak" |
|
| 86 | + | cat > "$1" |
|
| 87 | + | chmod 600 "$1" |
|
| 88 | + | ' _ "$CONF" || fail "Could not write $CONF." |
|
| 89 | + | ||
| 90 | + | notify "Connecting to $SSID..." |
|
| 91 | + | ||
| 92 | + | doas sh -c ' |
|
| 93 | + | # wpa_supplicant is a supervised OpenRC service on this host and will not |
|
| 94 | + | # reread its config on its own, so restart it rather than signalling it. |
|
| 95 | + | # This also covers the case where dm-wifi-disable stopped it outright. |
|
| 96 | + | # ifup is no help here: wlan0 has no stanza in /etc/network/interfaces. |
|
| 97 | + | rc-service wpa_supplicant restart >/dev/null 2>&1 |
|
| 98 | + | ||
| 99 | + | # Give the supplicant time to associate before asking for a lease — a |
|
| 100 | + | # DHCP request sent before association just burns its whole timeout. |
|
| 101 | + | i=0 |
|
| 102 | + | while [ "$i" -lt 20 ]; do |
|
| 103 | + | iw dev "$1" link 2>/dev/null | grep -q "^Connected to" && break |
|
| 104 | + | sleep 1 |
|
| 105 | + | i=$((i + 1)) |
|
| 106 | + | done |
|
| 107 | + | ||
| 108 | + | # Replace any DHCP client left over from a previous connect; without this |
|
| 109 | + | # they stack up, one per run, all renewing the same lease. |
|
| 110 | + | PIDFILE=/var/run/udhcpc.$1.pid |
|
| 111 | + | if [ -f "$PIDFILE" ]; then |
|
| 112 | + | kill "$(cat "$PIDFILE")" 2>/dev/null |
|
| 113 | + | rm -f "$PIDFILE" |
|
| 114 | + | fi |
|
| 115 | + | ip addr flush dev "$1" 2>/dev/null |
|
| 116 | + | ||
| 117 | + | # Same invocation ifupdown-ng uses for a dhcp stanza. udhcpc daemonises |
|
| 118 | + | # once it has the lease, so this returns with the address already set. |
|
| 119 | + | udhcpc -b -R -p "$PIDFILE" -i "$1" -x "hostname:$(hostname)" >/dev/null 2>&1 |
|
| 120 | + | ' _ "$IFACE" |
|
| 121 | + | ||
| 122 | + | if ip addr show "$IFACE" | grep -q "inet "; then |
|
| 123 | + | IP=$(ip -4 addr show "$IFACE" | awk '/inet /{print $2; exit}') |
|
| 124 | + | notify "✓ Connected to $SSID on $IFACE — $IP" |
|
| 125 | + | else |
|
| 126 | + | fail "✗ Failed to connect to $SSID" |
|
| 127 | + | fi |
| 1 | + | #!/bin/sh |
|
| 2 | + | # Requires: dmenu, libnotify, openrc, ifupdown-ng, doas |
|
| 3 | + | # dmenu counterpart of wifi-disable. The confirm prompt runs as the calling |
|
| 4 | + | # user; only the teardown is handed to doas. |
|
| 5 | + | set -u |
|
| 6 | + | ||
| 7 | + | IFACE="${1:-wlan0}" |
|
| 8 | + | NOTIFY_ID=7302 |
|
| 9 | + | ||
| 10 | + | notify() { |
|
| 11 | + | if command -v dunstify >/dev/null 2>&1; then |
|
| 12 | + | dunstify -r "$NOTIFY_ID" ${2:+-u "$2"} "WiFi" "$1" |
|
| 13 | + | else |
|
| 14 | + | notify-send ${2:+-u "$2"} "WiFi" "$1" |
|
| 15 | + | fi |
|
| 16 | + | } |
|
| 17 | + | ||
| 18 | + | fail() { notify "$1" critical; exit 1; } |
|
| 19 | + | ||
| 20 | + | # doas asks for a passphrase on a tty, and launched from a keybinding there |
|
| 21 | + | # is none — fail with something readable instead of hanging invisibly. |
|
| 22 | + | doas -n true 2>/dev/null || fail "doas needs a password; add a nopass rule for this user." |
|
| 23 | + | ||
| 24 | + | # Default to No so a stray Enter never takes the link down. |
|
| 25 | + | [ "$(printf 'No\nYes\n' | dmenu -c -i -l 2 -p "Disable WiFi on $IFACE?")" = "Yes" ] || exit 0 |
|
| 26 | + | ||
| 27 | + | notify "Disabling $IFACE..." |
|
| 28 | + | ||
| 29 | + | doas sh -c ' |
|
| 30 | + | # wpa_supplicant runs under supervise-daemon (supervisor= in |
|
| 31 | + | # /etc/init.d/wpa_supplicant), which respawns the daemon a couple of |
|
| 32 | + | # seconds after any kill. Stopping the service is what takes the |
|
| 33 | + | # supervisor down with it; pkill only ever kills the child. |
|
| 34 | + | rc-service -s -q wpa_supplicant stop |
|
| 35 | + | ||
| 36 | + | # wlan0 has no stanza in /etc/network/interfaces, so ifdown is a no-op on |
|
| 37 | + | # this host and leaves the DHCP client behind. Try it anyway for hosts |
|
| 38 | + | # where the interface *is* configured, then clean up by pidfile. |
|
| 39 | + | ifdown "$1" 2>/dev/null |
|
| 40 | + | ||
| 41 | + | PIDFILE=/var/run/udhcpc.$1.pid |
|
| 42 | + | if [ -f "$PIDFILE" ]; then |
|
| 43 | + | kill "$(cat "$PIDFILE")" 2>/dev/null |
|
| 44 | + | rm -f "$PIDFILE" |
|
| 45 | + | fi |
|
| 46 | + | ||
| 47 | + | ip addr flush dev "$1" 2>/dev/null |
|
| 48 | + | ip link set "$1" down |
|
| 49 | + | ' _ "$IFACE" |
|
| 50 | + | ||
| 51 | + | notify "$IFACE disabled" |
| 1 | + | #!/bin/sh |
|
| 2 | + | # Requires: gum, ifupdown-ng |
|
| 3 | + | # Bringing interfaces up/down needs root. |
|
| 4 | + | set -u |
|
| 5 | + | ||
| 6 | + | IFACE="${1:-eth0}" |
|
| 7 | + | WIFI="${2:-wlan0}" |
|
| 8 | + | ||
| 9 | + | if [ "$(id -u)" -ne 0 ]; then |
|
| 10 | + | exec doas "$0" "$@" |
|
| 11 | + | fi |
|
| 12 | + | ||
| 13 | + | gum confirm "Switch to $IFACE and disable $WIFI?" || exit 0 |
|
| 14 | + | ||
| 15 | + | gum spin --spinner dot --title "Switching to wired..." -- sh -c ' |
|
| 16 | + | ifdown "$2" 2>/dev/null |
|
| 17 | + | ip link set "$2" down 2>/dev/null |
|
| 18 | + | pkill wpa_supplicant 2>/dev/null |
|
| 19 | + | ifup "$1" |
|
| 20 | + | ' _ "$IFACE" "$WIFI" |
|
| 21 | + | ||
| 22 | + | if ip addr show "$IFACE" | grep -q "inet "; then |
|
| 23 | + | IP=$(ip -4 addr show "$IFACE" | grep inet | awk '{print $2}') |
|
| 24 | + | gum style --foreground 46 --bold "✓ $IFACE up — $IP" |
|
| 25 | + | else |
|
| 26 | + | gum style --foreground 196 --bold "✗ $IFACE failed to come up" |
|
| 27 | + | fi |
| 1 | + | #!/bin/sh |
|
| 2 | + | # Requires: gum, ifupdown-ng |
|
| 3 | + | # Bringing interfaces down needs root. |
|
| 4 | + | set -u |
|
| 5 | + | ||
| 6 | + | IFACE="${1:-eth0}" |
|
| 7 | + | ||
| 8 | + | if [ "$(id -u)" -ne 0 ]; then |
|
| 9 | + | exec doas "$0" "$@" |
|
| 10 | + | fi |
|
| 11 | + | ||
| 12 | + | gum confirm "Disable $IFACE?" || exit 0 |
|
| 13 | + | ||
| 14 | + | gum spin --spinner dot --title "Disabling $IFACE..." -- sh -c ' |
|
| 15 | + | ifdown "$1" 2>/dev/null |
|
| 16 | + | ip link set "$1" down |
|
| 17 | + | ' _ "$IFACE" |
|
| 18 | + | ||
| 19 | + | gum style --foreground 214 "$IFACE disabled" |
| 1 | + | #!/usr/bin/env bash |
|
| 2 | + | ||
| 3 | + | LOCATION=$1 |
|
| 4 | + | ||
| 5 | + | # data=$(curl -sSL https://api.weather.gov/points/$LOCATION) |
|
| 6 | + | # echo $data |
|
| 7 | + | ||
| 8 | + | curl -sSL https://api.weather.gov/points/$LOCATION | jq -r '.properties.forecast' |
| 1 | + | #!/bin/sh |
|
| 2 | + | # Resolve the sibling scripts relative to this one rather than through PATH. |
|
| 3 | + | DIR=$(dirname "$0") |
|
| 4 | + | ||
| 5 | + | # BusyBox's `ip` has no `-br`, so build the brief view by hand. |
|
| 6 | + | net_status() { |
|
| 7 | + | ip -4 addr show | awk ' |
|
| 8 | + | function flush() { |
|
| 9 | + | if (iface != "") printf "%-16s %-8s %s\n", iface, state, addrs |
|
| 10 | + | } |
|
| 11 | + | /^[0-9]+: / { |
|
| 12 | + | flush() |
|
| 13 | + | iface = $2; sub(/:$/, "", iface) |
|
| 14 | + | addrs = "" |
|
| 15 | + | state = "DOWN" |
|
| 16 | + | if ($0 ~ /state UP/) state = "UP" |
|
| 17 | + | else if ($0 ~ /state UNKNOWN/) state = "UNKNOWN" |
|
| 18 | + | } |
|
| 19 | + | /^[[:space:]]*inet / { addrs = addrs (addrs ? " " : "") $2 } |
|
| 20 | + | END { flush() } |
|
| 21 | + | ' |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | CHOICE=$(gum choose "Connect to WiFi" "Disable WiFi" "Connect eth0" "Disable eth0" "Show status" "Quit") |
|
| 25 | + | ||
| 26 | + | case "$CHOICE" in |
|
| 27 | + | "Connect to WiFi") "$DIR/wifi-connect" ;; |
|
| 28 | + | "Disable WiFi") "$DIR/wifi-disable" ;; |
|
| 29 | + | "Connect eth0") "$DIR/eth0-connect" ;; |
|
| 30 | + | "Disable eth0") "$DIR/eth0-disable" ;; |
|
| 31 | + | "Show status") |
|
| 32 | + | gum style --border normal --padding "1 2" --border-foreground 212 \ |
|
| 33 | + | "$(net_status)" |
|
| 34 | + | ;; |
|
| 35 | + | *) exit 0 ;; |
|
| 36 | + | esac |
| 1 | + | #!/usr/bin/env bash |
|
| 2 | + | ||
| 3 | + | folder="$HOME/notes" |
|
| 4 | + | ||
| 5 | + | mkdir -p "$folder" |
|
| 6 | + | ||
| 7 | + | # BusyBox setsid has no -f; background with & instead. |
|
| 8 | + | launch () { |
|
| 9 | + | setsid "$term" -e nvim "$1" >/dev/null 2>&1 & |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | : "${TERMINAL:=st}" |
|
| 13 | + | term="$(command -v "$TERMINAL")" || { |
|
| 14 | + | echo "notes: terminal '$TERMINAL' not found" >&2 |
|
| 15 | + | exit 1 |
|
| 16 | + | } |
|
| 17 | + | ||
| 18 | + | newnote () { \ |
|
| 19 | + | dir="$(find "$folder" -type d | sort | dmenu -c -l 5 -i -p 'Choose directory: ')" || exit 0 |
|
| 20 | + | : "${dir:=$folder}" |
|
| 21 | + | name="$(echo "" | dmenu -c -sb "#a3be8c" -nf "#d8dee9" -p "Enter a name: " <&-)" || exit 0 |
|
| 22 | + | : "${name:=$(date +%F_%H-%M-%S)}" |
|
| 23 | + | launch "${dir%/}/${name}.md" |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | selected () { \ |
|
| 27 | + | choice=$( |
|
| 28 | + | { echo "New" |
|
| 29 | + | find "$folder" -type f -name '*.md' -exec stat -c '%Y %n' {} + 2>/dev/null \ |
|
| 30 | + | | sort -nr | cut -d' ' -f2- | sed "s|^$folder/||" |
|
| 31 | + | } | dmenu -c -l 5 -i -p "Choose note or create new: " |
|
| 32 | + | ) |
|
| 33 | + | case $choice in |
|
| 34 | + | New) newnote ;; |
|
| 35 | + | *.md) launch "$folder/$choice" ;; |
|
| 36 | + | *) exit ;; |
|
| 37 | + | esac |
|
| 38 | + | } |
|
| 39 | + | ||
| 40 | + | selected |
| 1 | + | #!/usr/bin/env bash |
|
| 2 | + | # basic stats checker script mostly adapted from statusbar, may need changes to work on your system |
|
| 3 | + | ||
| 4 | + | CPU_TEMP=$(sensors | awk ' |
|
| 5 | + | /^Tdie|^Tctl|^Package id|^Core 0|^CPU|^temp1/ { |
|
| 6 | + | gsub(/[+°C]/, ""); |
|
| 7 | + | for (i=1; i<=NF; i++) { |
|
| 8 | + | if ($i ~ /^[0-9]+(\.[0-9]+)?$/) { |
|
| 9 | + | gsub(/\..*/, "", $i); |
|
| 10 | + | print $i; |
|
| 11 | + | exit; |
|
| 12 | + | } |
|
| 13 | + | } |
|
| 14 | + | }') |
|
| 15 | + | ||
| 16 | + | MEM_USE=$(free -m | awk '/^Mem/ {print ($3)/1024}'| cut -c-4) |
|
| 17 | + | CPU_USE=$(iostat -c | awk 'NR>=4 && NR <=4' | awk '{print "User CPU: "$1"%\nSystem CPU: ",$3"%"}') |
|
| 18 | + | GPU_TEMP=$(sensors | awk '/^edge/ {print substr($2,2)}') |
|
| 19 | + | ||
| 20 | + | ||
| 21 | + | DISK1=$(df -h ~/ | awk 'NR==2 {print $4}') |
|
| 22 | + | DISK2=$(df -h / | awk 'NR==2 {print $4}') |
|
| 23 | + | ||
| 24 | + | # [[ -n $(amixer get Capture | awk '/\[on\]/') ]] && MIC="on" || MIC="off" |
|
| 25 | + | UPDATE=$(stat -c %y /lib/apk/db/installed | awk '{print $1}') |
|
| 26 | + | KERNEL=$(uname -r) |
|
| 27 | + | ||
| 28 | + | echo -e "Stats $CPU_USE |
|
| 29 | + | ||
| 30 | + | GPU temp: $GPU_TEMP |
|
| 31 | + | CPU temp: $CPU_TEMP°C |
|
| 32 | + | ||
| 33 | + | Memory use: ${MEM_USE}g |
|
| 34 | + | ||
| 35 | + | Home remaining: $DISK1 |
|
| 36 | + | Root remaining: $DISK2 |
|
| 37 | + | ||
| 38 | + | Last update: $UPDATE $KERNEL" \ |
|
| 39 | + | | bat -l cpuinfo --style=grid |
|
| 40 | + |
| 1 | + | #!/usr/bin/env bash |
|
| 2 | + | ||
| 3 | + | case "$(printf "kill\nzzz\nreboot\nshutdown" | dmenu -i -c -l 4)" in |
|
| 4 | + | kill) ps -u "$USER" -o pid,comm,%cpu,%mem | dmenu -i -c -l 10 -p Kill: | awk '{print $1}' | xargs -r kill ;; |
|
| 5 | + | zzz) slock systemctl suspend -i ;; |
|
| 6 | + | reboot) systemctl reboot -i ;; |
|
| 7 | + | shutdown) shutdown now ;; |
|
| 8 | + | *) exit 1 ;; |
|
| 9 | + | esac |
| 1 | + | #!/usr/bin/env bash |
|
| 2 | + | ||
| 3 | + | histfile="$HOME/.cache/cliphist" |
|
| 4 | + | placeholder="<NEWLINE>" |
|
| 5 | + | ||
| 6 | + | highlight() { |
|
| 7 | + | clip=$(xclip -o -selection primary | xclip -i -f -selection clipboard 2>/dev/null) ;} |
|
| 8 | + | ||
| 9 | + | output() { |
|
| 10 | + | clip=$(xclip -i -f -selection clipboard 2>/dev/null) ;} |
|
| 11 | + | ||
| 12 | + | write() { |
|
| 13 | + | [ -f "$histfile" ] || notify-send "Creating $histfile"; touch $histfile |
|
| 14 | + | [ -z "$clip" ] && exit 0 |
|
| 15 | + | multiline=$(echo "$clip" | sed ':a;N;$!ba;s/\n/'"$placeholder"'/g') |
|
| 16 | + | grep -Fxq "$multiline" "$histfile" || echo "$multiline" >> "$histfile" |
|
| 17 | + | notification=$(echo \"$multiline\") ;} |
|
| 18 | + | ||
| 19 | + | sel() { |
|
| 20 | + | selection=$(tac "$histfile" | dmenu -b -l 5 -i -p "Clipboard history:") |
|
| 21 | + | [ -n "$selection" ] && echo "$selection" | sed "s/$placeholder/\n/g" | xclip -i -selection clipboard && notification="Copied to clipboard!" ;} |
|
| 22 | + | ||
| 23 | + | case "$1" in |
|
| 24 | + | add) highlight && write ;; |
|
| 25 | + | out) output && write ;; |
|
| 26 | + | sel) sel ;; |
|
| 27 | + | *) printf "$0 | File: $histfile\n\nadd - copies primary selection to clipboard, and adds to history file\nout - pipe commands to copy output to clipboard, and add to history file\nsel - select from history file with dmenu and recopy!\n" ; exit 0 ;; |
|
| 28 | + | esac |
|
| 29 | + | ||
| 30 | + | [ -z "$notification" ] || notify-send "$notification" |
| 1 | + | #!/bin/sh |
|
| 2 | + | # Requires: wireplumber (wpctl) |
|
| 3 | + | # Lower the default sink. Usage: vol-down [STEP_PERCENT] (default 5) |
|
| 4 | + | set -u |
|
| 5 | + | ||
| 6 | + | STEP="${1:-5}" |
|
| 7 | + | ||
| 8 | + | wpctl set-volume @DEFAULT_AUDIO_SINK@ "${STEP}%-" || exit 1 |
|
| 9 | + | exec "$(dirname "$0")/vol-status" -n |
| 1 | + | #!/bin/sh |
|
| 2 | + | # Requires: wireplumber (wpctl) |
|
| 3 | + | # Mute the default sink. Usage: vol-mute [toggle|on|off] (default toggle) |
|
| 4 | + | # Pass -s / --source to act on the default microphone instead. |
|
| 5 | + | set -u |
|
| 6 | + | ||
| 7 | + | TARGET="@DEFAULT_AUDIO_SINK@" |
|
| 8 | + | case "${1:-}" in |
|
| 9 | + | -s|--source) TARGET="@DEFAULT_AUDIO_SOURCE@"; shift ;; |
|
| 10 | + | esac |
|
| 11 | + | ||
| 12 | + | case "${1:-toggle}" in |
|
| 13 | + | toggle|"") ARG=toggle ;; |
|
| 14 | + | on|1|mute) ARG=1 ;; |
|
| 15 | + | off|0|unmute) ARG=0 ;; |
|
| 16 | + | *) echo "usage: vol-mute [-s] [toggle|on|off]" >&2; exit 2 ;; |
|
| 17 | + | esac |
|
| 18 | + | ||
| 19 | + | wpctl set-mute "$TARGET" "$ARG" || exit 1 |
|
| 20 | + | ||
| 21 | + | if [ "$TARGET" = "@DEFAULT_AUDIO_SOURCE@" ]; then |
|
| 22 | + | STATE=$(wpctl get-volume @DEFAULT_AUDIO_SOURCE@ 2>/dev/null) |
|
| 23 | + | case "$STATE" in *MUTED*) echo "mic muted" ;; *) echo "mic live" ;; esac |
|
| 24 | + | exit 0 |
|
| 25 | + | fi |
|
| 26 | + | ||
| 27 | + | exec "$(dirname "$0")/vol-status" -n |
| 1 | + | #!/bin/sh |
|
| 2 | + | # Requires: wireplumber (wpctl), optionally libnotify |
|
| 3 | + | # Prints the default sink's volume. Shared by vol-up / vol-down / vol-mute. |
|
| 4 | + | # Usage: vol-status [-n] -n also fires a desktop notification |
|
| 5 | + | set -u |
|
| 6 | + | ||
| 7 | + | NOTIFY=0 |
|
| 8 | + | [ "${1:-}" = "-n" ] && NOTIFY=1 |
|
| 9 | + | ||
| 10 | + | RAW=$(wpctl get-volume @DEFAULT_AUDIO_SINK@ 2>/dev/null) |
|
| 11 | + | if [ -z "$RAW" ]; then |
|
| 12 | + | echo "vol-status: no default sink (is pipewire/wireplumber running?)" >&2 |
|
| 13 | + | exit 1 |
|
| 14 | + | fi |
|
| 15 | + | ||
| 16 | + | # "Volume: 0.45" or "Volume: 0.45 [MUTED]" |
|
| 17 | + | PCT=$(printf '%s\n' "$RAW" | awk '{ printf "%d", $2 * 100 + 0.5 }') |
|
| 18 | + | case "$RAW" in |
|
| 19 | + | *MUTED*) MUTED=1 ;; |
|
| 20 | + | *) MUTED=0 ;; |
|
| 21 | + | esac |
|
| 22 | + | ||
| 23 | + | if [ "$MUTED" -eq 1 ]; then |
|
| 24 | + | TEXT="muted ($PCT%)" |
|
| 25 | + | ICON="audio-volume-muted" |
|
| 26 | + | else |
|
| 27 | + | TEXT="$PCT%" |
|
| 28 | + | if [ "$PCT" -ge 66 ]; then ICON="audio-volume-high" |
|
| 29 | + | elif [ "$PCT" -ge 33 ]; then ICON="audio-volume-medium" |
|
| 30 | + | elif [ "$PCT" -gt 0 ]; then ICON="audio-volume-low" |
|
| 31 | + | else ICON="audio-volume-muted" |
|
| 32 | + | fi |
|
| 33 | + | fi |
|
| 34 | + | ||
| 35 | + | echo "$TEXT" |
|
| 36 | + | ||
| 37 | + | # Only bother with notifications when there is a session to show them in. |
|
| 38 | + | [ "$NOTIFY" -eq 1 ] || exit 0 |
|
| 39 | + | [ -n "${WAYLAND_DISPLAY:-}${DISPLAY:-}" ] || exit 0 |
|
| 40 | + | command -v notify-send >/dev/null 2>&1 || exit 0 |
|
| 41 | + | ||
| 42 | + | notify-send \ |
|
| 43 | + | -a volume \ |
|
| 44 | + | -i "$ICON" \ |
|
| 45 | + | -h string:x-canonical-private-synchronous:volume \ |
|
| 46 | + | -h "int:value:$PCT" \ |
|
| 47 | + | "Volume" "$TEXT" |
| 1 | + | #!/bin/sh |
|
| 2 | + | # Requires: wireplumber (wpctl) |
|
| 3 | + | # Raise the default sink. Usage: vol-up [STEP_PERCENT] (default 5) |
|
| 4 | + | # VOL_MAX caps the ceiling (1.0 = 100%); raise it if you want overamplification. |
|
| 5 | + | set -u |
|
| 6 | + | ||
| 7 | + | STEP="${1:-5}" |
|
| 8 | + | MAX="${VOL_MAX:-1.0}" |
|
| 9 | + | ||
| 10 | + | wpctl set-volume -l "$MAX" @DEFAULT_AUDIO_SINK@ "${STEP}%+" || exit 1 |
|
| 11 | + | exec "$(dirname "$0")/vol-status" -n |
| 1 | + | #!/usr/bin/env bash |
|
| 2 | + | ||
| 3 | + | set -euo pipefail |
|
| 4 | + | ||
| 5 | + | gridpoint=${WEATHER_GRIDPOINT:-MRX/23,5} |
|
| 6 | + | base_url="https://api.weather.gov/gridpoints/${gridpoint}/forecast" |
|
| 7 | + | ||
| 8 | + | cloudy= |
|
| 9 | + | fog= |
|
| 10 | + | hail= |
|
| 11 | + | hazy= |
|
| 12 | + | storm= |
|
| 13 | + | clear_night= |
|
| 14 | + | rain= |
|
| 15 | + | snow= |
|
| 16 | + | clear_day= |
|
| 17 | + | ||
| 18 | + | usage() { |
|
| 19 | + | cat <<'USAGE' |
|
| 20 | + | usage: weather.sh [-h|--hourly] [-j|--json] |
|
| 21 | + | ||
| 22 | + | -h, --hourly use the hourly forecast instead of the daily one |
|
| 23 | + | -j, --json print { "text": ..., "tooltip": ... } (waybar friendly) |
|
| 24 | + | ||
| 25 | + | env: |
|
| 26 | + | WEATHER_GRIDPOINT NWS gridpoint, default MRX/23,5 |
|
| 27 | + | USAGE |
|
| 28 | + | } |
|
| 29 | + | ||
| 30 | + | hourly=false |
|
| 31 | + | json=false |
|
| 32 | + | while (($#)); do |
|
| 33 | + | case $1 in |
|
| 34 | + | -h | --hourly) hourly=true ;; |
|
| 35 | + | -j | --json) json=true ;; |
|
| 36 | + | --help) usage && exit 0 ;; |
|
| 37 | + | *) usage >&2 && exit 1 ;; |
|
| 38 | + | esac |
|
| 39 | + | shift |
|
| 40 | + | done |
|
| 41 | + | ||
| 42 | + | $hourly && weather_url="$base_url/hourly" || weather_url="$base_url" |
|
| 43 | + | ||
| 44 | + | data=$(curl -sSL --fail --max-time 10 -H 'Accept: application/geo+json' "$weather_url" | |
|
| 45 | + | jq -r '.properties.periods[0] | [.icon, .temperature, .temperatureUnit, .shortForecast, .isDaytime] | @tsv') || { |
|
| 46 | + | echo "weather: request failed" >&2 |
|
| 47 | + | exit 1 |
|
| 48 | + | } |
|
| 49 | + | ||
| 50 | + | IFS=$'\t' read -r icon_url temp unit short is_day <<<"$data" |
|
| 51 | + | ||
| 52 | + | # https://api.weather.gov/icons/land/night/tsra_hi,20/bkn?size=small |
|
| 53 | + | # ^period ^condition |
|
| 54 | + | icon_path=${icon_url%%\?*} # drop the query string |
|
| 55 | + | icon_path=${icon_path#*/icons/} # land/night/tsra_hi,20/bkn |
|
| 56 | + | icon_path=${icon_path#*/} # night/tsra_hi,20/bkn |
|
| 57 | + | period=${icon_path%%/*} # day|night |
|
| 58 | + | condition=${icon_path#*/} |
|
| 59 | + | condition=${condition%%/*} # first of the (up to two) conditions |
|
| 60 | + | condition=${condition%%,*} # drop the ",20" precip chance |
|
| 61 | + | ||
| 62 | + | night=false |
|
| 63 | + | [[ $period == night || $is_day == false ]] && night=true |
|
| 64 | + | ||
| 65 | + | # NWS icon vocabulary -> glyph. |
|
| 66 | + | case $condition in |
|
| 67 | + | skc | few | hot | cold) $night && glyph=$clear_night || glyph=$clear_day ;; |
|
| 68 | + | sct | bkn | ovc | wind_skc | wind_few | wind_sct | wind_bkn | wind_ovc) glyph=$cloudy ;; |
|
| 69 | + | tsra | tsra_sct | tsra_hi | tornado | hurricane | tropical_storm) glyph=$storm ;; |
|
| 70 | + | rain | rain_showers | rain_showers_hi) glyph=$rain ;; |
|
| 71 | + | snow | blizzard | rain_snow | snow_sleet) glyph=$snow ;; |
|
| 72 | + | sleet | fzra | rain_fzra | snow_fzra | rain_sleet) glyph=$hail ;; |
|
| 73 | + | fog) glyph=$fog ;; |
|
| 74 | + | haze | smoke | dust) glyph=$hazy ;; |
|
| 75 | + | *) glyph="" ;; |
|
| 76 | + | esac |
|
| 77 | + | ||
| 78 | + | # Fall back to a keyword match on the forecast text, most specific first. |
|
| 79 | + | if [[ -z $glyph ]]; then |
|
| 80 | + | case ${short,,} in |
|
| 81 | + | *thunder* | *tstm* | *tornado* | *hurricane* | *tropical*) glyph=$storm ;; |
|
| 82 | + | *hail* | *sleet* | *freezing* | *ice*) glyph=$hail ;; |
|
| 83 | + | *snow* | *flurr* | *blizzard* | *wintry*) glyph=$snow ;; |
|
| 84 | + | *rain* | *shower* | *drizzle*) glyph=$rain ;; |
|
| 85 | + | *fog* | *mist*) glyph=$fog ;; |
|
| 86 | + | *haze* | *hazy* | *smoke* | *dust*) glyph=$hazy ;; |
|
| 87 | + | *cloud* | *overcast*) glyph=$cloudy ;; |
|
| 88 | + | *clear* | *sunny* | *fair* | *hot*) $night && glyph=$clear_night || glyph=$clear_day ;; |
|
| 89 | + | *) $night && glyph=$clear_night || glyph=$clear_day ;; |
|
| 90 | + | esac |
|
| 91 | + | fi |
|
| 92 | + | ||
| 93 | + | if $json; then |
|
| 94 | + | jq -cn --arg text "$glyph $temp°$unit" --arg tooltip "$short" \ |
|
| 95 | + | '{text: $text, tooltip: $tooltip}' |
|
| 96 | + | else |
|
| 97 | + | printf '%s %s°%s\n' "$glyph" "$temp" "$unit" |
|
| 98 | + | fi |
| 1 | + | #!/bin/sh |
|
| 2 | + | # Requires: gum, iw, wpa_supplicant, udhcpc, openrc |
|
| 3 | + | # Scanning and writing wpa_supplicant.conf both need root. |
|
| 4 | + | set -u |
|
| 5 | + | ||
| 6 | + | CONF=/etc/wpa_supplicant/wpa_supplicant.conf |
|
| 7 | + | ||
| 8 | + | if [ "$(id -u)" -ne 0 ]; then |
|
| 9 | + | exec doas "$0" "$@" |
|
| 10 | + | fi |
|
| 11 | + | ||
| 12 | + | # Ask the kernel which interfaces are wireless rather than assuming wlan0. |
|
| 13 | + | IFACES=$(iw dev | awk '$1 == "Interface" { print $2 }') |
|
| 14 | + | ||
| 15 | + | case $(printf '%s' "$IFACES" | grep -c .) in |
|
| 16 | + | 0) gum style --foreground 196 "No wireless interfaces found."; exit 1 ;; |
|
| 17 | + | 1) IFACE=$IFACES ;; |
|
| 18 | + | *) IFACE=$(printf '%s\n' "$IFACES" | gum choose --header "Interface:") ;; |
|
| 19 | + | esac |
|
| 20 | + | ||
| 21 | + | if [ -z "$IFACE" ]; then |
|
| 22 | + | exit 1 |
|
| 23 | + | fi |
|
| 24 | + | ||
| 25 | + | ip link set "$IFACE" up 2>/dev/null |
|
| 26 | + | ||
| 27 | + | SCAN=$(mktemp) |
|
| 28 | + | trap 'rm -f "$SCAN"' EXIT INT TERM |
|
| 29 | + | ||
| 30 | + | # The scan has to run inside the spinner, and into a file — gum spin owns |
|
| 31 | + | # stdout while it renders. `scan dump` reuses cached results if the card is |
|
| 32 | + | # busy (already associated), which is when a live scan tends to fail. |
|
| 33 | + | gum spin --spinner dot --title "Scanning for networks..." -- sh -c ' |
|
| 34 | + | iw dev "$1" scan > "$2" 2>/dev/null || iw dev "$1" scan dump > "$2" 2>/dev/null |
|
| 35 | + | ' _ "$IFACE" "$SCAN" |
|
| 36 | + | ||
| 37 | + | # Only real SSID lines: bare "SSID: " is a hidden network, and "SSID List" |
|
| 38 | + | # under extended capabilities is not a network at all. |
|
| 39 | + | SSIDS=$(sed -n 's/^[[:space:]]*SSID: \(..*\)$/\1/p' "$SCAN" | sort -u) |
|
| 40 | + | ||
| 41 | + | SSID="" |
|
| 42 | + | if [ -n "$SSIDS" ]; then |
|
| 43 | + | # Never pipe an empty list into gum filter: with no input it falls back to |
|
| 44 | + | # listing the current directory, which looks like a network list but isn't. |
|
| 45 | + | SSID=$(printf '%s\n' "$SSIDS" | gum filter --placeholder "Select a network...") |
|
| 46 | + | else |
|
| 47 | + | gum style --foreground 214 "No networks found on $IFACE — enter one manually." |
|
| 48 | + | fi |
|
| 49 | + | ||
| 50 | + | if [ -z "$SSID" ]; then |
|
| 51 | + | SSID=$(gum input --placeholder "SSID" --prompt "Network name: ") |
|
| 52 | + | fi |
|
| 53 | + | ||
| 54 | + | if [ -z "$SSID" ]; then |
|
| 55 | + | gum style --foreground 196 "No SSID entered, aborting." |
|
| 56 | + | exit 1 |
|
| 57 | + | fi |
|
| 58 | + | ||
| 59 | + | PASS=$(gum input --password --placeholder "leave empty for an open network" --prompt "Password: ") |
|
| 60 | + | ||
| 61 | + | # Build the new config off to the side: a rejected passphrase must not blank |
|
| 62 | + | # out the config that is currently working. |
|
| 63 | + | NEW=$(mktemp) |
|
| 64 | + | trap 'rm -f "$SCAN" "$NEW"' EXIT INT TERM |
|
| 65 | + | ||
| 66 | + | if [ -z "$PASS" ]; then |
|
| 67 | + | printf 'network={\n\tssid="%s"\n\tkey_mgmt=NONE\n}\n' "$SSID" > "$NEW" |
|
| 68 | + | elif ! wpa_passphrase "$SSID" "$PASS" 2>/dev/null | grep -v '^[[:space:]]*#psk=' > "$NEW"; then |
|
| 69 | + | gum style --foreground 196 "Rejected by wpa_passphrase — password must be 8-63 characters." |
|
| 70 | + | exit 1 |
|
| 71 | + | fi |
|
| 72 | + | ||
| 73 | + | if [ ! -s "$NEW" ]; then |
|
| 74 | + | gum style --foreground 196 "Could not generate a config for $SSID, aborting." |
|
| 75 | + | exit 1 |
|
| 76 | + | fi |
|
| 77 | + | ||
| 78 | + | [ -f "$CONF" ] && cp "$CONF" "$CONF.bak" |
|
| 79 | + | cat "$NEW" > "$CONF" |
|
| 80 | + | chmod 600 "$CONF" |
|
| 81 | + | ||
| 82 | + | gum spin --spinner dot --title "Connecting to $SSID..." -- sh -c ' |
|
| 83 | + | # wpa_supplicant is a supervised OpenRC service on this host and will not |
|
| 84 | + | # reread its config on its own, so restart it rather than signalling it. |
|
| 85 | + | # This also covers the case where wifi-disable stopped it outright. |
|
| 86 | + | # ifup is no help here: wlan0 has no stanza in /etc/network/interfaces. |
|
| 87 | + | rc-service wpa_supplicant restart >/dev/null 2>&1 |
|
| 88 | + | ||
| 89 | + | # Give the supplicant time to associate before asking for a lease — a |
|
| 90 | + | # DHCP request sent before association just burns its whole timeout. |
|
| 91 | + | i=0 |
|
| 92 | + | while [ "$i" -lt 20 ]; do |
|
| 93 | + | iw dev "$1" link 2>/dev/null | grep -q "^Connected to" && break |
|
| 94 | + | sleep 1 |
|
| 95 | + | i=$((i + 1)) |
|
| 96 | + | done |
|
| 97 | + | ||
| 98 | + | # Replace any DHCP client left over from a previous connect; without this |
|
| 99 | + | # they stack up, one per run, all renewing the same lease. |
|
| 100 | + | PIDFILE=/var/run/udhcpc.$1.pid |
|
| 101 | + | if [ -f "$PIDFILE" ]; then |
|
| 102 | + | kill "$(cat "$PIDFILE")" 2>/dev/null |
|
| 103 | + | rm -f "$PIDFILE" |
|
| 104 | + | fi |
|
| 105 | + | ip addr flush dev "$1" 2>/dev/null |
|
| 106 | + | ||
| 107 | + | # Same invocation ifupdown-ng uses for a dhcp stanza. udhcpc daemonises |
|
| 108 | + | # once it has the lease, so this returns with the address already set. |
|
| 109 | + | udhcpc -b -R -p "$PIDFILE" -i "$1" -x "hostname:$(hostname)" >/dev/null 2>&1 |
|
| 110 | + | ' _ "$IFACE" |
|
| 111 | + | ||
| 112 | + | if ip addr show "$IFACE" | grep -q "inet "; then |
|
| 113 | + | gum style --foreground 46 --bold "✓ Connected to $SSID on $IFACE" |
|
| 114 | + | else |
|
| 115 | + | gum style --foreground 196 --bold "✗ Failed to connect to $SSID" |
|
| 116 | + | fi |
| 1 | + | #!/bin/sh |
|
| 2 | + | # Requires: gum, openrc, ifupdown-ng |
|
| 3 | + | # Stopping the wpa_supplicant service and downing the link both need root. |
|
| 4 | + | set -u |
|
| 5 | + | ||
| 6 | + | IFACE="${1:-wlan0}" |
|
| 7 | + | ||
| 8 | + | if [ "$(id -u)" -ne 0 ]; then |
|
| 9 | + | exec doas "$0" "$@" |
|
| 10 | + | fi |
|
| 11 | + | ||
| 12 | + | gum confirm "Disable WiFi on $IFACE?" || exit 0 |
|
| 13 | + | ||
| 14 | + | gum spin --spinner dot --title "Disabling $IFACE..." -- sh -c ' |
|
| 15 | + | # wpa_supplicant runs under supervise-daemon (supervisor= in |
|
| 16 | + | # /etc/init.d/wpa_supplicant), which respawns the daemon a couple of |
|
| 17 | + | # seconds after any kill. Stopping the service is what takes the |
|
| 18 | + | # supervisor down with it; pkill only ever kills the child. |
|
| 19 | + | rc-service -s -q wpa_supplicant stop |
|
| 20 | + | ||
| 21 | + | # wlan0 has no stanza in /etc/network/interfaces, so ifdown is a no-op on |
|
| 22 | + | # this host and leaves the DHCP client behind. Try it anyway for hosts |
|
| 23 | + | # where the interface *is* configured, then clean up by pidfile. |
|
| 24 | + | ifdown "$1" 2>/dev/null |
|
| 25 | + | ||
| 26 | + | PIDFILE=/var/run/udhcpc.$1.pid |
|
| 27 | + | if [ -f "$PIDFILE" ]; then |
|
| 28 | + | kill "$(cat "$PIDFILE")" 2>/dev/null |
|
| 29 | + | rm -f "$PIDFILE" |
|
| 30 | + | fi |
|
| 31 | + | ||
| 32 | + | ip addr flush dev "$1" 2>/dev/null |
|
| 33 | + | ip link set "$1" down |
|
| 34 | + | ' _ "$IFACE" |
|
| 35 | + | ||
| 36 | + | gum style --foreground 214 "$IFACE disabled" |
| 1 | + | #!/bin/sh |
|
| 2 | + | # Requires: iw |
|
| 3 | + | # Prints the wireless link state, for the status bar and menus. |
|
| 4 | + | # Usage: wifi-status [IFACE] (default: first wireless interface) |
|
| 5 | + | set -u |
|
| 6 | + | ||
| 7 | + | # Ask the kernel which interfaces are wireless rather than assuming wlan0. |
|
| 8 | + | IFACE="${1:-$(iw dev 2>/dev/null | awk '$1 == "Interface" { print $2; exit }')}" |
|
| 9 | + | ||
| 10 | + | if [ -z "$IFACE" ]; then |
|
| 11 | + | echo "none" |
|
| 12 | + | exit 0 |
|
| 13 | + | fi |
|
| 14 | + | ||
| 15 | + | # `iw link` prints "Not connected." while the card is up but unassociated, and |
|
| 16 | + | # fails outright once wifi-disable has downed the link. |
|
| 17 | + | LINK=$(iw dev "$IFACE" link 2>/dev/null) || { echo "off"; exit 0; } |
|
| 18 | + | ||
| 19 | + | case "$LINK" in |
|
| 20 | + | "Connected to"*) ;; |
|
| 21 | + | *) echo "off"; exit 0 ;; |
|
| 22 | + | esac |
|
| 23 | + | ||
| 24 | + | SSID=$(printf '%s\n' "$LINK" | sed -n 's/^[[:space:]]*SSID: \(..*\)$/\1/p') |
|
| 25 | + | [ -n "$SSID" ] || SSID="unknown" |
|
| 26 | + | ||
| 27 | + | # Associated but without a lease carries no traffic — say so rather than |
|
| 28 | + | # showing a network name that looks connected. |
|
| 29 | + | if ip addr show "$IFACE" 2>/dev/null | grep -q "inet "; then |
|
| 30 | + | echo "$SSID" |
|
| 31 | + | else |
|
| 32 | + | echo "$SSID (no ip)" |
|
| 33 | + | fi |