Founder-approved 2026-09-03 (relay load side disconnected during build). systemd socket-activated, Tailscale-bound, token+source allowlist, EXIT-trap self-securing relay. 9/9 doorctl tests (auth matrix + relay cycle, mocked relay), full suite + shellcheck clean. https://projects.knownelement.com/issues/345
62 lines
1.9 KiB
Bash
Executable File
62 lines
1.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# bin/doorctl.sh — door unlock endpoint, systemd socket-activated.
|
|
#
|
|
# One instance per connection: systemd hands the accepted socket to
|
|
# stdin/stdout and exports REMOTE_ADDR. Reads a single HTTP request
|
|
# line; fires the door relay only for GET /unlock/<token> from an
|
|
# allowlisted source. Responds BEFORE the hold so callers are never
|
|
# blocked for the hold duration.
|
|
#
|
|
# Env (from /etc/default/doorman via the service unit):
|
|
# DOORMAN_UNLOCK_TOKEN shared secret in the URL path (required)
|
|
# DOORMAN_ALLOWED_SRC space-separated source IPs allowed to unlock
|
|
# DOORMAN_RELAY_DEV usbrelay device.channel (e.g. 3X9XI_1)
|
|
# DOORMAN_USBRELAY path to usbrelay binary
|
|
# DOORMAN_HOLD relay hold seconds
|
|
#
|
|
set -u
|
|
|
|
log() { logger -t doorctl -- "$1" 2>/dev/null || true; }
|
|
|
|
respond() { printf '%s\r\nContent-Length: 0\r\nConnection: close\r\n\r\n' "$1"; }
|
|
|
|
deny() {
|
|
respond 'HTTP/1.0 403 Forbidden'
|
|
log "DENY from ${REMOTE_ADDR:-unknown}: $2"
|
|
exit 0
|
|
}
|
|
|
|
read -r reqline || true
|
|
reqline=${reqline:-$'\r'}
|
|
method=${reqline%% *}
|
|
|
|
if [ "$method" != "GET" ]; then
|
|
deny "bad method"
|
|
fi
|
|
|
|
rest=${reqline#* }
|
|
path=${rest%% *}
|
|
|
|
[ "$path" = "/unlock/${DOORMAN_UNLOCK_TOKEN:-}" ] || deny "bad path or token"
|
|
|
|
allowed="${DOORMAN_ALLOWED_SRC:-}"
|
|
if [ -n "$allowed" ]; then
|
|
case " $allowed " in
|
|
*" ${REMOTE_ADDR:-} "*) : ;;
|
|
*) deny "source not allowlisted" ;;
|
|
esac
|
|
fi
|
|
|
|
hold="${DOORMAN_HOLD:-10}"
|
|
secure() { "${DOORMAN_USBRELAY:-/usr/bin/usbrelay}" "${DOORMAN_RELAY_DEV}=0" >/dev/null 2>&1; }
|
|
# Self-securing: whatever kills this script (systemd timeout, OOM, signal),
|
|
# the relay returns to 0 = door secure. Never rely on the happy path alone.
|
|
trap secure EXIT
|
|
log "UNLOCK from ${REMOTE_ADDR:-unknown}: ${DOORMAN_RELAY_DEV}=1 for ${hold}s"
|
|
respond 'HTTP/1.0 200 OK'
|
|
"${DOORMAN_USBRELAY:-/usr/bin/usbrelay}" "${DOORMAN_RELAY_DEV}=1" >/dev/null 2>&1
|
|
sleep "$hold"
|
|
secure
|
|
log "secure: ${DOORMAN_RELAY_DEV}=0"
|