feat(labelprint): 4x6 label print proxy on a Raspberry Pi
A Pi 3B+ (stickah.local) shares a Phomemo PM246 to the LAN as a plain CUPS queue, so any machine can print 4x6 labels -- fulfillr-site's shipping labels in particular -- without installing the vendor driver, which is x86-64 only. The role builds the TSPL CUPS driver from source instead. It is Debian, not Fedora, so it lives in its own inventory and playbook (make deploy-labelprint / check-labelprint) and the home.debyl.io roles can never run against it. make bootfs renders its cloud-init first-boot files onto a freshly imaged SD card from the same templates the role uses. The Wi-Fi credentials for the home and rescue networks are in the vault. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
be0d02b938
commit
6cd4d56de1
@@ -0,0 +1,11 @@
|
||||
# {{ ansible_managed }} -- rendered by `make bootfs`
|
||||
#
|
||||
# cloud-init applies user-data once per INSTANCE, not once per boot: rewrite
|
||||
# user-data on a card that has already booted and nothing happens, because
|
||||
# cloud-init recognises the instance id and skips straight to per-boot modules.
|
||||
#
|
||||
# Deriving the id from a hash of user-data itself fixes that. Re-running
|
||||
# `make bootfs` with no changes leaves the id alone, so a card keeps its
|
||||
# identity; change anything in user-data and the id changes with it, and the
|
||||
# Pi re-applies the new config on its next boot without a reflash.
|
||||
instance-id: {{ labelprint_hostname }}-{{ labelprint_user_data_id }}
|
||||
@@ -0,0 +1,18 @@
|
||||
# {{ ansible_managed }} -- rendered by `make bootfs`
|
||||
#
|
||||
# Ethernet only, on purpose. Wi-Fi is NOT configured here: cloud-init renders
|
||||
# this through netplan into a persistent profile of netplan's own, which is not
|
||||
# a file this repo manages or can readily update. The Wi-Fi profiles are written
|
||||
# straight into /etc/NetworkManager/system-connections by user-data instead, so
|
||||
# the image and Ansible manage the same files.
|
||||
#
|
||||
# A card that has already booted with Wi-Fi in network-config keeps netplan's
|
||||
# profile. That is fine, and useful -- see the "Coexisting with netplan" note in
|
||||
# roles/labelprint/tasks/wifi.yml.
|
||||
network:
|
||||
version: 2
|
||||
ethernets:
|
||||
eth0:
|
||||
dhcp4: true
|
||||
dhcp6: true
|
||||
optional: true
|
||||
@@ -0,0 +1,125 @@
|
||||
#cloud-config
|
||||
# {{ ansible_managed }} -- rendered by `make bootfs BOOTFS=...`
|
||||
#
|
||||
# First boot of the label print proxy. The job of this file is to make the Pi
|
||||
# REACHABLE and nothing more: a login that works, a network that works, and a
|
||||
# rescue access point for when it does not. The printer driver and the CUPS
|
||||
# queue are Ansible's job -- see roles/labelprint/.
|
||||
#
|
||||
# The Wi-Fi profiles and the rescue watchdog below are rendered from the very
|
||||
# same templates the role uses, so the first `make deploy-labelprint` reports no
|
||||
# change on any of them. That is the point: the Pi can already rescue itself
|
||||
# before Ansible has ever run.
|
||||
#
|
||||
# Applies on FIRST BOOT ONLY. Rewriting this file on a card that has already
|
||||
# booted does nothing -- reflash the image.
|
||||
|
||||
hostname: {{ labelprint_hostname }}
|
||||
manage_etc_hosts: true
|
||||
manage_resolv_conf: false
|
||||
timezone: America/New_York
|
||||
keyboard:
|
||||
model: pc105
|
||||
layout: "us"
|
||||
|
||||
apt:
|
||||
preserve_sources_list: true
|
||||
|
||||
users:
|
||||
- name: {{ labelprint_user }}
|
||||
shell: /bin/bash
|
||||
lock_passwd: false
|
||||
# Deliberately trivial, and deliberately not hashed: this password is
|
||||
# documented in roles/labelprint/README.md, so a hash of it would protect
|
||||
# nothing while pretending otherwise. It exists so that a Pi which will not
|
||||
# take the SSH key -- wrong key, reflashed card, someone else's laptop -- is
|
||||
# still reachable from the LAN or the rescue AP, which is the only place it
|
||||
# can be reached from at all (see templates/nftables.conf.j2).
|
||||
plain_text_passwd: {{ labelprint_user_password }}
|
||||
sudo: "ALL=(ALL) NOPASSWD:ALL"
|
||||
groups: [sudo, adm, lpadmin, plugdev, dialout, netdev, users]
|
||||
ssh_authorized_keys:
|
||||
- {{ lookup('file', labelprint_authorized_key_file) }}
|
||||
|
||||
# The key is what Ansible actually uses; the password is the fallback.
|
||||
ssh_pwauth: true
|
||||
disable_root: true
|
||||
chpasswd:
|
||||
expire: false
|
||||
|
||||
# Pre-installing what the role needs makes the first deploy quick, and means a
|
||||
# Pi that comes up on the rescue AP with no internet still has cups and
|
||||
# NetworkManager. Ansible installs the same list, so nothing here is load-bearing.
|
||||
package_update: true
|
||||
packages:
|
||||
{% for pkg in labelprint_deps %}
|
||||
- {{ pkg }}
|
||||
{% endfor %}
|
||||
|
||||
write_files:
|
||||
# Stop cloud-init rewriting the network on a future re-instance.
|
||||
- path: /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg
|
||||
owner: root:root
|
||||
permissions: '0644'
|
||||
content: |
|
||||
network: {config: disabled}
|
||||
|
||||
# Belt and braces. netplan can hand NetworkManager an "only manage what I
|
||||
# listed" policy, and what this image lists is eth0; a stock Raspberry Pi OS
|
||||
# leaves that policy empty, but an unmanaged wlan0 would take the rescue AP
|
||||
# down with it, so it is not worth depending on.
|
||||
- path: /etc/NetworkManager/conf.d/10-labelprint.conf
|
||||
owner: root:root
|
||||
permissions: '0644'
|
||||
content: |
|
||||
[keyfile]
|
||||
unmanaged-devices=none
|
||||
|
||||
[device]
|
||||
# A randomised MAC would give the Pi a different DHCP lease on every
|
||||
# association, which makes it hard to find on the UDM Pro's client list.
|
||||
wifi.scan-rand-mac-address=no
|
||||
|
||||
- path: /etc/NetworkManager/system-connections/home-wifi.nmconnection
|
||||
owner: root:root
|
||||
permissions: '0600'
|
||||
content: |
|
||||
{{ lookup('template', 'roles/labelprint/templates/home-wifi.nmconnection.j2') | indent(6) }}
|
||||
|
||||
- path: /etc/NetworkManager/system-connections/rescue-ap.nmconnection
|
||||
owner: root:root
|
||||
permissions: '0600'
|
||||
content: |
|
||||
{{ lookup('template', 'roles/labelprint/templates/rescue-ap.nmconnection.j2') | indent(6) }}
|
||||
|
||||
- path: /usr/local/sbin/wifi-rescue
|
||||
owner: root:root
|
||||
permissions: '0755'
|
||||
content: |
|
||||
{{ lookup('template', 'roles/labelprint/templates/wifi-rescue.sh.j2') | indent(6) }}
|
||||
|
||||
- path: /etc/systemd/system/wifi-rescue.service
|
||||
owner: root:root
|
||||
permissions: '0644'
|
||||
content: |
|
||||
{{ lookup('template', 'roles/labelprint/templates/wifi-rescue.service.j2') | indent(6) }}
|
||||
|
||||
- path: /etc/systemd/system/wifi-rescue.timer
|
||||
owner: root:root
|
||||
permissions: '0644'
|
||||
content: |
|
||||
{{ lookup('template', 'roles/labelprint/templates/wifi-rescue.timer.j2') | indent(6) }}
|
||||
|
||||
runcmd:
|
||||
# RPi OS soft-blocks the radio until a regulatory domain is known. cmdline.txt
|
||||
# carries cfg80211.ieee80211_regdom=US, but unblock anyway -- a blocked radio
|
||||
# is the one failure that takes the rescue AP down with it.
|
||||
- [rfkill, unblock, wifi]
|
||||
- [systemctl, enable, --now, ssh]
|
||||
- [systemctl, enable, --now, avahi-daemon]
|
||||
# Let NetworkManager pick up the keyfiles written above. The profile netplan
|
||||
# renders from network-config is left alone on purpose -- see the "Coexisting
|
||||
# with netplan" note in roles/labelprint/tasks/wifi.yml.
|
||||
- [nmcli, connection, reload]
|
||||
- [systemctl, daemon-reload]
|
||||
- [systemctl, enable, --now, wifi-rescue.timer]
|
||||
@@ -0,0 +1,97 @@
|
||||
# {{ ansible_managed }}
|
||||
#
|
||||
# CUPS on the label print proxy. The Pi does all the rendering, so macOS,
|
||||
# Windows and Linux clients add the queue driverless over IPP Everywhere /
|
||||
# AirPrint and never install a Phomemo driver.
|
||||
#
|
||||
# Reachable from the LAN and from the rescue access point only. Everything else
|
||||
# is refused here and dropped again at nftables.
|
||||
|
||||
LogLevel warn
|
||||
PageLogFormat
|
||||
MaxLogSize 1m
|
||||
# A label job is worth retrying: the printer is often powered off or out of
|
||||
# stock when the job is submitted, and the default is to bin the job outright.
|
||||
ErrorPolicy retry-job
|
||||
|
||||
# Only trusted, local networks reach this port -- see the Location blocks below
|
||||
# and roles/labelprint/templates/nftables.conf.j2. Listening on all interfaces
|
||||
# rather than a fixed address so the queue is still reachable at
|
||||
# {{ labelprint_ap_addr }} when the Pi has fallen back to its rescue AP.
|
||||
Listen 631
|
||||
Listen /run/cups/cups.sock
|
||||
|
||||
# Advertise over Bonjour/mDNS so clients discover the queue by themselves.
|
||||
Browsing On
|
||||
BrowseLocalProtocols dnssd
|
||||
# Dropping the _cups subtype is what makes macOS and iOS offer a driverless
|
||||
# "AirPrint" add instead of guessing at a Generic PostScript driver.
|
||||
BrowseDNSSDSubTypes _print,_universal
|
||||
|
||||
DefaultAuthType Basic
|
||||
WebInterface Yes
|
||||
|
||||
<Location />
|
||||
Order allow,deny
|
||||
Allow from {{ labelprint_lan_cidr }}
|
||||
Allow from {{ labelprint_ap_cidr }}
|
||||
Allow from localhost
|
||||
</Location>
|
||||
|
||||
<Location /admin>
|
||||
AuthType Default
|
||||
Require user @SYSTEM
|
||||
Order allow,deny
|
||||
Allow from {{ labelprint_lan_cidr }}
|
||||
Allow from {{ labelprint_ap_cidr }}
|
||||
</Location>
|
||||
|
||||
<Location /admin/conf>
|
||||
AuthType Default
|
||||
Require user @SYSTEM
|
||||
Order allow,deny
|
||||
Allow from {{ labelprint_lan_cidr }}
|
||||
Allow from {{ labelprint_ap_cidr }}
|
||||
</Location>
|
||||
|
||||
<Location /admin/log>
|
||||
AuthType Default
|
||||
Require user @SYSTEM
|
||||
Order allow,deny
|
||||
Allow from {{ labelprint_lan_cidr }}
|
||||
Allow from {{ labelprint_ap_cidr }}
|
||||
</Location>
|
||||
|
||||
<Policy default>
|
||||
JobPrivateAccess default
|
||||
JobPrivateValues default
|
||||
SubscriptionPrivateAccess default
|
||||
SubscriptionPrivateValues default
|
||||
|
||||
# Anyone on the LAN may print and manage their own jobs -- this is a label
|
||||
# printer in a workshop, not a shared office device with quotas.
|
||||
<Limit Create-Job Print-Job Print-URI Validate-Job>
|
||||
Order deny,allow
|
||||
</Limit>
|
||||
|
||||
<Limit Send-Document Send-URI Hold-Job Release-Job Restart-Job Purge-Jobs Set-Job-Attributes Create-Job-Subscription Renew-Subscription Cancel-Subscription Get-Notifications Reprocess-Job Cancel-Current-Job Suspend-Current-Job Resume-Job Cancel-Jobs CUPS-Authenticate-Job Close-Job CUPS-Move-Job Cancel-My-Jobs CUPS-Get-Document>
|
||||
Order deny,allow
|
||||
</Limit>
|
||||
|
||||
# Changing the printer itself needs a local admin.
|
||||
<Limit Pause-Printer Resume-Printer Enable-Printer Disable-Printer Pause-Printer-After-Current-Job Hold-New-Jobs Release-Held-New-Jobs Deactivate-Printer Activate-Printer Restart-Printer Shutdown-Printer Startup-Printer Promote-Job Schedule-Job-After Cancel-Job CUPS-Accept-Jobs CUPS-Reject-Jobs>
|
||||
AuthType Default
|
||||
Require user @SYSTEM
|
||||
Order deny,allow
|
||||
</Limit>
|
||||
|
||||
<Limit CUPS-Add-Modify-Printer CUPS-Delete-Printer CUPS-Add-Modify-Class CUPS-Delete-Class CUPS-Set-Default>
|
||||
AuthType Default
|
||||
Require user @SYSTEM
|
||||
Order deny,allow
|
||||
</Limit>
|
||||
|
||||
<Limit All>
|
||||
Order deny,allow
|
||||
</Limit>
|
||||
</Policy>
|
||||
@@ -0,0 +1,32 @@
|
||||
# {{ ansible_managed }}
|
||||
#
|
||||
# The normal, everyday Wi-Fi link. autoconnect-priority outranks anything
|
||||
# cloud-init/netplan rendered for the same SSID, so this is the profile
|
||||
# NetworkManager picks. autoconnect-retries=0 means retry forever rather than
|
||||
# giving up after four attempts and leaving the Pi off the network until
|
||||
# someone power-cycles it -- the rescue AP is the fallback, not the
|
||||
# destination.
|
||||
[connection]
|
||||
id=home-wifi
|
||||
uuid={{ (stickah_ssid ~ '-home-wifi') | to_uuid }}
|
||||
type=wifi
|
||||
interface-name=wlan0
|
||||
autoconnect=true
|
||||
autoconnect-priority=100
|
||||
autoconnect-retries=0
|
||||
|
||||
[wifi]
|
||||
mode=infrastructure
|
||||
ssid={{ stickah_ssid }}
|
||||
|
||||
[wifi-security]
|
||||
key-mgmt=wpa-psk
|
||||
# Accepts either a passphrase or the 64-hex precomputed PSK.
|
||||
psk={{ stickah_psk }}
|
||||
|
||||
[ipv4]
|
||||
method=auto
|
||||
|
||||
[ipv6]
|
||||
method=auto
|
||||
addr-gen-mode=default
|
||||
@@ -0,0 +1,63 @@
|
||||
#!/usr/sbin/nft -f
|
||||
# {{ ansible_managed }}
|
||||
#
|
||||
# The print proxy answers to the LAN and to its own rescue access point, and to
|
||||
# nothing else. This is the outer half of the same rule that cupsd enforces in
|
||||
# its Location blocks -- both are here on purpose, so a mistake in one is not
|
||||
# the only thing standing between the printer and the rest of the world.
|
||||
|
||||
# Declare-then-delete rather than `flush ruleset`: NetworkManager's shared mode
|
||||
# keeps its own table for the rescue AP's dnsmasq, and a global flush would take
|
||||
# that with it every time this file is reloaded.
|
||||
table inet labelprint
|
||||
delete table inet labelprint
|
||||
|
||||
table inet labelprint {
|
||||
set trusted {
|
||||
type ipv4_addr
|
||||
flags interval
|
||||
elements = { {{ labelprint_lan_cidr }}, {{ labelprint_ap_cidr }} }
|
||||
}
|
||||
|
||||
chain input {
|
||||
type filter hook input priority filter; policy drop;
|
||||
|
||||
ct state established,related accept
|
||||
ct state invalid drop
|
||||
iif lo accept
|
||||
|
||||
icmp type { echo-request, destination-unreachable, time-exceeded, parameter-problem } accept
|
||||
icmpv6 type { echo-request, destination-unreachable, packet-too-big, time-exceeded, parameter-problem, nd-neighbor-solicit, nd-neighbor-advert, nd-router-advert } accept
|
||||
|
||||
# DHCP replies to our own client. Broadcast, so conntrack does not see
|
||||
# them as related to the request we sent.
|
||||
udp dport 68 accept
|
||||
|
||||
# ssh and IPP, from the LAN or from a machine on the rescue AP.
|
||||
ip saddr @trusted tcp dport { 22, 631 } accept
|
||||
ip saddr @trusted udp dport 631 accept
|
||||
|
||||
# mDNS: how every client finds this printer, since it has no DNS record.
|
||||
ip saddr @trusted udp dport 5353 accept
|
||||
|
||||
# DHCP for whoever joins the rescue AP. Deliberately not restricted by
|
||||
# source address: a client asking for its first lease has no address
|
||||
# yet and sends DHCPDISCOVER from 0.0.0.0, so a source-matched rule
|
||||
# would mean the rescue network never hands out a lease at all. Only a
|
||||
# machine already associated to our own AP can reach this port.
|
||||
iifname "wlan0" udp dport 67 accept
|
||||
|
||||
# DNS, once they have an address.
|
||||
iifname "wlan0" ip saddr {{ labelprint_ap_cidr }} udp dport 53 accept
|
||||
iifname "wlan0" ip saddr {{ labelprint_ap_cidr }} tcp dport 53 accept
|
||||
}
|
||||
|
||||
# The rescue AP is a way in to this Pi, not a route to anywhere else.
|
||||
chain forward {
|
||||
type filter hook forward priority filter; policy drop;
|
||||
}
|
||||
|
||||
chain output {
|
||||
type filter hook output priority filter; policy accept;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
# {{ ansible_managed }}
|
||||
#
|
||||
# Rescue access point. Never autoconnects -- wifi-rescue brings it up only when
|
||||
# the home SSID is unreachable, so that a changed password or a dead router
|
||||
# leaves a way back in to reconfigure the Pi.
|
||||
#
|
||||
# Join this SSID and the Pi is at {{ labelprint_ap_addr }}: ssh pi@{{ labelprint_ap_addr }},
|
||||
# or print to it directly at ipp://{{ labelprint_ap_addr }}:631/printers/{{ labelprint_queue }}.
|
||||
[connection]
|
||||
id=rescue-ap
|
||||
uuid={{ (stickah_ssid_rescue ~ '-rescue-ap') | to_uuid }}
|
||||
type=wifi
|
||||
interface-name=wlan0
|
||||
autoconnect=false
|
||||
|
||||
[wifi]
|
||||
mode=ap
|
||||
ssid={{ stickah_ssid_rescue }}
|
||||
# The 3B+ radio does 5 GHz, but 2.4 GHz AP mode is what brcmfmac is reliable
|
||||
# at, and a rescue network only has to carry an SSH session.
|
||||
band=bg
|
||||
channel=6
|
||||
|
||||
[wifi-security]
|
||||
key-mgmt=wpa-psk
|
||||
proto=rsn
|
||||
pairwise=ccmp
|
||||
group=ccmp
|
||||
psk={{ stickah_psk_rescue }}
|
||||
|
||||
# "shared" makes NetworkManager run a dnsmasq for DHCP and DNS on this
|
||||
# interface, so a laptop that joins gets an address without any further setup.
|
||||
[ipv4]
|
||||
method=shared
|
||||
address1={{ labelprint_ap_addr }}/24
|
||||
|
||||
[ipv6]
|
||||
method=ignore
|
||||
@@ -0,0 +1,9 @@
|
||||
# {{ ansible_managed }}
|
||||
[Unit]
|
||||
Description=Wi-Fi rescue access point watchdog
|
||||
After=NetworkManager.service
|
||||
Requires=NetworkManager.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/sbin/wifi-rescue
|
||||
@@ -0,0 +1,132 @@
|
||||
#!/bin/sh
|
||||
# {{ ansible_managed }}
|
||||
#
|
||||
# Keeps the label print proxy reachable.
|
||||
#
|
||||
# Normally the Pi is a station on the home SSID. If that network is gone -- the
|
||||
# password changed, the AP died, the Pi was carried somewhere else -- it brings
|
||||
# up its own rescue access point so there is still a way in to reconfigure it.
|
||||
# It keeps checking, and hands the radio back the moment the home SSID returns.
|
||||
#
|
||||
# The 3B+ has one radio and brcmfmac will not hold an AP and a station link at
|
||||
# the same time, so this cannot listen for the home SSID while the AP is up.
|
||||
# Instead the AP drops for a few seconds every {{ labelprint_ap_rescan_secs }}s
|
||||
# to scan, and comes straight back if the home network is still missing.
|
||||
#
|
||||
# If you are working over the rescue AP and do not want the radio pulled out
|
||||
# from under your SSH session:
|
||||
#
|
||||
# touch /run/wifi-rescue.hold
|
||||
#
|
||||
# The hold expires by itself after {{ (labelprint_hold_max_age_secs / 60) | int }} minutes, so a forgotten hold file
|
||||
# cannot strand the Pi permanently.
|
||||
set -eu
|
||||
|
||||
HOME_CON=home-wifi
|
||||
AP_CON=rescue-ap
|
||||
SSID='{{ stickah_ssid }}'
|
||||
AP_SSID='{{ stickah_ssid_rescue }}'
|
||||
IFACE=wlan0
|
||||
HOLD=/run/wifi-rescue.hold
|
||||
STAMP=/run/wifi-rescue.ap-since
|
||||
RESCAN_SECS={{ labelprint_ap_rescan_secs }}
|
||||
HOLD_MAX_AGE={{ labelprint_hold_max_age_secs }}
|
||||
|
||||
log() { logger -t wifi-rescue -- "$@"; }
|
||||
|
||||
con_active() { nmcli -t -f NAME connection show --active | grep -qxF "$1"; }
|
||||
|
||||
# A default IPv4 route is the honest test for "on a real network". The rescue
|
||||
# AP uses NetworkManager's shared mode, which hands out addresses but installs
|
||||
# no default route, so the AP can never make this look true.
|
||||
online() { [ -n "$(ip -4 route show default 2>/dev/null)" ]; }
|
||||
|
||||
held() {
|
||||
[ -e "$HOLD" ] || return 1
|
||||
age=$(( $(date +%s) - $(stat -c %Y "$HOLD" 2>/dev/null || echo 0) ))
|
||||
if [ "$age" -lt "$HOLD_MAX_AGE" ]; then
|
||||
return 0
|
||||
fi
|
||||
log "hold file is ${age}s old; expiring it"
|
||||
rm -f "$HOLD"
|
||||
return 1
|
||||
}
|
||||
|
||||
start_ap() {
|
||||
con_active "$AP_CON" && return 0
|
||||
log "starting rescue AP '$AP_SSID' on {{ labelprint_ap_addr }}"
|
||||
if nmcli --wait 20 connection up "$AP_CON" >/dev/null 2>&1; then
|
||||
date +%s > "$STAMP"
|
||||
else
|
||||
log "ERROR: rescue AP failed to start"
|
||||
fi
|
||||
}
|
||||
|
||||
join_home() {
|
||||
# Bounded: the default 90s wait outlives the watchdog interval, and an
|
||||
# SSID that is not there is not going to appear in the next minute.
|
||||
#
|
||||
# --wait is a GLOBAL nmcli option and has to precede the subcommand. Written
|
||||
# as `connection up <id> --wait 20` it is rejected outright with "invalid
|
||||
# extra argument" -- and only for a connection that exists, so it looks fine
|
||||
# against a typo'd name. That failure mode is silent and total: every join
|
||||
# returns failure and the rescue AP never starts.
|
||||
nmcli --wait 20 connection up "$HOME_CON" >/dev/null 2>&1 || return 1
|
||||
online
|
||||
}
|
||||
|
||||
held && exit 0
|
||||
|
||||
if online; then
|
||||
if con_active "$AP_CON"; then
|
||||
log "back on the network; shutting the rescue AP down"
|
||||
nmcli connection down "$AP_CON" >/dev/null 2>&1 || true
|
||||
rm -f "$STAMP"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if con_active "$AP_CON"; then
|
||||
since=$(cat "$STAMP" 2>/dev/null || echo 0)
|
||||
[ $(( $(date +%s) - since )) -lt "$RESCAN_SECS" ] && exit 0
|
||||
|
||||
log "rescue AP up for ${RESCAN_SECS}s; dropping it to scan for '$SSID'"
|
||||
nmcli connection down "$AP_CON" >/dev/null 2>&1 || true
|
||||
sleep 2
|
||||
nmcli device wifi rescan ifname "$IFACE" >/dev/null 2>&1 || true
|
||||
sleep 5
|
||||
|
||||
scan=$(nmcli -t -f SSID device wifi list ifname "$IFACE" 2>/dev/null || true)
|
||||
if printf '%s\n' "$scan" | grep -qxF "$SSID"; then
|
||||
log "'$SSID' is back; rejoining"
|
||||
try=yes
|
||||
elif [ -z "$(printf '%s' "$scan" | tr -d '[:space:]')" ]; then
|
||||
# Nothing at all came back. Either the radio has not settled after
|
||||
# dropping the AP, or the home SSID is hidden and will never show up in
|
||||
# a scan. Worth 20 seconds to find out.
|
||||
log "scan came back empty; trying '$SSID' anyway"
|
||||
try=yes
|
||||
else
|
||||
# Other networks are visible and ours is not, so it really is gone.
|
||||
# Straight back to the AP -- no point spending the join timeout.
|
||||
try=no
|
||||
fi
|
||||
|
||||
if [ "$try" = yes ]; then
|
||||
if join_home; then
|
||||
log "rejoined '$SSID'"
|
||||
rm -f "$STAMP"
|
||||
exit 0
|
||||
fi
|
||||
log "join failed; returning to the rescue AP"
|
||||
fi
|
||||
start_ap
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log "offline and no rescue AP; trying '$SSID'"
|
||||
if join_home; then
|
||||
log "joined '$SSID'"
|
||||
exit 0
|
||||
fi
|
||||
start_ap
|
||||
@@ -0,0 +1,13 @@
|
||||
# {{ ansible_managed }}
|
||||
[Unit]
|
||||
Description=Run the Wi-Fi rescue watchdog every {{ labelprint_watchdog_interval_secs }}s
|
||||
|
||||
[Timer]
|
||||
# Waits for NetworkManager to have had a fair go at the home SSID before the
|
||||
# first check, so a slow DHCP lease at boot does not trip the rescue AP.
|
||||
OnBootSec=90
|
||||
OnUnitActiveSec={{ labelprint_watchdog_interval_secs }}
|
||||
AccuracySec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
Reference in New Issue
Block a user