diff --git a/aarch64/Dockerfile b/aarch64/Dockerfile index e0ece0f..3e7edd2 100644 --- a/aarch64/Dockerfile +++ b/aarch64/Dockerfile @@ -6,7 +6,6 @@ ARG SDK=acap-native-sdk FROM ${REPO}/${SDK}:${VERSION}-${ARCH}-ubuntu${UBUNTU_VERSION} -# Building the ACAP application COPY common/app /opt/app/ COPY aarch64/app/manifest.json /opt/app/manifest.json WORKDIR /opt/app diff --git a/aarch64_ROOT/Dockerfile b/aarch64_ROOT/Dockerfile index e69bdcc..c011a1c 100644 --- a/aarch64_ROOT/Dockerfile +++ b/aarch64_ROOT/Dockerfile @@ -6,7 +6,6 @@ ARG SDK=acap-native-sdk FROM ${REPO}/${SDK}:${VERSION}-${ARCH}-ubuntu${UBUNTU_VERSION} -# Building the ACAP application COPY common/app /opt/app/ COPY aarch64_ROOT/app/manifest.json /opt/app/manifest.json WORKDIR /opt/app diff --git a/arm/Dockerfile b/arm/Dockerfile index ff5f7bc..7c6e197 100644 --- a/arm/Dockerfile +++ b/arm/Dockerfile @@ -6,7 +6,6 @@ ARG SDK=acap-native-sdk FROM ${REPO}/${SDK}:${VERSION}-${ARCH}-ubuntu${UBUNTU_VERSION} -# Building the ACAP application COPY common/app /opt/app/ COPY arm/app/manifest.json /opt/app/manifest.json WORKDIR /opt/app diff --git a/arm_ROOT/Dockerfile b/arm_ROOT/Dockerfile index 7a4e8bb..b39cf3f 100644 --- a/arm_ROOT/Dockerfile +++ b/arm_ROOT/Dockerfile @@ -6,7 +6,6 @@ ARG SDK=acap-native-sdk FROM ${REPO}/${SDK}:${VERSION}-${ARCH}-ubuntu${UBUNTU_VERSION} -# Building the ACAP application COPY common/app /opt/app/ COPY arm_ROOT/app/manifest.json /opt/app/manifest.json WORKDIR /opt/app diff --git a/arm_acap3/Dockerfile b/arm_acap3/Dockerfile index 2d04284..7fd57e7 100644 --- a/arm_acap3/Dockerfile +++ b/arm_acap3/Dockerfile @@ -7,18 +7,15 @@ RUN apt-get update -qq && apt-get install -y --no-install-recommends upx-ucl && COPY arm_acap3/app /opt/app/ WORKDIR /opt/app -# Rename the shell startup script (the ELF launcher will take the Tailscale_VPN name) +# The ELF launcher takes over 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. +# ACAP 3 elflibcheck needs APPNAME to be ELF and uses pidof(APPNAME) for status. 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 +# UPX so the binaries 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 @@ -26,20 +23,15 @@ RUN . /opt/axis/acapsdk/environment-setup* && \ # 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). +# Runtime files live in localdata/; symlink them so the web UI can fetch them. RUN ln -sf ../localdata/tailscaled.log html/tailscaled.log -# Symlink the runtime status.json (written by start.sh from `tailscale status -# --json`) into html/ so the web UI can read Tailscale's authoritative state. RUN ln -sf ../localdata/status.json html/status.json -# 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. +# create-package.sh hardcodes STARTMODE="never" (ignoring package.conf) unless +# RESTRICTION_STARTMODE is set, so repack the .eap with respawn. RUN for eap in *.eap; do \ tmpdir=$(mktemp -d) && tar xf "$eap" -C "$tmpdir" && \ sed -i 's/STARTMODE="never"/STARTMODE="respawn"/' "$tmpdir/package.conf" && \ diff --git a/arm_acap3/app/html/index.html b/arm_acap3/app/html/index.html index 05b13ce..14073ba 100644 --- a/arm_acap3/app/html/index.html +++ b/arm_acap3/app/html/index.html @@ -300,14 +300,12 @@ -
Checking...
-
Update available:
@@ -316,7 +314,6 @@
- - -
Service Log
@@ -436,11 +431,11 @@ tsIP = allIPs.length ? allIPs[allIPs.length - 1] : null; } - // Primary: extract hostname from Axis syslog header (always the real device hostname) + // The syslog header always carries the real device hostname. var node = null; var hostLine = txt.match(/\d{4}-\d{2}-\d{2}T[\d:.]+[+-]\d{2}:\d{2}\s+(\S+)\s+\[/); if (hostLine) node = hostLine[1]; - // Fallback: popBrowserAuthNow/StartLoginInteractiveAs (may contain stale acap-tailscale_vpn) + // Fallback; may contain a stale acap-tailscale_vpn name. if (!node) { var nodeMatches = txt.match(/popBrowserAuthNow\("([^"]+)"\)/g); if (!nodeMatches) nodeMatches = txt.match(/StartLoginInteractiveAs\("([^"]+)"\)/g); @@ -493,7 +488,6 @@ if (acap3ip) tsIP = acap3ip[1]; } - // Cache when found, restore from cache when missing cacheSet('ip', tsIP); cacheSet('node', node); cacheSet('tailnet', tailnet); cacheSet('version', version); tsIP = tsIP || cacheGet('ip'); node = node || cacheGet('node'); @@ -609,10 +603,8 @@ .catch(function() { return false; }); } - // ACAP3: also fetch the raw tailscaled.log (symlinked into html/) so the parser - // can find IP, version, tailnet and Running state from tailscaled's own output. + // ACAP3: tailscaled's own log has the IP, version, tailnet and Running state. var DAEMON_LOG_URL = 'tailscaled.log'; - // Authoritative backend state published by start.sh (symlinked into html/). var STATUS_URL = 'status.json'; // Ground truth published by start.sh from `tailscale status --json`. @@ -633,7 +625,6 @@ if (st.Version) result.version = String(st.Version).split('-')[0]; if (bs === 'Running' && self.Online === true) { - // Genuinely connected and reachable on the tailnet result.state = 'connected'; result.url = null; result.ip = ip4 || result.ip; @@ -645,9 +636,8 @@ result.state = 'connecting'; result.url = st.AuthURL || result.url; } else if (bs === 'Running') { - // Backend running but node not online: transient network drop or the - // node was removed/expired and needs re-auth. Not connected. Keep any - // login URL the log parser found so the login button still appears. + // Not online: transient drop or node removed/expired. Keep the log + // parser's URL so the login button still appears. result.state = 'connecting'; result.url = st.AuthURL || result.url; } else if (bs === 'Stopped') { @@ -672,8 +662,8 @@ var txt = res[0] + '\n' + res[1]; var st = res[2]; var result = parse(txt); - renderLogs(res[0]); // show syslog in log panel; daemon log is too verbose - // Always verify with the app status API - logs can have stale entries + renderLogs(res[0]); // daemon log is too verbose to show + // Logs can have stale entries; confirm the app is running. checkAppRunning().then(function(running) { if (!running) { result.state = 'disconnected'; diff --git a/arm_acap3/app/launcher.c b/arm_acap3/app/launcher.c index 19cdd42..4548e0a 100644 --- a/arm_acap3/app/launcher.c +++ b/arm_acap3/app/launcher.c @@ -4,19 +4,9 @@ #include /* - * ACAP 3 supervisor launcher for Tailscale_VPN. - * - * elflibcheck.sh requires APPNAME to be an ELF binary. - * acap-startstop / respawnd / list.cgi all use pidof(APPNAME) for status. - * - * This binary NEVER exits voluntarily — it loops restarting start.sh if it - * dies, so: - * - pidof Tailscale_VPN always finds this process → UI shows "Running" - * - respawnd never triggers (it only fires when APPNAME exits) - * - If tailscaled OOMs and start.sh exits, we cleanly restart it - * - * To stop the app, acap-startstop calls stop_daemon which sends SIGTERM here. - * We forward SIGTERM/SIGINT to the child and then exit. + * ACAP 3 launcher: elflibcheck needs APPNAME to be ELF, and acap-startstop, + * respawnd and list.cgi use pidof(APPNAME). So this stays resident, restarts + * start.sh whenever it dies, and only exits on SIGTERM/SIGINT. */ static volatile int g_stop = 0; @@ -37,7 +27,6 @@ int main(void) while (!g_stop) { pid_t pid = fork(); if (pid == 0) { - /* child: reset signals and exec start.sh */ signal(SIGTERM, SIG_DFL); signal(SIGINT, SIG_DFL); execl("/usr/local/packages/Tailscale_VPN/start.sh", @@ -59,7 +48,6 @@ int main(void) g_child = -1; if (!g_stop) { - /* start.sh died unexpectedly — wait before restarting */ sleep(3); } } diff --git a/build.sh b/build.sh index 523f38d..0c856cd 100755 --- a/build.sh +++ b/build.sh @@ -4,13 +4,9 @@ # ./build.sh # build every variant # ./build.sh aarch64 arm # build only the named variant folders # -# Downloads the prebuilt Tailscale binaries, strips them, then builds each -# variant folder that contains an app/ directory. Variant folders map to the -# .eap suffixes used in releases: *_ROOT -> _root, *_acap3 -> _acap3. -# -# Override the container runtime with RUNTIME=docker|podman. -# TAILSCALE_VERSION pins the upstream binaries; it defaults to whatever -# ci/upstream-version.sh resolves. +# Variant folders map to release .eap suffixes: *_ROOT -> _root, *_acap3 -> _acap3. +# RUNTIME=docker|podman forces a container runtime; TAILSCALE_VERSION overrides +# the version resolved by ci/upstream-version.sh. set -eu REPO_ROOT=$(cd -P "$(dirname "$0")" && pwd) @@ -57,10 +53,8 @@ fetch_arch() { fetch_arch arm arm fetch_arch arm64 arm64 -# Tailscale is the only upstream here that ships binaries with symbols, so the -# strip is worth ~23 MB per package. It runs inside the SDK container: relying -# on host cross-binutils meant a machine without them silently produced an -# unstripped package under the same version number. +# Upstream ships unstripped binaries; stripping saves ~23 MB per package. Do it +# inside the SDK container: without host cross-binutils it silently did nothing. SDK_IMAGE=axisecp/acap-native-sdk:12.10.0 SDK_UBUNTU=ubuntu24.04 diff --git a/common/app/html/index.html b/common/app/html/index.html index 19dcfc3..312f2ba 100644 --- a/common/app/html/index.html +++ b/common/app/html/index.html @@ -351,14 +351,12 @@
-
Checking...
-
Update available:
@@ -367,7 +365,6 @@
- - - + -
Settings
@@ -474,7 +469,6 @@
-
Service Log
@@ -559,11 +553,11 @@ tsIP = allIPs.length ? allIPs[allIPs.length - 1] : null; } - // Primary: extract hostname from Axis syslog header (always the real device hostname) + // The syslog header always carries the real device hostname. var node = null; var hostLine = txt.match(/\d{4}-\d{2}-\d{2}T[\d:.]+[+-]\d{2}:\d{2}\s+(\S+)\s+\[/); if (hostLine) node = hostLine[1]; - // Fallback: popBrowserAuthNow/StartLoginInteractiveAs (may contain stale acap-tailscale_vpn) + // Fallback; may contain a stale acap-tailscale_vpn name. if (!node) { var nodeMatches = txt.match(/popBrowserAuthNow\("([^"]+)"\)/g); if (!nodeMatches) nodeMatches = txt.match(/StartLoginInteractiveAs\("([^"]+)"\)/g); @@ -605,7 +599,7 @@ } } - // Parse proxy ports from log — use last match so old entries don't win + // Use the last match so older log entries don't win. var httpPort = null; var httpProxyMatches = txt.match(/HTTP\/HTTPS proxy: http:\/\/127\.0\.0\.1:(\d+)/g); if (httpProxyMatches) { var m = httpProxyMatches[httpProxyMatches.length - 1].match(/:(\d+)$/); if (m) httpPort = m[1]; } @@ -613,7 +607,6 @@ var socksProxyMatches = txt.match(/SOCKS5 proxy:\s+127\.0\.0\.1:(\d+)/g); if (socksProxyMatches) { var ms = socksProxyMatches[socksProxyMatches.length - 1].match(/:(\d+)$/); if (ms) socksPort = ms[1]; } - // Cache when found, restore from cache when missing cacheSet('ip', tsIP); cacheSet('node', node); cacheSet('tailnet', tailnet); cacheSet('version', version); cacheSet('http-port', httpPort); cacheSet('socks-port', socksPort); tsIP = tsIP || cacheGet('ip'); @@ -627,9 +620,8 @@ var lastState = stateLines.length ? stateLines[stateLines.length - 1] : ''; var isRunning = /-> Running/.test(lastState); - // Fallbacks only when syslog has rotated and no state transitions are visible. - // If we CAN see state lines (e.g. "-> NeedsLogin"), trust them over our own - // "Tailscale VPN is running" message which stays in syslog indefinitely. + // Only when syslog rotated away all state lines: our own "Tailscale VPN + // is running" line lingers in syslog and must not override them. if (!isRunning && stateLines.length === 0) { isRunning = /Tailscale VPN is running/.test(txt) || /health\(warnable=[^)]+\): ok/.test(txt) || @@ -638,12 +630,11 @@ /localapi:/.test(txt); } - // If an auth URL appears AFTER the last Running state, re-auth is needed - // (handles stale Running entries in syslog after reinstall or token expiry) + // An auth URL after the last Running state means re-auth is needed + // (stale Running entries after reinstall or token expiry). if (isRunning && latestUrl) { - // Use the LATEST of '-> Running' (tailscaled state) or 'Tailscale VPN is running' - // (our shell log). The shell log is written AFTER auth completes, so it correctly - // post-dates the auth URL when connection succeeds. + // Our shell log line is written after auth completes, so it post-dates + // the auth URL on success. var lastRunIdx = txt.lastIndexOf('-> Running'); var lastRunningMsgIdx = txt.lastIndexOf('Tailscale VPN is running'); if (lastRunningMsgIdx > lastRunIdx) lastRunIdx = lastRunningMsgIdx; @@ -707,7 +698,6 @@ auth.style.display = 'none'; } - // Proxy card is always visible — update ports whenever known if (r.httpPort) document.getElementById('ts-http-proxy').textContent = 'http://127.0.0.1:' + r.httpPort; if (r.socksPort) document.getElementById('ts-socks-proxy').textContent = '127.0.0.1:' + r.socksPort; @@ -761,7 +751,6 @@ if (st.Version) result.version = String(st.Version).split('-')[0]; if (bs === 'Running' && self.Online === true) { - // Genuinely connected and reachable on the tailnet result.state = 'connected'; result.url = null; result.ip = ip4 || result.ip; @@ -773,11 +762,8 @@ result.state = 'connecting'; result.url = st.AuthURL || result.url; } else if (bs === 'Running') { - // Backend running but node not online: either a transient network - // drop (no action needed) or the node was removed/expired and needs - // re-auth. Not connected. Keep any login URL the log parser found - // (status.json's AuthURL lags during the `tailscale up` re-auth - // window) so the login button still appears when re-auth is needed. + // Not online: transient drop or node removed/expired. Keep the log + // parser's URL; status.json's AuthURL lags during re-auth. result.state = 'connecting'; result.url = st.AuthURL || result.url; } else if (bs === 'Stopped') { @@ -801,7 +787,7 @@ var st = arr[1]; var result = parse(txt || ''); if (txt) renderLogs(txt); - // Verify the app is actually running - status.json can be stale if stopped + // status.json goes stale when the app is stopped. checkAppRunning().then(function(running) { if (!running) { result.state = 'disconnected'; @@ -869,7 +855,7 @@ return 0; } - // Settings — load current param values and save on submit + // Settings var PARAM_URL = '/axis-cgi/param.cgi'; var serverInput = document.getElementById('input-server'); var authInput = document.getElementById('input-authkey'); @@ -881,17 +867,11 @@ var saveBtn = document.getElementById('save-btn'); var saveStatus = document.getElementById('save-status'); - // param.cgi is used when available; on devices that lack it (e.g. some - // recorder/NVR-class devices) we fall back to the app's own endpoint, - // exposed through the manifest reverseProxy mapping at API_URL. + // Fallback for devices without param.cgi (e.g. recorders). var API_URL = '/local/' + APP + '/api/settings'; - // Whether this build exposes local HTTP/SOCKS5 proxies (absent on ROOT - // builds, which use kernel networking directly). Detected from whichever - // settings response actually comes back — set once and used to hide the - // proxy card/fields and to keep them out of the save request, since - // param.cgi errors the whole call's status line if asked to set a - // parameter name the manifest never registered. + // False on ROOT builds (no local proxy). Also keeps the ports out of saves: + // param.cgi fails the whole update on a name the manifest never registered. var hasProxyPorts = false; function toggleProxyUi(visible) { @@ -915,8 +895,7 @@ var dm = txt.match(/root\.\S+\.AcceptDNS=(.*)/); var rm = txt.match(/root\.\S+\.AcceptRoutes=(.*)/); var avm = txt.match(/root\.\S+\.AdvertiseRoutes=(.*)/); - // If none of the expected keys are present the endpoint isn't param.cgi - // (e.g. a generic 404 page); signal the caller to use the fallback. + // No expected keys means this isn't param.cgi output (e.g. a 404 page). if (!sm && !hm && !km) return false; if (sm) serverInput.value = sm[1].trim(); if (am) authInput.value = am[1].trim(); @@ -979,8 +958,7 @@ .then(function(r) { return r.ok ? r.text() : Promise.reject(); }) .then(function(txt) { if (/OK/.test(txt)) { - // The app applies the change and restarts its tunnel itself, - // so no separate control.cgi restart is needed here. + // The app restarts its tunnel itself; no control.cgi call needed. setStatus('Saved. Restarting...', 'ok'); } else { setStatus('Error saving settings', 'err'); @@ -1011,15 +989,13 @@ .then(function(txt) { if (/^OK/.test(txt.trim())) { setStatus('Saved. Restarting...', 'ok'); - // Restart the app so new settings take effect return fetch('/axis-cgi/applications/control.cgi?action=restart&package=' + APP, { method: 'POST', credentials: 'same-origin' }); } - // param.cgi reachable but rejected the update — surface the error. setStatus('Error: ' + txt.trim(), 'err'); }) .catch(function() { - // param.cgi unavailable (e.g. recorder-class device) — use the fallback. + // param.cgi unavailable (e.g. recorder-class device). return saveViaFallback(httpPort, socksPort); }) .then(function() { saveBtn.disabled = false; }) diff --git a/common/app/param_bridge.c b/common/app/param_bridge.c index 1d390a2..887efb8 100644 --- a/common/app/param_bridge.c +++ b/common/app/param_bridge.c @@ -1,22 +1,10 @@ // Copyright (C) 2024 Mo3he // SPDX-License-Identifier: GPL-3.0-or-later -/** - * ACAP parameter bridge for Tailscale VPN. - * - * Responsibilities: - * 1. Read Tailscale parameters from the ACAP parameter store (axparameter). - * 2. Write them to CONFIG_FILE so the shell script can source them. - * 3. Launch the shell script (Tailscale_VPN_run) as a child process. - * 4. On any parameter change: rewrite CONFIG_FILE and do a full stop+restart - * of the child so the new config is picked up. - * Rapid changes within 300 ms are coalesced into a single restart. - * 5. Watchdog: if the child exits unexpectedly, restart it. - * - * Shared across the userspace-networking variants (unprivileged 'sdk' ACAP - * user) and the ROOT / kernel-networking variant. Build with -DHAS_PROXY_PORTS - * for the userspace variants, which exposes the HTTP/SOCKS5 proxy port - * parameters; the ROOT variant omits them since it has no local proxy. +/* + * ACAP parameter bridge for Tailscale VPN: mirrors axparameter values into + * CONFIG_FILE, runs Tailscale_VPN_run as a child and restarts it on changes. + * Build with -DHAS_PROXY_PORTS for the userspace variants; ROOT has no proxy. */ #include @@ -70,10 +58,8 @@ static const char *cache_get(char **field, const char *fallback) { return (*field && **field) ? *field : fallback; } -/* Ensure a parameter exists in the device parameter database. On in-place ACAP - * upgrades a newly introduced manifest parameter is not always auto-registered, - * which makes param.cgi return a 404 when the web UI tries to set it. Creating - * it here is idempotent: if it already exists, ax_parameter_add fails harmlessly. */ +/* In-place upgrades don't always register new manifest params (param.cgi then + * 404s); ax_parameter_add fails harmlessly if the param already exists. */ static void ensure_param(AXParameter *handle, const char *name, const char *def) { GError *err = NULL; if (!ax_parameter_add(handle, name, def, "string", &err)) { @@ -127,7 +113,7 @@ static gboolean watchdog_cb(gpointer G_GNUC_UNUSED data) { int exit_code = WEXITSTATUS(status); syslog(LOG_WARNING, "child exited (status %d), restarting", exit_code); child_pid = -1; - /* If child exited 0, auth succeeded — clear AuthKey via axparameter */ + /* Legacy AuthKey clear: the run script only exits 0 from its TERM/INT trap. */ if (exit_code == 0 && g_ax_handle && cfg_auth_key && *cfg_auth_key) { GError *err = NULL; if (ax_parameter_set(g_ax_handle, "AuthKey", "", TRUE, &err)) { @@ -147,10 +133,8 @@ static gboolean watchdog_cb(gpointer G_GNUC_UNUSED data) { /* ── auth-key sentinel ───────────────────────────────────────────────────── */ -/* The run script drops SENTINEL_FILE after a successful `tailscale up` that - * used a one-time auth key. Clear the stored AuthKey so it is not reused and - * disappears from the settings UI. This replaces the old exit-code-0 path, - * which never fired because tailscaled keeps the child alive indefinitely. */ +/* The run script drops SENTINEL_FILE after `tailscale up` used the auth key. + * The exit-code-0 path in watchdog_cb rarely fires since the child stays up. */ static gboolean authkey_sentinel_cb(gpointer G_GNUC_UNUSED data) { if (access(SENTINEL_FILE, F_OK) != 0) return G_SOURCE_CONTINUE; @@ -277,11 +261,8 @@ static void parameter_changed(const gchar *name, const gchar *value, } /* ── embedded settings HTTP server (reverse-proxy fallback) ────────────────── - * Some AXIS device classes (e.g. recorders/NVRs) do not expose the legacy - * /axis-cgi/param.cgi VAPIX endpoint, so the web UI cannot load or save - * settings through it. This tiny HTTP server, reached through the manifest - * reverseProxy mapping at /local/Tailscale_VPN/api/settings, lets the web UI - * fall back to reading and writing the parameters directly. */ + * For devices without /axis-cgi/param.cgi (e.g. recorders); reached via the + * manifest reverseProxy at /local/Tailscale_VPN/api/settings. */ #define HTTP_PORT 2201 @@ -344,9 +325,8 @@ static gchar *http_build_settings_json(AXParameter *handle) { g_free(val); } g_string_append_c(out, '}'); - /* g_string_free(out, FALSE) is inlined by glib >= 2.76 headers into a call - * to g_string_free_and_steal(), which doesn't exist in older glib runtimes - * (e.g. AXIS OS 11.x). Copy out and fully free instead to stay portable. */ + /* Not g_string_free(out, FALSE): glib >= 2.76 headers turn it into + * g_string_free_and_steal(), missing from older runtimes (AXIS OS 11.x). */ gchar *json_result = g_strdup(out->str); g_string_free(out, TRUE); return json_result; @@ -544,11 +524,9 @@ int main(void) { openlog(APP_NAME, LOG_PID, LOG_USER); syslog(LOG_INFO, "starting"); - /* Ensure localdata dir exists */ mkdir("/usr/local/packages/Tailscale_VPN/localdata", 0755); - /* Drop any stale auth-key sentinel from a previous run so we don't clear a - * freshly configured key before it has been used. */ + /* A stale sentinel would clear a freshly configured key before use. */ unlink(SENTINEL_FILE); AXParameter *handle = ax_parameter_new(APP_NAME, &error); diff --git a/docs/index.html b/docs/index.html index b769136..d2e323d 100644 --- a/docs/index.html +++ b/docs/index.html @@ -346,7 +346,6 @@
-
Open Source · ACAP Package

Tailscale VPN for
Axis Cameras

@@ -363,7 +362,6 @@
-
Disclaimer: @@ -373,7 +371,6 @@
-
🔒
@@ -407,12 +404,10 @@
-

Download

Pick the right variant for your device and Axis OS version.

-
Root · Legacy

Root (Full Networking)

@@ -444,7 +438,6 @@
-
ACAP3 · Legacy

ACAP3 (Older Axis OS)

@@ -459,7 +452,6 @@
-

Installation

@@ -487,7 +479,6 @@
-