Skip to main content

ryra_core/
error.rs

1use std::path::PathBuf;
2
3#[derive(Debug, thiserror::Error)]
4pub enum Error {
5    #[error("config not found at {0}")]
6    ConfigNotFound(PathBuf),
7
8    #[error("failed to read {path}: {source}")]
9    FileRead {
10        path: PathBuf,
11        source: std::io::Error,
12    },
13
14    #[error("failed to write {path}: {source}")]
15    FileWrite {
16        path: PathBuf,
17        source: std::io::Error,
18    },
19
20    #[error("failed to parse {path}: {source}")]
21    TomlParse {
22        path: PathBuf,
23        source: toml::de::Error,
24    },
25
26    #[error("failed to serialize config: {0}")]
27    TomlSerialize(#[from] toml::ser::Error),
28
29    #[error(
30        "service {name} not found in any registry{}",
31        crate::registry::format_service_suggestions(suggestions)
32    )]
33    ServiceNotFound {
34        name: String,
35        suggestions: Vec<String>,
36    },
37
38    #[error("service {0} is already installed")]
39    ServiceAlreadyInstalled(String),
40
41    #[error("service {0} is not installed")]
42    ServiceNotInstalled(String),
43
44    #[error(
45        "service {0} has leftover state from a prior install (incomplete install or preserved remove)"
46    )]
47    ServiceIncomplete(String),
48
49    #[error("{service} requires the following services to be installed first: {}", missing.join(", "))]
50    MissingRequiredServices {
51        service: String,
52        missing: Vec<String>,
53    },
54
55    #[error("registry {0} not found")]
56    RegistryNotFound(String),
57
58    #[error("no ports available in range {start}–{end}")]
59    PortsExhausted { start: u16, end: u16 },
60
61    #[error("port {port} is already in use")]
62    PortConflict { port: u16 },
63
64    #[error("git command failed: {0}")]
65    Git(String),
66
67    #[error("systemctl command failed: {0}")]
68    Systemctl(String),
69
70    #[error("tailscale: {0}")]
71    Tailscale(String),
72
73    #[error("directory creation failed for {path}: {source}")]
74    DirCreate {
75        path: PathBuf,
76        source: std::io::Error,
77    },
78
79    #[error("template rendering failed: {0}")]
80    Template(String),
81
82    #[error(
83        "auth integration requires an auth provider to be configured first — run `ryra add authelia`"
84    )]
85    AuthNotConfigured,
86
87    #[error("service {0} does not support native OIDC auth")]
88    NoOidcSupport(String),
89
90    #[error(
91        "authelia is local-only at {auth_url}, but {service} will be reachable at \
92         {service_url}. Off-host clients (e.g., other devices on your tailnet) can't \
93         resolve `*.internal` hostnames, so the OIDC redirect from {service} back \
94         to authelia would fail.\n\n\
95         Fix: re-install authelia at the same exposure as {service}:\n  \
96         ryra remove authelia --purge\n  \
97         ryra add authelia --tailscale  (or --url <public-https-url>)"
98    )]
99    AuthExposureMismatch {
100        auth_url: String,
101        service: String,
102        service_url: String,
103    },
104
105    #[error(
106        "{service} uses --auth and authelia is exposed at {auth_url}, but no reverse \
107         proxy is installed. The OIDC back-channel between {service} and authelia runs \
108         through Caddy as the internal TLS terminator, so Caddy must be installed first.\n\n\
109         Fix:\n  \
110         ryra add caddy\n  \
111         then re-run your `ryra add {service} --auth ...` command"
112    )]
113    AuthRequiresReverseProxy { service: String, auth_url: String },
114
115    #[error("{0}")]
116    UnsupportedArchitecture(String),
117
118    #[error("service '{service}' has no env_group named '{group}'{hint}")]
119    UnknownEnvGroup {
120        service: String,
121        group: String,
122        hint: String,
123    },
124
125    #[error("`ryra configure` can't change {field} for service '{service}'. {workaround}")]
126    ConfigureUnsupported {
127        service: String,
128        field: String,
129        workaround: String,
130    },
131
132    #[error("could not determine home directory: set $HOME")]
133    HomeDirNotFound,
134
135    #[error("invalid service reference: {0}")]
136    InvalidServiceRef(String),
137
138    #[error("registry configuration error: {0}")]
139    RegistryConfig(String),
140
141    #[error("quadlet bundle error: {0}")]
142    Bundle(String),
143
144    #[error("auth context error: {0}")]
145    AuthContext(String),
146
147    #[error("config validation failed: {0}")]
148    ConfigValidation(String),
149
150    #[error(
151        "{service}: {} hand-edited file(s) would be overwritten — re-run with --force to overwrite, or back up your changes first:\n  {}",
152        paths.len(),
153        paths.iter().map(|p| p.display().to_string()).collect::<Vec<_>>().join("\n  ")
154    )]
155    HandEditedFiles {
156        service: String,
157        paths: Vec<PathBuf>,
158    },
159
160    #[error("no backups found for service '{0}' — `ryra upgrade` creates them, run that first")]
161    NoBackup(String),
162
163    #[error(
164        "service '{service}' has no backup at timestamp {stamp} — run `ryra revert {service}` to use the most recent"
165    )]
166    BackupNotFound { service: String, stamp: String },
167
168    #[error(
169        "service '{0}' does not declare backup support — the service author must set `backup = true` under [integrations] in its service.toml first"
170    )]
171    BackupNotSupported(String),
172
173    #[error("backup repository is not configured — run `ryra backup configure` first")]
174    BackupRepoNotConfigured,
175
176    #[error("backup is not enabled for service '{0}' — re-install with `ryra add {0} --backup`")]
177    BackupNotEnabled(String),
178
179    #[error(
180        "no snapshots found for service '{0}' in the backup repository — has `ryra backup run` ever succeeded?"
181    )]
182    BackupNoSnapshots(String),
183
184    #[error("restic command failed: {0}")]
185    Restic(String),
186
187    #[error("backup hook '{hook}' for service '{service}' failed: {message}")]
188    BackupHookFailed {
189        service: String,
190        hook: String,
191        message: String,
192    },
193
194    #[error(
195        "service '{service}' was backed up at manifest hash {backed_up} but current install is at {current} — pass --force to restore anyway, or --migrate-to=<dir> to extract without touching the live install"
196    )]
197    BackupVersionMismatch {
198        service: String,
199        backed_up: String,
200        current: String,
201    },
202}
203
204pub type Result<T> = std::result::Result<T, Error>;