Files
Axis_Cam_Tailscale/common/app/Tailscale_VPN_run
T
Weston Blieden 47c3894002 Consolidate variants into common/app and prepare for AXIS OS 13
Much of this work is AXIS OS 13 preparation. Of the OS 13 breaking
changes, all are now addressed except one: recompiled against the
updated SDK for 64-bit time (Y2038), migrated to Manifest Schema v2
with declared OS compatibility, audited all binaries for executable
stack (all clean, GNU_STACK rw-), and verified the web UI end to end
over HTTPS. The only outstanding item is signing through the Axis
ACAP Portal, pending a registered vendorId.

The four ACAP 4 variants (aarch64, armv7hf, and their ROOT versions)
carried byte-identical copies of the C bridge, run script, web UI, and
Makefile per architecture, diverging only between standard and ROOT.
Merge them into a single common/app/ tree:

- param_bridge.c: proxy-port parameters gated behind -DHAS_PROXY_PORTS
  (set via EXTRA_CFLAGS in the standard Dockerfiles); ROOT builds omit
  them as before
- Tailscale_VPN_run: variant passed as $1 ("standard"/"root") selects
  userspace vs kernel networking, port-collision checks, and IP
  forwarding for advertised routes
- index.html: detects proxy support at runtime from the settings
  response, hiding the proxy card and keeping the params out of save
  requests on ROOT builds (fixes ROOT UI always showing proxy fields
  and falsely reporting save errors)

Standard variants move to ACAP Native SDK 12.10.0 and Manifest Schema
v2 (vendorId, compatibleOsVersions); verified installable and working
on OS 10.12, 11.11, and 12.10, so OS 13 readiness costs no backward
compatibility. ROOT variants intentionally stay on SDK 1.15.1 since
OS 12+ never runs root apps.

All builds (including arm_acap3) now use the repository root as build
context with -f <variant>/Dockerfile; CI updated accordingly and a
.dockerignore added to keep the context lean. Tailscale binaries are
no longer tracked in git; *.eap outputs are now gitignored.

README: correct the standard variant's floor to OS 10.12+ and ROOT to
10.12-11.x (both live-verified), update build/update instructions for
the shared tree, and check off completed OS 13 readiness items.
2026-07-03 10:38:14 +02:00

178 lines
6.1 KiB
Bash

