Skip to content

Commit 3025df7

Browse files
authored
refactor: handle errors from localapi more explicitly (#90)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved error handling for Tailscale Local API interactions to reduce crashes and improve stability. * Mutating and read operations now catch API failures, log them, and return safe defaults or no-ops instead of propagating errors. * Increased resilience to temporary connectivity or API errors so core functionality continues uninterrupted. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Derek Kaser <[email protected]>
1 parent 64e6fcd commit 3025df7

File tree

1 file changed

+88
-13
lines changed
  • src/usr/local/php/unraid-tailscale-utils/unraid-tailscale-utils

1 file changed

+88
-13
lines changed

src/usr/local/php/unraid-tailscale-utils/unraid-tailscale-utils/LocalAPI.php

Lines changed: 88 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -76,49 +76,107 @@ private function tailscaleLocalAPI(string $url, APIMethods $method = APIMethods:
7676

7777
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
7878

79-
$out = curl_exec($ch) ?: false;
79+
$out = curl_exec($ch);
80+
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
8081
curl_close($ch);
82+
83+
if ($out === false) {
84+
throw new \RuntimeException("Tailscale Local API request failed for URL: {$url}");
85+
}
86+
87+
if ($http_code < 200 || $http_code >= 300) {
88+
throw new \RuntimeException("Tailscale Local API returned HTTP {$http_code} for URL: {$url}");
89+
}
90+
8191
return strval($out);
8292
}
8393

94+
private function decodeJSONResponse(string $response): \stdClass
95+
{
96+
$decoded = json_decode($response);
97+
98+
if (json_last_error() !== JSON_ERROR_NONE) {
99+
throw new \RuntimeException("Failed to decode JSON response: " . json_last_error_msg());
100+
}
101+
102+
return (object) $decoded;
103+
}
104+
84105
public function getStatus(): \stdClass
85106
{
86-
return (object) json_decode($this->tailscaleLocalAPI('v0/status'));
107+
try {
108+
return $this->decodeJSONResponse($this->tailscaleLocalAPI('v0/status'));
109+
} catch (\RuntimeException $e) {
110+
$this->utils->logmsg("Failed to get status: " . $e->getMessage());
111+
return new \stdClass();
112+
}
87113
}
88114

89115
public function getPrefs(): \stdClass
90116
{
91-
return (object) json_decode($this->tailscaleLocalAPI('v0/prefs'));
117+
try {
118+
return $this->decodeJSONResponse($this->tailscaleLocalAPI('v0/prefs'));
119+
} catch (\RuntimeException $e) {
120+
$this->utils->logmsg("Failed to get prefs: " . $e->getMessage());
121+
return new \stdClass();
122+
}
92123
}
93124

94125
public function getTkaStatus(): \stdClass
95126
{
96-
return (object) json_decode($this->tailscaleLocalAPI('v0/tka/status'));
127+
try {
128+
return $this->decodeJSONResponse($this->tailscaleLocalAPI('v0/tka/status'));
129+
} catch (\RuntimeException $e) {
130+
$this->utils->logmsg("Failed to get TKA status: " . $e->getMessage());
131+
return new \stdClass();
132+
}
97133
}
98134

99135
public function getServeConfig(): \stdClass
100136
{
101-
return (object) json_decode($this->tailscaleLocalAPI('v0/serve-config'));
137+
try {
138+
return $this->decodeJSONResponse($this->tailscaleLocalAPI('v0/serve-config'));
139+
} catch (\RuntimeException $e) {
140+
$this->utils->logmsg("Failed to get serve config: " . $e->getMessage());
141+
return new \stdClass();
142+
}
102143
}
103144

104145
public function getPacketFilterRules(): \stdClass
105146
{
106-
return (object) json_decode($this->tailscaleLocalAPI('v0/debug-packet-filter-rules'));
147+
try {
148+
return $this->decodeJSONResponse($this->tailscaleLocalAPI('v0/debug-packet-filter-rules'));
149+
} catch (\RuntimeException $e) {
150+
$this->utils->logmsg("Failed to get packet filter rules: " . $e->getMessage());
151+
return new \stdClass();
152+
}
107153
}
108154

109155
public function resetServeConfig(): void
110156
{
111-
$this->tailscaleLocalAPI("v0/serve-config", APIMethods::POST, new \stdClass());
157+
try {
158+
$this->tailscaleLocalAPI("v0/serve-config", APIMethods::POST, new \stdClass());
159+
} catch (\RuntimeException $e) {
160+
$this->utils->logmsg("Failed to reset serve config: " . $e->getMessage());
161+
}
112162
}
113163

114164
public function setServeConfig(ServeConfig $serveConfig): void
115165
{
116-
$this->tailscaleLocalAPI("v0/serve-config", APIMethods::POST, $serveConfig->getConfig());
166+
try {
167+
$this->tailscaleLocalAPI("v0/serve-config", APIMethods::POST, $serveConfig->getConfig());
168+
} catch (\RuntimeException $e) {
169+
$this->utils->logmsg("Failed to set serve config: " . $e->getMessage());
170+
}
117171
}
118172

119173
public function postLoginInteractive(): void
120174
{
121-
$this->tailscaleLocalAPI('v0/login-interactive', APIMethods::POST);
175+
try {
176+
$this->tailscaleLocalAPI('v0/login-interactive', APIMethods::POST);
177+
} catch (\RuntimeException $e) {
178+
$this->utils->logmsg("Failed to post login interactive: " . $e->getMessage());
179+
}
122180
}
123181

124182
public function patchPref(string $key, mixed $value): void
@@ -127,18 +185,31 @@ public function patchPref(string $key, mixed $value): void
127185
$body[$key] = $value;
128186
$body["{$key}Set"] = true;
129187

130-
$this->tailscaleLocalAPI('v0/prefs', APIMethods::PATCH, (object) $body);
188+
try {
189+
$this->tailscaleLocalAPI('v0/prefs', APIMethods::PATCH, (object) $body);
190+
} catch (\RuntimeException $e) {
191+
$this->utils->logmsg("Failed to patch pref {$key}: " . $e->getMessage());
192+
}
131193
}
132194

