mirror of
https://gitlab.com/fabinfra/fabaccess/bffh.git
synced 2024-12-22 11:43:49 +01:00
Merge branch 'feature/bettererrors' into development
* feature/bettererrors: Switch out anyhow for miette More trace output of role checking Better errors when the db directory is missing
This commit is contained in:
commit
9100811c50
190
Cargo.lock
generated
190
Cargo.lock
generated
@ -23,6 +23,21 @@ dependencies = [
|
||||
"pretty",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.7.5"
|
||||
@ -46,6 +61,15 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "annotate-snippets"
|
||||
version = "0.9.1"
|
||||
@ -64,12 +88,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27"
|
||||
|
||||
[[package]]
|
||||
name = "api"
|
||||
version = "0.3.2"
|
||||
@ -275,6 +293,21 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.0"
|
||||
@ -800,13 +833,13 @@ dependencies = [
|
||||
name = "diflouroborane"
|
||||
version = "0.4.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"api",
|
||||
"async-channel",
|
||||
"async-compat",
|
||||
"async-net",
|
||||
"async-oneshot",
|
||||
"async-trait",
|
||||
"backtrace",
|
||||
"capnp",
|
||||
"capnp-rpc",
|
||||
"chrono",
|
||||
@ -826,6 +859,7 @@ dependencies = [
|
||||
"libc",
|
||||
"linkme",
|
||||
"lmdb-rkv",
|
||||
"miette",
|
||||
"nix",
|
||||
"once_cell",
|
||||
"pin-utils",
|
||||
@ -847,6 +881,7 @@ dependencies = [
|
||||
"signal-hook",
|
||||
"signal-hook-async-std",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"toml",
|
||||
"tracing",
|
||||
"tracing-futures",
|
||||
@ -1195,6 +1230,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
|
||||
|
||||
[[package]]
|
||||
name = "git2"
|
||||
version = "0.14.4"
|
||||
@ -1328,6 +1369,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_ci"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb"
|
||||
|
||||
[[package]]
|
||||
name = "is_debug"
|
||||
version = "1.0.1"
|
||||
@ -1543,6 +1590,46 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miette"
|
||||
version = "4.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c90329e44f9208b55f45711f9558cec15d7ef8295cc65ecd6d4188ae8edc58c"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"backtrace",
|
||||
"miette-derive",
|
||||
"once_cell",
|
||||
"owo-colors",
|
||||
"supports-color",
|
||||
"supports-hyperlinks",
|
||||
"supports-unicode",
|
||||
"terminal_size",
|
||||
"textwrap 0.15.0",
|
||||
"thiserror",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miette-derive"
|
||||
version = "4.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b5bc45b761bcf1b5e6e6c4128cd93b84c218721a8d9b894aa0aff4ed180174c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.1"
|
||||
@ -1639,6 +1726,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.28.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.10.0"
|
||||
@ -1678,6 +1774,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "owo-colors"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b"
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.0.0"
|
||||
@ -1973,6 +2075,8 @@ version = "1.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
@ -2134,6 +2238,12 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.0"
|
||||
@ -2429,6 +2539,12 @@ version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
|
||||
|
||||
[[package]]
|
||||
name = "smawk"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.4"
|
||||
@ -2473,6 +2589,34 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "supports-color"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4872ced36b91d47bae8a214a683fe54e7078875b399dfa251df346c9b547d1f9"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"is_ci",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "supports-hyperlinks"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "590b34f7c5f01ecc9d78dba4b3f445f31df750a67621cf31626f3b7441ce6406"
|
||||
dependencies = [
|
||||
"atty",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "supports-unicode"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8b945e45b417b125a8ec51f1b7df2f8df7920367700d1f98aedd21e5735f8b2"
|
||||
dependencies = [
|
||||
"atty",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.88"
|
||||
@ -2507,6 +2651,16 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
@ -2521,21 +2675,26 @@ name = "textwrap"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||
dependencies = [
|
||||
"smawk",
|
||||
"unicode-linebreak",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.30"
|
||||
version = "1.0.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
|
||||
checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.30"
|
||||
version = "1.0.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
|
||||
checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -2773,6 +2932,15 @@ version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-linebreak"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f"
|
||||
dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.19"
|
||||
|
@ -37,7 +37,9 @@ pin-utils = "0.1.0"
|
||||
futures-util = "0.3"
|
||||
futures-lite = "1.12.0"
|
||||
async-net = "1.6.1"
|
||||
anyhow = "1.0.56"
|
||||
backtrace = "0.3.65"
|
||||
miette = { version = "4.7.1", features = ["fancy"] }
|
||||
thiserror = "1.0.31"
|
||||
toml = "0.5.8"
|
||||
|
||||
# Well-known paths/dirs for e.g. cache
|
||||
|
@ -12,6 +12,7 @@ use std::future::Future;
|
||||
|
||||
use std::pin::Pin;
|
||||
|
||||
use miette::IntoDiagnostic;
|
||||
use std::task::{Context, Poll};
|
||||
use std::time::Duration;
|
||||
|
||||
@ -110,11 +111,11 @@ static ROOT_CERTS: Lazy<RootCertStore> = Lazy::new(|| {
|
||||
store
|
||||
});
|
||||
|
||||
pub fn load(executor: Executor, config: &Config, resources: ResourcesHandle) -> anyhow::Result<()> {
|
||||
pub fn load(executor: Executor, config: &Config, resources: ResourcesHandle) -> miette::Result<()> {
|
||||
let span = tracing::info_span!("loading actors");
|
||||
let _guard = span;
|
||||
|
||||
let mqtt_url = Url::parse(config.mqtt_url.as_str())?;
|
||||
let mqtt_url = Url::parse(config.mqtt_url.as_str()).into_diagnostic()?;
|
||||
let (transport, default_port) = match mqtt_url.scheme() {
|
||||
"mqtts" | "ssl" => (
|
||||
rumqttc::Transport::tls_with_config(
|
||||
@ -131,12 +132,12 @@ pub fn load(executor: Executor, config: &Config, resources: ResourcesHandle) ->
|
||||
|
||||
scheme => {
|
||||
tracing::error!(%scheme, "MQTT url uses invalid scheme");
|
||||
anyhow::bail!("invalid config");
|
||||
miette::bail!("invalid config");
|
||||
}
|
||||
};
|
||||
let host = mqtt_url.host_str().ok_or_else(|| {
|
||||
tracing::error!("MQTT url must contain a hostname");
|
||||
anyhow::anyhow!("invalid config")
|
||||
miette::miette!("invalid config")
|
||||
})?;
|
||||
let port = mqtt_url.port().unwrap_or(default_port);
|
||||
|
||||
@ -167,7 +168,7 @@ pub fn load(executor: Executor, config: &Config, resources: ResourcesHandle) ->
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::error!(?error, "MQTT connection failed");
|
||||
anyhow::bail!("mqtt connection failed")
|
||||
miette::bail!("mqtt connection failed")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::users::Users;
|
||||
use miette::{Context, IntoDiagnostic};
|
||||
use rsasl::error::SessionError;
|
||||
use rsasl::mechname::Mechname;
|
||||
use rsasl::property::{AuthId, Password};
|
||||
@ -127,8 +128,13 @@ impl AuthenticationHandle {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&self, mechanism: &Mechname) -> anyhow::Result<Session> {
|
||||
Ok(self.inner.rsasl.server_start(mechanism)?)
|
||||
pub fn start(&self, mechanism: &Mechname) -> miette::Result<Session> {
|
||||
Ok(self
|
||||
.inner
|
||||
.rsasl
|
||||
.server_start(mechanism)
|
||||
.into_diagnostic()
|
||||
.wrap_err("Failed to start a SASL authentication with the given mechanism")?)
|
||||
}
|
||||
|
||||
pub fn list_available_mechs(&self) -> impl IntoIterator<Item = &Mechname> {
|
||||
|
@ -67,17 +67,20 @@ impl Roles {
|
||||
role_id: &String,
|
||||
perm: &Permission,
|
||||
) -> bool {
|
||||
let _guard = tracing::debug_span!("tally", %role_id, perm=perm.as_str());
|
||||
if let Some(role) = self.get(role_id) {
|
||||
// Only check and tally parents of a role at the role itself if it's the first time we
|
||||
// see it
|
||||
if !roles.contains(role_id) {
|
||||
for perm_rule in role.permissions.iter() {
|
||||
if perm_rule.match_perm(perm) {
|
||||
tracing::debug!("Permission granted by direct role");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for parent in role.parents.iter() {
|
||||
if self.permitted_tally(roles, parent, perm) {
|
||||
tracing::debug!(%parent, "Permission granted by parent role");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -86,10 +89,13 @@ impl Roles {
|
||||
}
|
||||
}
|
||||
|
||||
tracing::trace!(%role_id, "Permission not granted by role");
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_permitted(&self, user: &UserData, perm: impl AsRef<Permission>) -> bool {
|
||||
let perm = perm.as_ref();
|
||||
tracing::debug!(perm = perm.as_str(), "Checking permission");
|
||||
let mut seen = HashSet::new();
|
||||
for role_id in user.roles.iter() {
|
||||
if self.permitted_tally(&mut seen, role_id, perm.as_ref()) {
|
||||
|
@ -60,7 +60,7 @@ impl APIServer {
|
||||
acceptor: TlsAcceptor,
|
||||
sessionmanager: SessionManager,
|
||||
authentication: AuthenticationHandle,
|
||||
) -> anyhow::Result<Self> {
|
||||
) -> miette::Result<Self> {
|
||||
let span = tracing::info_span!("binding API listen sockets");
|
||||
let _guard = span.enter();
|
||||
|
||||
|
@ -1,7 +1,81 @@
|
||||
use thiserror::Error;
|
||||
|
||||
mod raw;
|
||||
|
||||
use miette::{Diagnostic, LabeledSpan, Severity, SourceCode};
|
||||
pub use raw::RawDB;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
|
||||
mod typed;
|
||||
pub use typed::{Adapter, AlignedAdapter, ArchivedValue, DB};
|
||||
|
||||
pub type Error = lmdb::Error;
|
||||
pub type ErrorO = lmdb::Error;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Error)]
|
||||
#[error(transparent)]
|
||||
pub struct Error(#[from] lmdb::Error);
|
||||
|
||||
impl Diagnostic for Error {
|
||||
fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
|
||||
Some(Box::new(match self.0 {
|
||||
lmdb::Error::KeyExist => "bffh::db::raw::key_exists".to_string(),
|
||||
lmdb::Error::NotFound => "bffh::db::raw::not_found".to_string(),
|
||||
lmdb::Error::PageNotFound => "bffh::db::raw::page_not_found".to_string(),
|
||||
lmdb::Error::Corrupted => "bffh::db::raw::corrupted".to_string(),
|
||||
lmdb::Error::Panic => "bffh::db::raw::panic".to_string(),
|
||||
lmdb::Error::VersionMismatch => "bffh::db::raw::version_mismatch".to_string(),
|
||||
lmdb::Error::Invalid => "bffh::db::raw::invalid".to_string(),
|
||||
lmdb::Error::MapFull => "bffh::db::raw::map_full".to_string(),
|
||||
lmdb::Error::DbsFull => "bffh::db::raw::dbs_full".to_string(),
|
||||
lmdb::Error::ReadersFull => "bffh::db::raw::readers_full".to_string(),
|
||||
lmdb::Error::TlsFull => "bffh::db::raw::tls_full".to_string(),
|
||||
lmdb::Error::TxnFull => "bffh::db::raw::txn_full".to_string(),
|
||||
lmdb::Error::CursorFull => "bffh::db::raw::cursor_full".to_string(),
|
||||
lmdb::Error::PageFull => "bffh::db::raw::page_full".to_string(),
|
||||
lmdb::Error::MapResized => "bffh::db::raw::map_resized".to_string(),
|
||||
lmdb::Error::Incompatible => "bffh::db::raw::incompatible".to_string(),
|
||||
lmdb::Error::BadRslot => "bffh::db::raw::bad_rslot".to_string(),
|
||||
lmdb::Error::BadTxn => "bffh::db::raw::bad_txn".to_string(),
|
||||
lmdb::Error::BadValSize => "bffh::db::raw::bad_val_size".to_string(),
|
||||
lmdb::Error::BadDbi => "bffh::db::raw::bad_dbi".to_string(),
|
||||
lmdb::Error::Other(n) => format!("bffh::db::raw::e{}", n),
|
||||
}))
|
||||
}
|
||||
|
||||
fn severity(&self) -> Option<Severity> {
|
||||
Some(Severity::Error)
|
||||
}
|
||||
|
||||
fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
|
||||
match self.0 {
|
||||
lmdb::Error::KeyExist => Some(Box::new("The provided key already exists in the database")),
|
||||
lmdb::Error::NotFound => Some(Box::new("The requested key was not found in the database")),
|
||||
lmdb::Error::PageNotFound => Some(Box::new("The requested page was not found. This usually indicates corruption.")),
|
||||
lmdb::Error::Corrupted => None,
|
||||
lmdb::Error::Panic => None,
|
||||
lmdb::Error::VersionMismatch => None,
|
||||
lmdb::Error::Invalid => None,
|
||||
lmdb::Error::MapFull => None,
|
||||
lmdb::Error::DbsFull => None,
|
||||
lmdb::Error::ReadersFull => None,
|
||||
lmdb::Error::TlsFull => None,
|
||||
lmdb::Error::TxnFull => None,
|
||||
lmdb::Error::CursorFull => None,
|
||||
lmdb::Error::PageFull => None,
|
||||
lmdb::Error::MapResized => None,
|
||||
lmdb::Error::Incompatible => None,
|
||||
lmdb::Error::BadRslot => Some(Box::new("This usually indicates that the operation can't complete because an incompatible transaction is still open.")),
|
||||
lmdb::Error::BadTxn => None,
|
||||
lmdb::Error::BadValSize => None,
|
||||
lmdb::Error::BadDbi => None,
|
||||
lmdb::Error::Other(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
use super::Result;
|
||||
use lmdb::{DatabaseFlags, Environment, RwTransaction, Transaction, WriteFlags};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -142,11 +142,11 @@ impl<A: Adapter> DB<A> {
|
||||
}
|
||||
|
||||
pub fn del(&self, txn: &mut RwTransaction, key: &impl AsRef<[u8]>) -> Result<(), db::Error> {
|
||||
self.db.del::<_, &[u8]>(txn, key, None)
|
||||
Ok(self.db.del::<_, &[u8]>(txn, key, None)?)
|
||||
}
|
||||
|
||||
pub fn clear(&self, txn: &mut RwTransaction) -> Result<(), db::Error> {
|
||||
self.db.clear(txn)
|
||||
Ok(self.db.clear(txn)?)
|
||||
}
|
||||
|
||||
pub fn get_all<'txn, T: Transaction>(
|
||||
|
160
bffhd/error.rs
160
bffhd/error.rs
@ -1,74 +1,116 @@
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::db;
|
||||
use rsasl::error::SessionError;
|
||||
use std::any::TypeId;
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt;
|
||||
use std::fmt::Display;
|
||||
use std::io;
|
||||
|
||||
type DBError = db::Error;
|
||||
use crate::resources::state::db::StateDBError;
|
||||
use backtrace::{Backtrace, BacktraceFmt, PrintFmt};
|
||||
use miette::{Diagnostic, LabeledSpan, Severity, SourceCode};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TracedError<E: Diagnostic> {
|
||||
pub inner: E,
|
||||
pub backtrace: Backtrace,
|
||||
}
|
||||
|
||||
impl<E: Diagnostic> fmt::Display for TracedError<E> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
writeln!(f, "Error: {}", self.inner)?;
|
||||
|
||||
let cwd = std::env::current_dir();
|
||||
let mut print_path =
|
||||
move |fmt: &mut fmt::Formatter<'_>, path: backtrace::BytesOrWideString<'_>| {
|
||||
let path = path.into_path_buf();
|
||||
if let Ok(cwd) = &cwd {
|
||||
if let Ok(suffix) = path.strip_prefix(cwd) {
|
||||
return fmt::Display::fmt(&suffix.display(), fmt);
|
||||
}
|
||||
}
|
||||
fmt::Display::fmt(&path.display(), fmt)
|
||||
};
|
||||
let mut bf = BacktraceFmt::new(f, PrintFmt::Short, &mut print_path);
|
||||
bf.add_context()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: 'static + Diagnostic> StdError for TracedError<E> {
|
||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||
self.inner.source()
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: 'static + Diagnostic> Diagnostic for TracedError<E> {
|
||||
#[inline(always)]
|
||||
fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
|
||||
self.inner.code()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn severity(&self) -> Option<Severity> {
|
||||
self.inner.severity()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
|
||||
self.inner.help()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
|
||||
self.inner.url()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn source_code(&self) -> Option<&dyn SourceCode> {
|
||||
self.inner.source_code()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
|
||||
self.inner.labels()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
|
||||
self.inner.related()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
|
||||
self.inner.diagnostic_source()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
/// Shared error type
|
||||
pub enum Error {
|
||||
pub enum BffhError {
|
||||
#[error("SASL error: {0:?}")]
|
||||
SASL(SessionError),
|
||||
IO(io::Error),
|
||||
Boxed(Box<dyn std::error::Error>),
|
||||
Capnp(capnp::Error),
|
||||
DB(DBError),
|
||||
#[error("IO error: {0}")]
|
||||
IO(#[from] io::Error),
|
||||
#[error("IO error: {0}")]
|
||||
Boxed(#[from] Box<dyn std::error::Error>),
|
||||
#[error("IO error: {0}")]
|
||||
Capnp(#[from] capnp::Error),
|
||||
#[error("IO error: {0}")]
|
||||
DB(#[from] db::Error),
|
||||
#[error("You do not have the permission required to do that.")]
|
||||
Denied,
|
||||
#[error("State DB operation failed")]
|
||||
StateDB(#[from] StateDBError),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Error::SASL(e) => {
|
||||
write!(f, "SASL Error: {}", e)
|
||||
}
|
||||
Error::IO(e) => {
|
||||
write!(f, "IO Error: {}", e)
|
||||
}
|
||||
Error::Boxed(e) => {
|
||||
write!(f, "{}", e)
|
||||
}
|
||||
Error::Capnp(e) => {
|
||||
write!(f, "Cap'n Proto Error: {}", e)
|
||||
}
|
||||
Error::DB(e) => {
|
||||
write!(f, "DB Error: {:?}", e)
|
||||
}
|
||||
Error::Denied => {
|
||||
write!(f, "You do not have the permission required to do that.")
|
||||
}
|
||||
}
|
||||
impl From<SessionError> for BffhError {
|
||||
fn from(e: SessionError) -> Self {
|
||||
Self::SASL(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SessionError> for Error {
|
||||
fn from(e: SessionError) -> Error {
|
||||
Error::SASL(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(e: io::Error) -> Error {
|
||||
Error::IO(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<dyn std::error::Error>> for Error {
|
||||
fn from(e: Box<dyn std::error::Error>) -> Error {
|
||||
Error::Boxed(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<capnp::Error> for Error {
|
||||
fn from(e: capnp::Error) -> Error {
|
||||
Error::Capnp(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DBError> for Error {
|
||||
fn from(e: DBError) -> Error {
|
||||
Error::DB(e)
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
pub type Result<T> = std::result::Result<T, BffhError>;
|
||||
|
23
bffhd/lib.rs
23
bffhd/lib.rs
@ -42,9 +42,8 @@ mod tls;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
use futures_util::StreamExt;
|
||||
use miette::{Context, IntoDiagnostic, Report};
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
use crate::audit::AuditLog;
|
||||
@ -75,7 +74,7 @@ pub struct Diflouroborane {
|
||||
pub static RESOURCES: OnceCell<ResourcesHandle> = OnceCell::new();
|
||||
|
||||
impl Diflouroborane {
|
||||
pub fn new(config: Config) -> anyhow::Result<Self> {
|
||||
pub fn new(config: Config) -> miette::Result<Self> {
|
||||
logging::init(&config.logging);
|
||||
tracing::info!(version = env::VERSION, "Starting BFFH");
|
||||
|
||||
@ -85,13 +84,15 @@ impl Diflouroborane {
|
||||
let executor = Executor::new();
|
||||
|
||||
let env = StateDB::open_env(&config.db_path)?;
|
||||
let statedb =
|
||||
StateDB::create_with_env(env.clone()).context("Failed to open state DB file")?;
|
||||
|
||||
let users = Users::new(env.clone()).context("Failed to open users DB file")?;
|
||||
let statedb = StateDB::create_with_env(env.clone())?;
|
||||
|
||||
let users = Users::new(env.clone())?;
|
||||
let roles = Roles::new(config.roles.clone());
|
||||
|
||||
let _audit_log = AuditLog::new(&config).context("Failed to initialize audit log")?;
|
||||
let _audit_log = AuditLog::new(&config)
|
||||
.into_diagnostic()
|
||||
.wrap_err("Failed to initialize audit log")?;
|
||||
|
||||
let resources = ResourcesHandle::new(config.machines.iter().map(|(id, desc)| {
|
||||
Resource::new(Arc::new(resources::Inner::new(
|
||||
@ -112,13 +113,15 @@ impl Diflouroborane {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> anyhow::Result<()> {
|
||||
pub fn run(&mut self) -> miette::Result<()> {
|
||||
let mut signals = signal_hook_async_std::Signals::new(&[SIGINT, SIGQUIT, SIGTERM])
|
||||
.context("Failed to construct signal handler")?;
|
||||
.into_diagnostic()
|
||||
.wrap_err("Failed to construct signal handler")?;
|
||||
|
||||
actors::load(self.executor.clone(), &self.config, self.resources.clone())?;
|
||||
|
||||
let tlsconfig = TlsConfig::new(self.config.tlskeylog.as_ref(), !self.config.is_quiet())?;
|
||||
let tlsconfig = TlsConfig::new(self.config.tlskeylog.as_ref(), !self.config.is_quiet())
|
||||
.into_diagnostic()?;
|
||||
let acceptor = tlsconfig.make_tls_acceptor(&self.config.tlsconfig)?;
|
||||
|
||||
let sessionmanager = SessionManager::new(self.users.clone(), self.roles.clone());
|
||||
|
@ -1,6 +1,12 @@
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::db;
|
||||
use crate::db::{AlignedAdapter, ArchivedValue, RawDB, DB};
|
||||
use lmdb::{DatabaseFlags, Environment, EnvironmentFlags, Transaction, WriteFlags};
|
||||
use miette::{Diagnostic, LabeledSpan, Severity, SourceCode};
|
||||
use std::any::TypeId;
|
||||
use std::error::Error;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::{path::Path, sync::Arc};
|
||||
|
||||
use crate::resources::state::State;
|
||||
@ -11,8 +17,24 @@ pub struct StateDB {
|
||||
db: DB<AlignedAdapter<State>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
pub enum StateDBError {
|
||||
#[error("opening the state db environment failed")]
|
||||
#[diagnostic(
|
||||
code(bffh::db::state::open_env),
|
||||
help("does the parent directory for state_db exist?")
|
||||
)]
|
||||
OpenEnv(#[source] db::Error),
|
||||
#[error("opening the state db failed")]
|
||||
#[diagnostic(code(bffh::db::state::open))]
|
||||
Open(#[source] db::Error),
|
||||
#[error("creating the state db failed")]
|
||||
#[diagnostic(code(bffh::db::state::create))]
|
||||
Create(#[source] db::Error),
|
||||
}
|
||||
|
||||
impl StateDB {
|
||||
pub fn open_env<P: AsRef<Path>>(path: P) -> lmdb::Result<Arc<Environment>> {
|
||||
pub fn open_env<P: AsRef<Path>>(path: P) -> Result<Arc<Environment>, StateDBError> {
|
||||
Environment::new()
|
||||
.set_flags(
|
||||
EnvironmentFlags::WRITE_MAP
|
||||
@ -23,6 +45,7 @@ impl StateDB {
|
||||
.set_max_dbs(8)
|
||||
.open(path.as_ref())
|
||||
.map(Arc::new)
|
||||
.map_err(|e| StateDBError::OpenEnv(e.into()))
|
||||
}
|
||||
|
||||
fn new(env: Arc<Environment>, db: RawDB) -> Self {
|
||||
@ -30,30 +53,32 @@ impl StateDB {
|
||||
Self { env, db }
|
||||
}
|
||||
|
||||
pub fn open_with_env(env: Arc<Environment>) -> lmdb::Result<Self> {
|
||||
let db = unsafe { RawDB::open(&env, Some("state"))? };
|
||||
pub fn open_with_env(env: Arc<Environment>) -> Result<Self, StateDBError> {
|
||||
let db = unsafe { RawDB::open(&env, Some("state")) };
|
||||
let db = db.map_err(|e| StateDBError::Open(e.into()))?;
|
||||
Ok(Self::new(env, db))
|
||||
}
|
||||
|
||||
pub fn open<P: AsRef<Path>>(path: P) -> lmdb::Result<Self> {
|
||||
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, StateDBError> {
|
||||
let env = Self::open_env(path)?;
|
||||
Self::open_with_env(env)
|
||||
}
|
||||
|
||||
pub fn create_with_env(env: Arc<Environment>) -> lmdb::Result<Self> {
|
||||
pub fn create_with_env(env: Arc<Environment>) -> Result<Self, StateDBError> {
|
||||
let flags = DatabaseFlags::empty();
|
||||
let db = unsafe { RawDB::create(&env, Some("state"), flags)? };
|
||||
let db = unsafe { RawDB::create(&env, Some("state"), flags) };
|
||||
let db = db.map_err(|e| StateDBError::Create(e.into()))?;
|
||||
|
||||
Ok(Self::new(env, db))
|
||||
}
|
||||
|
||||
pub fn create<P: AsRef<Path>>(path: P) -> lmdb::Result<Self> {
|
||||
pub fn create<P: AsRef<Path>>(path: P) -> Result<Self, StateDBError> {
|
||||
let env = Self::open_env(path)?;
|
||||
Self::create_with_env(env)
|
||||
}
|
||||
|
||||
pub fn begin_ro_txn(&self) -> Result<impl Transaction + '_, db::Error> {
|
||||
self.env.begin_ro_txn()
|
||||
self.env.begin_ro_txn().map_err(db::Error::from)
|
||||
}
|
||||
|
||||
pub fn get(&self, key: impl AsRef<[u8]>) -> Result<Option<ArchivedValue<State>>, db::Error> {
|
||||
@ -72,7 +97,7 @@ impl StateDB {
|
||||
let mut txn = self.env.begin_rw_txn()?;
|
||||
let flags = WriteFlags::empty();
|
||||
self.db.put(&mut txn, key, val, flags)?;
|
||||
txn.commit()
|
||||
Ok(txn.commit()?)
|
||||
}
|
||||
}
|
||||
|
||||
|
22
bffhd/tls.rs
22
bffhd/tls.rs
@ -6,6 +6,7 @@ use std::sync::Arc;
|
||||
|
||||
use crate::capnp::TlsListen;
|
||||
use futures_rustls::TlsAcceptor;
|
||||
use miette::IntoDiagnostic;
|
||||
use rustls::version::{TLS12, TLS13};
|
||||
use rustls::{Certificate, PrivateKey, ServerConfig, SupportedCipherSuite};
|
||||
use tracing::Level;
|
||||
@ -74,26 +75,27 @@ impl TlsConfig {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_tls_acceptor(&self, config: &TlsListen) -> anyhow::Result<TlsAcceptor> {
|
||||
pub fn make_tls_acceptor(&self, config: &TlsListen) -> miette::Result<TlsAcceptor> {
|
||||
let span = tracing::debug_span!("tls");
|
||||
let _guard = span.enter();
|
||||
|
||||
tracing::debug!(path = %config.certfile.as_path().display(), "reading certificates");
|
||||
let mut certfp = BufReader::new(File::open(config.certfile.as_path())?);
|
||||
let certs = rustls_pemfile::certs(&mut certfp)?
|
||||
let mut certfp = BufReader::new(File::open(config.certfile.as_path()).into_diagnostic()?);
|
||||
let certs = rustls_pemfile::certs(&mut certfp)
|
||||
.into_diagnostic()?
|
||||
.into_iter()
|
||||
.map(Certificate)
|
||||
.collect();
|
||||
|
||||
tracing::debug!(path = %config.keyfile.as_path().display(), "reading private key");
|
||||
let mut keyfp = BufReader::new(File::open(config.keyfile.as_path())?);
|
||||
let key = match rustls_pemfile::read_one(&mut keyfp)? {
|
||||
let mut keyfp = BufReader::new(File::open(config.keyfile.as_path()).into_diagnostic()?);
|
||||
let key = match rustls_pemfile::read_one(&mut keyfp).into_diagnostic()? {
|
||||
Some(rustls_pemfile::Item::PKCS8Key(key) | rustls_pemfile::Item::RSAKey(key)) => {
|
||||
PrivateKey(key)
|
||||
}
|
||||
_ => {
|
||||
tracing::error!("private key file invalid");
|
||||
anyhow::bail!("private key file must contain a PEM-encoded private key")
|
||||
miette::bail!("private key file must contain a PEM-encoded private key")
|
||||
}
|
||||
};
|
||||
|
||||
@ -105,15 +107,17 @@ impl TlsConfig {
|
||||
match min.as_str() {
|
||||
"tls12" => tls_builder.with_protocol_versions(&[&TLS12]),
|
||||
"tls13" => tls_builder.with_protocol_versions(&[&TLS13]),
|
||||
x => anyhow::bail!("TLS version {} is invalid", x),
|
||||
x => miette::bail!("TLS version {} is invalid", x),
|
||||
}
|
||||
} else {
|
||||
tls_builder.with_safe_default_protocol_versions()
|
||||
}?;
|
||||
}
|
||||
.into_diagnostic()?;
|
||||
|
||||
let mut tls_config = tls_builder
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(certs, key)?;
|
||||
.with_single_cert(certs, key)
|
||||
.into_diagnostic()?;
|
||||
|
||||
if let Some(keylog) = &self.keylog {
|
||||
tls_config.key_log = keylog.clone();
|
||||
|
@ -2,7 +2,7 @@ use lmdb::{DatabaseFlags, Environment, RwTransaction, Transaction, WriteFlags};
|
||||
use rkyv::Infallible;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Context;
|
||||
use miette::{Context, IntoDiagnostic};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::db;
|
||||
@ -28,9 +28,11 @@ pub struct User {
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn check_password(&self, pwd: &[u8]) -> anyhow::Result<bool> {
|
||||
pub fn check_password(&self, pwd: &[u8]) -> miette::Result<bool> {
|
||||
if let Some(ref encoded) = self.userdata.passwd {
|
||||
argon2::verify_encoded(encoded, pwd).context("Stored password is an invalid string")
|
||||
argon2::verify_encoded(encoded, pwd)
|
||||
.into_diagnostic()
|
||||
.wrap_err("Stored password is an invalid string")
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
@ -109,7 +111,7 @@ impl UserDB {
|
||||
// TODO: Make an userdb-specific Transaction newtype to make this safe
|
||||
pub unsafe fn get_rw_txn(&self) -> Result<RwTransaction, db::Error> {
|
||||
// The returned transaction is only valid for *this* environment.
|
||||
self.env.begin_rw_txn()
|
||||
Ok(self.env.begin_rw_txn()?)
|
||||
}
|
||||
|
||||
pub unsafe fn new(env: Arc<Environment>, db: RawDB) -> Self {
|
||||
|
@ -1,10 +1,10 @@
|
||||
use anyhow::Context;
|
||||
use lmdb::{Environment, Transaction};
|
||||
use once_cell::sync::OnceCell;
|
||||
use rkyv::{Archive, Deserialize, Infallible, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{Display, Formatter, Write};
|
||||
|
||||
use miette::{Context, IntoDiagnostic};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
@ -65,7 +65,7 @@ pub struct Users {
|
||||
}
|
||||
|
||||
impl Users {
|
||||
pub fn new(env: Arc<Environment>) -> anyhow::Result<Self> {
|
||||
pub fn new(env: Arc<Environment>) -> miette::Result<Self> {
|
||||
let span = tracing::debug_span!("users", ?env, "Creating Users handle");
|
||||
let _guard = span.enter();
|
||||
|
||||
@ -74,7 +74,7 @@ impl Users {
|
||||
tracing::debug!("Global resource not yet initialized, initializing…");
|
||||
unsafe { UserDB::create(env) }
|
||||
})
|
||||
.context("Failed to open userdb")?;
|
||||
.wrap_err("Failed to open userdb")?;
|
||||
|
||||
Ok(Self { userdb })
|
||||
}
|
||||
@ -90,19 +90,19 @@ impl Users {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn put_user(&self, uid: &str, user: &db::User) -> Result<(), lmdb::Error> {
|
||||
pub fn put_user(&self, uid: &str, user: &db::User) -> Result<(), crate::db::Error> {
|
||||
tracing::trace!(uid, ?user, "Updating user");
|
||||
self.userdb.put(uid, user)
|
||||
}
|
||||
|
||||
pub fn del_user(&self, uid: &str) -> Result<(), lmdb::Error> {
|
||||
pub fn del_user(&self, uid: &str) -> Result<(), crate::db::Error> {
|
||||
tracing::trace!(uid, "Deleting user");
|
||||
self.userdb.delete(uid)
|
||||
}
|
||||
|
||||
pub fn load_file<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||
let f = std::fs::read(path)?;
|
||||
let map: HashMap<String, UserData> = toml::from_slice(&f)?;
|
||||
pub fn load_file<P: AsRef<Path>>(&self, path: P) -> miette::Result<()> {
|
||||
let f = std::fs::read(path).into_diagnostic()?;
|
||||
let map: HashMap<String, UserData> = toml::from_slice(&f).into_diagnostic()?;
|
||||
|
||||
let mut txn = unsafe { self.userdb.get_rw_txn()? };
|
||||
|
||||
@ -132,7 +132,7 @@ impl Users {
|
||||
}
|
||||
}
|
||||
|
||||
txn.commit()?;
|
||||
txn.commit().map_err(crate::db::Error::from)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use std::{env, io, io::Write, path::PathBuf};
|
||||
|
||||
use nix::NixPath;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
fn main() -> miette::Result<()> {
|
||||
// Argument parsing
|
||||
// values for the name, description and version are pulled from `Cargo.toml`.
|
||||
let matches = Command::new(clap::crate_name!())
|
||||
|
Loading…
Reference in New Issue
Block a user