Compare commits

..
11 changed files with 105 additions and 44 deletions
+7 -8
View File
@@ -6,7 +6,7 @@ This repository provides an **ACAP package** that installs the [Tailscale VPN cl
- Secure remote access to cameras
- Easy to install via EAP package
- Works on **Axis OS 11.11+** (non-root version)
- Works on **Axis OS 12+** (non-root version)
- Works on **legacy Axis OS 9.x / 10.x** via the ACAP 3 variant
- Based on **WireGuard VPN** technology
@@ -14,9 +14,8 @@ This repository provides an **ACAP package** that installs the [Tailscale VPN cl
[![License](https://img.shields.io/github/license/Mo3he/Axis_Cam_Tailscale)](LICENSE)
![Total Downloads](https://img.shields.io/github/downloads/Mo3he/Axis_Cam_Tailscale/total?style=flat&label=Downloads&color=blue)
[![Sponsor](https://img.shields.io/badge/sponsor-%E2%9D%A4-lightgrey?logo=github)](https://github.com/sponsors/Mo3he)
[![Buy Me A Coffee](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/mo3he)
> **Disclaimer:** This is an independent, community-developed ACAP package and is not an official Axis Communications product. It is not affiliated with, endorsed by, or supported by Axis Communications AB. Use it at your own risk. For official Axis software, visit axis.com
> **Disclaimer:** This is an independent, community-developed ACAP package and is not an official Axis Communications product. It was developed entirely on personal time and is not affiliated with, endorsed by, or supported by Axis Communications AB. Use it at your own risk. For official Axis software, visit axis.com
> **Tailscale Notice:** Tailscale is a product of Tailscale Inc. This package independently redistributes the Tailscale binaries under the [BSD 3-Clause License](LICENSE) and is not affiliated with, endorsed by, or supported by Tailscale Inc. For the official Tailscale client, visit [tailscale.com](https://tailscale.com).
---
@@ -126,11 +125,11 @@ docker cp $(docker create <package_name>):/opt/app ./build
## Good News
Tailscale ACAP can now run **without root privileges**, making it compatible with **Axis OS 11.11+**.
Tailscale ACAP can now run **without root privileges**, making it compatible with **Axis OS 12+**.
- Runs in **user space networking mode**.
For **full kernel networking**, use the **ROOT** version. Note: ROOT mode requires Axis OS 11.11–11.x — Axis OS 12 and later removed root access for third-party applications.
For **full networking features**, use the **ROOT** version on Axis OS < 12.
### Legacy camera support (Axis OS 9.x / 10.x)
@@ -165,11 +164,11 @@ The Tailscale ACAP is compatible with Axis cameras with **ARM** and **AARCH64**-
|---|---|---|---|
| `aarch64` | AArch64 | 11.11+ (ACAP 4) | Standard, userspace networking, configurable proxy ports |
| `armv7hf` | ARMv7 | 11.11+ (ACAP 4) | Standard, userspace networking, configurable proxy ports |
| `aarch64_root` | AArch64 | 11.11 – 11.x (ACAP 4) | Full kernel networking (root) — not supported on OS 12+ |
| `armv7hf_root` | ARMv7 | 11.11 – 11.x (ACAP 4) | Full kernel networking (root) — not supported on OS 12+ |
| `aarch64_root` | AArch64 | 10 or earlier | Full kernel networking (root) |
| `armv7hf_root` | ARMv7 | 10 or earlier | Full kernel networking (root) |
| `armv7hf_acap3` | ARMv7 | **9.x – 10.x** | Legacy cameras, ACAP SDK 3 |
> Not sure which variant to use? Check **System → Properties → Firmware version** on your camera. Axis OS 12+ → use the standard variant (`aarch64` or `armv7hf`). Axis OS 11.11–11.x → standard variant, or ROOT if you need kernel networking. Axis OS 9.x/10.x on ARMv7 → use `armv7hf_acap3`.
> Not sure which variant to use? Check **System → Properties → Firmware version** on your camera. Axis OS 11.11+ → use the standard variant (`aarch64` or `armv7hf`). Axis OS 9/10 on ARMv7 → use `armv7hf_acap3`. Axis OS 10 or earlier on AArch64 → use `aarch64_root`.
You can verify your device details using the following command:
+17 -5
View File
@@ -11,7 +11,7 @@
--border: #262a35;
--text: #e4e6ed;
--muted: #8b8fa3;
--accent: #2e2d2d;
--accent: #4f8ff7;
--green: #22c55e;
--yellow: #f59e0b;
--red: #ef4444;
@@ -26,7 +26,7 @@
--border: #e0e3e8;
--text: #1a1a2e;
--muted: #6b7084;
--accent: #2e2d2d;
--accent: #2563eb;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
@@ -231,7 +231,7 @@
word-break: break-all;
}
.log-box .log-line { display: block; }
.log-box .log-line:hover { background: rgba(46,45,45,0.06); }
.log-box .log-line:hover { background: rgba(79,143,247,0.06); }
.log-line .ts { color: var(--muted); opacity: 0.6; }
.log-line .msg-info { color: var(--accent); }
.log-line .msg-warn { color: var(--yellow); }
@@ -291,8 +291,8 @@
padding: 12px 16px;
border-radius: 8px;
margin-bottom: 14px;
background: rgba(46,45,45,0.1);
border: 1px solid rgba(46,45,45,0.2);
background: rgba(79,143,247,0.1);
border: 1px solid rgba(79,143,247,0.2);
}
.update-banner.visible { display: flex; }
.update-banner .update-text {
@@ -326,6 +326,18 @@
<div class="header">
<div class="header-left">
<svg width="26" height="26" viewBox="0 0 128 128" fill="none">
<rect width="128" height="128" rx="28" fill="#0166FF"/>
<circle cx="32" cy="32" r="13" fill="white" opacity="0.4"/>
<circle cx="64" cy="32" r="13" fill="white" opacity="0.4"/>
<circle cx="96" cy="32" r="13" fill="white" opacity="0.4"/>
<circle cx="32" cy="64" r="13" fill="white"/>
<circle cx="64" cy="64" r="13" fill="white"/>
<circle cx="96" cy="64" r="13" fill="white"/>
<circle cx="32" cy="96" r="13" fill="white" opacity="0.4"/>
<circle cx="64" cy="96" r="13" fill="white"/>
<circle cx="96" cy="96" r="13" fill="white" opacity="0.4"/>
</svg>
<h1>Tailscale VPN</h1>
</div>
<button class="theme-btn" id="themeToggle" aria-label="Toggle theme">
+3 -2
View File
@@ -35,7 +35,6 @@
#define CONFIG_FILE "/usr/local/packages/Tailscale_VPN/localdata/params.conf"
#define RUN_SCRIPT "/usr/local/packages/Tailscale_VPN/Tailscale_VPN_run"
static AXParameter *g_ax_handle = NULL;
static pid_t child_pid = -1;
static guint reload_timer_id = 0;
@@ -103,7 +102,7 @@ static gboolean watchdog_cb(gpointer G_GNUC_UNUSED data) {
/* If child exited 0, auth succeeded — clear AuthKey via axparameter */
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)) {
if (ax_parameter_set(g_ax_handle, "AuthKey", "", &err)) {
free(cfg_auth_key); cfg_auth_key = strdup("");
syslog(LOG_INFO, "AuthKey cleared after successful auth");
} else {
@@ -163,6 +162,8 @@ static void write_config_file(void) {
/* ── ACAP parameter callback ─────────────────────────────────────────────── */
static AXParameter *g_ax_handle = NULL;
static gboolean debounced_restart(gpointer G_GNUC_UNUSED data) {
reload_timer_id = 0;
if (g_ax_handle)
+17 -5
View File
@@ -11,7 +11,7 @@
--border: #262a35;
--text: #e4e6ed;
--muted: #8b8fa3;
--accent: #2e2d2d;
--accent: #4f8ff7;
--green: #22c55e;
--yellow: #f59e0b;
--red: #ef4444;
@@ -26,7 +26,7 @@
--border: #e0e3e8;
--text: #1a1a2e;
--muted: #6b7084;
--accent: #2e2d2d;
--accent: #2563eb;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
@@ -231,7 +231,7 @@
word-break: break-all;
}
.log-box .log-line { display: block; }
.log-box .log-line:hover { background: rgba(46,45,45,0.06); }
.log-box .log-line:hover { background: rgba(79,143,247,0.06); }
.log-line .ts { color: var(--muted); opacity: 0.6; }
.log-line .msg-info { color: var(--accent); }
.log-line .msg-warn { color: var(--yellow); }
@@ -291,8 +291,8 @@
padding: 12px 16px;
border-radius: 8px;
margin-bottom: 14px;
background: rgba(46,45,45,0.1);
border: 1px solid rgba(46,45,45,0.2);
background: rgba(79,143,247,0.1);
border: 1px solid rgba(79,143,247,0.2);
}
.update-banner.visible { display: flex; }
.update-banner .update-text {
@@ -326,6 +326,18 @@
<div class="header">
<div class="header-left">
<svg width="26" height="26" viewBox="0 0 128 128" fill="none">
<rect width="128" height="128" rx="28" fill="#0166FF"/>
<circle cx="32" cy="32" r="13" fill="white" opacity="0.4"/>
<circle cx="64" cy="32" r="13" fill="white" opacity="0.4"/>
<circle cx="96" cy="32" r="13" fill="white" opacity="0.4"/>
<circle cx="32" cy="64" r="13" fill="white"/>
<circle cx="64" cy="64" r="13" fill="white"/>
<circle cx="96" cy="64" r="13" fill="white"/>
<circle cx="32" cy="96" r="13" fill="white" opacity="0.4"/>
<circle cx="64" cy="96" r="13" fill="white"/>
<circle cx="96" cy="96" r="13" fill="white" opacity="0.4"/>
</svg>
<h1>Tailscale VPN</h1>
</div>
<button class="theme-btn" id="themeToggle" aria-label="Toggle theme">
+3 -2
View File
@@ -25,7 +25,6 @@
#define CONFIG_FILE "/usr/local/packages/Tailscale_VPN/localdata/params.conf"
#define RUN_SCRIPT "/usr/local/packages/Tailscale_VPN/Tailscale_VPN_run"
static AXParameter *g_ax_handle = NULL;
static pid_t child_pid = -1;
static guint reload_timer_id = 0;
@@ -87,7 +86,7 @@ static gboolean watchdog_cb(gpointer G_GNUC_UNUSED data) {
/* If child exited 0, auth succeeded — clear AuthKey via axparameter */
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)) {
if (ax_parameter_set(g_ax_handle, "AuthKey", "", &err)) {
free(cfg_auth_key); cfg_auth_key = strdup("");
syslog(LOG_INFO, "AuthKey cleared after successful auth");
} else {
@@ -137,6 +136,8 @@ static void write_config_file(void) {
cache_get(&cfg_custom_server, "(default)"));
}
static AXParameter *g_ax_handle = NULL;
static gboolean debounced_restart(gpointer G_GNUC_UNUSED data) {
reload_timer_id = 0;
if (g_ax_handle)
+17 -5
View File
@@ -11,7 +11,7 @@
--border: #262a35;
--text: #e4e6ed;
--muted: #8b8fa3;
--accent: #2e2d2d;
--accent: #4f8ff7;
--green: #22c55e;
--yellow: #f59e0b;
--red: #ef4444;
@@ -26,7 +26,7 @@
--border: #e0e3e8;
--text: #1a1a2e;
--muted: #6b7084;
--accent: #2e2d2d;
--accent: #2563eb;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
@@ -231,7 +231,7 @@
word-break: break-all;
}
.log-box .log-line { display: block; }
.log-box .log-line:hover { background: rgba(46,45,45,0.06); }
.log-box .log-line:hover { background: rgba(79,143,247,0.06); }
.log-line .ts { color: var(--muted); opacity: 0.6; }
.log-line .msg-info { color: var(--accent); }
.log-line .msg-warn { color: var(--yellow); }
@@ -291,8 +291,8 @@
padding: 12px 16px;
border-radius: 8px;
margin-bottom: 14px;
background: rgba(46,45,45,0.1);
border: 1px solid rgba(46,45,45,0.2);
background: rgba(79,143,247,0.1);
border: 1px solid rgba(79,143,247,0.2);
}
.update-banner.visible { display: flex; }
.update-banner .update-text {
@@ -326,6 +326,18 @@
<div class="header">
<div class="header-left">
<svg width="26" height="26" viewBox="0 0 128 128" fill="none">
<rect width="128" height="128" rx="28" fill="#0166FF"/>
<circle cx="32" cy="32" r="13" fill="white" opacity="0.4"/>
<circle cx="64" cy="32" r="13" fill="white" opacity="0.4"/>
<circle cx="96" cy="32" r="13" fill="white" opacity="0.4"/>
<circle cx="32" cy="64" r="13" fill="white"/>
<circle cx="64" cy="64" r="13" fill="white"/>
<circle cx="96" cy="64" r="13" fill="white"/>
<circle cx="32" cy="96" r="13" fill="white" opacity="0.4"/>
<circle cx="64" cy="96" r="13" fill="white"/>
<circle cx="96" cy="96" r="13" fill="white" opacity="0.4"/>
</svg>
<h1>Tailscale VPN</h1>
</div>
<button class="theme-btn" id="themeToggle" aria-label="Toggle theme">
+3 -2
View File
@@ -37,7 +37,6 @@
static pid_t child_pid = -1;
static guint reload_timer_id = 0;
static AXParameter *g_ax_handle = NULL;
static char *cfg_custom_server = NULL;
static char *cfg_auth_key = NULL;
@@ -103,7 +102,7 @@ static gboolean watchdog_cb(gpointer G_GNUC_UNUSED data) {
/* If child exited 0, auth succeeded — clear AuthKey via axparameter */
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)) {
if (ax_parameter_set(g_ax_handle, "AuthKey", "", &err)) {
free(cfg_auth_key); cfg_auth_key = strdup("");
syslog(LOG_INFO, "AuthKey cleared after successful auth");
} else {
@@ -163,6 +162,8 @@ static void write_config_file(void) {
/* ── ACAP parameter callback ─────────────────────────────────────────────── */
static AXParameter *g_ax_handle = NULL;
static gboolean debounced_restart(gpointer G_GNUC_UNUSED data) {
reload_timer_id = 0;
if (g_ax_handle)
+17 -5
View File
@@ -11,7 +11,7 @@
--border: #262a35;
--text: #e4e6ed;
--muted: #8b8fa3;
--accent: #2e2d2d;
--accent: #4f8ff7;
--green: #22c55e;
--yellow: #f59e0b;
--red: #ef4444;
@@ -26,7 +26,7 @@
--border: #e0e3e8;
--text: #1a1a2e;
--muted: #6b7084;
--accent: #2e2d2d;
--accent: #2563eb;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
@@ -231,7 +231,7 @@
word-break: break-all;
}
.log-box .log-line { display: block; }
.log-box .log-line:hover { background: rgba(46,45,45,0.06); }
.log-box .log-line:hover { background: rgba(79,143,247,0.06); }
.log-line .ts { color: var(--muted); opacity: 0.6; }
.log-line .msg-info { color: var(--accent); }
.log-line .msg-warn { color: var(--yellow); }
@@ -291,8 +291,8 @@
padding: 12px 16px;
border-radius: 8px;
margin-bottom: 14px;
background: rgba(46,45,45,0.1);
border: 1px solid rgba(46,45,45,0.2);
background: rgba(79,143,247,0.1);
border: 1px solid rgba(79,143,247,0.2);
}
.update-banner.visible { display: flex; }
.update-banner .update-text {
@@ -326,6 +326,18 @@
<div class="header">
<div class="header-left">
<svg width="26" height="26" viewBox="0 0 128 128" fill="none">
<rect width="128" height="128" rx="28" fill="#0166FF"/>
<circle cx="32" cy="32" r="13" fill="white" opacity="0.4"/>
<circle cx="64" cy="32" r="13" fill="white" opacity="0.4"/>
<circle cx="96" cy="32" r="13" fill="white" opacity="0.4"/>
<circle cx="32" cy="64" r="13" fill="white"/>
<circle cx="64" cy="64" r="13" fill="white"/>
<circle cx="96" cy="64" r="13" fill="white"/>
<circle cx="32" cy="96" r="13" fill="white" opacity="0.4"/>
<circle cx="64" cy="96" r="13" fill="white"/>
<circle cx="96" cy="96" r="13" fill="white" opacity="0.4"/>
</svg>
<h1>Tailscale VPN</h1>
</div>
<button class="theme-btn" id="themeToggle" aria-label="Toggle theme">
+3 -2
View File
@@ -27,7 +27,6 @@
static pid_t child_pid = -1;
static guint reload_timer_id = 0;
static AXParameter *g_ax_handle = NULL;
static char *cfg_custom_server = NULL;
static char *cfg_auth_key = NULL;
@@ -87,7 +86,7 @@ static gboolean watchdog_cb(gpointer G_GNUC_UNUSED data) {
/* If child exited 0, auth succeeded — clear AuthKey via axparameter */
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)) {
if (ax_parameter_set(g_ax_handle, "AuthKey", "", &err)) {
free(cfg_auth_key); cfg_auth_key = strdup("");
syslog(LOG_INFO, "AuthKey cleared after successful auth");
} else {
@@ -137,6 +136,8 @@ static void write_config_file(void) {
cache_get(&cfg_custom_server, "(default)"));
}
static AXParameter *g_ax_handle = NULL;
static gboolean debounced_restart(gpointer G_GNUC_UNUSED data) {
reload_timer_id = 0;
if (g_ax_handle)
+17 -5
View File
@@ -11,7 +11,7 @@
--border: #262a35;
--text: #e4e6ed;
--muted: #8b8fa3;
--accent: #2e2d2d;
--accent: #4f8ff7;
--green: #22c55e;
--yellow: #f59e0b;
--red: #ef4444;
@@ -26,7 +26,7 @@
--border: #e0e3e8;
--text: #1a1a2e;
--muted: #6b7084;
--accent: #2e2d2d;
--accent: #2563eb;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
@@ -231,7 +231,7 @@
word-break: break-all;
}
.log-box .log-line { display: block; }
.log-box .log-line:hover { background: rgba(46,45,45,0.06); }
.log-box .log-line:hover { background: rgba(79,143,247,0.06); }
.log-line .ts { color: var(--muted); opacity: 0.6; }
.log-line .msg-info { color: var(--accent); }
.log-line .msg-warn { color: var(--yellow); }
@@ -257,8 +257,8 @@
padding: 12px 16px;
border-radius: 8px;
margin-bottom: 14px;
background: rgba(46,45,45,0.1);
border: 1px solid rgba(46,45,45,0.2);
background: rgba(79,143,247,0.1);
border: 1px solid rgba(79,143,247,0.2);
}
.update-banner.visible { display: flex; }
.update-banner .update-text {
@@ -292,6 +292,18 @@
<div class="header">
<div class="header-left">
<svg width="26" height="26" viewBox="0 0 128 128" fill="none">
<rect width="128" height="128" rx="28" fill="#0166FF"/>
<circle cx="32" cy="32" r="13" fill="white" opacity="0.4"/>
<circle cx="64" cy="32" r="13" fill="white" opacity="0.4"/>
<circle cx="96" cy="32" r="13" fill="white" opacity="0.4"/>
<circle cx="32" cy="64" r="13" fill="white"/>
<circle cx="64" cy="64" r="13" fill="white"/>
<circle cx="96" cy="64" r="13" fill="white"/>
<circle cx="32" cy="96" r="13" fill="white" opacity="0.4"/>
<circle cx="64" cy="96" r="13" fill="white"/>
<circle cx="96" cy="96" r="13" fill="white" opacity="0.4"/>
</svg>
<h1>Tailscale VPN</h1>
</div>
<button class="theme-btn" id="themeToggle" aria-label="Toggle theme">
+1 -3
View File
@@ -334,7 +334,6 @@
<a href="#install">Install</a>
<a href="https://github.com/Mo3he/Axis_Cam_Tailscale" target="_blank" rel="noopener">GitHub</a>
<a href="https://github.com/sponsors/Mo3he" target="_blank" rel="noopener" style="color: #db61a2;">Sponsor</a>
<a href="https://buymeacoffee.com/mo3he" target="_blank" rel="noopener" style="display:inline-flex;align-items:center;gap:0.35rem;background:#FFDD00;color:#000;font-weight:700;font-size:0.85rem;padding:0.35rem 0.85rem;border-radius:8px;line-height:1;">&#9749; Buy me a coffee</a>
<button class="theme-toggle" id="themeToggle" aria-label="Toggle theme">
<svg id="iconSun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
<svg id="iconMoon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
@@ -364,7 +363,7 @@
<div style="background:rgba(79,143,247,0.07);border:1px solid rgba(79,143,247,0.18);border-radius:10px;padding:12px 18px;font-size:12.5px;color:var(--muted);line-height:1.6;">
<strong style="color:var(--text);">Disclaimer:</strong>
This is an independent, community-developed ACAP package and is <strong style="color:var(--text);">not an official Axis Communications product</strong>.
It is not affiliated with, endorsed by, or supported by Axis Communications AB.
It was developed entirely on personal time and is not affiliated with, endorsed by, or supported by Axis Communications AB.
Use at your own risk. For official Axis software, visit <a href="https://www.axis.com" target="_blank" rel="noopener">axis.com</a>.
</div>
</div>
@@ -493,7 +492,6 @@
<a href="https://github.com/Mo3he/Axis_Cam_Tailscale" target="_blank" rel="noopener">GitHub</a>
<a href="https://github.com/Mo3he/Axis_Cam_Tailscale/releases" target="_blank" rel="noopener">Releases</a>
<a href="https://github.com/sponsors/Mo3he" target="_blank" rel="noopener">Sponsor</a>
<a href="https://buymeacoffee.com/mo3he" target="_blank" rel="noopener">Buy Me a Coffee</a>
<a href="https://tailscale.com/" target="_blank" rel="noopener">Tailscale</a>
<a href="https://www.axis.com/" target="_blank" rel="noopener">Axis</a>
</div>