133195
public function postTkaSign(string $key): void
134196
{
135197
$body = ["NodeKey" => $key];
136-
$this->tailscaleLocalAPI("v0/tka/sign", APIMethods::POST, (object) $body);
198+
199+
try {
200+
$this->tailscaleLocalAPI("v0/tka/sign", APIMethods::POST, (object) $body);
201+
} catch (\RuntimeException $e) {
202+
$this->utils->logmsg("Failed to sign TKA key: " . $e->getMessage());
203+
}
137204
}
138205

139206
public function expireKey(): void
140207
{
141-
$this->tailscaleLocalAPI('v0/set-expiry-sooner?expiry=0', APIMethods::POST);
208+
try {
209+
$this->tailscaleLocalAPI('v0/set-expiry-sooner?expiry=0', APIMethods::POST);
210+
} catch (\RuntimeException $e) {
211+
$this->utils->logmsg("Failed to expire key: " . $e->getMessage());
212+
}
142213
}
143214

144215
public function setAutoUpdate(bool $enabled): void
@@ -147,6 +218,10 @@ public function setAutoUpdate(bool $enabled): void
147218
$body["AutoUpdate"] = ["Apply" => $enabled, "Check" => $enabled];
148219
$body["AutoUpdateSet"] = ["ApplySet" => true, "CheckSet" => true];
149220

150-
$this->tailscaleLocalAPI("v0/prefs", APIMethods::PATCH, (object) $body);
221+
try {
222+
$this->tailscaleLocalAPI("v0/prefs", APIMethods::PATCH, (object) $body);
223+
} catch (\RuntimeException $e) {
224+
$this->utils->logmsg("Failed to set AutoUpdate: " . $e->getMessage());
225+
}
151226
}
152227
}

0 commit comments

Comments
 (0)