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… +
+ + + + + + +
+ + +
+
Service Log
+ +
+ + - \ 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… +
+ + + + + + +
+ + +
+
Service Log
+ +
+ + - \ 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… +
+ + + + + + +
+ + +
+
Service Log
+ +
+ + - \ 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… +
+ + + + + + +
+ + +
+
Service Log
+ +
+ + - \ 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… +
+ + + + + + +
+ + +
+
Service Log
+ +
+ + - \ 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… +
+ + + + + + +
+ + +
+
Service Log
+ +
+ + + + + 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" + } + ] + } + } +}