#!/bin/sh # kit-connect daemon — manages Tailscale + reverse SSH tunnel. # Config: /etc/kit-connect/connect.conf (fetched from hub on install) # One SPK, all routers. Hub assigns ports and keys. PKG="kit-connect" PKG_DIR="/var/packages/${PKG}/target" CONF="/etc/${PKG}/connect.conf" KEY="$PKG_DIR/bin/connect_id_ed25519" DAEMON_PID_FILE="$PKG_DIR/var/daemon.pid" LOG_FILE="$PKG_DIR/var/connect.log" LOG_TAG="kit-connect" log() { logger -t "$LOG_TAG" -p local0.warn "$*" printf '%s %s: %s\n' "$(date '+%b %d %H:%M:%S')" "$LOG_TAG" "$*" >> "$LOG_FILE" _lc=$(wc -l < "$LOG_FILE" 2>/dev/null || echo 0) [ "$_lc" -gt 100 ] && tail -n 60 "$LOG_FILE" > "${LOG_FILE}.tmp" && mv "${LOG_FILE}.tmp" "$LOG_FILE" } mkdir -p "$PKG_DIR/var" chmod 600 "$KEY" 2>/dev/null # ── Load config ──────────────────────────────────────────────────── load_config() { [ -f "$CONF" ] && . "$CONF" 2>/dev/null : "${DEVICE_ID:=unknown}" : "${HUB_HOST:=162.243.83.36}" : "${HUB_PORT:=8080}" : "${TUNNEL_ENABLE:=1}" : "${TAILSCALE_ENABLE:=1}" # Tunnel settings (hub-assigned or defaults) : "${TUNNEL_REMOTE_HOST:=162.243.83.36}" : "${TUNNEL_REMOTE_USER:=node}" : "${TUNNEL_REMOTE_SSH_PORT:=22}" : "${TUNNEL_REMOTE_PORT:=0}" # 0 = auto-assign by hub : "${TUNNEL_LOCAL_SSH_PORT:=2223}" : "${TUNNEL_RETRY_DELAY:=30}" # Tailscale settings : "${TAILSCALE_STATEDIR:=/var/packages/Tailscale/var/state}" : "${TAILSCALE_SOCKET:=/var/packages/Tailscale/var/run/tailscaled.sock}" : "${TAILSCALE_AUTH_KEY:=}" : "${TAILSCALE_HOSTNAME:=${DEVICE_ID}}" # Tailscale binary location (varies: SRM has /usr/local/bin, GL has /usr/bin) : "${TAILSCALE_BIN:=$(command -v tailscale 2>/dev/null || echo /usr/local/bin/tailscale)}" : "${TAILSCALED_BIN:=$(command -v tailscaled 2>/dev/null || echo /usr/local/bin/tailscaled)}" # watchdog interval (seconds between forward health checks) : "${WATCHDOG_INTERVAL:=60}" : "${FORWARD_FAIL_LIMIT:=2}" } log "EVENT=DAEMON_START device=${DEVICE_ID}" # ── Start Tailscale ──────────────────────────────────────────────── start_tailscale() { [ "$TAILSCALE_ENABLE" != "1" ] && { log "tailscale: disabled in config"; return 0; } [ -z "$TAILSCALE_AUTH_KEY" ] && { log "tailscale: no auth key — skipping"; return 0; } if [ ! -x "$TAILSCALED_BIN" ]; then log "tailscale: tailscaled not found at $TAILSCALED_BIN (SRM PATH lacks /usr/local/bin)" return 1 fi if [ ! -x "$TAILSCALE_BIN" ]; then log "tailscale: binary not found at $TAILSCALE_BIN" return 1 fi mkdir -p "$(dirname "$TAILSCALE_STATEDIR")" "$(dirname "$TAILSCALE_SOCKET")" # Already running? if "$TAILSCALE_BIN" --socket="$TAILSCALE_SOCKET" status >/dev/null 2>&1; then log "tailscale: already connected — $( "$TAILSCALE_BIN" --socket="$TAILSCALE_SOCKET" ip -4 2>/dev/null || echo no-ip)" return 0 fi # Start tailscaled (userspace mode for SRM compat) log "tailscale: starting tailscaled..." /usr/bin/setsid "$TAILSCALED_BIN" \ --statedir="$TAILSCALE_STATEDIR" \ --tun=userspace-networking \ --socket="$TAILSCALE_SOCKET" \ >/dev/null 2>&1 & sleep 4 # Authenticate log "tailscale: authenticating with auth key..." "$TAILSCALE_BIN" --socket="$TAILSCALE_SOCKET" up \ --auth-key "$TAILSCALE_AUTH_KEY" \ --hostname "${TAILSCALE_HOSTNAME}" \ --accept-routes=false \ --accept-dns=false 2>&1 | while read line; do log "tailscale: $line"; done ts_ip=$("$TAILSCALE_BIN" --socket="$TAILSCALE_SOCKET" ip -4 2>/dev/null || echo "unknown") log "tailscale: connected — IP=${ts_ip}" } # ── Start reverse SSH tunnel ─────────────────────────────────────── start_tunnel() { [ "$TUNNEL_ENABLE" != "1" ] && { log "tunnel: disabled in config"; return 0; } [ "$TUNNEL_REMOTE_PORT" = "0" ] && { log "tunnel: no port assigned — run hub registration first"; return 0; } log "tunnel: opening R:0.0.0.0:${TUNNEL_REMOTE_PORT} -> 127.0.0.1:${TUNNEL_LOCAL_SSH_PORT}" ssh \ -o StrictHostKeyChecking=no \ -o UserKnownHostsFile=/dev/null \ -o ServerAliveInterval=15 \ -o ServerAliveCountMax=3 \ -o ExitOnForwardFailure=yes \ -o BatchMode=yes \ -p "${TUNNEL_REMOTE_SSH_PORT}" \ -N \ -R "0.0.0.0:${TUNNEL_REMOTE_PORT}:127.0.0.1:${TUNNEL_LOCAL_SSH_PORT}" \ -i "$KEY" \ "${TUNNEL_REMOTE_USER}@${TUNNEL_REMOTE_HOST}" \ 2>"$PKG_DIR/var/ssh_err.tmp" & SSH_PID=$! echo "$SSH_PID" > "$PKG_DIR/var/tunnel.pid" log "tunnel: SSH PID=${SSH_PID} port=${TUNNEL_REMOTE_PORT}" # Watchdog loop _fail_count=0 while kill -0 "$SSH_PID" 2>/dev/null; do sleep "$WATCHDOG_INTERVAL" if ! kill -0 "$SSH_PID" 2>/dev/null; then break; fi # Probe forward health if timeout 10 ssh \ -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ -o ConnectTimeout=5 -o BatchMode=yes \ -p "${TUNNEL_REMOTE_SSH_PORT}" -i "$KEY" \ "${TUNNEL_REMOTE_USER}@${TUNNEL_REMOTE_HOST}" \ "timeout 3 bash -c 'echo >/dev/tcp/127.0.0.1/${TUNNEL_REMOTE_PORT}' 2>/dev/null" \ 2>/dev/null; then [ "$_fail_count" -gt 0 ] && log "tunnel: forward recovered after ${_fail_count} checks" _fail_count=0 else _fail_count=$((_fail_count + 1)) log "tunnel: forward check failed (${_fail_count}/${FORWARD_FAIL_LIMIT})" [ "$_fail_count" -ge "$FORWARD_FAIL_LIMIT" ] && break fi done [ -f "$PKG_DIR/var/tunnel.pid" ] && kill "$(cat "$PKG_DIR/var/tunnel.pid")" 2>/dev/null rm -f "$PKG_DIR/var/tunnel.pid" log "tunnel: exited — will retry in ${TUNNEL_RETRY_DELAY}s" sleep "$TUNNEL_RETRY_DELAY" return 1 } # ── Hub registration — get assigned port ─────────────────────────── register_with_hub() { # Already registered — skip if [ "$TUNNEL_REMOTE_PORT" != "0" ] && [ -n "$TUNNEL_REMOTE_PORT" ]; then return 0 fi log "hub: registering device ${DEVICE_ID}..." # --max-time 15: total timeout (connect + response). SRM busybox curl needs this # or the daemon hangs forever if the hub accepts the TCP connection but # never sends the HTTP response. resp="" resp=$(curl -s --connect-timeout 10 --max-time 15 \ "http://${HUB_HOST}:${HUB_PORT}/api/register/${DEVICE_ID}" 2>/dev/null) || true if [ -z "$resp" ]; then log "hub: unreachable — will retry next cycle" return 1 fi # Parse JSON response — POSIX-safe, no jq dependency port="" tskey="" port=$(printf '%s' "$resp" | sed -n 's/.*"tunnel_port"[[:space:]]*:[[:space:]]*\([0-9]*\).*/\1/p') tskey=$(printf '%s' "$resp" | sed -n 's/.*"tailscale_auth_key"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p') if [ -n "$port" ] && [ "$port" != "0" ]; then sed -i "s/^TUNNEL_REMOTE_PORT=.*/TUNNEL_REMOTE_PORT=${port}/" "$CONF" 2>/dev/null TUNNEL_REMOTE_PORT="$port" log "hub: assigned port ${port}" fi if [ -n "$tskey" ] && [ "$tskey" != "$TAILSCALE_AUTH_KEY" ]; then sed -i "s|^TAILSCALE_AUTH_KEY=.*|TAILSCALE_AUTH_KEY=${tskey}|" "$CONF" 2>/dev/null TAILSCALE_AUTH_KEY="$tskey" log "hub: updated Tailscale key" fi } # ── Main ──────────────────────────────────────────────────────────── load_config echo "$$" > "$DAEMON_PID_FILE" while true; do load_config # Register with hub if needed register_with_hub 2>/dev/null || true # Start Tailscale in background (stays running) start_tailscale 2>/dev/null || true # Start tunnel (blocking — restarts on failure) start_tunnel 2>/dev/null || true log "daemon: both services running" wait # Wait for any child to die, then restart done