#!/bin/sh
# Tailscale VPN run script — called by the param_bridge C binary.
# Config is sourced from $STATE_DIR/params.conf (written by param_bridge).
# $1 selects the variant: "standard" (userspace networking + local proxies)
# or "root" (kernel networking, no local proxy). Defaults to "standard".
VARIANT="${1:-standard}"
killall tailscaled 2>/dev/null || true
APP_DIR="/usr/local/packages/Tailscale_VPN"
STATE_DIR="$APP_DIR/localdata"
TAILSCALED_PATH="$APP_DIR/lib/tailscaled"
TAILSCALE_PATH="$APP_DIR/lib/tailscale"
SOCKET_PATH="$STATE_DIR/tailscaled.sock"
mkdir -p "$STATE_DIR"
chmod 755 $TAILSCALED_PATH
chmod 755 $TAILSCALE_PATH
# Defaults — overridden by sourcing params.conf written by param_bridge
CUSTOM_SERVER=""
AUTH_KEY=""
CONF_HTTP="8080"
CONF_SOCKS="1080"
ACCEPT_DNS="false"
ACCEPT_ROUTES="false"
ADVERTISE_ROUTES=""
if [ -f "$STATE_DIR/params.conf" ]; then
. "$STATE_DIR/params.conf"
fi
if [ "$VARIANT" = "root" ]; then
logger -t "Tailscale_VPN" "Starting (root mode): custom_server=${CUSTOM_SERVER:-(default)}"
else
logger -t "Tailscale_VPN" "Starting: http_port=$CONF_HTTP socks_port=$CONF_SOCKS custom_server=${CUSTOM_SERVER:-(default)}"
# Check whether a TCP port is already bound
is_port_in_use() {
local port=$1
local hex_port
hex_port=$(printf '%04X' "$port")
grep -q ":${hex_port} " /proc/net/tcp 2>/dev/null && return 0
grep -q ":${hex_port} " /proc/net/tcp6 2>/dev/null && return 0
return 1
}
if is_port_in_use "$CONF_HTTP"; then
logger -t "Tailscale_VPN" "ERROR: HTTP proxy port $CONF_HTTP is already in use. Change it in Settings."
exit 1
fi
if is_port_in_use "$CONF_SOCKS"; then
logger -t "Tailscale_VPN" "ERROR: SOCKS5 port $CONF_SOCKS is already in use. Change it in Settings."
exit 1
fi
fi
logger -t "Tailscale_VPN" "Starting tailscaled daemon"
if [ "$VARIANT" = "root" ]; then
$TAILSCALED_PATH \
--state="$STATE_DIR/tailscaled.state" \
--socket=$SOCKET_PATH \
>/dev/null 2>&1 &
else
$TAILSCALED_PATH \
--state="$STATE_DIR/tailscaled.state" \
--socket=$SOCKET_PATH \
--socks5-server=localhost:$CONF_SOCKS \
--outbound-http-proxy-listen=localhost:$CONF_HTTP \
--tun=userspace-networking \
>/dev/null 2>&1 &
fi
TAILSCALED_PID=$!
sleep 2
TAILSCALE_CMD="$TAILSCALE_PATH --socket=$SOCKET_PATH up --reset --hostname=$(hostname)"
if [ -n "$CUSTOM_SERVER" ]; then
TAILSCALE_CMD="$TAILSCALE_CMD --login-server $CUSTOM_SERVER"
fi
if [ -n "$AUTH_KEY" ]; then
TAILSCALE_CMD="$TAILSCALE_CMD --authkey $AUTH_KEY"
fi
if [ "$ACCEPT_DNS" = "true" ]; then
TAILSCALE_CMD="$TAILSCALE_CMD --accept-dns=true"
fi
if [ "$ACCEPT_ROUTES" = "true" ]; then
TAILSCALE_CMD="$TAILSCALE_CMD --accept-routes=true"
fi
# Advertise LAN subnets so this camera acts as a subnet router. Comma-separated
# CIDRs (e.g. 192.168.1.0/24,10.0.0.0/8). In userspace-networking mode the
# tailscaled netstack forwards tailnet traffic to these subnets, so no kernel IP
# forwarding is required. In kernel-networking (root) mode the host must
# forward packets between the tailnet and the LAN, so enable IP forwarding.
# Routes must still be approved in the Tailscale admin console either way.
if [ -n "$ADVERTISE_ROUTES" ]; then
if [ "$VARIANT" = "root" ]; then
echo 1 > /proc/sys/net/ipv4/ip_forward 2>/dev/null || true
echo 1 > /proc/sys/net/ipv6/conf/all/forwarding 2>/dev/null || true
fi
TAILSCALE_CMD="$TAILSCALE_CMD --advertise-routes=$ADVERTISE_ROUTES"
fi
# Run `tailscale up` in the background and act on its outcome. If the node needs
# (re-)authentication, `up` blocks until the user logs in; backgrounding it
# ensures the status publisher below keeps running so the UI can surface the
# login URL (tailscaled reports BackendState=NeedsLogin + AuthURL while waiting).
# NOTE: `up` runs synchronously *inside* this backgrounded block so its real exit
# code is captured directly. We must NOT background `up` separately and `wait`
# for it from here, because in POSIX sh `wait` only works on children of the
# current shell — a subshell waiting on the parent's child returns 127.
{
eval "$TAILSCALE_CMD"
up_exit=$?
if [ "$up_exit" -eq 0 ]; then
if [ "$VARIANT" = "root" ]; then
logger -t "Tailscale_VPN" "Tailscale VPN is running (root mode)"
else
logger -t "Tailscale_VPN" "Tailscale VPN is running"
fi
# Auth succeeded with a one-time auth key — signal param_bridge to clear it
if [ -n "$AUTH_KEY" ]; then
: > "$STATE_DIR/authkey_clear"
fi
else
logger -t "Tailscale_VPN" "ERROR: tailscale up failed (exit $up_exit)"
fi
} &
TAILSCALE_UP_PID=$!
if [ "$VARIANT" != "root" ]; then
logger -t "Tailscale_VPN" "HTTP/HTTPS proxy: http://127.0.0.1:$CONF_HTTP"
logger -t "Tailscale_VPN" "SOCKS5 proxy: 127.0.0.1:$CONF_SOCKS"
fi
# Publish tailscale's real backend state as JSON for the web UI to consume.
# This is the authoritative connection signal (BackendState / Self.Online /
# TailscaleIPs / AuthURL) instead of scraping syslog. Served statically at
# /local/Tailscale_VPN/status.json.
STATUS_FILE="$APP_DIR/html/status.json"
publish_status() {
if "$TAILSCALE_PATH" --socket="$SOCKET_PATH" status --json > "$STATUS_FILE.tmp" 2>/dev/null; then
mv "$STATUS_FILE.tmp" "$STATUS_FILE" 2>/dev/null
chmod 644 "$STATUS_FILE" 2>/dev/null
else
rm -f "$STATUS_FILE.tmp" 2>/dev/null
fi
}
status_loop() {
while true; do
publish_status
sleep 5
done
}
status_loop &
STATUS_LOOP_PID=$!
# Clean up the status writer, up watcher, daemon and published status on
# stop/restart so param_bridge (which signals this script) leaves no orphans or
# stale state.
cleanup() {
[ -n "$STATUS_LOOP_PID" ] && kill "$STATUS_LOOP_PID" 2>/dev/null
[ -n "$TAILSCALE_UP_PID" ] && kill "$TAILSCALE_UP_PID" 2>/dev/null
[ -n "$TAILSCALED_PID" ] && kill "$TAILSCALED_PID" 2>/dev/null
rm -f "$STATUS_FILE" 2>/dev/null
exit 0
}
trap cleanup TERM INT
wait $TAILSCALED_PID