From 86c6ecb385232ad595c5ae066642560c88580412 Mon Sep 17 00:00:00 2001 From: Weston Blieden Date: Tue, 14 Apr 2026 21:04:04 +0200 Subject: [PATCH] =?UTF-8?q?fix(acap3):=20fetch=20tailscaled.log=20for=20st?= =?UTF-8?q?atus=20details,=20fix=20connecting=E2=86=92connected=20for=20al?= =?UTF-8?q?l=20variants?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - arm_acap3 start.sh: log IP, version and auth URL to syslog via 'tailscale ip/version' - arm_acap3 index.html: fetch tailscaled.log (symlinked into html/) in addition to syslog so IP, version, tailnet and -> Running state are always available - arm_acap3 Dockerfile: bake html/tailscaled.log symlink into .eap - all variants index.html: upgrade 'connecting' (without auth URL) to 'connected' when VAPIX list.cgi confirms Status=Running (was only upgrading from 'disconnected') --- aarch64/app/html/index.html | 2 +- aarch64_ROOT/app/html/index.html | 2 +- aarch64_custom/app/html/index.html | 2 +- arm/app/html/index.html | 2 +- arm_ROOT/app/html/index.html | 2 +- arm_acap3/Dockerfile | 44 ++ arm_acap3/app/Tailscale_VPN | 65 +++ arm_acap3/app/html/index.html | 707 +++++++++++++++++++++++++++++ arm_custom/app/html/index.html | 2 +- 9 files changed, 822 insertions(+), 6 deletions(-) create mode 100644 arm_acap3/Dockerfile create mode 100755 arm_acap3/app/Tailscale_VPN create mode 100644 arm_acap3/app/html/index.html diff --git a/aarch64/app/html/index.html b/aarch64/app/html/index.html index 589fdf0..6183343 100644 --- a/aarch64/app/html/index.html +++ b/aarch64/app/html/index.html @@ -618,7 +618,7 @@ checkAppRunning().then(function(running) { if (!running) { result.state = 'disconnected'; - } else if (result.state === 'disconnected') { + } else if (!result.url && result.state !== 'connected') { // App is running but syslog is empty/rotated result.state = 'connected'; result.ip = result.ip || cacheGet('ip'); diff --git a/aarch64_ROOT/app/html/index.html b/aarch64_ROOT/app/html/index.html index 33db9b6..e5a4419 100644 --- a/aarch64_ROOT/app/html/index.html +++ b/aarch64_ROOT/app/html/index.html @@ -618,7 +618,7 @@ checkAppRunning().then(function(running) { if (!running) { result.state = 'disconnected'; - } else if (result.state === 'disconnected') { + } else if (!result.url && result.state !== 'connected') { result.state = 'connected'; result.ip = result.ip || cacheGet('ip'); result.node = result.node || cacheGet('node'); diff --git a/aarch64_custom/app/html/index.html b/aarch64_custom/app/html/index.html index 0963bc8..9358891 100644 --- a/aarch64_custom/app/html/index.html +++ b/aarch64_custom/app/html/index.html @@ -626,7 +626,7 @@ checkAppRunning().then(function(running) { if (!running) { result.state = 'disconnected'; - } else if (result.state === 'disconnected') { + } else if (!result.url && result.state !== 'connected') { result.state = 'connected'; result.ip = result.ip || cacheGet('ip'); result.node = result.node || cacheGet('node'); diff --git a/arm/app/html/index.html b/arm/app/html/index.html index 33db9b6..e5a4419 100644 --- a/arm/app/html/index.html +++ b/arm/app/html/index.html @@ -618,7 +618,7 @@ checkAppRunning().then(function(running) { if (!running) { result.state = 'disconnected'; - } else if (result.state === 'disconnected') { + } else if (!result.url && result.state !== 'connected') { result.state = 'connected'; result.ip = result.ip || cacheGet('ip'); result.node = result.node || cacheGet('node'); diff --git a/arm_ROOT/app/html/index.html b/arm_ROOT/app/html/index.html index 33db9b6..e5a4419 100644 --- a/arm_ROOT/app/html/index.html +++ b/arm_ROOT/app/html/index.html @@ -618,7 +618,7 @@ checkAppRunning().then(function(running) { if (!running) { result.state = 'disconnected'; - } else if (result.state === 'disconnected') { + } else if (!result.url && result.state !== 'connected') { result.state = 'connected'; result.ip = result.ip || cacheGet('ip'); result.node = result.node || cacheGet('node'); diff --git a/arm_acap3/Dockerfile b/arm_acap3/Dockerfile new file mode 100644 index 0000000..bc0b665 --- /dev/null +++ b/arm_acap3/Dockerfile @@ -0,0 +1,44 @@ +ARG UBUNTU_VERSION=20.04 +FROM axisecp/acap-sdk:3.5-armv7hf-ubuntu${UBUNTU_VERSION} + +RUN apt-get update -qq && apt-get install -y --no-install-recommends upx-ucl && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +COPY ./app /opt/app/ +WORKDIR /opt/app + +# Rename the shell startup script (the ELF launcher will take the Tailscale_VPN name) +RUN mv Tailscale_VPN start.sh && chmod +x start.sh + +# Compile a minimal ELF launcher as APPNAME. +# ACAP 3 elflibcheck requires an ELF binary and uses pidof(APPNAME) for status. +# We fork+exec start.sh so the parent "Tailscale_VPN" process stays resident, +# making pidof find it and the camera UI correctly show Running/Stopped. +RUN . /opt/axis/acapsdk/environment-setup* && \ + ${CC} -o Tailscale_VPN launcher.c && \ + ${STRIP} -s Tailscale_VPN + +# Strip then UPX-compress the Tailscale binaries so they fit on flash +RUN . /opt/axis/acapsdk/environment-setup* && \ + ${STRIP} -s lib/tailscale lib/tailscaled 2>/dev/null || true && \ + upx --best lib/tailscale lib/tailscaled + +# ACAP 3 firmware expects the settings page at the app root, not in html/ +RUN cp html/index.html index.html + +# Symlink tailscaled.log into html/ so the web UI can fetch it via HTTP. +# The log is written at runtime to localdata/ (resolved path at runtime). +RUN ln -sf ../localdata/tailscaled.log html/tailscaled.log + +# Build and package +RUN . /opt/axis/acapsdk/environment-setup* && create-package.sh ./ + +# Patch STARTMODE: create-package.sh hardcodes "never" unless RESTRICTION_STARTMODE is set, +# but the ACAP 3 SDK does not honour our package.conf's STARTMODE=respawn without it. +# Repack the .eap with the corrected value. +RUN for eap in *.eap; do \ + tmpdir=$(mktemp -d) && tar xf "$eap" -C "$tmpdir" && \ + sed -i 's/STARTMODE="never"/STARTMODE="respawn"/' "$tmpdir/package.conf" && \ + (cd "$tmpdir" && tar czf "/opt/app/$eap" .) && \ + rm -rf "$tmpdir"; \ + done diff --git a/arm_acap3/app/Tailscale_VPN b/arm_acap3/app/Tailscale_VPN new file mode 100755 index 0000000..88cb99d --- /dev/null +++ b/arm_acap3/app/Tailscale_VPN @@ -0,0 +1,65 @@ +#!/bin/sh + +APP_DIR="/usr/local/packages/Tailscale_VPN" +STATE_DIR="$APP_DIR/localdata" + +logger -t "Tailscale_VPN" "Starting Tailscale VPN service (userspace networking)" + +mkdir -p "$STATE_DIR" +chmod 755 "$APP_DIR/lib/tailscale" +chmod 755 "$APP_DIR/lib/tailscaled" + +# Kill any leftover daemon from a previous run +killall tailscaled 2>/dev/null || true + +logger -t "Tailscale_VPN" "Starting tailscaled daemon" +# Log to file (not piped through logger) -- avoids extra logger process holding +# tailscaled stdout open, which prevents our wait loop from detecting exit +"$APP_DIR/lib/tailscaled" \ + --state="$STATE_DIR/tailscaled.state" \ + --socket="$STATE_DIR/tailscaled.sock" \ + --socks5-server=localhost:1055 \ + --outbound-http-proxy-listen=localhost:8080 \ + --tun=userspace-networking \ + >> "$STATE_DIR/tailscaled.log" 2>&1 & +TAILSCALED_PID=$! + +# Wait for socket to appear (up to 15 seconds) +i=0 +while [ $i -lt 15 ] && [ ! -S "$STATE_DIR/tailscaled.sock" ]; do + sleep 1 + i=$((i + 1)) +done + +logger -t "Tailscale_VPN" "Connecting to Tailscale network" +# --timeout=10s: tailscale up exits promptly after connecting (or giving up), +# preventing two large Go binaries running simultaneously and causing OOM on +# cameras with limited RAM (e.g. 222 MB). +# Capture output so we can extract auth URL and log it to syslog for the web UI. +UP_OUT=$("$APP_DIR/lib/tailscale" \ + --socket="$STATE_DIR/tailscaled.sock" \ + up --hostname="$(hostname)" --timeout=10s 2>&1) || true +echo "$UP_OUT" >> "$STATE_DIR/tailscaled.log" + +# If an auth URL was returned, log it so the web UI can show it +AUTH_URL=$(echo "$UP_OUT" | grep -o 'https://login\.tailscale\.com/[^ ]*' | head -1) +[ -n "$AUTH_URL" ] && logger -t "Tailscale_VPN" "Auth required: $AUTH_URL" + +# Log IP and version into syslog so the web UI details panel can populate +TS_IP=$("$APP_DIR/lib/tailscale" --socket="$STATE_DIR/tailscaled.sock" ip -4 2>/dev/null | head -1) +TS_VER=$("$APP_DIR/lib/tailscale" --socket="$STATE_DIR/tailscaled.sock" version 2>/dev/null | head -1) +[ -n "$TS_IP" ] && logger -t "Tailscale_VPN" "Tailscale IP: $TS_IP" +[ -n "$TS_VER" ] && logger -t "Tailscale_VPN" "Tailscale version: $TS_VER" + +logger -t "Tailscale_VPN" "Tailscale VPN is running" +logger -t "Tailscale_VPN" "HTTP/HTTPS proxy: http://127.0.0.1:8080" +logger -t "Tailscale_VPN" "SOCKS5 proxy: 127.0.0.1:1055" + +# Monitoring loop: stay alive while tailscaled is running. +# This keeps the parent Tailscale_VPN (C launcher) in the process table +# so pidof finds it and the camera web UI shows "Running" instead of "Stopped". +while kill -0 "$TAILSCALED_PID" 2>/dev/null; do + sleep 5 +done + +logger -t "Tailscale_VPN" "tailscaled exited" diff --git a/arm_acap3/app/html/index.html b/arm_acap3/app/html/index.html new file mode 100644 index 0000000..c474532 --- /dev/null +++ b/arm_acap3/app/html/index.html @@ -0,0 +1,707 @@ + + + + + Tailscale VPN + + + + +
+
+ + + + + + + + + + + + +

Tailscale VPN

+
+ +
+ + +
+ + Checking... + +
+ + +
+
Update available:
+ + + Download + +
+ + + + + + + + +
+
+
Service Log
+
+ + +
+
+
Loading logs...
+
+ +
+ + Auto-refresh every 5s +
+ + + + + diff --git a/arm_custom/app/html/index.html b/arm_custom/app/html/index.html index 0963bc8..9358891 100644 --- a/arm_custom/app/html/index.html +++ b/arm_custom/app/html/index.html @@ -626,7 +626,7 @@ checkAppRunning().then(function(running) { if (!running) { result.state = 'disconnected'; - } else if (result.state === 'disconnected') { + } else if (!result.url && result.state !== 'connected') { result.state = 'connected'; result.ip = result.ip || cacheGet('ip'); result.node = result.node || cacheGet('node');