Quellcode durchsuchen

* Started integrating btconsole with btfsd.
* Revamped the configuration system to be more composable.

Matthew Carr vor 1 Jahr
Ursprung
Commit
8d73ccee00

+ 76 - 208
Cargo.lock

@@ -17,17 +17,6 @@ version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
 
-[[package]]
-name = "ahash"
-version = "0.7.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
-dependencies = [
- "getrandom",
- "once_cell",
- "version_check",
-]
-
 [[package]]
 name = "aho-corasick"
 version = "1.0.1"
@@ -84,6 +73,12 @@ dependencies = [
  "syn 2.0.13",
 ]
 
+[[package]]
+name = "atomic"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba"
+
 [[package]]
 name = "atty"
 version = "0.2.14"
@@ -239,15 +234,6 @@ version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
-[[package]]
-name = "block-buffer"
-version = "0.10.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
-dependencies = [
- "generic-array",
-]
-
 [[package]]
 name = "boolinator"
 version = "2.4.0"
@@ -260,7 +246,7 @@ version = "0.1.0"
 dependencies = [
  "anyhow",
  "btlib",
- "config",
+ "figment",
  "serde",
 ]
 
@@ -279,6 +265,7 @@ dependencies = [
  "hyper",
  "log",
  "regex",
+ "serde",
  "tokio",
  "tower",
  "tower-http",
@@ -332,14 +319,15 @@ version = "0.1.0"
 dependencies = [
  "anyhow",
  "btconfig",
+ "btconsole",
  "btfproto",
  "btlib",
  "btlib-tests",
  "btmsg",
  "btserde",
- "config",
  "ctor",
  "env_logger",
+ "figment",
  "libc",
  "log",
  "serde",
@@ -359,9 +347,9 @@ dependencies = [
  "btlib",
  "btmsg",
  "btserde",
- "config",
  "ctor",
  "env_logger",
+ "figment",
  "fuse-backend-rs",
  "futures",
  "libc",
@@ -450,7 +438,7 @@ dependencies = [
  "btconfig",
  "btlib",
  "btserde",
- "config",
+ "figment",
  "serde",
  "tempdir",
  "termion",
@@ -604,25 +592,6 @@ dependencies = [
  "unicode-width",
 ]
 
-[[package]]
-name = "config"
-version = "0.13.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7"
-dependencies = [
- "async-trait",
- "json5",
- "lazy_static",
- "nom",
- "pathdiff",
- "ron",
- "rust-ini",
- "serde",
- "serde_json",
- "toml",
- "yaml-rust",
-]
-
 [[package]]
 name = "console_error_panic_hook"
 version = "0.1.7"
@@ -655,15 +624,6 @@ version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
 
-[[package]]
-name = "cpufeatures"
-version = "0.2.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58"
-dependencies = [
- "libc",
-]
-
 [[package]]
 name = "criterion"
 version = "0.4.0"
@@ -743,16 +703,6 @@ dependencies = [
  "cfg-if",
 ]
 
-[[package]]
-name = "crypto-common"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
-dependencies = [
- "generic-array",
- "typenum",
-]
-
 [[package]]
 name = "ctor"
 version = "0.1.23"
@@ -837,22 +787,6 @@ dependencies = [
  "const-oid",
 ]
 
-[[package]]
-name = "digest"
-version = "0.10.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
-dependencies = [
- "block-buffer",
- "crypto-common",
-]
-
-[[package]]
-name = "dlv-list"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257"
-
 [[package]]
 name = "either"
 version = "1.8.0"
@@ -924,6 +858,19 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
+[[package]]
+name = "figment"
+version = "0.10.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e56602b469b2201400dec66a66aec5a9b8761ee97cd1b8c96ab2483fcc16cc9"
+dependencies = [
+ "atomic",
+ "pear",
+ "serde",
+ "uncased",
+ "version_check",
+]
+
 [[package]]
 name = "fnv"
 version = "1.0.7"
@@ -1075,16 +1022,6 @@ dependencies = [
  "slab",
 ]
 
-[[package]]
-name = "generic-array"
-version = "0.14.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
-dependencies = [
- "typenum",
- "version_check",
-]
-
 [[package]]
 name = "getrandom"
 version = "0.2.8"
@@ -1303,9 +1240,6 @@ name = "hashbrown"
 version = "0.12.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
-dependencies = [
- "ahash",
-]
 
 [[package]]
 name = "heck"
@@ -1462,6 +1396,12 @@ dependencies = [
  "hashbrown",
 ]
 
+[[package]]
+name = "inlinable_string"
+version = "0.1.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb"
+
 [[package]]
 name = "io-lifetimes"
 version = "1.0.10"
@@ -1507,17 +1447,6 @@ dependencies = [
  "wasm-bindgen",
 ]
 
-[[package]]
-name = "json5"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1"
-dependencies = [
- "pest",
- "pest_derive",
- "serde",
-]
-
 [[package]]
 name = "lazy_static"
 version = "1.4.0"
@@ -1564,12 +1493,6 @@ dependencies = [
  "cc",
 ]
 
-[[package]]
-name = "linked-hash-map"
-version = "0.5.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
-
 [[package]]
 name = "linux-raw-sys"
 version = "0.3.7"
@@ -1838,16 +1761,6 @@ dependencies = [
  "vcpkg",
 ]
 
-[[package]]
-name = "ordered-multimap"
-version = "0.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a"
-dependencies = [
- "dlv-list",
- "hashbrown",
-]
-
 [[package]]
 name = "os_str_bytes"
 version = "6.4.1"
@@ -1861,10 +1774,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba"
 
 [[package]]
-name = "pathdiff"
-version = "0.2.1"
+name = "pear"
+version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
+checksum = "0ec95680a7087503575284e5063e14b694b7a9c0b065e5dceec661e0497127e8"
+dependencies = [
+ "inlinable_string",
+ "pear_codegen",
+ "yansi",
+]
+
+[[package]]
+name = "pear_codegen"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9661a3a53f93f09f2ea882018e4d7c88f6ff2956d809a276060476fd8c879d3c"
+dependencies = [
+ "proc-macro2",
+ "proc-macro2-diagnostics",
+ "quote",
+ "syn 2.0.13",
+]
 
 [[package]]
 name = "peeking_take_while"
@@ -1897,40 +1827,6 @@ dependencies = [
  "ucd-trie",
 ]
 
-[[package]]
-name = "pest_derive"
-version = "2.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b79d4c71c865a25a4322296122e3924d30bc8ee0834c8bfc8b95f7f054afbfb"
-dependencies = [
- "pest",
- "pest_generator",
-]
-
-[[package]]
-name = "pest_generator"
-version = "2.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c435bf1076437b851ebc8edc3a18442796b30f1728ffea6262d59bbe28b077e"
-dependencies = [
- "pest",
- "pest_meta",
- "proc-macro2",
- "quote",
- "syn 2.0.13",
-]
-
-[[package]]
-name = "pest_meta"
-version = "2.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "745a452f8eb71e39ffd8ee32b3c5f51d03845f99786fa9b68db6ff509c505411"
-dependencies = [
- "once_cell",
- "pest",
- "sha2",
-]
-
 [[package]]
 name = "picky-asn1"
 version = "0.3.3"
@@ -2103,6 +1999,19 @@ dependencies = [
  "unicode-ident",
 ]
 
+[[package]]
+name = "proc-macro2-diagnostics"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "606c4ba35817e2922a308af55ad51bab3645b59eae5c570d4a6cf07e36bd493b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.13",
+ "version_check",
+ "yansi",
+]
+
 [[package]]
 name = "prokio"
 version = "0.1.0"
@@ -2328,27 +2237,6 @@ dependencies = [
  "winapi",
 ]
 
-[[package]]
-name = "ron"
-version = "0.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a"
-dependencies = [
- "base64",
- "bitflags",
- "serde",
-]
-
-[[package]]
-name = "rust-ini"
-version = "0.18.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df"
-dependencies = [
- "cfg-if",
- "ordered-multimap",
-]
-
 [[package]]
 name = "rustc-demangle"
 version = "0.1.21"
@@ -2604,17 +2492,6 @@ dependencies = [
  "serde",
 ]
 
-[[package]]
-name = "sha2"
-version = "0.10.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
-dependencies = [
- "cfg-if",
- "cpufeatures",
- "digest",
-]
-
 [[package]]
 name = "shlex"
 version = "1.1.0"
@@ -2921,15 +2798,6 @@ dependencies = [
  "tracing",
 ]
 
-[[package]]
-name = "toml"
-version = "0.5.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
-dependencies = [
- "serde",
-]
-
 [[package]]
 name = "tower"
 version = "0.4.13"
@@ -3055,18 +2923,21 @@ dependencies = [
  "target-lexicon",
 ]
 
-[[package]]
-name = "typenum"
-version = "1.16.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
-
 [[package]]
 name = "ucd-trie"
 version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
 
+[[package]]
+name = "uncased"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b9bc53168a4be7402ab86c3aad243a84dd7381d09be0eddc81280c1da95ca68"
+dependencies = [
+ "version_check",
+]
+
 [[package]]
 name = "unicase"
 version = "2.6.0"
@@ -3484,13 +3355,10 @@ dependencies = [
 ]
 
 [[package]]
-name = "yaml-rust"
-version = "0.4.5"
+name = "yansi"
+version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
-dependencies = [
- "linked-hash-map",
-]
+checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
 
 [[package]]
 name = "yew"

+ 1 - 1
crates/btconfig/Cargo.toml

@@ -7,6 +7,6 @@ edition = "2021"
 
 [dependencies]
 btlib = { path = "../btlib" }
-config = "0.13.3"
+figment = { version = "0.10.8", features = ["env"] }
 anyhow = { version = "1.0.66", features = ["std", "backtrace"] }
 serde = { version = "^1.0.136", features = ["derive"] }

+ 59 - 60
crates/btconfig/src/lib.rs

@@ -1,15 +1,29 @@
 use btlib::{
     self,
     crypto::{file_cred_store::FileCredStore, tpm::TpmCredStore, CredStore, CredStoreMut, Creds},
-    error::BtErr,
     Result,
 };
-use config::{
-    builder::{AsyncState, BuilderState, DefaultState},
-    ConfigBuilder, Environment,
-};
-use serde::Deserialize;
-use std::{path::PathBuf, sync::Arc};
+use figment::{providers::Env, Figment, Metadata, Provider};
+use serde::{Deserialize, Serialize};
+use std::{path::PathBuf, result::Result as StdResult, sync::Arc};
+
+pub const SYS_CONFIG_DIR: &str = "/etc/blocktree";
+
+/// Returns the path of the current user's configuration directory.
+pub fn user_config_dir() -> PathBuf {
+    let home = std::env::var("HOME");
+    let base = if let Ok(ref home) = home { home } else { "." };
+    let mut path = PathBuf::from(base);
+    path.push(".config/blocktree");
+    path
+}
+
+/// Returns the default directory to store blocks in.
+pub fn default_block_dir() -> PathBuf {
+    let mut path = user_config_dir();
+    path.push("blocks");
+    path
+}
 
 /// Attempts to unwrap the [Option] field in `$config` called `$name`. This macro internal
 /// uses the `?` operator, and can only be used in methods which return a [Result] which has an
@@ -33,63 +47,26 @@ macro_rules! get_settings {
     };
 }
 
-fn environment_source() -> Environment {
-    Environment::with_prefix("BT").separator("_")
-}
-
-fn set_defaults<St: BuilderState>(builder: ConfigBuilder<St>) -> Result<ConfigBuilder<St>> {
-    builder
-        .set_default(CredStoreCfg::TAG, "File")?
-        .set_default(CredStoreCfg::PATH, "./state/file_cred_store")
-        .bterr()
-}
-
-/// An extension to [ConfigBuilder] which allows it to be configured using Blocktree's default
-/// settings.
-pub trait ConfigBuilderExt: Sized {
-    /// Configure this [ConfigBuilder] with Blocktree's default settings.
+pub trait FigmentExt: Sized {
     fn btconfig(self) -> Result<Self>;
 }
 
-impl ConfigBuilderExt for ConfigBuilder<DefaultState> {
+impl FigmentExt for Figment {
     fn btconfig(self) -> Result<Self> {
-        let builder = set_defaults(self)?.add_source(environment_source());
-        Ok(builder)
-    }
-}
-
-impl ConfigBuilderExt for ConfigBuilder<AsyncState> {
-    fn btconfig(self) -> Result<Self> {
-        let builder = set_defaults(self)?.add_source(environment_source());
-        Ok(builder)
-    }
-}
-
-#[macro_export]
-macro_rules! config_keys {
-    {
-        const $struct_key_name:ident = $struct_key:literal;
-        $(const $field_key_name:ident = $field_key:literal;)+
-    } => {
-        #[allow(dead_code)]
-        const $struct_key_name: &str = $struct_key;
-        $(
-            #[allow(dead_code)]
-            const $field_key_name: &str = concat!($struct_key, ".", $field_key);
-        )+
+        Ok(self.merge(Env::prefixed("BT_").split("_")))
     }
 }
 
-#[derive(Debug, Clone, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
 #[serde(tag = "type")]
 /// Configuration which specifies how to construct a [btlib::crypto::CredStore].
-pub enum CredStoreCfg {
-    /// Specifies that a [btlib::crypto::file_cred_store::FileCredStore] should be used.
+pub enum CredStoreConfig {
+    /// Use a local file as a credential store.
     File {
         /// The path where the credential store is located.
         path: PathBuf,
     },
-    /// Specifies that a [btlib::crypto::tpm::TpmCredStore] should be used.
+    /// Use the configured TPM as a credential store.
     Tpm {
         /// The state path to use for the TPM credential store.
         path: PathBuf,
@@ -98,12 +75,34 @@ pub enum CredStoreCfg {
     },
 }
 
-impl CredStoreCfg {
-    config_keys! {
-        const STRUCT_KEY = "credstore";
-        const TAG = "type";
-        const PATH = "path";
-        const TABRMD = "tabrmd";
+impl CredStoreConfig {
+    pub fn from<T: Provider>(provider: T) -> StdResult<Self, figment::Error> {
+        Figment::from(provider).extract()
+    }
+
+    pub fn figment() -> Figment {
+        Figment::from(CredStoreConfig::default())
+    }
+}
+
+impl Default for CredStoreConfig {
+    fn default() -> Self {
+        let mut path = user_config_dir();
+        path.push("file_credstore");
+        CredStoreConfig::File { path }
+    }
+}
+
+impl Provider for CredStoreConfig {
+    fn metadata(&self) -> figment::Metadata {
+        Metadata::named("btconfig")
+    }
+
+    fn data(
+        &self,
+    ) -> StdResult<figment::value::Map<figment::Profile, figment::value::Dict>, figment::Error>
+    {
+        figment::providers::Serialized::defaults(Self::default()).data()
     }
 }
 
@@ -124,11 +123,11 @@ macro_rules! impl_consume {
         #[doc = concat!("Calls `", stringify!($name), "` with the credential store created based on the configuration data in `self`")]
         pub fn $name<F: $consumer>(self, consumer: F) -> Result<F::Output> {
             match self {
-                CredStoreCfg::File { path } => {
+                CredStoreConfig::File { path } => {
                     let store = FileCredStore::new(path)?;
                     Ok(consumer.$name(store))
                 }
-                CredStoreCfg::Tpm { path, tabrmd } => {
+                CredStoreConfig::Tpm { path, tabrmd } => {
                     let store = TpmCredStore::from_tabrmd(&tabrmd, path)?;
                     Ok(consumer.$name(store))
                 }
@@ -137,7 +136,7 @@ macro_rules! impl_consume {
     }
 }
 
-impl CredStoreCfg {
+impl CredStoreConfig {
     impl_consume!(consume, CredStoreConsumer);
     impl_consume!(consume_mut, CredStoreMutConsumer);
 }

+ 1 - 2
crates/btconsole-client/src/bin/btconsole-client-bin.rs

@@ -1,6 +1,5 @@
 use btconsole_client::App;
-use yew;
 
 fn main() {
     yew::Renderer::<App>::new().hydrate();
-}
+}

+ 1 - 4
crates/btconsole/Cargo.toml

@@ -5,10 +5,6 @@ edition = "2021"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
-[[bin]]
-name = "btconsole-bin"
-required-features = ["tokio/rt-multi-thread", "env_logger", "ctrlc"]
-
 [dependencies]
 btlib = { path = "../btlib" }
 btconsole-client = { path = "../btconsole-client", default-features=false }
@@ -23,5 +19,6 @@ axum = "0.6.18"
 futures = "0.3.25"
 log = "0.4.17"
 regex = "1.8.1"
+serde = { version = "^1.0.136", features = ["derive"] }
 env_logger = { version = "0.9.0", optional = true }
 ctrlc = { version = "3.3.0", optional = true }

+ 0 - 4
crates/btconsole/autobuild.sh

@@ -1,4 +0,0 @@
-#!/usr/bin/env bash
-# Automatically builds btconsole and btconsole-client when files in their respective crates change.
-set -euo pipefail
-cargo watch -- sh -c 'cd ../btconsole-client && trunk build && cd - && cargo run --features="tokio/rt-multi-thread env_logger ctrlc" --bin btconsole-bin'

+ 0 - 27
crates/btconsole/src/bin/btconsole-bin.rs

@@ -1,27 +0,0 @@
-use btconsole::{ServerConfig, start_server};
-use btlib::{Result, log::BuilderExt};
-use std::{
-    net::SocketAddr,
-    path::PathBuf,
-};
-
-const DEFAULT_LOG_LEVEL: &str = "info";
-
-#[tokio::main]
-async fn main() -> Result<()> {
-        if std::env::var("RUST_LOG").is_err() {
-            std::env::set_var("RUST_LOG", DEFAULT_LOG_LEVEL)
-        }
-        env_logger::Builder::from_default_env().btformat().init();
-
-        let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
-        let dist_path = PathBuf::from("../../target/dist");
-        let config = ServerConfig::new(addr, dist_path);
-        let mut server = start_server(config).await.unwrap();
-        let mut signal = Some(server.take_shutdown_signal().unwrap());
-        ctrlc::set_handler(move || {
-            signal.take().map(|tx| tx.send(()).expect("failed to send shutdown signal"));
-        }).expect("failed to install the CTRL-C handler");
-
-        server.has_shutdown().await
-}

+ 66 - 56
crates/btconsole/src/lib.rs

@@ -1,26 +1,24 @@
-use std::{
-    path::{PathBuf},
-    net::SocketAddr,
-    result::Result as StdResult,
-};
+use std::{net::SocketAddr, path::PathBuf, result::Result as StdResult};
 
+use axum::{response::Html, routing::get, Router};
 use btconsole_client::App;
 use btlib::{bterr, Result};
 use bytes::{Bytes, BytesMut};
-use tokio::{task::JoinHandle, sync::oneshot};
-use yew::ServerRenderer;
-use axum::{Router, response::Html, routing::get};
-use tower_http::services::ServeDir;
-use tower::Service;
 use futures::FutureExt;
 use regex::Regex;
+use serde::{Deserialize, Serialize};
+use tokio::{sync::oneshot, task::JoinHandle};
+use tower::Service;
+use tower_http::services::ServeDir;
+use yew::ServerRenderer;
 
-pub struct ServerConfig {
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct BtConsoleConfig {
     addr: SocketAddr,
     dist_path: PathBuf,
 }
 
-impl ServerConfig {
+impl BtConsoleConfig {
     pub fn new(addr: SocketAddr, asset_path: PathBuf) -> Self {
         Self {
             addr,
@@ -29,19 +27,71 @@ impl ServerConfig {
     }
 }
 
-pub struct ServerHandle {
+impl Default for BtConsoleConfig {
+    fn default() -> Self {
+        Self {
+            addr: SocketAddr::from(([127, 0, 0, 1], 3000)),
+            dist_path: "../../target/dist".into(),
+        }
+    }
+}
+
+pub struct BtConsole {
     handle: JoinHandle<StdResult<(), hyper::Error>>,
     stop: Option<oneshot::Sender<()>>,
 }
 
-impl ServerHandle {
+impl BtConsole {
     fn new(handle: JoinHandle<StdResult<(), hyper::Error>>, stop: oneshot::Sender<()>) -> Self {
-        Self { handle, stop: Some(stop) }
+        Self {
+            handle,
+            stop: Some(stop),
+        }
+    }
+
+    pub async fn start(config: BtConsoleConfig) -> Result<Self> {
+        let rendered: Bytes = {
+            let index_path = config.dist_path.join("index.html");
+            let (index, rendered) = tokio::join!(
+                tokio::fs::read_to_string(&index_path),
+                ServerRenderer::<App>::new().render(),
+            );
+            let index = index?;
+            let regex = Regex::new(r"</?body>")?;
+            let fields = regex.split(&index);
+            let mut bytes = BytesMut::with_capacity(index.len() + rendered.len() + 13);
+            for (index, entry) in fields.enumerate() {
+                if 1 == index {
+                    bytes.extend_from_slice(b"<body>");
+                    bytes.extend_from_slice(rendered.as_bytes());
+                    bytes.extend_from_slice(b"</body>");
+                } else {
+                    bytes.extend_from_slice(entry.as_bytes());
+                }
+            }
+            bytes.into()
+        };
+        let mut serve_dir = ServeDir::new(config.dist_path).append_index_html_on_directories(false);
+
+        let app = Router::new()
+            .route("/", get(|| async move { Html(rendered) }))
+            .route("/*path", get(move |req| serve_dir.call(req)));
+
+        log::info!("listening at http://{}", config.addr);
+
+        let (tx, rx) = oneshot::channel::<()>();
+        let server = axum::Server::bind(&config.addr)
+            .serve(app.into_make_service())
+            .with_graceful_shutdown(rx.map(|result| result.unwrap()));
+        let handle = tokio::spawn(server);
+
+        Ok(Self::new(handle, tx))
     }
 
     pub fn signal_shutdown(&mut self) -> Result<()> {
         if let Some(stop) = self.stop.take() {
-            stop.send(()).map_err(|_| bterr!("failed to send stop signal to server"))?;
+            stop.send(())
+                .map_err(|_| bterr!("failed to send stop signal to server"))?;
         }
         Ok(())
     }
@@ -54,43 +104,3 @@ impl ServerHandle {
         self.handle.await?.map_err(|err| err.into())
     }
 }
-
-pub async fn start_server(config: ServerConfig) -> Result<ServerHandle> {
-    let rendered: Bytes = {
-        let index_path = config.dist_path.join("index.html");
-        let (index, rendered) = tokio::join!(
-            tokio::fs::read_to_string(&index_path),
-            ServerRenderer::<App>::new().render(),
-        );
-        let index = index?;
-        let regex = Regex::new("<\\/?body>")?;
-        let fields = regex.split(&index);
-        let mut bytes = BytesMut::with_capacity(index.len() + rendered.len() + 13);
-        for (index, entry) in fields.enumerate() {
-            if 1 == index {
-                bytes.extend_from_slice(b"<body>");
-                bytes.extend_from_slice(rendered.as_bytes());
-                bytes.extend_from_slice(b"</body>");
-            } else {
-                bytes.extend_from_slice(entry.as_bytes());
-            }
-        }
-        bytes.into()
-    };
-    let mut serve_dir = ServeDir::new(config.dist_path)
-        .append_index_html_on_directories(false);
-
-    let app = Router::new()
-        .route("/", get(|| async move { Html(rendered.clone()) } ))
-        .route("/*path", get(move |req| serve_dir.call(req)));
-
-    log::info!("listening at http://{}", config.addr);
-
-    let (tx, rx) = oneshot::channel::<()>();
-    let server = axum::Server::bind(&config.addr)
-        .serve(app.into_make_service())
-        .with_graceful_shutdown(rx.map(|result| result.unwrap()));
-    let handle = tokio::spawn(server);
-
-    Ok(ServerHandle::new(handle, tx))
-}

+ 2 - 1
crates/btfsd/Cargo.toml

@@ -10,13 +10,14 @@ btlib = { path = "../btlib" }
 btmsg = { path = "../btmsg" }
 btfproto = { path = "../btfproto" }
 btconfig = { path = "../btconfig" }
+btconsole = { path = "../btconsole" }
 tokio = { version = "1.24.2", features = ["rt", "rt-multi-thread", "time"] }
 log = "0.4.17"
 env_logger = "0.9.0"
 btlib-tests = { path = "../btlib-tests" }
 anyhow = { version = "1.0.66", features = ["std", "backtrace"] }
 serde = { version = "^1.0.136", features = ["derive"] }
-config = "0.13.3"
+figment = "0.10.8"
 
 [dev-dependencies]
 swtpm-harness = { path = "../swtpm-harness" }

+ 4 - 0
crates/btfsd/autobuild.sh

@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+# Automatically builds btconsole and btconsole-client when files in their respective crates change.
+set -euo pipefail
+RUST_LOG=warn,btfsd=debug,btconsole=debug,btmsg=debug cargo watch -- sh -c 'cd ../btconsole-client && trunk build && cd - && cargo run'

+ 40 - 17
crates/btfsd/src/main.rs

@@ -1,35 +1,48 @@
 // SPDX-License-Identifier: AGPL-3.0-or-later
-use ::config::Config;
-use btconfig::{ConfigBuilderExt, CredStoreCfg, NodeCredConsumer};
+use btconfig::{CredStoreConfig, FigmentExt, NodeCredConsumer};
+use btconsole::{BtConsole, BtConsoleConfig};
 use btfproto::{
     local_fs::{LocalFs, ModeAuthorizer},
     server::{new_fs_server, FsProvider},
 };
-use btlib::{crypto::Creds, error::DisplayErr, log::BuilderExt, Result};
+use btlib::{bterr, crypto::Creds, error::DisplayErr, log::BuilderExt, Result};
 use btmsg::Receiver;
+use figment::{providers::Serialized, Figment};
+use serde::{Deserialize, Serialize};
 use std::{net::IpAddr, path::PathBuf, sync::Arc};
 
-use serde::Deserialize;
-
-#[derive(Debug, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
 struct AppConfig {
-    credstore: CredStoreCfg,
+    credstore: CredStoreConfig,
+    /// The IP address to listen for filesystem connection on.
     ipaddr: IpAddr,
+    /// The path in the local filesystem where blocks are stored.
     blockdir: PathBuf,
+    /// Configuration for the btconsole webapp.
+    console: Option<BtConsoleConfig>,
 }
 
 impl AppConfig {
     fn new() -> Result<AppConfig> {
-        Config::builder()
-            .set_default("ipaddr", "127.0.0.1")?
-            .set_default("blockdir", "./state/blocks")?
+        Figment::new()
+            .merge(Serialized::defaults(Self::default()))
             .btconfig()?
-            .build()?
-            .try_deserialize()
+            .extract()
             .map_err(|err| err.into())
     }
 }
 
+impl Default for AppConfig {
+    fn default() -> Self {
+        Self {
+            credstore: CredStoreConfig::default(),
+            ipaddr: IpAddr::from([127, 0, 0, 1]),
+            blockdir: btconfig::default_block_dir(),
+            console: Some(BtConsoleConfig::default()),
+        }
+    }
+}
+
 async fn provider(block_dir: PathBuf, creds: Arc<dyn Creds>) -> Result<impl FsProvider> {
     if block_dir.exists() {
         LocalFs::new_existing(block_dir, creds, ModeAuthorizer)
@@ -48,10 +61,18 @@ async fn receiver(config: AppConfig) -> Result<impl Receiver> {
 #[tokio::main]
 async fn main() -> Result<()> {
     env_logger::Builder::from_default_env().btformat().init();
-    let config = AppConfig::new()?;
-    let receiver = receiver(config).await?;
+    let mut config = AppConfig::new()?;
+    let console_config = config
+        .console
+        .take()
+        .ok_or_else(|| bterr!("No BtConsole configuration was provided"))?;
+    let (console, receiver) = tokio::join!(BtConsole::start(console_config), receiver(config));
+    let receiver = receiver?;
+    let console = console?;
     log::debug!("ready to accept connections");
-    receiver.complete()?.await.display_err()?;
+    let (console, receiver) = tokio::join!(console.has_shutdown(), receiver.complete()?);
+    console?;
+    receiver.display_err()?;
     Ok(())
 }
 
@@ -95,7 +116,7 @@ mod tests {
         ipaddr: IpAddr,
     ) -> FileTestCase<impl Receiver, impl Transmitter> {
         let file_store_path = dir.path().join("cred_store");
-        let credstore = CredStoreCfg::File {
+        let credstore = CredStoreConfig::File {
             path: file_store_path.clone(),
         };
 
@@ -107,6 +128,7 @@ mod tests {
             credstore,
             ipaddr,
             blockdir: dir.path().join(BT_DIR),
+            console: Some(BtConsoleConfig::default()),
         };
         let rx = receiver(config).await.unwrap();
         let tx = rx.transmitter(rx.addr().clone()).await.unwrap();
@@ -140,7 +162,7 @@ mod tests {
         ipaddr: IpAddr,
     ) -> TestCase<impl Receiver, impl Transmitter> {
         let swtpm = harness.swtpm();
-        let credstore = CredStoreCfg::Tpm {
+        let credstore = CredStoreConfig::Tpm {
             path: swtpm.state_path().to_owned(),
             tabrmd: swtpm.tabrmd_config().to_owned(),
         };
@@ -148,6 +170,7 @@ mod tests {
             credstore,
             ipaddr,
             blockdir: dir.path().join(BT_DIR),
+            console: Some(BtConsoleConfig::default()),
         };
         let rx = receiver(config).await.unwrap();
         let tx = rx.transmitter(rx.addr().clone()).await.unwrap();

+ 1 - 1
crates/btfuse/Cargo.toml

@@ -22,7 +22,7 @@ libc = { version = "0.2.137" }
 serde_json = "1.0.92"
 futures = "0.3.25"
 serde = { version = "^1.0.136", features = ["derive"] }
-config = "0.13.3"
+figment = "0.10.8"
 
 [dev-dependencies]
 btfproto-tests = { path = "../btfproto-tests" }

+ 17 - 10
crates/btfuse/src/fuse_daemon.rs

@@ -1,5 +1,5 @@
 // SPDX-License-Identifier: AGPL-3.0-or-later
-use crate::{fuse_fs::FuseFs, PathExt};
+use crate::fuse_fs::FuseFs;
 
 use btfproto::server::FsProvider;
 use btlib::{bterr, BlockPath, Result};
@@ -22,7 +22,7 @@ use tokio::{sync::oneshot, task::JoinSet};
 pub use private::FuseDaemon;
 
 mod private {
-    use std::num::NonZeroUsize;
+    use std::{fs::DirBuilder, num::NonZeroUsize};
 
     use super::*;
 
@@ -59,15 +59,15 @@ mod private {
         const FSTYPE: &str = "bt";
 
         pub fn new<P: 'static + FsProvider>(
-            mnt_path: PathBuf,
-            mnt_options: &str,
+            mntdir: PathBuf,
+            mntoptions: &str,
             num_tasks: Option<NonZeroUsize>,
             fallback_path: Arc<BlockPath>,
             mounted_signal: Option<oneshot::Sender<()>>,
             provider: P,
         ) -> Result<Self> {
             let server = Arc::new(Server::new(FuseFs::new(provider, fallback_path)));
-            let session = Self::session(mnt_path, mnt_options)?;
+            let session = Self::session(mntdir, mntoptions)?;
             if let Some(tx) = mounted_signal {
                 tx.send(())
                     .map_err(|_| bterr!("failed to send mounted signal"))?;
@@ -95,11 +95,18 @@ mod private {
             Ok(Self { set, session })
         }
 
-        fn session<T: AsRef<Path>>(mnt_path: T, mnt_options: &str) -> Result<FuseSession> {
-            mnt_path.try_create_dir()?;
-            let mut session =
-                FuseSession::new(mnt_path.as_ref(), Self::FSNAME, Self::FSTYPE, false)?;
-            session.set_fuse_file(mount_at(mnt_path, mnt_options)?);
+        fn session<T: AsRef<Path>>(mntdir: T, mntoptions: &str) -> Result<FuseSession> {
+            DirBuilder::new()
+                .recursive(true)
+                .create(mntdir.as_ref())
+                .map_err(|err| {
+                    bterr!(err).context(format!(
+                        "failed to create mntdir: '{}'",
+                        mntdir.as_ref().display()
+                    ))
+                })?;
+            let mut session = FuseSession::new(mntdir.as_ref(), Self::FSNAME, Self::FSTYPE, false)?;
+            session.set_fuse_file(mount_at(mntdir, mntoptions)?);
             Ok(session)
         }
 

+ 37 - 22
crates/btfuse/src/main.rs

@@ -1,19 +1,18 @@
 // SPDX-License-Identifier: AGPL-3.0-or-later
 mod fuse_daemon;
-use btconfig::{config_keys, ConfigBuilderExt, CredStoreCfg, NodeCredConsumer};
+use btconfig::{CredStoreConfig, FigmentExt, NodeCredConsumer};
 use fuse_daemon::FuseDaemon;
 mod fuse_fs;
 
-use ::config::Config as ExtConfig;
 use btfproto::{client::FsClient, local_fs::LocalFs, server::FsProvider};
 use btlib::{
     bterr,
     crypto::{Creds, CredsPriv},
-    error::BtErr,
     Result,
 };
 use btmsg::{transmitter, BlockAddr};
-use serde::Deserialize;
+use figment::{providers::Serialized, Figment};
+use serde::{Deserialize, Serialize};
 use std::{
     fs::{self},
     io,
@@ -23,42 +22,58 @@ use std::{
 };
 use tokio::sync::oneshot;
 
-#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
 #[serde(tag = "type")]
 pub enum FsKind {
+    /// Indicates that a local filesystem provider should be used.
     Local { path: PathBuf },
+    /// Indicates that a remote filesystem provider should be used.
     Remote { addr: BlockAddr },
 }
 
-impl FsKind {
-    config_keys! {
-        const STRUCT_KEY = "fskind";
-        const TAG = "type";
-        const PATH = "path";
-        const ADDR = "addr";
+impl Default for FsKind {
+    fn default() -> Self {
+        Self::Local {
+            path: btconfig::default_block_dir(),
+        }
     }
 }
 
-#[derive(Debug, Clone, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
 struct AppConfig {
-    credstore: CredStoreCfg,
+    /// Configures the credential store used.
+    credstore: CredStoreConfig,
+    /// Configures the filesystem provider used.
     fskind: FsKind,
+    /// Sets the directory where the filesystem will be mounted.
     mntdir: PathBuf,
+    /// Specifies the options to use for the filesystem mount.
     mntoptions: String,
+    /// Prescribes the number of threads to use for handling FUSE messages from the kernel. If
+    /// not specified, then the number of threads will be equal to the number of logical cores on
+    /// the system.
     threads: Option<NonZeroUsize>,
 }
 
 impl AppConfig {
     fn new() -> Result<Self> {
-        ExtConfig::builder()
-            .set_default(FsKind::TAG, "Local")?
-            .set_default(FsKind::PATH, "./state/blocks")?
-            .set_default("mntdir", "./state/mnt")?
-            .set_default("mntoptions", "default_permissions")?
+        Figment::new()
+            .merge(Serialized::defaults(AppConfig::default()))
             .btconfig()?
-            .build()?
-            .try_deserialize()
-            .bterr()
+            .extract()
+            .map_err(|err| err.into())
+    }
+}
+
+impl Default for AppConfig {
+    fn default() -> Self {
+        Self {
+            credstore: CredStoreConfig::default(),
+            fskind: FsKind::default(),
+            mntdir: "./btfuse_mnt".into(),
+            mntoptions: "default_permissions".into(),
+            threads: None,
+        }
     }
 }
 
@@ -303,7 +318,7 @@ mod test {
         let config = AppConfig {
             threads: Some(NonZeroUsize::new(1).unwrap()),
             mntdir: tmp.path().join("mnt"),
-            credstore: CredStoreCfg::Tpm {
+            credstore: CredStoreConfig::Tpm {
                 path: swtpm.state_path().to_owned().into(),
                 tabrmd: swtpm.tabrmd_config().to_owned(),
             },

+ 15 - 4
crates/btmsg/src/lib.rs

@@ -22,6 +22,7 @@ use quinn::{Connection, ConnectionError, Endpoint, RecvStream, SendStream};
 use serde::{de::DeserializeOwned, Deserialize, Serialize};
 use std::{
     any::Any,
+    fmt::Display,
     hash::Hash,
     io,
     marker::PhantomData,
@@ -89,18 +90,21 @@ pub trait SendMsg<'de>: CallMsg<'de> {}
 /// used to get a socket address for the block this address refers to.
 #[derive(PartialEq, Eq, Hash, Clone, Debug, Serialize, Deserialize)]
 pub struct BlockAddr {
-    ip_addr: IpAddr,
+    ipaddr: IpAddr,
     #[serde(with = "smart_ptr")]
     path: Arc<BlockPath>,
 }
 
 impl BlockAddr {
     pub fn new(ip_addr: IpAddr, path: Arc<BlockPath>) -> Self {
-        Self { ip_addr, path }
+        Self {
+            ipaddr: ip_addr,
+            path,
+        }
     }
 
     pub fn ip_addr(&self) -> IpAddr {
-        self.ip_addr
+        self.ipaddr
     }
 
     pub fn path(&self) -> &BlockPath {
@@ -113,7 +117,13 @@ impl BlockAddr {
 
     /// Returns the socket address of the block this instance refers to.
     pub fn socket_addr(&self) -> Result<SocketAddr> {
-        Ok(SocketAddr::new(self.ip_addr, self.port()?))
+        Ok(SocketAddr::new(self.ipaddr, self.port()?))
+    }
+}
+
+impl Display for BlockAddr {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}@{}", self.path, self.ipaddr)
     }
 }
 
@@ -472,6 +482,7 @@ impl QuicReceiver {
         resolver: Arc<CertResolver>,
         callback: F,
     ) -> Result<Self> {
+        log::info!("starting QuicReceiver with address {}", recv_addr);
         let socket_addr = recv_addr.socket_addr()?;
         let endpoint = Endpoint::server(server_config(resolver.clone())?, socket_addr)?;
         let (stop_tx, stop_rx) = broadcast::channel(1);

+ 2 - 2
crates/btprovision/Cargo.toml

@@ -10,7 +10,7 @@ btlib = { path = "../btlib" }
 btconfig = { path = "../btconfig" }
 btserde = { path = "../btserde" }
 serde = { version = "^1.0.136", features = ["derive"] }
-config = "0.13.3"
 anyhow = { version = "1.0.66", features = ["std", "backtrace"] }
 termion = "2.0.1"
-tempdir = { version = "0.3.7" }
+tempdir = { version = "0.3.7" }
+figment = "0.10.8"

+ 25 - 13
crates/btprovision/src/main.rs

@@ -1,6 +1,5 @@
 use btconfig::{
-    get_setting, get_settings, ConfigBuilderExt, CredStoreCfg, CredStoreConsumer,
-    CredStoreMutConsumer,
+    get_setting, get_settings, CredStoreConfig, CredStoreConsumer, CredStoreMutConsumer, FigmentExt,
 };
 use btlib::{
     bterr,
@@ -8,8 +7,8 @@ use btlib::{
     Decompose, Epoch, Principal, Principaled, RelBlockPath, Result, Writecap,
 };
 use btserde::{read_from, write_to};
-use config::Config;
-use serde::Deserialize;
+use figment::{providers::Serialized, Figment};
+use serde::{Deserialize, Serialize};
 use std::{
     fs::OpenOptions,
     io::{BufReader, BufWriter, Cursor, Write},
@@ -19,10 +18,10 @@ use std::{
 use tempdir::TempDir;
 use termion::input::TermRead;
 
-#[derive(Debug, Clone, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
 struct AppConfig {
     /// The configuration for the [CredStore] to use.
-    credstore: CredStoreCfg,
+    credstore: CredStoreConfig,
     /// The root password.
     password: Option<String>,
     /// The number of seconds after the UNIX epoch when the credentials expire.
@@ -34,13 +33,26 @@ struct AppConfig {
 
 impl AppConfig {
     fn new() -> Result<Self> {
+        Figment::new()
+            .merge(Serialized::defaults(AppConfig::default()))
+            .btconfig()?
+            .extract()
+            .map_err(|err| err.into())
+    }
+}
+
+impl Default for AppConfig {
+    fn default() -> Self {
         const DEFAULT_VALID_FOR: u64 = 60 * 60 * 24 * 365;
         let expires = Epoch::now() + Duration::from_secs(DEFAULT_VALID_FOR);
-        Ok(Config::builder()
-            .set_default("writecapexpires", expires.value().to_string().as_str())?
-            .btconfig()?
-            .build()?
-            .try_deserialize()?)
+        Self {
+            credstore: CredStoreConfig::default(),
+            password: None,
+            writecapexpires: Some(expires.value()),
+            writecappath: None,
+            writecapissuee: None,
+            writecapsavepath: None,
+        }
     }
 }
 
@@ -268,10 +280,10 @@ mod tests {
         let password = "devalued".to_string();
         let dir = TempDir::new("btprovision").unwrap();
         let writecap_save_path = dir.path().join("writecap");
-        let root_store = CredStoreCfg::File {
+        let root_store = CredStoreConfig::File {
             path: dir.path().join("root_store"),
         };
-        let node_store = CredStoreCfg::File {
+        let node_store = CredStoreConfig::File {
             path: dir.path().join("node_store"),
         };