diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index bc8ca28..3d46c0f 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -103,9 +103,6 @@ jobs:
cp tailscale_bins/tailscaled_arm64 "$folder/app/lib/tailscaled"
fi
- # Stage updated binaries for commit
- git add "$folder/app/lib/tailscale" "$folder/app/lib/tailscaled"
-
# Detect variant suffix for .eap naming
if [[ "$FOLDER_NAME" == *_ROOT ]]; then
VARIANT="_root"
diff --git a/.gitignore b/.gitignore
index 4875680..bb8f716 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,7 @@ build/
# Do not track downloaded Tailscale tarballs and temp bins
tailscale_bins/
*.tgz
+
+# Tailscale binaries - downloaded fresh by CI at build time, not stored in git
+*/app/lib/tailscale
+*/app/lib/tailscaled
diff --git a/aarch64/app/Tailscale_VPN b/aarch64/app/Tailscale_VPN
index 93902d5..82c875d 100755
--- a/aarch64/app/Tailscale_VPN
+++ b/aarch64/app/Tailscale_VPN
@@ -1,30 +1,30 @@
#!/bin/sh
-echo "Starting Service"
-
-# Define paths
APP_DIR="/usr/local/packages/Tailscale_VPN"
STATE_DIR="$APP_DIR/localdata"
-# Create localdata directory if it doesn't exist
+logger -t "Tailscale_VPN" "Starting Tailscale VPN service"
+
mkdir -p "$STATE_DIR"
+chmod 755 "$APP_DIR/lib/tailscale"
+chmod 755 "$APP_DIR/lib/tailscaled"
-# Set permissions
-chmod 777 "$APP_DIR/lib/tailscale"
-chmod 777 "$APP_DIR/lib/tailscaled"
+# Kill any leftover daemon from a previous run
+killall tailscaled 2>/dev/null || true
-# Start tailscaled with state stored in localdata
+logger -t "Tailscale_VPN" "Starting tailscaled daemon (userspace networking)"
"$APP_DIR/lib/tailscaled" \
--state="$STATE_DIR/tailscaled.state" \
--socket="$STATE_DIR/tailscaled.sock" \
--socks5-server=localhost:1055 \
--tun=userspace-networking &
+TAILSCALED_PID=$!
-echo "Service Started"
sleep 2
-echo "Scroll to Bottom for link"
-# Run tailscale up with the socket in localdata
+logger -t "Tailscale_VPN" "Connecting to Tailscale network (scroll to bottom for auth URL if prompted)"
"$APP_DIR/lib/tailscale" --socket="$STATE_DIR/tailscaled.sock" up
-wait
+logger -t "Tailscale_VPN" "Tailscale VPN is running"
+wait $TAILSCALED_PID
+logger -t "Tailscale_VPN" "tailscaled exited"
diff --git a/aarch64/app/html/index.html b/aarch64/app/html/index.html
index f0888d3..cd419e5 100644
--- a/aarch64/app/html/index.html
+++ b/aarch64/app/html/index.html
@@ -1,19 +1,258 @@
-
-
+
- Scroll to the bottom to find authentication URL
-
+
+ header {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 16px;
+ }
+
+ header svg { flex-shrink: 0; }
+
+ header h1 {
+ font-size: 18px;
+ font-weight: 600;
+ color: #1a1a2e;
+ }
+
+ .card {
+ background: #fff;
+ border-radius: 10px;
+ padding: 18px 20px;
+ margin-bottom: 14px;
+ box-shadow: 0 1px 4px rgba(0,0,0,0.07);
+ }
+
+ /* ── Status row ── */
+ .status-row {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 12px;
+ }
+
+ .dot {
+ width: 11px;
+ height: 11px;
+ border-radius: 50%;
+ flex-shrink: 0;
+ }
+
+ .dot.connected { background: #22c55e; box-shadow: 0 0 0 3px rgba(34,197,94,.2); }
+ .dot.connecting { background: #f59e0b; box-shadow: 0 0 0 3px rgba(245,158,11,.2); }
+ .dot.disconnected { background: #ef4444; box-shadow: 0 0 0 3px rgba(239,68,68,.2); }
+
+ .status-label {
+ font-size: 15px;
+ font-weight: 600;
+ }
+
+ /* ── Auth URL block ── */
+ #auth-block {
+ background: #fffbeb;
+ border: 1px solid #fde68a;
+ border-radius: 8px;
+ padding: 14px 16px;
+ }
+
+ #auth-block p {
+ font-size: 12px;
+ color: #92400e;
+ margin-bottom: 10px;
+ font-weight: 500;
+ }
+
+ #auth-link {
+ display: inline-block;
+ background: #0166ff;
+ color: #fff;
+ text-decoration: none;
+ font-weight: 600;
+ font-size: 13px;
+ padding: 8px 16px;
+ border-radius: 6px;
+ margin-bottom: 10px;
+ }
+
+ #auth-link:hover { background: #0052cc; }
+
+ #auth-url-text {
+ display: block;
+ font-size: 11px;
+ color: #888;
+ word-break: break-all;
+ font-family: monospace;
+ }
+
+ /* ── Info grid (connected state) ── */
+ .info-grid {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ gap: 6px 16px;
+ font-size: 13px;
+ }
+
+ .info-grid .label { color: #888; }
+ .info-grid .value { font-weight: 500; font-family: monospace; }
+
+ /* ── Log section ── */
+ .log-header {
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.6px;
+ color: #999;
+ margin-bottom: 10px;
+ }
+
+ #log-frame {
+ width: 100%;
+ height: 500px;
+ border: 1px solid #e8e8ec;
+ border-radius: 6px;
+ display: block;
+ }
+
-
-
-
+
+
+
+
+
+ Tailscale VPN
+
+
+
+
+
+
+ Checking…
+
+
+
+
+
Open this link in a browser to authenticate this device:
+
Authenticate →
+
+
+
+
+
+ Tailscale IP—
+ Node—
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/aarch64/app/lib/.gitkeep b/aarch64/app/lib/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/aarch64/app/manifest.json b/aarch64/app/manifest.json
index bc877dd..eb369c7 100644
--- a/aarch64/app/manifest.json
+++ b/aarch64/app/manifest.json
@@ -7,7 +7,7 @@
"vendor": "Tailscale - Packaged by Mo3he",
"embeddedSdkVersion": "3.0",
"vendorUrl": "https://www.tailscale.com",
- "runMode": "once",
+ "runMode": "respawn",
"version": "1.96.2",
"architecture": "aarch64"
},
diff --git a/aarch64_ROOT/app/Tailscale_VPN b/aarch64_ROOT/app/Tailscale_VPN
index bd4f634..eff7e43 100755
--- a/aarch64_ROOT/app/Tailscale_VPN
+++ b/aarch64_ROOT/app/Tailscale_VPN
@@ -1,28 +1,28 @@
#!/bin/sh
-echo "Starting Service"
-
-# Define paths
APP_DIR="/usr/local/packages/Tailscale_VPN"
STATE_DIR="$APP_DIR/localdata"
-# Create localdata directory if it doesn't exist
+logger -t "Tailscale_VPN" "Starting Tailscale VPN service (root mode)"
+
mkdir -p "$STATE_DIR"
+chmod 755 "$APP_DIR/lib/tailscale"
+chmod 755 "$APP_DIR/lib/tailscaled"
-# Set permissions
-chmod 777 "$APP_DIR/lib/tailscale"
-chmod 777 "$APP_DIR/lib/tailscaled"
+# Kill any leftover daemon from a previous run
+killall tailscaled 2>/dev/null || true
-# Start tailscaled with state stored in localdata
+logger -t "Tailscale_VPN" "Starting tailscaled daemon"
"$APP_DIR/lib/tailscaled" \
--state="$STATE_DIR/tailscaled.state" \
--socket="$STATE_DIR/tailscaled.sock" &
+TAILSCALED_PID=$!
-echo "Service Started"
sleep 2
-echo "Scroll to Bottom for link"
-# Run tailscale up with the socket in localdata
+logger -t "Tailscale_VPN" "Connecting to Tailscale network (scroll to bottom for auth URL if prompted)"
"$APP_DIR/lib/tailscale" --socket="$STATE_DIR/tailscaled.sock" up --accept-routes
-wait
+logger -t "Tailscale_VPN" "Tailscale VPN is running"
+wait $TAILSCALED_PID
+logger -t "Tailscale_VPN" "tailscaled exited"
diff --git a/aarch64_ROOT/app/html/index.html b/aarch64_ROOT/app/html/index.html
index f0888d3..cd419e5 100644
--- a/aarch64_ROOT/app/html/index.html
+++ b/aarch64_ROOT/app/html/index.html
@@ -1,19 +1,258 @@
-
-
+
- Scroll to the bottom to find authentication URL
-
+
+ header {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 16px;
+ }
+
+ header svg { flex-shrink: 0; }
+
+ header h1 {
+ font-size: 18px;
+ font-weight: 600;
+ color: #1a1a2e;
+ }
+
+ .card {
+ background: #fff;
+ border-radius: 10px;
+ padding: 18px 20px;
+ margin-bottom: 14px;
+ box-shadow: 0 1px 4px rgba(0,0,0,0.07);
+ }
+
+ /* ── Status row ── */
+ .status-row {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 12px;
+ }
+
+ .dot {
+ width: 11px;
+ height: 11px;
+ border-radius: 50%;
+ flex-shrink: 0;
+ }
+
+ .dot.connected { background: #22c55e; box-shadow: 0 0 0 3px rgba(34,197,94,.2); }
+ .dot.connecting { background: #f59e0b; box-shadow: 0 0 0 3px rgba(245,158,11,.2); }
+ .dot.disconnected { background: #ef4444; box-shadow: 0 0 0 3px rgba(239,68,68,.2); }
+
+ .status-label {
+ font-size: 15px;
+ font-weight: 600;
+ }
+
+ /* ── Auth URL block ── */
+ #auth-block {
+ background: #fffbeb;
+ border: 1px solid #fde68a;
+ border-radius: 8px;
+ padding: 14px 16px;
+ }
+
+ #auth-block p {
+ font-size: 12px;
+ color: #92400e;
+ margin-bottom: 10px;
+ font-weight: 500;
+ }
+
+ #auth-link {
+ display: inline-block;
+ background: #0166ff;
+ color: #fff;
+ text-decoration: none;
+ font-weight: 600;
+ font-size: 13px;
+ padding: 8px 16px;
+ border-radius: 6px;
+ margin-bottom: 10px;
+ }
+
+ #auth-link:hover { background: #0052cc; }
+
+ #auth-url-text {
+ display: block;
+ font-size: 11px;
+ color: #888;
+ word-break: break-all;
+ font-family: monospace;
+ }
+
+ /* ── Info grid (connected state) ── */
+ .info-grid {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ gap: 6px 16px;
+ font-size: 13px;
+ }
+
+ .info-grid .label { color: #888; }
+ .info-grid .value { font-weight: 500; font-family: monospace; }
+
+ /* ── Log section ── */
+ .log-header {
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.6px;
+ color: #999;
+ margin-bottom: 10px;
+ }
+
+ #log-frame {
+ width: 100%;
+ height: 500px;
+ border: 1px solid #e8e8ec;
+ border-radius: 6px;
+ display: block;
+ }
+
-
-
-
+
+
+
+
+
+ Tailscale VPN
+
+
+
+
+
+
+ Checking…
+
+
+
+
+
Open this link in a browser to authenticate this device:
+
Authenticate →
+
+
+
+
+
+ Tailscale IP—
+ Node—
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/aarch64_ROOT/app/lib/.gitkeep b/aarch64_ROOT/app/lib/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/aarch64_ROOT/app/manifest.json b/aarch64_ROOT/app/manifest.json
index ae76ec6..b6824d6 100644
--- a/aarch64_ROOT/app/manifest.json
+++ b/aarch64_ROOT/app/manifest.json
@@ -11,7 +11,7 @@
"username": "root"
},
"vendorUrl": "https://www.tailscale.com",
- "runMode": "once",
+ "runMode": "respawn",
"version": "1.96.2",
"architecture": "aarch64"
},
diff --git a/aarch64_custom/app/html/index.html b/aarch64_custom/app/html/index.html
index 6672898..31670ea 100644
--- a/aarch64_custom/app/html/index.html
+++ b/aarch64_custom/app/html/index.html
@@ -1,19 +1,258 @@
-
-
+
- Scroll to the bottom to find authentication URL
-
+
+ header {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 16px;
+ }
+
+ header svg { flex-shrink: 0; }
+
+ header h1 {
+ font-size: 18px;
+ font-weight: 600;
+ color: #1a1a2e;
+ }
+
+ .card {
+ background: #fff;
+ border-radius: 10px;
+ padding: 18px 20px;
+ margin-bottom: 14px;
+ box-shadow: 0 1px 4px rgba(0,0,0,0.07);
+ }
+
+ /* ── Status row ── */
+ .status-row {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 12px;
+ }
+
+ .dot {
+ width: 11px;
+ height: 11px;
+ border-radius: 50%;
+ flex-shrink: 0;
+ }
+
+ .dot.connected { background: #22c55e; box-shadow: 0 0 0 3px rgba(34,197,94,.2); }
+ .dot.connecting { background: #f59e0b; box-shadow: 0 0 0 3px rgba(245,158,11,.2); }
+ .dot.disconnected { background: #ef4444; box-shadow: 0 0 0 3px rgba(239,68,68,.2); }
+
+ .status-label {
+ font-size: 15px;
+ font-weight: 600;
+ }
+
+ /* ── Auth URL block ── */
+ #auth-block {
+ background: #fffbeb;
+ border: 1px solid #fde68a;
+ border-radius: 8px;
+ padding: 14px 16px;
+ }
+
+ #auth-block p {
+ font-size: 12px;
+ color: #92400e;
+ margin-bottom: 10px;
+ font-weight: 500;
+ }
+
+ #auth-link {
+ display: inline-block;
+ background: #0166ff;
+ color: #fff;
+ text-decoration: none;
+ font-weight: 600;
+ font-size: 13px;
+ padding: 8px 16px;
+ border-radius: 6px;
+ margin-bottom: 10px;
+ }
+
+ #auth-link:hover { background: #0052cc; }
+
+ #auth-url-text {
+ display: block;
+ font-size: 11px;
+ color: #888;
+ word-break: break-all;
+ font-family: monospace;
+ }
+
+ /* ── Info grid (connected state) ── */
+ .info-grid {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ gap: 6px 16px;
+ font-size: 13px;
+ }
+
+ .info-grid .label { color: #888; }
+ .info-grid .value { font-weight: 500; font-family: monospace; }
+
+ /* ── Log section ── */
+ .log-header {
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.6px;
+ color: #999;
+ margin-bottom: 10px;
+ }
+
+ #log-frame {
+ width: 100%;
+ height: 500px;
+ border: 1px solid #e8e8ec;
+ border-radius: 6px;
+ display: block;
+ }
+
-
-
-
+
+
+
+
+
+ Tailscale VPN
+
+
+
+
+
+
+ Checking…
+
+
+
+
+
Open this link in a browser to authenticate this device:
+
Authenticate →
+
+
+
+
+
+ Tailscale IP—
+ Node—
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/aarch64_custom/app/lib/.gitkeep b/aarch64_custom/app/lib/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/aarch64_custom/app/lib/start_tailscale.sh b/aarch64_custom/app/lib/start_tailscale.sh
index d41ee90..2a358db 100644
--- a/aarch64_custom/app/lib/start_tailscale.sh
+++ b/aarch64_custom/app/lib/start_tailscale.sh
@@ -2,7 +2,7 @@
# Make sure this script terminates any existing Tailscale processes before starting new ones
# Kill any existing tailscaled processes
-pkill -f tailscaled || true
+killall tailscaled 2>/dev/null || true
# Simple script to start Tailscale with custom configuration
APP_DIR="/usr/local/packages/serverconfig"
@@ -64,6 +64,14 @@ fi
# Connect to Tailscale network
logger -t "tailscale_script" "Running: $TAILSCALE_CMD"
eval $TAILSCALE_CMD
+UP_EXIT=$?
+
+# Clear auth key from config after first use — Tailscale auth keys are single-use
+# and the node identity is persisted in tailscaled.state, so the key is no longer needed.
+if [ -n "$AUTH_KEY" ] && [ "$UP_EXIT" -eq 0 ] && [ -f "$CONFIG_FILE" ]; then
+ logger -t "tailscale_script" "Clearing auth key from config after successful authentication"
+ printf 'custom_server=%s\nauth_key=\n' "$CUSTOM_SERVER" > "$CONFIG_FILE"
+fi
# Keep the script running to maintain the tailscaled process
logger -t "tailscale_script" "Tailscale VPN is running"
diff --git a/arm/app/Tailscale_VPN b/arm/app/Tailscale_VPN
index 93902d5..82c875d 100755
--- a/arm/app/Tailscale_VPN
+++ b/arm/app/Tailscale_VPN
@@ -1,30 +1,30 @@
#!/bin/sh
-echo "Starting Service"
-
-# Define paths
APP_DIR="/usr/local/packages/Tailscale_VPN"
STATE_DIR="$APP_DIR/localdata"
-# Create localdata directory if it doesn't exist
+logger -t "Tailscale_VPN" "Starting Tailscale VPN service"
+
mkdir -p "$STATE_DIR"
+chmod 755 "$APP_DIR/lib/tailscale"
+chmod 755 "$APP_DIR/lib/tailscaled"
-# Set permissions
-chmod 777 "$APP_DIR/lib/tailscale"
-chmod 777 "$APP_DIR/lib/tailscaled"
+# Kill any leftover daemon from a previous run
+killall tailscaled 2>/dev/null || true
-# Start tailscaled with state stored in localdata
+logger -t "Tailscale_VPN" "Starting tailscaled daemon (userspace networking)"
"$APP_DIR/lib/tailscaled" \
--state="$STATE_DIR/tailscaled.state" \
--socket="$STATE_DIR/tailscaled.sock" \
--socks5-server=localhost:1055 \
--tun=userspace-networking &
+TAILSCALED_PID=$!
-echo "Service Started"
sleep 2
-echo "Scroll to Bottom for link"
-# Run tailscale up with the socket in localdata
+logger -t "Tailscale_VPN" "Connecting to Tailscale network (scroll to bottom for auth URL if prompted)"
"$APP_DIR/lib/tailscale" --socket="$STATE_DIR/tailscaled.sock" up
-wait
+logger -t "Tailscale_VPN" "Tailscale VPN is running"
+wait $TAILSCALED_PID
+logger -t "Tailscale_VPN" "tailscaled exited"
diff --git a/arm/app/html/index.html b/arm/app/html/index.html
index f0888d3..cd419e5 100644
--- a/arm/app/html/index.html
+++ b/arm/app/html/index.html
@@ -1,19 +1,258 @@
-
-
+
- Scroll to the bottom to find authentication URL
-
+
+ header {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 16px;
+ }
+
+ header svg { flex-shrink: 0; }
+
+ header h1 {
+ font-size: 18px;
+ font-weight: 600;
+ color: #1a1a2e;
+ }
+
+ .card {
+ background: #fff;
+ border-radius: 10px;
+ padding: 18px 20px;
+ margin-bottom: 14px;
+ box-shadow: 0 1px 4px rgba(0,0,0,0.07);
+ }
+
+ /* ── Status row ── */
+ .status-row {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 12px;
+ }
+
+ .dot {
+ width: 11px;
+ height: 11px;
+ border-radius: 50%;
+ flex-shrink: 0;
+ }
+
+ .dot.connected { background: #22c55e; box-shadow: 0 0 0 3px rgba(34,197,94,.2); }
+ .dot.connecting { background: #f59e0b; box-shadow: 0 0 0 3px rgba(245,158,11,.2); }
+ .dot.disconnected { background: #ef4444; box-shadow: 0 0 0 3px rgba(239,68,68,.2); }
+
+ .status-label {
+ font-size: 15px;
+ font-weight: 600;
+ }
+
+ /* ── Auth URL block ── */
+ #auth-block {
+ background: #fffbeb;
+ border: 1px solid #fde68a;
+ border-radius: 8px;
+ padding: 14px 16px;
+ }
+
+ #auth-block p {
+ font-size: 12px;
+ color: #92400e;
+ margin-bottom: 10px;
+ font-weight: 500;
+ }
+
+ #auth-link {
+ display: inline-block;
+ background: #0166ff;
+ color: #fff;
+ text-decoration: none;
+ font-weight: 600;
+ font-size: 13px;
+ padding: 8px 16px;
+ border-radius: 6px;
+ margin-bottom: 10px;
+ }
+
+ #auth-link:hover { background: #0052cc; }
+
+ #auth-url-text {
+ display: block;
+ font-size: 11px;
+ color: #888;
+ word-break: break-all;
+ font-family: monospace;
+ }
+
+ /* ── Info grid (connected state) ── */
+ .info-grid {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ gap: 6px 16px;
+ font-size: 13px;
+ }
+
+ .info-grid .label { color: #888; }
+ .info-grid .value { font-weight: 500; font-family: monospace; }
+
+ /* ── Log section ── */
+ .log-header {
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.6px;
+ color: #999;
+ margin-bottom: 10px;
+ }
+
+ #log-frame {
+ width: 100%;
+ height: 500px;
+ border: 1px solid #e8e8ec;
+ border-radius: 6px;
+ display: block;
+ }
+
-
-
-
+
+
+
+
+
+ Tailscale VPN
+
+
+
+
+
+
+ Checking…
+
+
+
+
+
Open this link in a browser to authenticate this device:
+
Authenticate →
+
+
+
+
+
+ Tailscale IP—
+ Node—
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/arm/app/lib/.gitkeep b/arm/app/lib/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/arm/app/manifest.json b/arm/app/manifest.json
index 6c66a6a..2fe1be1 100644
--- a/arm/app/manifest.json
+++ b/arm/app/manifest.json
@@ -7,7 +7,7 @@
"vendor": "Tailscale - Packaged by Mo3he",
"embeddedSdkVersion": "3.0",
"vendorUrl": "https://www.tailscale.com",
- "runMode": "once",
+ "runMode": "respawn",
"version": "1.96.2",
"architecture": "armv7hf"
},
diff --git a/arm_ROOT/app/Tailscale_VPN b/arm_ROOT/app/Tailscale_VPN
index bd4f634..eff7e43 100755
--- a/arm_ROOT/app/Tailscale_VPN
+++ b/arm_ROOT/app/Tailscale_VPN
@@ -1,28 +1,28 @@
#!/bin/sh
-echo "Starting Service"
-
-# Define paths
APP_DIR="/usr/local/packages/Tailscale_VPN"
STATE_DIR="$APP_DIR/localdata"
-# Create localdata directory if it doesn't exist
+logger -t "Tailscale_VPN" "Starting Tailscale VPN service (root mode)"
+
mkdir -p "$STATE_DIR"
+chmod 755 "$APP_DIR/lib/tailscale"
+chmod 755 "$APP_DIR/lib/tailscaled"
-# Set permissions
-chmod 777 "$APP_DIR/lib/tailscale"
-chmod 777 "$APP_DIR/lib/tailscaled"
+# Kill any leftover daemon from a previous run
+killall tailscaled 2>/dev/null || true
-# Start tailscaled with state stored in localdata
+logger -t "Tailscale_VPN" "Starting tailscaled daemon"
"$APP_DIR/lib/tailscaled" \
--state="$STATE_DIR/tailscaled.state" \
--socket="$STATE_DIR/tailscaled.sock" &
+TAILSCALED_PID=$!
-echo "Service Started"
sleep 2
-echo "Scroll to Bottom for link"
-# Run tailscale up with the socket in localdata
+logger -t "Tailscale_VPN" "Connecting to Tailscale network (scroll to bottom for auth URL if prompted)"
"$APP_DIR/lib/tailscale" --socket="$STATE_DIR/tailscaled.sock" up --accept-routes
-wait
+logger -t "Tailscale_VPN" "Tailscale VPN is running"
+wait $TAILSCALED_PID
+logger -t "Tailscale_VPN" "tailscaled exited"
diff --git a/arm_ROOT/app/html/index.html b/arm_ROOT/app/html/index.html
index f0888d3..cd419e5 100644
--- a/arm_ROOT/app/html/index.html
+++ b/arm_ROOT/app/html/index.html
@@ -1,19 +1,258 @@
-
-
+
- Scroll to the bottom to find authentication URL
-
+
+ header {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 16px;
+ }
+
+ header svg { flex-shrink: 0; }
+
+ header h1 {
+ font-size: 18px;
+ font-weight: 600;
+ color: #1a1a2e;
+ }
+
+ .card {
+ background: #fff;
+ border-radius: 10px;
+ padding: 18px 20px;
+ margin-bottom: 14px;
+ box-shadow: 0 1px 4px rgba(0,0,0,0.07);
+ }
+
+ /* ── Status row ── */
+ .status-row {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 12px;
+ }
+
+ .dot {
+ width: 11px;
+ height: 11px;
+ border-radius: 50%;
+ flex-shrink: 0;
+ }
+
+ .dot.connected { background: #22c55e; box-shadow: 0 0 0 3px rgba(34,197,94,.2); }
+ .dot.connecting { background: #f59e0b; box-shadow: 0 0 0 3px rgba(245,158,11,.2); }
+ .dot.disconnected { background: #ef4444; box-shadow: 0 0 0 3px rgba(239,68,68,.2); }
+
+ .status-label {
+ font-size: 15px;
+ font-weight: 600;
+ }
+
+ /* ── Auth URL block ── */
+ #auth-block {
+ background: #fffbeb;
+ border: 1px solid #fde68a;
+ border-radius: 8px;
+ padding: 14px 16px;
+ }
+
+ #auth-block p {
+ font-size: 12px;
+ color: #92400e;
+ margin-bottom: 10px;
+ font-weight: 500;
+ }
+
+ #auth-link {
+ display: inline-block;
+ background: #0166ff;
+ color: #fff;
+ text-decoration: none;
+ font-weight: 600;
+ font-size: 13px;
+ padding: 8px 16px;
+ border-radius: 6px;
+ margin-bottom: 10px;
+ }
+
+ #auth-link:hover { background: #0052cc; }
+
+ #auth-url-text {
+ display: block;
+ font-size: 11px;
+ color: #888;
+ word-break: break-all;
+ font-family: monospace;
+ }
+
+ /* ── Info grid (connected state) ── */
+ .info-grid {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ gap: 6px 16px;
+ font-size: 13px;
+ }
+
+ .info-grid .label { color: #888; }
+ .info-grid .value { font-weight: 500; font-family: monospace; }
+
+ /* ── Log section ── */
+ .log-header {
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.6px;
+ color: #999;
+ margin-bottom: 10px;
+ }
+
+ #log-frame {
+ width: 100%;
+ height: 500px;
+ border: 1px solid #e8e8ec;
+ border-radius: 6px;
+ display: block;
+ }
+
-
-
-
+
+
+
+
+
+ Tailscale VPN
+
+
+
+
+
+
+ Checking…
+
+
+
+
+
Open this link in a browser to authenticate this device:
+
Authenticate →
+
+
+
+
+
+ Tailscale IP—
+ Node—
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/arm_ROOT/app/lib/.gitkeep b/arm_ROOT/app/lib/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/arm_ROOT/app/manifest.json b/arm_ROOT/app/manifest.json
index f704983..6fbed54 100644
--- a/arm_ROOT/app/manifest.json
+++ b/arm_ROOT/app/manifest.json
@@ -11,7 +11,7 @@
"username": "root"
},
"vendorUrl": "https://www.tailscale.com",
- "runMode": "once",
+ "runMode": "respawn",
"version": "1.96.2",
"architecture": "armv7hf"
},
diff --git a/arm_custom/Dockerfile b/arm_custom/Dockerfile
new file mode 100644
index 0000000..09b006f
--- /dev/null
+++ b/arm_custom/Dockerfile
@@ -0,0 +1,12 @@
+ARG ARCH=armv7hf
+ARG VERSION=12.3.0
+ARG UBUNTU_VERSION=24.04
+ARG REPO=axisecp
+ARG SDK=acap-native-sdk
+
+FROM ${REPO}/${SDK}:${VERSION}-${ARCH}-ubuntu${UBUNTU_VERSION}
+
+# Building the ACAP application
+COPY ./app /opt/app/
+WORKDIR /opt/app
+RUN . /opt/axis/acapsdk/environment-setup* && acap-build .
diff --git a/arm_custom/app/Makefile b/arm_custom/app/Makefile
new file mode 100644
index 0000000..393a400
--- /dev/null
+++ b/arm_custom/app/Makefile
@@ -0,0 +1,29 @@
+PROGS = serverconfig
+SRCS = config_updater.c
+OBJS = $(SRCS:.c=.o)
+
+PKGS = glib-2.0 gio-2.0 axparameter
+
+CFLAGS += $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) pkg-config --cflags $(PKGS))
+LDLIBS += $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) pkg-config --libs $(PKGS))
+
+CFLAGS += -Wall \
+ -Wextra \
+ -Wformat=2 \
+ -Wpointer-arith \
+ -Wbad-function-cast \
+ -Wstrict-prototypes \
+ -Wmissing-prototypes \
+ -Winline \
+ -Wdisabled-optimization \
+ -Wfloat-equal \
+ -W \
+ -Werror
+
+all: $(PROGS)
+
+$(PROGS): $(OBJS)
+ $(CC) $(LDFLAGS) $^ $(LIBS) $(LDLIBS) -o $@
+
+clean:
+ rm -f $(PROGS) *.o *.eap* *_LICENSE.txt package.conf* param.conf tmp*
\ No newline at end of file
diff --git a/arm_custom/app/config_updater.c b/arm_custom/app/config_updater.c
new file mode 100644
index 0000000..4fcfb43
--- /dev/null
+++ b/arm_custom/app/config_updater.c
@@ -0,0 +1,253 @@
+/**
+ * Simple file-based configuration updater for Tailscale
+ * This avoids the AXParameter system and just writes directly to a config file
+ */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define APP_NAME "serverconfig"
+#define APP_DIR "/usr/local/packages/serverconfig"
+#define STATE_DIR APP_DIR "/localdata"
+#define CONFIG_FILE STATE_DIR "/config.txt"
+#define SCRIPT_PATH "/usr/local/packages/serverconfig/start_tailscale.sh"
+#define SCRIPT_SOURCE "/usr/local/packages/serverconfig/lib/start_tailscale.sh"
+
+static gboolean signal_handler(gpointer loop) {
+ g_main_loop_quit((GMainLoop*)loop);
+ syslog(LOG_INFO, "Configuration updater stopping.");
+ return G_SOURCE_REMOVE;
+}
+
+// Create localdata directory
+static void ensure_localdata_exists(void) {
+ struct stat st = {0};
+
+ if (stat(STATE_DIR, &st) == -1) {
+ if (mkdir(STATE_DIR, 0755) != 0) {
+ syslog(LOG_ERR, "Failed to create localdata directory: %s", strerror(errno));
+ } else {
+ syslog(LOG_INFO, "Created localdata directory: %s", STATE_DIR);
+ }
+ }
+}
+
+// Copy script from lib folder to main directory
+static void copy_script_file(void) {
+ char buffer[4096];
+ ssize_t bytes_read, bytes_written;
+ int source_fd, dest_fd;
+
+ syslog(LOG_INFO, "Copying script from %s to %s", SCRIPT_SOURCE, SCRIPT_PATH);
+
+ // Open source file
+ source_fd = open(SCRIPT_SOURCE, O_RDONLY);
+ if (source_fd < 0) {
+ syslog(LOG_ERR, "Failed to open source script: %s", strerror(errno));
+ return;
+ }
+
+ // Open destination file (create if doesn't exist, truncate if exists)
+ dest_fd = open(SCRIPT_PATH, O_WRONLY | O_CREAT | O_TRUNC, 0755);
+ if (dest_fd < 0) {
+ syslog(LOG_ERR, "Failed to open destination script: %s", strerror(errno));
+ close(source_fd);
+ return;
+ }
+
+ // Copy the file
+ while ((bytes_read = read(source_fd, buffer, sizeof(buffer))) > 0) {
+ bytes_written = write(dest_fd, buffer, bytes_read);
+ if (bytes_written != bytes_read) {
+ syslog(LOG_ERR, "Error writing to destination file: %s", strerror(errno));
+ close(source_fd);
+ close(dest_fd);
+ return;
+ }
+ }
+
+ // Close file descriptors
+ close(source_fd);
+ close(dest_fd);
+
+ // Make the script executable
+ if (chmod(SCRIPT_PATH, 0755) != 0) {
+ syslog(LOG_ERR, "Failed to make script executable: %s", strerror(errno));
+ return;
+ }
+
+ syslog(LOG_INFO, "Script copied and made executable successfully");
+}
+
+// Execute the Tailscale script
+static void start_tailscale(void) {
+ syslog(LOG_INFO, "Starting Tailscale VPN script");
+
+ // Check if script exists, if not, copy it
+ struct stat st;
+ if (stat(SCRIPT_PATH, &st) != 0) {
+ syslog(LOG_INFO, "Script not found at %s, copying from lib folder", SCRIPT_PATH);
+ copy_script_file();
+ }
+
+ // Fork and execute the script
+ pid_t pid = fork();
+ if (pid < 0) {
+ syslog(LOG_ERR, "Failed to fork for Tailscale script: %s", strerror(errno));
+ return;
+ } else if (pid == 0) {
+ // Child process - execute the script
+ execl(SCRIPT_PATH, "start_tailscale.sh", NULL);
+
+ // If we get here, execl failed
+ syslog(LOG_ERR, "Failed to execute Tailscale script: %s", strerror(errno));
+ _exit(1);
+ }
+
+ syslog(LOG_INFO, "Tailscale script started with PID: %d", pid);
+}
+
+// Update the configuration file with current parameter values
+static void update_config_file(AXParameter* handle) {
+ GError* error = NULL;
+ gchar* server_value = NULL;
+ gchar* key_value = NULL;
+ FILE* file;
+
+ // Ensure localdata directory exists
+ ensure_localdata_exists();
+
+ // Get parameter values
+ if (!ax_parameter_get(handle, "CustomServer", &server_value, &error)) {
+ syslog(LOG_ERR, "Failed to get CustomServer: %s",
+ error ? error->message : "unknown error");
+ if (error) g_error_free(error);
+ error = NULL;
+ server_value = g_strdup("");
+ }
+
+ if (!ax_parameter_get(handle, "AuthKey", &key_value, &error)) {
+ syslog(LOG_ERR, "Failed to get AuthKey: %s",
+ error ? error->message : "unknown error");
+ if (error) g_error_free(error);
+ key_value = g_strdup("");
+ }
+
+ // Write to config file in localdata
+ file = fopen(CONFIG_FILE, "w");
+ if (file) {
+ fprintf(file, "custom_server=%s\n", server_value ? server_value : "");
+ fprintf(file, "auth_key=%s\n", key_value ? key_value : "");
+ fclose(file);
+
+ // Set permissions to ensure the file is readable
+ chmod(CONFIG_FILE, 0644);
+
+ syslog(LOG_INFO, "Updated configuration file in local custom_server=%s",
+ server_value ? server_value : "");
+ syslog(LOG_INFO, "Updated configuration file in local auth_key=%s",
+ key_value && strlen(key_value) > 0 ? "(set)" : "(empty)");
+ } else {
+ syslog(LOG_ERR, "Failed to open config file for writing: %s", strerror(errno));
+ }
+
+ // Clean up
+ g_free(server_value);
+ g_free(key_value);
+}
+
+// Handle parameter changes
+static void parameter_changed(const gchar* name, const gchar* value, gpointer handle_void_ptr) {
+ AXParameter* handle = handle_void_ptr;
+
+ // Extract simple parameter name from the fully qualified name
+ const char* simple_name = name;
+ const char* prefix = "root." APP_NAME ".";
+ if (strncmp(name, prefix, strlen(prefix)) == 0) {
+ simple_name = name + strlen(prefix);
+ }
+
+ syslog(LOG_INFO, "Parameter changed: %s = %s", simple_name, value);
+
+ // Update config file whenever any parameter changes
+ update_config_file(handle);
+
+ // Restart Tailscale to apply the new settings
+ start_tailscale();
+}
+
+int main(void) {
+ GError* error = NULL;
+ GMainLoop* loop = NULL;
+
+ // Open syslog for logging
+ openlog(APP_NAME, LOG_PID, LOG_USER);
+ syslog(LOG_INFO, "Config updater starting");
+
+ // Initialize parameter handling
+ AXParameter* handle = ax_parameter_new(APP_NAME, &error);
+ if (handle == NULL) {
+ syslog(LOG_ERR, "Failed to initialize parameters: %s",
+ error ? error->message : "unknown error");
+ if (error) g_error_free(error);
+ exit(1);
+ }
+
+ // Ensure localdata directory exists
+ ensure_localdata_exists();
+
+ // Ensure script is copied from lib folder
+ copy_script_file();
+
+ // Create initial config file
+ update_config_file(handle);
+
+ // Start Tailscale VPN script
+ start_tailscale();
+
+ // Register for parameter changes
+ if (!ax_parameter_register_callback(handle, "CustomServer", parameter_changed, handle, &error)) {
+ syslog(LOG_ERR, "Failed to register CustomServer callback: %s",
+ error ? error->message : "unknown error");
+ if (error) g_error_free(error);
+ error = NULL;
+ }
+
+ if (!ax_parameter_register_callback(handle, "AuthKey", parameter_changed, handle, &error)) {
+ syslog(LOG_ERR, "Failed to register AuthKey callback: %s",
+ error ? error->message : "unknown error");
+ if (error) g_error_free(error);
+ }
+
+ // Register for parameter changes with fully qualified names as fallback
+ if (!ax_parameter_register_callback(handle, "root." APP_NAME ".CustomServer", parameter_changed, handle, NULL)) {
+ syslog(LOG_INFO, "Fallback CustomServer registration failed (this may be normal)");
+ }
+ if (!ax_parameter_register_callback(handle, "root." APP_NAME ".AuthKey", parameter_changed, handle, NULL)) {
+ syslog(LOG_INFO, "Fallback AuthKey registration failed (this may be normal)");
+ }
+
+ // Set up main loop
+ loop = g_main_loop_new(NULL, FALSE);
+ g_unix_signal_add(SIGTERM, signal_handler, loop);
+ g_unix_signal_add(SIGINT, signal_handler, loop);
+
+ syslog(LOG_INFO, "Config updater running. Waiting for parameter changes...");
+ g_main_loop_run(loop);
+
+ // Clean up
+ g_main_loop_unref(loop);
+ ax_parameter_free(handle);
+
+ return 0;
+}
diff --git a/arm_custom/app/html/index.html b/arm_custom/app/html/index.html
new file mode 100644
index 0000000..31670ea
--- /dev/null
+++ b/arm_custom/app/html/index.html
@@ -0,0 +1,258 @@
+
+
+
+
+ Tailscale VPN
+
+
+
+
+
+
+
+ Tailscale VPN
+
+
+
+
+
+
+ Checking…
+
+
+
+
+
Open this link in a browser to authenticate this device:
+
Authenticate →
+
+
+
+
+
+ Tailscale IP—
+ Node—
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/arm_custom/app/lib/.gitkeep b/arm_custom/app/lib/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/arm_custom/app/lib/start_tailscale.sh b/arm_custom/app/lib/start_tailscale.sh
new file mode 100644
index 0000000..2a358db
--- /dev/null
+++ b/arm_custom/app/lib/start_tailscale.sh
@@ -0,0 +1,81 @@
+#!/bin/sh
+# Make sure this script terminates any existing Tailscale processes before starting new ones
+
+# Kill any existing tailscaled processes
+killall tailscaled 2>/dev/null || true
+
+# Simple script to start Tailscale with custom configuration
+APP_DIR="/usr/local/packages/serverconfig"
+STATE_DIR="$APP_DIR/localdata"
+CONFIG_FILE="$STATE_DIR/config.txt"
+TAILSCALED_PATH="$APP_DIR/lib/tailscaled"
+TAILSCALE_PATH="$APP_DIR/lib/tailscale"
+SOCKET_PATH="$STATE_DIR/tailscaled.sock"
+
+# Create localdata directory if it doesn't exist
+mkdir -p "$STATE_DIR"
+
+# Log to syslog
+logger -t "tailscale_script" "Starting Tailscale VPN service"
+
+# Set execute permissions
+chmod 755 $TAILSCALED_PATH
+chmod 755 $TAILSCALE_PATH
+
+# Read configuration (if exists)
+CUSTOM_SERVER=""
+AUTH_KEY=""
+if [ -f "$CONFIG_FILE" ]; then
+ logger -t "tailscale_script" "Reading configuration from $CONFIG_FILE"
+ # Read values from config file
+ while IFS='=' read -r key value; do
+ case "$key" in
+ "custom_server") CUSTOM_SERVER="$value" ;;
+ "auth_key") AUTH_KEY="$value" ;;
+ esac
+ done < "$CONFIG_FILE"
+fi
+
+# Start tailscaled with state stored in localdata
+logger -t "tailscale_script" "Starting tailscaled daemon"
+$TAILSCALED_PATH \
+ --state="$STATE_DIR/tailscaled.state" \
+ --socket=$SOCKET_PATH \
+ --socks5-server=localhost:1055 \
+ --tun=userspace-networking &
+TAILSCALED_PID=$!
+
+# Wait for tailscaled to initialize
+sleep 2
+
+# Build up the command based on available parameters
+TAILSCALE_CMD="$TAILSCALE_PATH --socket=$SOCKET_PATH up"
+
+if [ -n "$CUSTOM_SERVER" ]; then
+ logger -t "tailscale_script" "Using custom server: $CUSTOM_SERVER"
+ TAILSCALE_CMD="$TAILSCALE_CMD --login-server $CUSTOM_SERVER"
+fi
+
+if [ -n "$AUTH_KEY" ]; then
+ logger -t "tailscale_script" "Using authentication key"
+ TAILSCALE_CMD="$TAILSCALE_CMD --authkey $AUTH_KEY"
+fi
+
+# Connect to Tailscale network
+logger -t "tailscale_script" "Running: $TAILSCALE_CMD"
+eval $TAILSCALE_CMD
+UP_EXIT=$?
+
+# Clear auth key from config after first use — Tailscale auth keys are single-use
+# and the node identity is persisted in tailscaled.state, so the key is no longer needed.
+if [ -n "$AUTH_KEY" ] && [ "$UP_EXIT" -eq 0 ] && [ -f "$CONFIG_FILE" ]; then
+ logger -t "tailscale_script" "Clearing auth key from config after successful authentication"
+ printf 'custom_server=%s\nauth_key=\n' "$CUSTOM_SERVER" > "$CONFIG_FILE"
+fi
+
+# Keep the script running to maintain the tailscaled process
+logger -t "tailscale_script" "Tailscale VPN is running"
+logger -t "tailscale_script" "To change settings, modify parameters in ACAP web interface"
+
+# Wait for tailscaled process to exit
+wait $TAILSCALED_PID
diff --git a/arm_custom/app/manifest.json b/arm_custom/app/manifest.json
new file mode 100644
index 0000000..cdd683e
--- /dev/null
+++ b/arm_custom/app/manifest.json
@@ -0,0 +1,30 @@
+{
+ "schemaVersion": "1.7.3",
+ "acapPackageConf": {
+ "setup": {
+ "friendlyName": "Tailscale VPN",
+ "appName": "serverconfig",
+ "vendor": "Tailscale - Packaged by Mo3he",
+ "embeddedSdkVersion": "3.0",
+ "vendorUrl": "https://www.tailscale.com",
+ "runMode": "respawn",
+ "version": "1.96.2",
+ "architecture": "armv7hf"
+ },
+ "configuration": {
+ "settingPage": "index.html",
+ "paramConfig": [
+ {
+ "name": "CustomServer",
+ "default": "",
+ "type": "string"
+ },
+ {
+ "name": "AuthKey",
+ "default": "",
+ "type": "string"
+ }
+ ]
+ }
+ }
+}