#!/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
