Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ You can start or stop Tailscale from the app's main menu. When Tailscale is runn

You can enable or disable starting Tailscale on boot from the app's settings menu.

### Custom login server

If you are using a self-hosted [Headscale](https://headscale.net/) or Tailscale Enterprise control plane, you can point the pak at your custom login server:

1. In the Tailscale pak menu, select **Login server**.
2. Choose **Custom URL** and enter the HTTPS address of your Headscale or enterprise control plane (for example, `https://headscale.example.com`).
3. Save your changes.

The pak will remember the value you enter and will launch Tailscale with the `--login-server` flag whenever applicable. Switching the option back to **Default** clears the custom URL and reverts to Tailscale's public control plane.

## Debug Logging

Debug logs are written to the `$SDCARD_PATH/.userdata/$PLATFORM/logs/` folder.
Expand Down
27 changes: 25 additions & 2 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,36 @@
"settings": [
{
"name": "Enable",
"options": ["false", "true"],
"options": [
"false",
"true"
],
"selected": 0
},
{
"name": "Start on boot",
"options": ["false", "true"],
"options": [
"false",
"true"
],
"selected": 0
},
{
"name": "Login server",
"options": [
"Default",
"Custom URL"
],
"selected": 0,
"features": {
"description": "Set to Custom URL to edit the login server domain",
"requires_input": true,
"input_label": "Login server URL",
"input_placeholder": "https://headscale.example.com",
"input_description": "Optionally provide a custom Headscale or Tailscale Enterprise login server URL.",
"input_key": "login_server_url",
"input_field_type": "text"
}
}
]
}
120 changes: 118 additions & 2 deletions launch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ HUMAN_READABLE_NAME="Tailscale VPN"
LAUNCHES_SCRIPT="false"

TAILSCALE_AUTHKEY_FILE="$SDCARD_PATH/authkey"
TAILSCALE_LOGIN_SERVER_STATE_FILE="$USERDATA_PATH/$PAK_NAME/login_server.json"

service_off() {
killall "$SERVICE_NAME"
}


show_message() {
message="$1"
seconds="$2"
Expand Down Expand Up @@ -119,6 +121,82 @@ get_service_pid() {
fi
}

load_login_server_state() {
state_file="$TAILSCALE_LOGIN_SERVER_STATE_FILE"
if [ ! -f "$state_file" ]; then
return 1
fi
if ! jq -e 'type == "object"' "$state_file" >/dev/null 2>&1; then
rm -f "$state_file"
return 1
fi
jq -rM '.' "$state_file"
}

parse_login_server_from_settings() {
settings_json="$1"
login_server=$(printf "%s" "$settings_json" | jq -rM '
def trim: gsub("^\\s+|\\s+$"; "");
(.settings[2] // {}) as $login |
if ($login.selected // 0) == 1 then
($login.input.login_server_url // "" | trim)
else
""
end
' 2>/dev/null)

if [ -z "$login_server" ]; then
return 1
fi

printf "%s\n" "$login_server"
}

save_login_server_state() {
settings_json="$1"
state_file="$TAILSCALE_LOGIN_SERVER_STATE_FILE"
tmp_file="${state_file}.tmp"

login_server_json=$(printf "%s" "$settings_json" | jq -c '
def trim: gsub("^\\s+|\\s+$"; "");
(.settings[2] // {}) as $login |
($login.input.login_server_url // "" | trim) as $url |
if ($login.selected // 0) == 1 and ($url | length) > 0 then
{selected: 1, url: $url}
else
empty
end
' 2>/dev/null)

if [ -n "$login_server_json" ]; then
printf "%s\n" "$login_server_json" >"$tmp_file"
mv "$tmp_file" "$state_file"
else
rm -f "$state_file"
fi
}

tailscale_get_login_server() {
settings_json="$1"

if [ -n "$settings_json" ]; then
if login_server_candidate="$(parse_login_server_from_settings "$settings_json")"; then
printf "%s\n" "$login_server_candidate"
return 0
fi
fi

if saved_state="$(load_login_server_state)"; then
login_server_url=$(printf "%s" "$saved_state" | jq -rM '(.url // "") | gsub("^\\s+|\\s+$"; "")' 2>/dev/null)
if [ -n "$login_server_url" ]; then
printf "%s\n" "$login_server_url"
return 0
fi
fi

return 1
}

current_settings() {
minui_list_file="/tmp/${PAK_NAME}-settings.json"
rm -f "$minui_list_file"
Expand All @@ -134,13 +212,38 @@ current_settings() {
mv "$minui_list_file.tmp" "$minui_list_file"
fi

saved_login_server="$(load_login_server_state)"
saved_login_server_url=""
saved_login_server_selected="0"
if [ -n "$saved_login_server" ]; then
saved_login_server_selected=$(printf "%s" "$saved_login_server" | jq -rM '.selected // 0')
saved_login_server_url=$(printf "%s" "$saved_login_server" | jq -rM '.url // ""')
fi

if [ -n "$saved_login_server_url" ]; then
jq --arg url "$saved_login_server_url" '.settings[2].input.login_server_url = $url' "$minui_list_file" >"$minui_list_file.tmp"
mv "$minui_list_file.tmp" "$minui_list_file"
fi

if [ "$saved_login_server_selected" = "1" ] && [ -n "$saved_login_server_url" ]; then
jq '.settings[2].selected = 1' "$minui_list_file" >"$minui_list_file.tmp"
mv "$minui_list_file.tmp" "$minui_list_file"
fi

cat "$minui_list_file"
}

tailscale_login() {
authkey="$1"
login_server="$2"

tailscale_args="--authkey=$authkey --hostname=minui --accept-routes --accept-dns"
if [ -n "$login_server" ]; then
tailscale_args="$tailscale_args --login-server=$login_server"
fi

if ! tailscale up --authkey="$authkey" --hostname="minui" --accept-routes --accept-dns; then
# shellcheck disable=SC2086
if ! tailscale up $tailscale_args; then
return 1
fi
}
Expand Down Expand Up @@ -286,6 +389,15 @@ main() {
old_start_on_boot="$(jq -rM '.settings[1].selected' "/tmp/${PAK_NAME}-old-settings.json")"
start_on_boot="$(jq -rM '.settings[1].selected' "/tmp/${PAK_NAME}-new-settings.json")"

old_login_selection="$(jq -rM '.settings[2].selected // 0' "/tmp/${PAK_NAME}-old-settings.json")"
old_login_input="$(jq -rM '.settings[2].input.login_server_url // ""' "/tmp/${PAK_NAME}-old-settings.json")"
new_login_selection="$(jq -rM '.settings[2].selected // 0' "/tmp/${PAK_NAME}-new-settings.json")"
new_login_input="$(jq -rM '.settings[2].input.login_server_url // ""' "/tmp/${PAK_NAME}-new-settings.json")"

if [ "$old_login_selection" != "$new_login_selection" ] || [ "$old_login_input" != "$new_login_input" ]; then
save_login_server_state "$new_settings"
fi

if [ "$old_enabled" != "$enabled" ]; then
if [ "$enabled" = "1" ]; then
show_message "Starting $HUMAN_READABLE_NAME." 2
Expand All @@ -306,7 +418,11 @@ main() {
service_off
continue
fi
if ! tailscale_login "$authkey"; then

login_server="$(tailscale_get_login_server "$new_settings")"
save_login_server_state "$new_settings"

if ! tailscale_login "$authkey" "$login_server"; then
show_message "Failed to login to $HUMAN_READABLE_NAME." 2
service_off
continue
Expand Down
Loading