-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdebug.ts
More file actions
155 lines (137 loc) · 4.77 KB
/
debug.ts
File metadata and controls
155 lines (137 loc) · 4.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import TailscaleLocalAPI, { type Device } from "./src/index"
async function main() {
let output = "=== Tailscale LocalAPI Debug Tool ===\n\n"
const client = new TailscaleLocalAPI()
let allPassed = true
function getProp<T = unknown>(obj: unknown, keys: string[]): T | undefined {
if (!obj || typeof obj !== "object") return undefined
const o = obj as Record<string, unknown>
for (const k of keys) {
if (Object.prototype.hasOwnProperty.call(o, k) && o[k] != null)
return o[k] as T
}
return undefined
}
function formatIps(device: unknown): string {
const raw = getProp(device, [
"tailscaleIps",
"TailscaleIPs",
"tailscaleIPs",
"TailscaleIps",
"addrs",
"Addrs",
"addresses",
"Addresses",
"curAddr",
"CurAddr",
"ip",
"IP",
"ips",
"IPs"
])
if (Array.isArray(raw)) return (raw as string[]).join(", ")
if (typeof raw === "string" && raw.length > 0) return raw
if (raw == null) return "N/A"
try {
return String(raw)
} catch {
return "N/A"
}
}
try {
output += "1. Status:\n"
const status = await client.status()
output += ` Version: ${status.version}\n`
output += ` Backend State: ${status.backendState}\n`
if (status.backendState === "NeedsLogin") {
output +=
" ❌ Not logged in. Please run 'tailscale up' to login and then re-run this tool.\n"
if (status.authUrl) output += ` Auth URL: ${status.authUrl}\n`
allPassed = false
}
output += ` Self: ${status.self?.dnsName} (${status.self?.tailscaleIps?.[0] || "N/A"})\n`
output += ` Peers: ${Object.keys(status.peer || {}).length}\n`
output += "\n2. Current Profile:\n"
try {
const profile = await client.getCurrentProfile()
if (profile) {
output += ` ID: ${profile.id}\n`
output += ` Name: ${profile.name}\n`
output += ` Login: ${profile.loginName || "N/A"}\n`
} else {
output += " No profile found\n"
}
} catch (error: unknown) {
const err = error as { message?: string }
output += ` ❌ Failed to fetch profile: ${err.message || error}\n`
allPassed = false
}
output += "\n3. Preferences:\n"
try {
const prefs = await client.getPrefs()
const hostName = prefs?.hostName || prefs?.hostName || "N/A"
output += ` HostName: ${hostName}\n`
} catch (error: unknown) {
const err = error as { message?: string }
output += ` ❌ Failed to fetch preferences: ${err.message || error}\n`
allPassed = false
}
output += "\n4. DERP Map:\n"
try {
const derpMap = await client.getDERPMap()
const regionsCount = derpMap?.regions
? Object.keys(derpMap.regions).length
: 0
output += ` Regions: ${regionsCount}\n`
} catch (error: unknown) {
const err = error as { message?: string }
output += ` ❌ Failed to fetch DERP map: ${err.message || error}\n`
allPassed = false
}
output += "\n5. Tailnet Devices:\n"
try {
// Combine self and peers into devices list
const devices: Device[] = [
{ ...status.self, isCurrent: true },
...Object.values(status.peer || {})
]
if (devices.length === 0) {
output += " No devices found\n"
} else {
devices.forEach((device: Device, index: number) => {
const name = device.dnsName || device.hostName || "Unknown"
const ips = formatIps(device)
const os = device.os || "Unknown"
const online = device.online ? "Online" : "Offline"
const lastSeen = device.lastSeen
? new Date(device.lastSeen).toLocaleString()
: "N/A"
const selfMark = device.isCurrent ? " (current device)" : ""
const statusText = device.online
? online
: `${online} (last seen: ${lastSeen})`
output += ` ${index + 1}. ${name}${selfMark}\n`
output += ` IPs: ${ips}\n`
output += ` OS: ${os}\n`
output += ` Status: ${statusText}\n`
output += "\n" // Empty line for spacing
})
}
} catch (error: unknown) {
const err = error as { message?: string }
output += ` ❌ Failed to list devices: ${err.message || error}\n`
allPassed = false
}
output += `\n=== ${allPassed ? "All checks passed" : "Some checks failed (see above)"} ===\n`
if (!allPassed) {
output +=
"\nNote: Some endpoints may not be available in CLI mode on Windows or this Tailscale version.\n"
}
console.log(output)
} catch (error: unknown) {
const err = error as { message?: string }
console.error("\n❌ Critical error:", err.message || error)
process.exit(1)
}
}
main()