Compare commits

...
Author SHA1 Message Date
Weston Blieden fd5cec50bb feat: add Accept DNS and Accept Routes toggles to all ACAP 4 variants
Restores the toggle implementation that shipped in the v1.96.4-dns-routes
release but was never committed to main, so weekly auto-builds (v1.98.x)
regressed and dropped the feature.

- manifest.json: register AcceptDNS / AcceptRoutes parameters
- param_bridge.c: cache, load, persist and live-reload the new params
- Tailscale_VPN_run: append --accept-dns / --accept-routes when enabled; add --reset
- html/index.html: add the Settings toggles
2026-06-10 12:24:09 +02:00
Weston Blieden 9a35f4e584 docs: add section on accessing tailnet services from the camera 2026-06-10 12:23:17 +02:00
github-actions[bot] 896fe380ff Update Tailscale to v1.98.4 2026-06-02 05:00:37 +00:00
18 changed files with 342 additions and 17 deletions
+31
View File
@@ -27,6 +27,7 @@ This repository provides an **ACAP package** that installs the [Tailscale VPN cl
- [Usage](#usage)
- [Settings](#settings)
- [Proxy Support](#proxy-support)
- [Accessing Tailnet Services from the Camera](#accessing-tailnet-services-from-the-camera)
- [Updating Tailscale](#updating-tailscale)
- [Purpose](#purpose)
- [Useful Links](#useful-links)
@@ -103,6 +104,36 @@ For ACAP apps or services that support SOCKS5, set their proxy to `127.0.0.1:<po
---
## Accessing Tailnet Services from the Camera
There is an important asymmetry to understand. Making the camera **reachable from** the tailnet (browsing to it, VAPIX, SSH from another tailnet node) works on every build. The harder direction is the camera **reaching out to** a tailnet peer, for example mounting an SMB/CIFS network share hosted on another node. How well this works depends on which build you use.
### Why the build matters
| Build | Networking mode | Camera-initiated access to tailnet peers |
|---|---|---|
| Non-root (`aarch64`, `armv7hf`) and `armv7hf_acap3` | `--tun=userspace-networking` (no kernel `tailscale0` interface) | Only through the local **SOCKS5 / HTTP proxies**, and only for **proxy-aware** apps. Firmware system services (the SMB share client, NTP, etc.) are proxy-unaware, so they **cannot** reach a peer's `100.x` Tailscale IP directly. |
| **ROOT** (`aarch64_root`, `armv7hf_root`) | Kernel networking with a real `tailscale0` interface | Peer `100.x` IPs are routable at the OS level, so firmware services **can** connect directly. Enable **Accept Routes** to also reach subnets behind other nodes. |
In short: on non-root builds the proxies cover apps that know how to use a proxy, but a system feature like "add network share" opens a raw socket that never touches the tunnel. The ROOT build is the clean way to let the camera *consume* tailnet services.
### Plan B: reverse-SSH tunnel (works even on non-root)
If you cannot use the ROOT build but still need the camera to mount a share on a machine that is on your tailnet, you can make the remote share appear **local** to the camera with a reverse SSH tunnel. Because the destination becomes `127.0.0.1`, the proxy-unaware SMB client never has to route over the tailnet.
From a computer that has both the share and tailnet access to the camera:
```bash
# Forward the camera's local port 445 back to the SMB share on this machine
ssh -R 445:localhost:445 root@<camera-tailscale-ip>
```
Then, in the camera's **System → Storage → Add network share** dialog, use `127.0.0.1` as the share host and connect.
> **Requires root on the camera.** Port 445 is privileged, so binding it needs a root-capable build (e.g. developer certificates installed, or the ROOT variant). If the share dialog ever supports a custom (unprivileged) SMB port, the same trick works as a non-root SSH user.
---
## Updating Tailscale
- New `.eap` files are auto-built and released **weekly** (if a new Tailscale version is available).
+11 -1
View File
@@ -18,6 +18,8 @@ CUSTOM_SERVER=""
AUTH_KEY=""
CONF_HTTP="8080"
CONF_SOCKS="1080"
ACCEPT_DNS="false"
ACCEPT_ROUTES="false"
if [ -f "$STATE_DIR/params.conf" ]; then
. "$STATE_DIR/params.conf"
@@ -56,7 +58,7 @@ TAILSCALED_PID=$!
sleep 2
TAILSCALE_CMD="$TAILSCALE_PATH --socket=$SOCKET_PATH up --hostname=$(hostname)"
TAILSCALE_CMD="$TAILSCALE_PATH --socket=$SOCKET_PATH up --reset --hostname=$(hostname)"
if [ -n "$CUSTOM_SERVER" ]; then
TAILSCALE_CMD="$TAILSCALE_CMD --login-server $CUSTOM_SERVER"
@@ -66,6 +68,14 @@ 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
eval $TAILSCALE_CMD
UP_EXIT=$?
+46 -1
View File
@@ -272,6 +272,23 @@
.save-status.ok { color: var(--green); }
.save-status.err { color: var(--red); }
/* Toggle switch */
.toggle-row { display: flex; align-items: flex-start; gap: 12px; }
.toggle-switch { position: relative; width: 36px; height: 20px; flex-shrink: 0; margin-top: 2px; }
.toggle-switch input { opacity: 0; width: 0; height: 0; position: absolute; }
.toggle-slider {
position: absolute; cursor: pointer; inset: 0;
background: var(--border); border-radius: 20px; transition: background 0.2s;
}
.toggle-slider:before {
content: ''; position: absolute;
height: 14px; width: 14px; left: 3px; bottom: 3px;
background: white; border-radius: 50%; transition: transform 0.2s;
}
.toggle-switch input:checked + .toggle-slider { background: var(--green); }
.toggle-switch input:checked + .toggle-slider:before { transform: translateX(16px); }
.toggle-info { flex: 1; }
/* Refresh indicator */
.refresh-bar {
display: flex;
@@ -425,6 +442,26 @@
<input class="settings-input" id="input-socks-port" type="text" autocomplete="off" placeholder="1080">
<span class="settings-hint">Port for the SOCKS5 proxy. Default: 1080.</span>
</div>
<div class="settings-row toggle-row">
<label class="toggle-switch">
<input type="checkbox" id="input-accept-dns">
<span class="toggle-slider"></span>
</label>
<div class="toggle-info">
<div class="settings-label">Accept DNS</div>
<span class="settings-hint">Pass <code>--accept-dns=true</code> to tailscale up. Allows the tailnet to push DNS settings to this device. Off by default to avoid overriding the camera&apos;s DNS configuration.</span>
</div>
</div>
<div class="settings-row toggle-row">
<label class="toggle-switch">
<input type="checkbox" id="input-accept-routes">
<span class="toggle-slider"></span>
</label>
<div class="toggle-info">
<div class="settings-label">Accept Routes</div>
<span class="settings-hint">Pass <code>--accept-routes=true</code> to tailscale up. Allows this device to use subnet routes advertised by other nodes in the tailnet.</span>
</div>
</div>
<div class="settings-actions">
<span class="save-status" id="save-status"></span>
<button class="save-btn" id="save-btn">Save &amp; Restart</button>
@@ -779,6 +816,8 @@
var authInput = document.getElementById('input-authkey');
var httpPortInput = document.getElementById('input-http-port');
var socksPortInput= document.getElementById('input-socks-port');
var acceptDnsInput = document.getElementById('input-accept-dns');
var acceptRoutesInput = document.getElementById('input-accept-routes');
var saveBtn = document.getElementById('save-btn');
var saveStatus = document.getElementById('save-status');
@@ -790,10 +829,14 @@
var am = txt.match(/root\.\S+\.AuthKey=(.*)/);
var hm = txt.match(/root\.\S+\.HttpProxyPort=(.*)/);
var km = txt.match(/root\.\S+\.Socks5Port=(.*)/);
var dm = txt.match(/root\.\S+\.AcceptDNS=(.*)/);
var rm = txt.match(/root\.\S+\.AcceptRoutes=(.*)/);
if (sm) serverInput.value = sm[1].trim();
if (am) authInput.value = am[1].trim();
if (hm) httpPortInput.value = hm[1].trim();
if (km) socksPortInput.value = km[1].trim();
if (dm) acceptDnsInput.checked = dm[1].trim() === 'true';
if (rm) acceptRoutesInput.checked = rm[1].trim() === 'true';
// Update proxy display card with authoritative param values
// and overwrite the localStorage cache so stale ports don't win on next render
var httpPort = hm ? hm[1].trim() : null;
@@ -819,7 +862,9 @@
'&root.' + APP + '.CustomServer=' + encodeURIComponent(serverInput.value.trim()) +
'&root.' + APP + '.AuthKey=' + encodeURIComponent(authInput.value.trim()) +
'&root.' + APP + '.HttpProxyPort=' + encodeURIComponent(httpPort) +
'&root.' + APP + '.Socks5Port=' + encodeURIComponent(socksPort);
'&root.' + APP + '.Socks5Port=' + encodeURIComponent(socksPort) +
'&root.' + APP + '.AcceptDNS=' + (acceptDnsInput.checked ? 'true' : 'false') +
'&root.' + APP + '.AcceptRoutes=' + (acceptRoutesInput.checked ? 'true' : 'false');
fetch(PARAM_URL, {
method: 'POST',
credentials: 'same-origin',
+11 -1
View File
@@ -8,7 +8,7 @@
"embeddedSdkVersion": "3.0",
"vendorUrl": "https://github.com/Mo3he/Axis_Cam_Tailscale",
"runMode": "respawn",
"version": "1.98.3",
"version": "1.98.4",
"architecture": "aarch64"
},
"configuration": {
@@ -33,6 +33,16 @@
"name": "Socks5Port",
"default": "1080",
"type": "string"
},
{
"name": "AcceptDNS",
"default": "false",
"type": "string"
},
{
"name": "AcceptRoutes",
"default": "false",
"type": "string"
}
]
}
+10 -1
View File
@@ -43,6 +43,8 @@ static char *cfg_custom_server = NULL;
static char *cfg_auth_key = NULL;
static char *cfg_http_proxy_port = NULL;
static char *cfg_socks5_port = NULL;
static char *cfg_accept_dns = NULL;
static char *cfg_accept_routes = NULL;
static void cache_set(char **field, const char *value) {
if (!value) return;
@@ -139,6 +141,8 @@ static void load_config_cache(AXParameter *handle) {
LOAD("AuthKey", cfg_auth_key)
LOAD("HttpProxyPort", cfg_http_proxy_port)
LOAD("Socks5Port", cfg_socks5_port)
LOAD("AcceptDNS", cfg_accept_dns)
LOAD("AcceptRoutes", cfg_accept_routes)
#undef LOAD
}
@@ -153,6 +157,8 @@ static void write_config_file(void) {
fprintf(f, "AUTH_KEY=%s\n", cache_get(&cfg_auth_key, ""));
fprintf(f, "CONF_HTTP=%s\n", cache_get(&cfg_http_proxy_port, "8080"));
fprintf(f, "CONF_SOCKS=%s\n", cache_get(&cfg_socks5_port, "1080"));
fprintf(f, "ACCEPT_DNS=%s\n", cache_get(&cfg_accept_dns, "false"));
fprintf(f, "ACCEPT_ROUTES=%s\n", cache_get(&cfg_accept_routes, "false"));
fclose(f);
chmod(CONFIG_FILE, 0600);
syslog(LOG_INFO, "config updated: http=%s socks=%s server=%s",
@@ -185,6 +191,8 @@ static void parameter_changed(const gchar *name, const gchar *value,
else if (strcmp(short_name, "AuthKey") == 0) cache_set(&cfg_auth_key, value);
else if (strcmp(short_name, "HttpProxyPort") == 0) cache_set(&cfg_http_proxy_port, value);
else if (strcmp(short_name, "Socks5Port") == 0) cache_set(&cfg_socks5_port, value);
else if (strcmp(short_name, "AcceptDNS") == 0) cache_set(&cfg_accept_dns, value);
else if (strcmp(short_name, "AcceptRoutes") == 0) cache_set(&cfg_accept_routes, value);
if (reload_timer_id)
g_source_remove(reload_timer_id);
@@ -225,7 +233,8 @@ int main(void) {
start_child();
const char *params[] = {
"CustomServer", "AuthKey", "HttpProxyPort", "Socks5Port"
"CustomServer", "AuthKey", "HttpProxyPort", "Socks5Port",
"AcceptDNS", "AcceptRoutes"
};
for (size_t i = 0; i < sizeof(params) / sizeof(params[0]); i++) {
if (!ax_parameter_register_callback(handle, params[i],
+11 -1
View File
@@ -15,6 +15,8 @@ chmod 755 $TAILSCALE_PATH
CUSTOM_SERVER=""
AUTH_KEY=""
ACCEPT_DNS="false"
ACCEPT_ROUTES="false"
if [ -f "$STATE_DIR/params.conf" ]; then
. "$STATE_DIR/params.conf"
@@ -30,7 +32,7 @@ TAILSCALED_PID=$!
sleep 2
TAILSCALE_CMD="$TAILSCALE_PATH --socket=$SOCKET_PATH up --accept-routes --hostname=$(hostname)"
TAILSCALE_CMD="$TAILSCALE_PATH --socket=$SOCKET_PATH up --reset --hostname=$(hostname)"
if [ -n "$CUSTOM_SERVER" ]; then
TAILSCALE_CMD="$TAILSCALE_CMD --login-server $CUSTOM_SERVER"
@@ -40,6 +42,14 @@ 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
eval $TAILSCALE_CMD
UP_EXIT=$?
+46 -1
View File
@@ -272,6 +272,23 @@
.save-status.ok { color: var(--green); }
.save-status.err { color: var(--red); }
/* Toggle switch */
.toggle-row { display: flex; align-items: flex-start; gap: 12px; }
.toggle-switch { position: relative; width: 36px; height: 20px; flex-shrink: 0; margin-top: 2px; }
.toggle-switch input { opacity: 0; width: 0; height: 0; position: absolute; }
.toggle-slider {
position: absolute; cursor: pointer; inset: 0;
background: var(--border); border-radius: 20px; transition: background 0.2s;
}
.toggle-slider:before {
content: ''; position: absolute;
height: 14px; width: 14px; left: 3px; bottom: 3px;
background: white; border-radius: 50%; transition: transform 0.2s;
}
.toggle-switch input:checked + .toggle-slider { background: var(--green); }
.toggle-switch input:checked + .toggle-slider:before { transform: translateX(16px); }
.toggle-info { flex: 1; }
/* Refresh indicator */
.refresh-bar {
display: flex;
@@ -425,6 +442,26 @@
<input class="settings-input" id="input-socks-port" type="text" autocomplete="off" placeholder="1080">
<span class="settings-hint">Port for the SOCKS5 proxy. Default: 1080.</span>
</div>
<div class="settings-row toggle-row">
<label class="toggle-switch">
<input type="checkbox" id="input-accept-dns">
<span class="toggle-slider"></span>
</label>
<div class="toggle-info">
<div class="settings-label">Accept DNS</div>
<span class="settings-hint">Pass <code>--accept-dns=true</code> to tailscale up. Allows the tailnet to push DNS settings to this device. Off by default to avoid overriding the camera&apos;s DNS configuration.</span>
</div>
</div>
<div class="settings-row toggle-row">
<label class="toggle-switch">
<input type="checkbox" id="input-accept-routes">
<span class="toggle-slider"></span>
</label>
<div class="toggle-info">
<div class="settings-label">Accept Routes</div>
<span class="settings-hint">Pass <code>--accept-routes=true</code> to tailscale up. Allows this device to use subnet routes advertised by other nodes in the tailnet.</span>
</div>
</div>
<div class="settings-actions">
<span class="save-status" id="save-status"></span>
<button class="save-btn" id="save-btn">Save &amp; Restart</button>
@@ -779,6 +816,8 @@
var authInput = document.getElementById('input-authkey');
var httpPortInput = document.getElementById('input-http-port');
var socksPortInput= document.getElementById('input-socks-port');
var acceptDnsInput = document.getElementById('input-accept-dns');
var acceptRoutesInput = document.getElementById('input-accept-routes');
var saveBtn = document.getElementById('save-btn');
var saveStatus = document.getElementById('save-status');
@@ -790,10 +829,14 @@
var am = txt.match(/root\.\S+\.AuthKey=(.*)/);
var hm = txt.match(/root\.\S+\.HttpProxyPort=(.*)/);
var km = txt.match(/root\.\S+\.Socks5Port=(.*)/);
var dm = txt.match(/root\.\S+\.AcceptDNS=(.*)/);
var rm = txt.match(/root\.\S+\.AcceptRoutes=(.*)/);
if (sm) serverInput.value = sm[1].trim();
if (am) authInput.value = am[1].trim();
if (hm) httpPortInput.value = hm[1].trim();
if (km) socksPortInput.value = km[1].trim();
if (dm) acceptDnsInput.checked = dm[1].trim() === 'true';
if (rm) acceptRoutesInput.checked = rm[1].trim() === 'true';
// Update proxy display card with authoritative param values
// and overwrite the localStorage cache so stale ports don't win on next render
var httpPort = hm ? hm[1].trim() : null;
@@ -819,7 +862,9 @@
'&root.' + APP + '.CustomServer=' + encodeURIComponent(serverInput.value.trim()) +
'&root.' + APP + '.AuthKey=' + encodeURIComponent(authInput.value.trim()) +
'&root.' + APP + '.HttpProxyPort=' + encodeURIComponent(httpPort) +
'&root.' + APP + '.Socks5Port=' + encodeURIComponent(socksPort);
'&root.' + APP + '.Socks5Port=' + encodeURIComponent(socksPort) +
'&root.' + APP + '.AcceptDNS=' + (acceptDnsInput.checked ? 'true' : 'false') +
'&root.' + APP + '.AcceptRoutes=' + (acceptRoutesInput.checked ? 'true' : 'false');
fetch(PARAM_URL, {
method: 'POST',
credentials: 'same-origin',
+11 -1
View File
@@ -12,7 +12,7 @@
},
"vendorUrl": "https://github.com/Mo3he/Axis_Cam_Tailscale",
"runMode": "respawn",
"version": "1.98.3",
"version": "1.98.4",
"architecture": "aarch64"
},
"configuration": {
@@ -27,6 +27,16 @@
"name": "AuthKey",
"default": "",
"type": "string"
},
{
"name": "AcceptDNS",
"default": "false",
"type": "string"
},
{
"name": "AcceptRoutes",
"default": "false",
"type": "string"
}
]
}
+9 -1
View File
@@ -31,6 +31,8 @@ static guint reload_timer_id = 0;
static char *cfg_custom_server = NULL;
static char *cfg_auth_key = NULL;
static char *cfg_accept_dns = NULL;
static char *cfg_accept_routes = NULL;
static void cache_set(char **field, const char *value) {
if (!value) return;
@@ -119,6 +121,8 @@ static void load_config_cache(AXParameter *handle) {
LOAD("CustomServer", cfg_custom_server)
LOAD("AuthKey", cfg_auth_key)
LOAD("AcceptDNS", cfg_accept_dns)
LOAD("AcceptRoutes", cfg_accept_routes)
#undef LOAD
}
@@ -131,6 +135,8 @@ static void write_config_file(void) {
}
fprintf(f, "CUSTOM_SERVER=%s\n", cache_get(&cfg_custom_server, ""));
fprintf(f, "AUTH_KEY=%s\n", cache_get(&cfg_auth_key, ""));
fprintf(f, "ACCEPT_DNS=%s\n", cache_get(&cfg_accept_dns, "false"));
fprintf(f, "ACCEPT_ROUTES=%s\n", cache_get(&cfg_accept_routes, "false"));
fclose(f);
chmod(CONFIG_FILE, 0600);
syslog(LOG_INFO, "config updated: server=%s",
@@ -156,6 +162,8 @@ static void parameter_changed(const gchar *name, const gchar *value,
if (strcmp(short_name, "CustomServer") == 0) cache_set(&cfg_custom_server, value);
else if (strcmp(short_name, "AuthKey") == 0) cache_set(&cfg_auth_key, value);
else if (strcmp(short_name, "AcceptDNS") == 0) cache_set(&cfg_accept_dns, value);
else if (strcmp(short_name, "AcceptRoutes") == 0) cache_set(&cfg_accept_routes, value);
if (reload_timer_id)
g_source_remove(reload_timer_id);
@@ -190,7 +198,7 @@ int main(void) {
write_config_file();
start_child();
const char *params[] = { "CustomServer", "AuthKey" };
const char *params[] = { "CustomServer", "AuthKey", "AcceptDNS", "AcceptRoutes" };
for (size_t i = 0; i < sizeof(params) / sizeof(params[0]); i++) {
if (!ax_parameter_register_callback(handle, params[i],
parameter_changed, handle, &error)) {
+11 -1
View File
@@ -18,6 +18,8 @@ CUSTOM_SERVER=""
AUTH_KEY=""
CONF_HTTP="8080"
CONF_SOCKS="1080"
ACCEPT_DNS="false"
ACCEPT_ROUTES="false"
if [ -f "$STATE_DIR/params.conf" ]; then
. "$STATE_DIR/params.conf"
@@ -56,7 +58,7 @@ TAILSCALED_PID=$!
sleep 2
TAILSCALE_CMD="$TAILSCALE_PATH --socket=$SOCKET_PATH up --hostname=$(hostname)"
TAILSCALE_CMD="$TAILSCALE_PATH --socket=$SOCKET_PATH up --reset --hostname=$(hostname)"
if [ -n "$CUSTOM_SERVER" ]; then
TAILSCALE_CMD="$TAILSCALE_CMD --login-server $CUSTOM_SERVER"
@@ -66,6 +68,14 @@ 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
eval $TAILSCALE_CMD
UP_EXIT=$?
+46 -1
View File
@@ -272,6 +272,23 @@
.save-status.ok { color: var(--green); }
.save-status.err { color: var(--red); }
/* Toggle switch */
.toggle-row { display: flex; align-items: flex-start; gap: 12px; }
.toggle-switch { position: relative; width: 36px; height: 20px; flex-shrink: 0; margin-top: 2px; }
.toggle-switch input { opacity: 0; width: 0; height: 0; position: absolute; }
.toggle-slider {
position: absolute; cursor: pointer; inset: 0;
background: var(--border); border-radius: 20px; transition: background 0.2s;
}
.toggle-slider:before {
content: ''; position: absolute;
height: 14px; width: 14px; left: 3px; bottom: 3px;
background: white; border-radius: 50%; transition: transform 0.2s;
}
.toggle-switch input:checked + .toggle-slider { background: var(--green); }
.toggle-switch input:checked + .toggle-slider:before { transform: translateX(16px); }
.toggle-info { flex: 1; }
/* Refresh indicator */
.refresh-bar {
display: flex;
@@ -425,6 +442,26 @@
<input class="settings-input" id="input-socks-port" type="text" autocomplete="off" placeholder="1080">
<span class="settings-hint">Port for the SOCKS5 proxy. Default: 1080.</span>
</div>
<div class="settings-row toggle-row">
<label class="toggle-switch">
<input type="checkbox" id="input-accept-dns">
<span class="toggle-slider"></span>
</label>
<div class="toggle-info">
<div class="settings-label">Accept DNS</div>
<span class="settings-hint">Pass <code>--accept-dns=true</code> to tailscale up. Allows the tailnet to push DNS settings to this device. Off by default to avoid overriding the camera&apos;s DNS configuration.</span>
</div>
</div>
<div class="settings-row toggle-row">
<label class="toggle-switch">
<input type="checkbox" id="input-accept-routes">
<span class="toggle-slider"></span>
</label>
<div class="toggle-info">
<div class="settings-label">Accept Routes</div>
<span class="settings-hint">Pass <code>--accept-routes=true</code> to tailscale up. Allows this device to use subnet routes advertised by other nodes in the tailnet.</span>
</div>
</div>
<div class="settings-actions">
<span class="save-status" id="save-status"></span>
<button class="save-btn" id="save-btn">Save &amp; Restart</button>
@@ -779,6 +816,8 @@
var authInput = document.getElementById('input-authkey');
var httpPortInput = document.getElementById('input-http-port');
var socksPortInput= document.getElementById('input-socks-port');
var acceptDnsInput = document.getElementById('input-accept-dns');
var acceptRoutesInput = document.getElementById('input-accept-routes');
var saveBtn = document.getElementById('save-btn');
var saveStatus = document.getElementById('save-status');
@@ -790,10 +829,14 @@
var am = txt.match(/root\.\S+\.AuthKey=(.*)/);
var hm = txt.match(/root\.\S+\.HttpProxyPort=(.*)/);
var km = txt.match(/root\.\S+\.Socks5Port=(.*)/);
var dm = txt.match(/root\.\S+\.AcceptDNS=(.*)/);
var rm = txt.match(/root\.\S+\.AcceptRoutes=(.*)/);
if (sm) serverInput.value = sm[1].trim();
if (am) authInput.value = am[1].trim();
if (hm) httpPortInput.value = hm[1].trim();
if (km) socksPortInput.value = km[1].trim();
if (dm) acceptDnsInput.checked = dm[1].trim() === 'true';
if (rm) acceptRoutesInput.checked = rm[1].trim() === 'true';
// Update proxy display card with authoritative param values
// and overwrite the localStorage cache so stale ports don't win on next render
var httpPort = hm ? hm[1].trim() : null;
@@ -819,7 +862,9 @@
'&root.' + APP + '.CustomServer=' + encodeURIComponent(serverInput.value.trim()) +
'&root.' + APP + '.AuthKey=' + encodeURIComponent(authInput.value.trim()) +
'&root.' + APP + '.HttpProxyPort=' + encodeURIComponent(httpPort) +
'&root.' + APP + '.Socks5Port=' + encodeURIComponent(socksPort);
'&root.' + APP + '.Socks5Port=' + encodeURIComponent(socksPort) +
'&root.' + APP + '.AcceptDNS=' + (acceptDnsInput.checked ? 'true' : 'false') +
'&root.' + APP + '.AcceptRoutes=' + (acceptRoutesInput.checked ? 'true' : 'false');
fetch(PARAM_URL, {
method: 'POST',
credentials: 'same-origin',
+11 -1
View File
@@ -8,7 +8,7 @@
"embeddedSdkVersion": "3.0",
"vendorUrl": "https://github.com/Mo3he/Axis_Cam_Tailscale",
"runMode": "respawn",
"version": "1.98.3",
"version": "1.98.4",
"architecture": "armv7hf"
},
"configuration": {
@@ -33,6 +33,16 @@
"name": "Socks5Port",
"default": "1080",
"type": "string"
},
{
"name": "AcceptDNS",
"default": "false",
"type": "string"
},
{
"name": "AcceptRoutes",
"default": "false",
"type": "string"
}
]
}
+10 -1
View File
@@ -43,6 +43,8 @@ static char *cfg_custom_server = NULL;
static char *cfg_auth_key = NULL;
static char *cfg_http_proxy_port = NULL;
static char *cfg_socks5_port = NULL;
static char *cfg_accept_dns = NULL;
static char *cfg_accept_routes = NULL;
static void cache_set(char **field, const char *value) {
if (!value) return;
@@ -139,6 +141,8 @@ static void load_config_cache(AXParameter *handle) {
LOAD("AuthKey", cfg_auth_key)
LOAD("HttpProxyPort", cfg_http_proxy_port)
LOAD("Socks5Port", cfg_socks5_port)
LOAD("AcceptDNS", cfg_accept_dns)
LOAD("AcceptRoutes", cfg_accept_routes)
#undef LOAD
}
@@ -153,6 +157,8 @@ static void write_config_file(void) {
fprintf(f, "AUTH_KEY=%s\n", cache_get(&cfg_auth_key, ""));
fprintf(f, "CONF_HTTP=%s\n", cache_get(&cfg_http_proxy_port, "8080"));
fprintf(f, "CONF_SOCKS=%s\n", cache_get(&cfg_socks5_port, "1080"));
fprintf(f, "ACCEPT_DNS=%s\n", cache_get(&cfg_accept_dns, "false"));
fprintf(f, "ACCEPT_ROUTES=%s\n", cache_get(&cfg_accept_routes, "false"));
fclose(f);
chmod(CONFIG_FILE, 0600);
syslog(LOG_INFO, "config updated: http=%s socks=%s server=%s",
@@ -185,6 +191,8 @@ static void parameter_changed(const gchar *name, const gchar *value,
else if (strcmp(short_name, "AuthKey") == 0) cache_set(&cfg_auth_key, value);
else if (strcmp(short_name, "HttpProxyPort") == 0) cache_set(&cfg_http_proxy_port, value);
else if (strcmp(short_name, "Socks5Port") == 0) cache_set(&cfg_socks5_port, value);
else if (strcmp(short_name, "AcceptDNS") == 0) cache_set(&cfg_accept_dns, value);
else if (strcmp(short_name, "AcceptRoutes") == 0) cache_set(&cfg_accept_routes, value);
if (reload_timer_id)
g_source_remove(reload_timer_id);
@@ -225,7 +233,8 @@ int main(void) {
start_child();
const char *params[] = {
"CustomServer", "AuthKey", "HttpProxyPort", "Socks5Port"
"CustomServer", "AuthKey", "HttpProxyPort", "Socks5Port",
"AcceptDNS", "AcceptRoutes"
};
for (size_t i = 0; i < sizeof(params) / sizeof(params[0]); i++) {
if (!ax_parameter_register_callback(handle, params[i],
+11 -1
View File
@@ -15,6 +15,8 @@ chmod 755 $TAILSCALE_PATH
CUSTOM_SERVER=""
AUTH_KEY=""
ACCEPT_DNS="false"
ACCEPT_ROUTES="false"
if [ -f "$STATE_DIR/params.conf" ]; then
. "$STATE_DIR/params.conf"
@@ -30,7 +32,7 @@ TAILSCALED_PID=$!
sleep 2
TAILSCALE_CMD="$TAILSCALE_PATH --socket=$SOCKET_PATH up --accept-routes --hostname=$(hostname)"
TAILSCALE_CMD="$TAILSCALE_PATH --socket=$SOCKET_PATH up --reset --hostname=$(hostname)"
if [ -n "$CUSTOM_SERVER" ]; then
TAILSCALE_CMD="$TAILSCALE_CMD --login-server $CUSTOM_SERVER"
@@ -40,6 +42,14 @@ 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
eval $TAILSCALE_CMD
UP_EXIT=$?
+46 -1
View File
@@ -272,6 +272,23 @@
.save-status.ok { color: var(--green); }
.save-status.err { color: var(--red); }
/* Toggle switch */
.toggle-row { display: flex; align-items: flex-start; gap: 12px; }
.toggle-switch { position: relative; width: 36px; height: 20px; flex-shrink: 0; margin-top: 2px; }
.toggle-switch input { opacity: 0; width: 0; height: 0; position: absolute; }
.toggle-slider {
position: absolute; cursor: pointer; inset: 0;
background: var(--border); border-radius: 20px; transition: background 0.2s;
}
.toggle-slider:before {
content: ''; position: absolute;
height: 14px; width: 14px; left: 3px; bottom: 3px;
background: white; border-radius: 50%; transition: transform 0.2s;
}
.toggle-switch input:checked + .toggle-slider { background: var(--green); }
.toggle-switch input:checked + .toggle-slider:before { transform: translateX(16px); }
.toggle-info { flex: 1; }
/* Refresh indicator */
.refresh-bar {
display: flex;
@@ -425,6 +442,26 @@
<input class="settings-input" id="input-socks-port" type="text" autocomplete="off" placeholder="1080">
<span class="settings-hint">Port for the SOCKS5 proxy. Default: 1080.</span>
</div>
<div class="settings-row toggle-row">
<label class="toggle-switch">
<input type="checkbox" id="input-accept-dns">
<span class="toggle-slider"></span>
</label>
<div class="toggle-info">
<div class="settings-label">Accept DNS</div>
<span class="settings-hint">Pass <code>--accept-dns=true</code> to tailscale up. Allows the tailnet to push DNS settings to this device. Off by default to avoid overriding the camera&apos;s DNS configuration.</span>
</div>
</div>
<div class="settings-row toggle-row">
<label class="toggle-switch">
<input type="checkbox" id="input-accept-routes">
<span class="toggle-slider"></span>
</label>
<div class="toggle-info">
<div class="settings-label">Accept Routes</div>
<span class="settings-hint">Pass <code>--accept-routes=true</code> to tailscale up. Allows this device to use subnet routes advertised by other nodes in the tailnet.</span>
</div>
</div>
<div class="settings-actions">
<span class="save-status" id="save-status"></span>
<button class="save-btn" id="save-btn">Save &amp; Restart</button>
@@ -779,6 +816,8 @@
var authInput = document.getElementById('input-authkey');
var httpPortInput = document.getElementById('input-http-port');
var socksPortInput= document.getElementById('input-socks-port');
var acceptDnsInput = document.getElementById('input-accept-dns');
var acceptRoutesInput = document.getElementById('input-accept-routes');
var saveBtn = document.getElementById('save-btn');
var saveStatus = document.getElementById('save-status');
@@ -790,10 +829,14 @@
var am = txt.match(/root\.\S+\.AuthKey=(.*)/);
var hm = txt.match(/root\.\S+\.HttpProxyPort=(.*)/);
var km = txt.match(/root\.\S+\.Socks5Port=(.*)/);
var dm = txt.match(/root\.\S+\.AcceptDNS=(.*)/);
var rm = txt.match(/root\.\S+\.AcceptRoutes=(.*)/);
if (sm) serverInput.value = sm[1].trim();
if (am) authInput.value = am[1].trim();
if (hm) httpPortInput.value = hm[1].trim();
if (km) socksPortInput.value = km[1].trim();
if (dm) acceptDnsInput.checked = dm[1].trim() === 'true';
if (rm) acceptRoutesInput.checked = rm[1].trim() === 'true';
// Update proxy display card with authoritative param values
// and overwrite the localStorage cache so stale ports don't win on next render
var httpPort = hm ? hm[1].trim() : null;
@@ -819,7 +862,9 @@
'&root.' + APP + '.CustomServer=' + encodeURIComponent(serverInput.value.trim()) +
'&root.' + APP + '.AuthKey=' + encodeURIComponent(authInput.value.trim()) +
'&root.' + APP + '.HttpProxyPort=' + encodeURIComponent(httpPort) +
'&root.' + APP + '.Socks5Port=' + encodeURIComponent(socksPort);
'&root.' + APP + '.Socks5Port=' + encodeURIComponent(socksPort) +
'&root.' + APP + '.AcceptDNS=' + (acceptDnsInput.checked ? 'true' : 'false') +
'&root.' + APP + '.AcceptRoutes=' + (acceptRoutesInput.checked ? 'true' : 'false');
fetch(PARAM_URL, {
method: 'POST',
credentials: 'same-origin',
+11 -1
View File
@@ -12,7 +12,7 @@
},
"vendorUrl": "https://github.com/Mo3he/Axis_Cam_Tailscale",
"runMode": "respawn",
"version": "1.98.3",
"version": "1.98.4",
"architecture": "armv7hf"
},
"configuration": {
@@ -27,6 +27,16 @@
"name": "AuthKey",
"default": "",
"type": "string"
},
{
"name": "AcceptDNS",
"default": "false",
"type": "string"
},
{
"name": "AcceptRoutes",
"default": "false",
"type": "string"
}
]
}
+9 -1
View File
@@ -31,6 +31,8 @@ static AXParameter *g_ax_handle = NULL;
static char *cfg_custom_server = NULL;
static char *cfg_auth_key = NULL;
static char *cfg_accept_dns = NULL;
static char *cfg_accept_routes = NULL;
static void cache_set(char **field, const char *value) {
if (!value) return;
@@ -119,6 +121,8 @@ static void load_config_cache(AXParameter *handle) {
LOAD("CustomServer", cfg_custom_server)
LOAD("AuthKey", cfg_auth_key)
LOAD("AcceptDNS", cfg_accept_dns)
LOAD("AcceptRoutes", cfg_accept_routes)
#undef LOAD
}
@@ -131,6 +135,8 @@ static void write_config_file(void) {
}
fprintf(f, "CUSTOM_SERVER=%s\n", cache_get(&cfg_custom_server, ""));
fprintf(f, "AUTH_KEY=%s\n", cache_get(&cfg_auth_key, ""));
fprintf(f, "ACCEPT_DNS=%s\n", cache_get(&cfg_accept_dns, "false"));
fprintf(f, "ACCEPT_ROUTES=%s\n", cache_get(&cfg_accept_routes, "false"));
fclose(f);
chmod(CONFIG_FILE, 0600);
syslog(LOG_INFO, "config updated: server=%s",
@@ -156,6 +162,8 @@ static void parameter_changed(const gchar *name, const gchar *value,
if (strcmp(short_name, "CustomServer") == 0) cache_set(&cfg_custom_server, value);
else if (strcmp(short_name, "AuthKey") == 0) cache_set(&cfg_auth_key, value);
else if (strcmp(short_name, "AcceptDNS") == 0) cache_set(&cfg_accept_dns, value);
else if (strcmp(short_name, "AcceptRoutes") == 0) cache_set(&cfg_accept_routes, value);
if (reload_timer_id)
g_source_remove(reload_timer_id);
@@ -190,7 +198,7 @@ int main(void) {
write_config_file();
start_child();
const char *params[] = { "CustomServer", "AuthKey" };
const char *params[] = { "CustomServer", "AuthKey", "AcceptDNS", "AcceptRoutes" };
for (size_t i = 0; i < sizeof(params) / sizeof(params[0]); i++) {
if (!ax_parameter_register_callback(handle, params[i],
parameter_changed, handle, &error)) {
+1 -1
View File
@@ -3,7 +3,7 @@ MENUNAME="Tailscale VPN"
VENDOR="Mo3he"
APPMAJORVERSION=1
APPMINORVERSION=98
APPMICROVERSION=3
APPMICROVERSION=4
APPTYPE=armv7hf
APPNAME=Tailscale_VPN
APPOPTS=""