#!/usr/bin/env python3 # Copyright (C) 2026 Bryan Joshua Pedini - gpl-3-or-later import subprocess import json import sys def get_tailscale_prefs(): """ Executes 'tailscale debug prefs' and returns the parsed JSON output. """ try: # Run the command and capture stdout result = subprocess.run( ["tailscale", "debug", "prefs"], capture_output=True, text=True, check=True ) return json.loads(result.stdout) except FileNotFoundError: print("Error: 'tailscale' command not found. Please ensure Tailscale is installed.", file=sys.stderr) sys.exit(1) except subprocess.CalledProcessError as e: print(f"Error running 'tailscale debug prefs': {e.stderr}", file=sys.stderr) sys.exit(1) except json.JSONDecodeError as e: print(f"Error parsing JSON output: {e}", file=sys.stderr) sys.exit(1) def process_advertise_routes(routes_array, append_func): """ Processes the AdvertiseRoutes array according to the specific logic: - Separates exit node routes (0.0.0.0/0, ::/0) from standard routes. - Appends '--advertise-exit-node' if exit node routes are found. - Appends '--advertise-routes=' with a comma-separated list of standard routes. """ exit_node_routes = {"0.0.0.0/0", "::/0"} has_exit_node = False standard_routes = [] if routes_array: for route in routes_array: if route in exit_node_routes: has_exit_node = True else: standard_routes.append(route) # If exit node routes are present, append the specific flag if has_exit_node: append_func("--advertise-exit-node") # If standard routes are present, append them as a comma-separated list if standard_routes: routes_str = ",".join(standard_routes) append_func(f"--advertise-routes={routes_str}") def main(): # 1. Get the JSON data from the command prefs = get_tailscale_prefs() # Initialize the main string list cmd_parts = ["tailscale up"] # Helper function to append to the main string list def append_to_cmd(string_part): cmd_parts.append(string_part) # 2. Process RouteAll # Default is false, so we only append if it is true if prefs.get("RouteAll", False): append_to_cmd("--accept-routes") # 3. Process CorpDNS # Default is true, so we only append if it is false (to set --accept-dns=false) if not prefs.get("CorpDNS", True): append_to_cmd("--accept-dns=false") # 4. Process AdvertiseRoutes # We use the dedicated function for the array logic routes = prefs.get("AdvertiseRoutes") if routes: process_advertise_routes(routes, append_to_cmd) # Output the resulting string print(" ".join(cmd_parts)) if __name__ == "__main__": main()