ALPHA: wire is a tool to deploy nixos systems
wire.althaea.zone/
1// SPDX-License-Identifier: AGPL-3.0-or-later
2// Copyright 2024-2025 wire Contributors
3
4#![allow(unused_assignments)]
5
6use std::{num::ParseIntError, path::PathBuf, process::ExitStatus, sync::mpsc::RecvError};
7
8use miette::{Diagnostic, SourceSpan};
9use nix_compat::flakeref::{FlakeRef, FlakeRefError};
10use thiserror::Error;
11use tokio::task::JoinError;
12
13use crate::hive::node::{Name, SwitchToConfigurationGoal};
14
15#[cfg(debug_assertions)]
16const DOCS_URL: &str = "http://localhost:5173/reference/errors.html";
17#[cfg(not(debug_assertions))]
18const DOCS_URL: &str = "https://wire.althaea.zone/reference/errors.html";
19
20#[derive(Debug, Diagnostic, Error)]
21pub enum KeyError {
22 #[diagnostic(
23 code(wire::key::File),
24 url("{DOCS_URL}#{}", self.code().unwrap())
25 )]
26 #[error("error reading file")]
27 File(#[source] std::io::Error),
28
29 #[diagnostic(
30 code(wire::key::SpawningCommand),
31 help("Ensure wire has the correct $PATH for this command"),
32 url("{DOCS_URL}#{}", self.code().unwrap())
33 )]
34 #[error("error spawning key command")]
35 CommandSpawnError {
36 #[source]
37 error: std::io::Error,
38
39 #[source_code]
40 command: String,
41
42 #[label(primary, "Program ran")]
43 command_span: Option<SourceSpan>,
44 },
45
46 #[diagnostic(
47 code(wire::key::Resolving),
48 url("{DOCS_URL}#{}", self.code().unwrap())
49 )]
50 #[error("Error resolving key command child process")]
51 CommandResolveError {
52 #[source]
53 error: std::io::Error,
54
55 #[source_code]
56 command: String,
57 },
58
59 #[diagnostic(
60 code(wire::key::CommandExit),
61 url("{DOCS_URL}#{}", self.code().unwrap())
62 )]
63 #[error("key command failed with status {}: {}", .0,.1)]
64 CommandError(ExitStatus, String),
65
66 #[diagnostic(
67 code(wire::key::Empty),
68 url("{DOCS_URL}#{}", self.code().unwrap())
69 )]
70 #[error("Command list empty")]
71 Empty,
72
73 #[diagnostic(
74 code(wire::key::ParseKeyPermissions),
75 help("Refer to the documentation for the format of key file permissions."),
76 url("{DOCS_URL}#{}", self.code().unwrap())
77 )]
78 #[error("Failed to parse key permissions")]
79 ParseKeyPermissions(#[source] ParseIntError),
80}
81
82#[derive(Debug, Diagnostic, Error)]
83pub enum ActivationError {
84 #[diagnostic(
85 code(wire::activation::SwitchToConfiguration),
86 url("{DOCS_URL}#{}", self.code().unwrap())
87 )]
88 #[error("failed to run switch-to-configuration {0} on node {1}")]
89 SwitchToConfigurationError(SwitchToConfigurationGoal, Name, #[source] CommandError),
90}
91
92#[derive(Debug, Diagnostic, Error)]
93pub enum NetworkError {
94 #[diagnostic(
95 code(wire::network::HostUnreachable),
96 help(
97 "If you failed due to a fault in DNS, note that a node can have multiple targets defined."
98 ),
99 url("{DOCS_URL}#{}", self.code().unwrap())
100 )]
101 #[error("Cannot reach host {host}")]
102 HostUnreachable {
103 host: String,
104 #[source]
105 source: CommandError,
106 },
107
108 #[diagnostic(
109 code(wire::network::HostUnreachableAfterReboot),
110 url("{DOCS_URL}#{}", self.code().unwrap())
111 )]
112 #[error("Failed to get regain connection to {0} after activation.")]
113 HostUnreachableAfterReboot(String),
114
115 #[diagnostic(
116 code(wire::network::HostsExhausted),
117 url("{DOCS_URL}#{}", self.code().unwrap())
118 )]
119 #[error("Ran out of contactable hosts")]
120 HostsExhausted,
121}
122
123#[derive(Debug, Diagnostic, Error)]
124pub enum HiveInitialisationError {
125 #[diagnostic(
126 code(wire::hive_init::NoHiveFound),
127 help(
128 "Double check the path is correct. You can adjust the hive path with `--path` when the hive lies outside of the CWD."
129 ),
130 url("{DOCS_URL}#{}", self.code().unwrap())
131 )]
132 #[error("No hive could be found in {}", .0.display())]
133 NoHiveFound(PathBuf),
134
135 #[diagnostic(
136 code(wire::hive_init::Parse),
137 help("If you cannot resolve this problem, please create an issue."),
138 url("{DOCS_URL}#{}", self.code().unwrap())
139 )]
140 #[error("Failed to parse internal wire json.")]
141 ParseEvaluateError(#[source] serde_json::Error),
142
143 #[diagnostic(
144 code(wire::hive_init::ParsePrefetch),
145 help("please create an issue."),
146 url("{DOCS_URL}#{}", self.code().unwrap())
147 )]
148 #[error("Failed to parse `nix flake prefetch --json`.")]
149 ParsePrefetchError(#[source] serde_json::Error),
150
151 #[diagnostic(
152 code(wire::hive_init::NodeDoesNotExist),
153 help("Please create an issue!"),
154 url("{DOCS_URL}#{}", self.code().unwrap())
155 )]
156 #[error("node {0} not exist in hive")]
157 NodeDoesNotExist(String),
158}
159
160#[derive(Debug, Diagnostic, Error)]
161pub enum HiveLocationError {
162 #[diagnostic(
163 code(wire::hive_location::MalformedPath),
164 url("{DOCS_URL}#{}", self.code().unwrap())
165 )]
166 #[error("Path was malformed: {}", .0.display())]
167 MalformedPath(PathBuf),
168
169 #[diagnostic(
170 code(wire::hive_location::Malformed),
171 url("{DOCS_URL}#{}", self.code().unwrap())
172 )]
173 #[error("--path was malformed")]
174 Malformed(#[source] FlakeRefError),
175
176 #[diagnostic(
177 code(wire::hive_location::TypeUnsupported),
178 url("{DOCS_URL}#{}", self.code().unwrap())
179 )]
180 #[error("The flakref had an unsupported type: {:#?}", .0)]
181 TypeUnsupported(Box<FlakeRef>),
182}
183
184#[derive(Debug, Diagnostic, Error)]
185pub enum CommandError {
186 #[diagnostic(
187 code(wire::command::TermAttrs),
188 url("{DOCS_URL}#{}", self.code().unwrap())
189 )]
190 #[error("Failed to set PTY attrs")]
191 TermAttrs(#[source] nix::errno::Errno),
192
193 #[diagnostic(
194 code(wire::command::PosixPipe),
195 url("{DOCS_URL}#{}", self.code().unwrap())
196 )]
197 #[error("There was an error in regards to a pipe")]
198 PosixPipe(#[source] nix::errno::Errno),
199
200 /// Error wrapped around `portable_pty`'s anyhow
201 /// errors
202 #[diagnostic(
203 code(wire::command::PortablePty),
204 url("{DOCS_URL}#{}", self.code().unwrap())
205 )]
206 #[error("There was an error from the portable_pty crate")]
207 PortablePty(#[source] anyhow::Error),
208
209 #[diagnostic(
210 code(wire::command::Joining),
211 url("{DOCS_URL}#{}", self.code().unwrap())
212 )]
213 #[error("Failed to join on some tokio task")]
214 JoinError(#[source] JoinError),
215
216 #[diagnostic(
217 code(wire::command::WaitForStatus),
218 url("{DOCS_URL}#{}", self.code().unwrap())
219 )]
220 #[error("Failed to wait for the child's status")]
221 WaitForStatus(#[source] std::io::Error),
222
223 #[diagnostic(
224 code(wire::detached::NoHandle),
225 help("This should never happen, please create an issue!"),
226 url("{DOCS_URL}#{}", self.code().unwrap())
227 )]
228 #[error("There was no handle to child io")]
229 NoHandle,
230
231 #[diagnostic(
232 code(wire::command::WritingClientStdout),
233 url("{DOCS_URL}#{}", self.code().unwrap())
234 )]
235 #[error("Failed to write to client stderr.")]
236 WritingClientStderr(#[source] std::io::Error),
237
238 #[diagnostic(
239 code(wire::command::WritingMasterStdin),
240 url("{DOCS_URL}#{}", self.code().unwrap())
241 )]
242 #[error("Failed to write to PTY master stdout.")]
243 WritingMasterStdout(#[source] std::io::Error),
244
245 #[diagnostic(
246 code(wire::command::Recv),
247 url("{DOCS_URL}#{}", self.code().unwrap()),
248 help("please create an issue!"),
249 )]
250 #[error("Failed to receive a message from the begin channel")]
251 RecvError(#[source] RecvError),
252
253 #[diagnostic(
254 code(wire::command::ThreadPanic),
255 url("{DOCS_URL}#{}", self.code().unwrap()),
256 help("please create an issue!"),
257 )]
258 #[error("Thread panicked")]
259 ThreadPanic,
260
261 #[diagnostic(
262 code(wire::command::CommandFailed),
263 url("{DOCS_URL}#{}", self.code().unwrap()),
264 help("`nix` commands are filtered, run with -vvv to view all"),
265 )]
266 #[error("{command_ran} failed ({reason}) with {code} (last 20 lines):\n{logs}")]
267 CommandFailed {
268 command_ran: String,
269 logs: String,
270 code: String,
271 reason: &'static str,
272 },
273
274 #[diagnostic(
275 code(wire::command::RuntimeDirectory),
276 url("{DOCS_URL}#{}", self.code().unwrap())
277 )]
278 #[error("error creating $XDG_RUNTIME_DIR/wire")]
279 RuntimeDirectory(#[source] std::io::Error),
280
281 #[diagnostic(
282 code(wire::command::RuntimeDirectoryMissing),
283 url("{DOCS_URL}#{}", self.code().unwrap())
284 )]
285 #[error("$XDG_RUNTIME_DIR could not be used.")]
286 RuntimeDirectoryMissing(#[source] std::env::VarError),
287
288 #[diagnostic(
289 code(wire::command::OneshotRecvError),
290 url("{DOCS_URL}#{}", self.code().unwrap())
291 )]
292 #[error("Error waiting for begin message")]
293 OneshotRecvError(#[source] tokio::sync::oneshot::error::RecvError),
294}
295
296#[derive(Debug, Diagnostic, Error)]
297pub enum HiveLibError {
298 #[error(transparent)]
299 #[diagnostic(transparent)]
300 HiveInitialisationError(HiveInitialisationError),
301
302 #[error(transparent)]
303 #[diagnostic(transparent)]
304 NetworkError(NetworkError),
305
306 #[error(transparent)]
307 #[diagnostic(transparent)]
308 ActivationError(ActivationError),
309
310 #[error(transparent)]
311 #[diagnostic(transparent)]
312 CommandError(CommandError),
313
314 #[error(transparent)]
315 #[diagnostic(transparent)]
316 HiveLocationError(HiveLocationError),
317
318 #[error("Failed to apply key {}", .0)]
319 KeyError(
320 String,
321 #[source]
322 #[diagnostic_source]
323 KeyError,
324 ),
325
326 #[diagnostic(
327 code(wire::BuildNode),
328 url("{DOCS_URL}#{}", self.code().unwrap())
329 )]
330 #[error("failed to build node {name}")]
331 NixBuildError {
332 name: Name,
333 #[source]
334 source: CommandError,
335 },
336
337 #[diagnostic(
338 code(wire::CopyPath),
339 url("{DOCS_URL}#{}", self.code().unwrap())
340 )]
341 #[error("failed to copy path {path} to node {name}")]
342 NixCopyError {
343 name: Name,
344 path: String,
345 #[source]
346 error: Box<CommandError>,
347 #[help]
348 help: Option<Box<String>>,
349 },
350
351 #[diagnostic(code(wire::Evaluate))]
352 #[error("failed to evaluate `{attribute}` from the context of a hive.")]
353 NixEvalError {
354 attribute: String,
355
356 #[source]
357 source: CommandError,
358
359 #[help]
360 help: Option<Box<String>>,
361 },
362
363 #[diagnostic(
364 code(wire::Encoding),
365 url("{DOCS_URL}#{}", self.code().unwrap())
366 )]
367 #[error("error encoding length delimited data")]
368 Encoding(#[source] std::io::Error),
369
370 #[diagnostic(
371 code(wire::SIGINT),
372 url("{DOCS_URL}#{}", self.code().unwrap())
373 )]
374 #[error("SIGINT received, shut down")]
375 Sigint,
376}