Sanitized mirror from private repository - 2026-04-20 01:32:01 UTC
Some checks failed
Documentation / Build Docusaurus (push) Failing after 5m3s
Documentation / Deploy to GitHub Pages (push) Has been skipped

This commit is contained in:
Gitea Mirror Bot
2026-04-20 01:32:01 +00:00
commit e7652c8dab
1445 changed files with 364095 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
[Unit]
Description=Tailscale re-auth watchdog
After=network-online.target tailscaled.service
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/etc/tailscale/watchdog.sh
Nice=5

View File

@@ -0,0 +1,10 @@
[Unit]
Description=Run tailscale watchdog every 5 min
[Timer]
OnBootSec=2min
OnUnitActiveSec=5min
Unit=tailscale-watchdog.service
[Install]
WantedBy=timers.target

View File

@@ -0,0 +1,91 @@
#!/usr/bin/env bash
# Tailscale re-auth watchdog for Steam Deck.
# Runs every 5 min via systemd timer. If tailscale is logged out / stopped,
# refreshes the /etc/hosts pin for headscale.vish.gg (Deck's own DNS may fail
# when off-LAN) and re-runs `tailscale up` with the stored reusable key.
set -u
LOG=/var/log/tailscale-watchdog.log
AUTHKEY_FILE=/etc/tailscale/authkey
HEADSCALE_HOST=headscale.vish.gg
LOGIN_SERVER=https://${HEADSCALE_HOST}:8443
TS=/opt/tailscale/tailscale
log() { printf '%s %s\n' "$(date -u +%FT%TZ)" "$*" >> "$LOG"; }
get_backend_state() {
"$TS" status --json 2>/dev/null | python3 -c '
import json, sys
try:
print(json.load(sys.stdin).get("BackendState", ""))
except Exception:
print("")
'
}
need_reauth() {
if ! pidof tailscaled >/dev/null; then
log "tailscaled not running"
return 0
fi
local state
state=$(get_backend_state)
case "$state" in
Running) return 1 ;;
NeedsLogin|Stopped|NoState|"") log "BackendState=$state"; return 0 ;;
*) return 1 ;;
esac
}
resolve_headscale_public() {
# DNS-over-HTTPS via Google (then Cloudflare). Returns an A record or empty.
python3 - "$HEADSCALE_HOST" <<'PY'
import json, sys, urllib.request, urllib.error
name = sys.argv[1]
for url in (
f"https://dns.google/resolve?name={name}&type=A",
f"https://1.1.1.1/dns-query?name={name}&type=A",
):
try:
req = urllib.request.Request(url, headers={"accept": "application/dns-json"})
with urllib.request.urlopen(req, timeout=4) as r:
d = json.load(r)
for a in d.get("Answer", []):
if a.get("type") == 1:
print(a["data"])
sys.exit(0)
except Exception:
continue
sys.exit(1)
PY
}
refresh_hosts_pin() {
local ip current
ip=$(resolve_headscale_public) || true
if [[ -z "$ip" ]]; then
log "could not resolve $HEADSCALE_HOST via DoH"
return
fi
current=$(grep -E "[[:space:]]${HEADSCALE_HOST}$" /etc/hosts | awk '{print $1}' | head -1)
if [[ "$current" != "$ip" ]]; then
sed -i.bak "/[[:space:]]${HEADSCALE_HOST}\$/d" /etc/hosts
printf '%s %s\n' "$ip" "$HEADSCALE_HOST" >> /etc/hosts
log "pinned $HEADSCALE_HOST -> $ip (was ${current:-none})"
fi
}
if need_reauth; then
refresh_hosts_pin
if [[ -r "$AUTHKEY_FILE" ]]; then
AUTHKEY=$(cat "$AUTHKEY_FILE")
if "$TS" up --login-server="$LOGIN_SERVER" --authkey="$AUTHKEY" --accept-routes=false --hostname=deck >> "$LOG" 2>&1; then
log "tailscale up succeeded"
else
log "tailscale up failed (rc=$?)"
fi
else
log "missing $AUTHKEY_FILE"
fi
fi