cli

Command-line interface for Radroots
git clone https://radroots.dev/git/cli.git
Log | Files | Refs | README | LICENSE

commit 7442409e410b8709a6d0330bc0c329d73e4d2caf
parent 0d11a1a0733a7c6ff7b05bed28ebecb718a2fd1f
Author: triesap <tyson@radroots.org>
Date:   Mon, 27 Apr 2026 21:06:30 +0000

cli: remove daemon runtime modules

- delete the daemon bridge and managed runtime modules
- remove direct radrootsd and runtime manager dependencies
- keep runtime errors scoped to active CLI paths
- declare direct events codec features used by farm encoding

Diffstat:
MCargo.lock | 910+------------------------------------------------------------------------------
MCargo.toml | 9+--------
Dsrc/runtime/daemon.rs | 832-------------------------------------------------------------------------------
Dsrc/runtime/management.rs | 1128-------------------------------------------------------------------------------
Msrc/runtime/mod.rs | 10+---------
5 files changed, 12 insertions(+), 2877 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -160,49 +160,6 @@ dependencies = [ ] [[package]] -name = "async-utility" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34a3b57207a7a1007832416c3e4862378c8451b4e8e093e436f48c2d3d2c151" -dependencies = [ - "futures-util", - "gloo-timers", - "tokio", - "wasm-bindgen-futures", -] - -[[package]] -name = "async-wsocket" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c92385c7c8b3eb2de1b78aeca225212e4c9a69a78b802832759b108681a5069" -dependencies = [ - "async-utility", - "futures", - "futures-util", - "js-sys", - "tokio", - "tokio-rustls", - "tokio-socks", - "tokio-tungstenite", - "url", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "atomic-destructor" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef49f5882e4b6afaac09ad239a4f8c70a24b8f2b0897edb1f706008efd109cf4" - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -311,12 +268,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" - -[[package]] name = "cbc" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -342,12 +293,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] name = "chacha20" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -561,17 +506,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", - "rand_core 0.6.4", + "rand_core", "typenum", ] [[package]] -name = "data-encoding" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" - -[[package]] name = "dbus" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -640,12 +579,6 @@ dependencies = [ ] [[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - -[[package]] name = "encoding_rs" version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -752,70 +685,6 @@ dependencies = [ ] [[package]] -name = "futures" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" - -[[package]] -name = "futures-io" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" - -[[package]] -name = "futures-sink" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" - -[[package]] -name = "futures-task" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" - -[[package]] -name = "futures-util" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", -] - -[[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -840,44 +709,18 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi 5.3.0", - "wasip2", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi 6.0.0", + "r-efi", "wasip2", "wasip3", ] [[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - -[[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -945,105 +788,6 @@ dependencies = [ ] [[package]] -name = "http" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "hyper" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", - "webpki-roots 1.0.6", -] - -[[package]] -name = "hyper-util" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] name = "iana-time-zone" version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1211,22 +955,6 @@ dependencies = [ ] [[package]] -name = "ipnet" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" - -[[package]] -name = "iri-string" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" -dependencies = [ - "memchr", - "serde", -] - -[[package]] name = "is_terminal_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1244,8 +972,6 @@ version = "0.3.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" dependencies = [ - "cfg-if", - "futures-util", "once_cell", "wasm-bindgen", ] @@ -1315,7 +1041,7 @@ dependencies = [ "bitflags", "libc", "plain", - "redox_syscall 0.7.4", + "redox_syscall", ] [[package]] @@ -1352,33 +1078,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] -name = "lru" -version = "0.16.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" - -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - -[[package]] name = "matchers" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1421,12 +1126,6 @@ dependencies = [ ] [[package]] -name = "negentropy" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0efe882e02d206d8d279c20eb40e03baf7cb5136a1476dc084a324fbc3ec42d" - -[[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1462,59 +1161,6 @@ dependencies = [ ] [[package]] -name = "nostr-database" -version = "0.44.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7462c9d8ae5ef6a28d66a192d399ad2530f1f2130b13186296dbb11bdef5b3d1" -dependencies = [ - "lru", - "nostr", - "tokio", -] - -[[package]] -name = "nostr-gossip" -version = "0.44.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade30de16869618919c6b5efc8258f47b654a98b51541eb77f85e8ec5e3c83a6" -dependencies = [ - "nostr", -] - -[[package]] -name = "nostr-relay-pool" -version = "0.44.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b1073ccfbaea5549fb914a9d52c68dab2aecda61535e5143dd73e95445a804b" -dependencies = [ - "async-utility", - "async-wsocket", - "atomic-destructor", - "hex", - "lru", - "negentropy", - "nostr", - "nostr-database", - "tokio", - "tracing", -] - -[[package]] -name = "nostr-sdk" -version = "0.44.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "471732576710e779b64f04c55e3f8b5292f865fea228436daf19694f0bf70393" -dependencies = [ - "async-utility", - "nostr", - "nostr-database", - "nostr-gossip", - "nostr-relay-pool", - "tokio", - "tracing", -] - -[[package]] name = "nu-ansi-term" version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1615,36 +1261,13 @@ dependencies = [ ] [[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.5.18", - "smallvec", - "windows-link", -] - -[[package]] name = "password-hash" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -1813,61 +1436,6 @@ dependencies = [ ] [[package]] -name = "quinn" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2", - "thiserror 2.0.18", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" -dependencies = [ - "bytes", - "getrandom 0.3.4", - "lru-slab", - "rand 0.9.2", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror 2.0.18", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.60.2", -] - -[[package]] name = "quote" version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1878,12 +1446,6 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "r-efi" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" @@ -1909,22 +1471,15 @@ dependencies = [ "radroots_protected_store", "radroots_replica_db", "radroots_replica_sync", - "radroots_runtime", - "radroots_runtime_distribution", - "radroots_runtime_manager", "radroots_runtime_paths", - "radroots_sdk", "radroots_secret_vault", "radroots_sql_core", "radroots_trade", - "reqwest", - "rhi", "serde", "serde_json", "tar", "tempfile", "thiserror 2.0.18", - "tokio", "toml", "url", "zeroize", @@ -1993,11 +1548,6 @@ name = "radroots_nostr" version = "0.1.0-alpha.2" dependencies = [ "nostr", - "nostr-sdk", - "radroots_events", - "radroots_events_codec", - "radroots_identity", - "reqwest", "serde", "serde_json", "thiserror 1.0.69", @@ -2105,7 +1655,6 @@ version = "0.1.0-alpha.2" dependencies = [ "anyhow", "chacha20poly1305", - "clap", "config", "getrandom 0.2.17", "radroots_log", @@ -2123,48 +1672,15 @@ dependencies = [ ] [[package]] -name = "radroots_runtime_distribution" +name = "radroots_runtime_paths" version = "0.1.0-alpha.2" dependencies = [ "serde", "thiserror 1.0.69", - "toml", ] [[package]] -name = "radroots_runtime_manager" -version = "0.1.0-alpha.2" -dependencies = [ - "flate2", - "radroots_runtime_paths", - "serde", - "tar", - "thiserror 1.0.69", - "toml", -] - -[[package]] -name = "radroots_runtime_paths" -version = "0.1.0-alpha.2" -dependencies = [ - "serde", - "thiserror 1.0.69", -] - -[[package]] -name = "radroots_sdk" -version = "0.1.0-alpha.2" -dependencies = [ - "radroots_events", - "radroots_events_codec", - "radroots_trade", - "reqwest", - "serde", - "serde_json", -] - -[[package]] -name = "radroots_secret_vault" +name = "radroots_secret_vault" version = "0.1.0-alpha.2" dependencies = [ "keyring", @@ -2210,18 +1726,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.5", + "rand_chacha", + "rand_core", ] [[package]] @@ -2231,17 +1737,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", + "rand_core", ] [[package]] @@ -2254,24 +1750,6 @@ dependencies = [ ] [[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - -[[package]] name = "redox_syscall" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2298,82 +1776,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] -name = "reqwest" -version = "0.12.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots 1.0.6", -] - -[[package]] -name = "rhi" -version = "0.1.0" -dependencies = [ - "anyhow", - "clap", - "radroots_core", - "radroots_events", - "radroots_events_codec", - "radroots_identity", - "radroots_nostr", - "radroots_runtime", - "radroots_runtime_paths", - "radroots_trade", - "serde", - "serde_json", - "thiserror 2.0.18", - "tokio", - "toml", - "tracing", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.17", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] name = "ron" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2442,12 +1844,6 @@ dependencies = [ ] [[package]] -name = "rustc-hash" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" - -[[package]] name = "rustix" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2461,53 +1857,12 @@ dependencies = [ ] [[package]] -name = "rustls" -version = "0.23.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" -dependencies = [ - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" -dependencies = [ - "web-time", - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] name = "salsa20" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2517,12 +1872,6 @@ dependencies = [ ] [[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] name = "scrypt" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2540,7 +1889,7 @@ version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ - "rand 0.8.5", + "rand", "secp256k1-sys", "serde", ] @@ -2649,29 +1998,6 @@ dependencies = [ ] [[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] name = "sha2" version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2714,28 +2040,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] -name = "socket2" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] name = "sqlite-wasm-rs" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2777,15 +2087,6 @@ dependencies = [ ] [[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] name = "synstructure" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2955,13 +2256,10 @@ version = "1.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd" dependencies = [ - "bytes", "libc", "mio", - "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", "tokio-macros", "windows-sys 0.61.2", ] @@ -2978,44 +2276,6 @@ dependencies = [ ] [[package]] -name = "tokio-rustls" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-socks" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" -dependencies = [ - "either", - "futures-util", - "thiserror 1.0.69", - "tokio", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" -dependencies = [ - "futures-util", - "log", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tungstenite", - "webpki-roots 0.26.11", -] - -[[package]] name = "toml" version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3057,58 +2317,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] -name = "tower" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" -dependencies = [ - "bitflags", - "bytes", - "futures-util", - "http", - "http-body", - "iri-string", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] name = "tracing" version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", - "tracing-attributes", "tracing-core", ] @@ -3125,17 +2339,6 @@ dependencies = [ ] [[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] name = "tracing-core" version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3175,12 +2378,6 @@ dependencies = [ ] [[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] name = "ts-rs" version = "11.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3203,25 +2400,6 @@ dependencies = [ ] [[package]] -name = "tungstenite" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" -dependencies = [ - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "rand 0.9.2", - "rustls", - "rustls-pki-types", - "sha1", - "thiserror 2.0.18", - "utf-8", -] - -[[package]] name = "typenum" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3293,12 +2471,6 @@ dependencies = [ ] [[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] name = "url" version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3312,12 +2484,6 @@ dependencies = [ ] [[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3368,15 +2534,6 @@ dependencies = [ ] [[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3415,16 +2572,6 @@ dependencies = [ ] [[package]] -name = "wasm-bindgen-futures" -version = "0.4.67" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] name = "wasm-bindgen-macro" version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3501,34 +2648,6 @@ dependencies = [ ] [[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-roots" -version = "0.26.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" -dependencies = [ - "webpki-roots 1.0.6", -] - -[[package]] -name = "webpki-roots" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" -dependencies = [ - "rustls-pki-types", -] - -[[package]] name = "winapi-util" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3598,15 +2717,6 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" diff --git a/Cargo.toml b/Cargo.toml @@ -26,7 +26,7 @@ clap = { version = "4.5", features = ["derive"] } getrandom = "0.2" radroots_core = { path = "../lib/crates/core", features = ["std", "serde"] } radroots_events = { path = "../lib/crates/events" } -radroots_events_codec = { path = "../lib/crates/events_codec", features = ["serde_json"] } +radroots_events_codec = { path = "../lib/crates/events_codec", features = ["nostr", "serde_json"] } radroots_identity = { path = "../lib/crates/identity" } radroots_log = { path = "../lib/crates/log" } radroots_nostr_accounts = { path = "../lib/crates/nostr_accounts", features = ["os-keyring"] } @@ -35,20 +35,13 @@ radroots_nostr_signer = { path = "../lib/crates/nostr_signer" } radroots_protected_store = { path = "../lib/crates/protected_store", features = ["std"] } radroots_replica_db = { path = "../lib/crates/replica_db" } radroots_replica_sync = { path = "../lib/crates/replica_sync" } -radroots_runtime_distribution = { path = "../lib/crates/runtime_distribution" } -radroots_runtime_manager = { path = "../lib/crates/runtime_manager" } radroots_runtime_paths = { path = "../lib/crates/runtime_paths" } -radroots_runtime = { path = "../lib/crates/runtime" } radroots_secret_vault = { path = "../lib/crates/secret_vault", features = ["std", "os-keyring"] } -radroots_sdk = { path = "../lib/crates/sdk", default-features = false, features = ["std", "serde", "serde_json", "radrootsd-client"] } radroots_sql_core = { path = "../lib/crates/sql_core", features = ["native"] } radroots_trade = { path = "../lib/crates/trade" } -rhi = { path = "../rhi" } -reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "2.0" -tokio = { version = "1", features = ["net", "rt-multi-thread", "time"] } toml = "0.8" url = "2.5" zeroize = "1.8" diff --git a/src/runtime/daemon.rs b/src/runtime/daemon.rs @@ -1,832 +0,0 @@ -use std::time::Duration; - -use radroots_events::farm::RadrootsFarm; -use radroots_events::listing::RadrootsListing; -use radroots_events::profile::{RadrootsProfile, RadrootsProfileType}; -use radroots_events::trade::RadrootsTradeOrder; -use radroots_sdk::{ - RadrootsSdkConfig, RadrootsdAuth, SdkPublishError, SdkRadrootsdFarmPublishOptions, - SdkRadrootsdListingPublishOptions, SdkRadrootsdProfilePublishOptions, - SdkRadrootsdSignerAuthority, SdkRadrootsdSignerSessionRef, SdkTransportMode, SignerConfig, -}; -use reqwest::blocking::Client; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; -use serde_json::Value; - -use crate::domain::runtime::{JobDetailView, JobSummaryView}; -use crate::runtime::config::RuntimeConfig; -use crate::runtime::provider; -use crate::runtime::signer::ActorWriteSignerAuthority; - -const BRIDGE_SOURCE: &str = "daemon bridge · durable write plane"; -const RPC_TIMEOUT_SECS: u64 = 2; - -#[derive(Debug)] -pub enum DaemonRpcError { - Unconfigured(String), - Unauthorized(String), - MethodUnavailable(String), - UnknownJob(String), - External(String), - InvalidResponse(String), - Remote(String), -} - -#[derive(Debug, Clone, Copy)] -enum RpcAuthMode { - None, - BridgeBearer, -} - -#[derive(Debug, Clone)] -struct RpcTarget { - url: String, - bridge_bearer_token: Option<String>, -} - -#[derive(Debug, Serialize)] -struct JsonRpcRequest<'a> { - jsonrpc: &'static str, - id: u64, - method: &'a str, - #[serde(skip_serializing_if = "Option::is_none")] - params: Option<Value>, -} - -#[derive(Debug, Deserialize)] -struct JsonRpcResponse<T> { - result: Option<T>, - error: Option<JsonRpcResponseError>, -} - -#[derive(Debug, Deserialize)] -struct JsonRpcResponseError { - code: i64, - message: String, -} - -#[derive(Debug, Clone, Deserialize)] -struct BridgeJobRemote { - job_id: String, - command: String, - status: String, - terminal: bool, - recovered_after_restart: bool, - requested_at_unix: u64, - completed_at_unix: Option<u64>, - signer_mode: String, - #[serde(default)] - signer_session_id: Option<String>, - event_id: Option<String>, - event_addr: Option<String>, - delivery_policy: String, - delivery_quorum: Option<usize>, - relay_count: usize, - acknowledged_relay_count: usize, - required_acknowledged_relay_count: usize, - attempt_count: usize, - relay_outcome_summary: String, - #[serde(default)] - attempt_summaries: Vec<String>, -} - -#[derive(Debug, Clone, Deserialize)] -struct Nip46SessionRemote { - session_id: String, - user_pubkey: Option<String>, - #[serde(default)] - permissions: Vec<String>, - authorized: bool, - #[serde(default)] - signer_authority: Option<ActorWriteSignerAuthority>, -} - -#[derive(Debug, Clone)] -pub struct BridgeListingPublishResult { - pub deduplicated: bool, - pub job_id: String, - pub idempotency_key: Option<String>, - pub status: String, - pub signer_mode: String, - pub signer_session_id: Option<String>, - pub event_kind: Option<u32>, - pub event_id: Option<String>, - pub event_addr: Option<String>, -} - -#[derive(Debug, Clone)] -pub struct BridgeEventPublishResult { - pub deduplicated: bool, - pub job_id: String, - pub idempotency_key: Option<String>, - pub status: String, - pub signer_mode: String, - pub signer_session_id: Option<String>, - pub event_kind: Option<u32>, - pub event_id: Option<String>, - pub event_addr: Option<String>, -} - -#[derive(Debug, Clone)] -pub struct BridgeOrderRequestResult { - pub deduplicated: bool, - pub job_id: String, - pub idempotency_key: Option<String>, - pub status: String, - pub signer_mode: String, - pub signer_session_id: Option<String>, - pub event_id: Option<String>, - pub event_addr: Option<String>, -} - -pub fn bridge_job_list(config: &RuntimeConfig) -> Result<Vec<JobSummaryView>, DaemonRpcError> { - bridge_jobs(config).map(|jobs| jobs.into_iter().map(map_job_summary_view).collect()) -} - -pub fn bridge_job( - config: &RuntimeConfig, - job_id: &str, -) -> Result<Option<JobDetailView>, DaemonRpcError> { - match bridge_job_status(config, job_id) { - Ok(job) => Ok(Some(map_job_detail_view(job))), - Err(DaemonRpcError::UnknownJob(_)) => Ok(None), - Err(error) => Err(error), - } -} - -pub fn bridge_listing_publish( - config: &RuntimeConfig, - listing: &RadrootsListing, - kind: u32, - idempotency_key: Option<&str>, - signer_session_id: Option<&str>, - signer_authority: Option<&ActorWriteSignerAuthority>, -) -> Result<BridgeListingPublishResult, DaemonRpcError> { - if kind != 30402 { - return Err(DaemonRpcError::External(format!( - "sdk listing publish only supports kind 30402, got {kind}" - ))); - } - - let Some(signer_session_id) = signer_session_id else { - return Err(DaemonRpcError::Unconfigured( - "listing publish requires a signer session id".to_owned(), - )); - }; - - let sdk = actor_write_sdk_client(config)?; - let session = SdkRadrootsdSignerSessionRef::from_session_id(signer_session_id.to_owned()); - let mut options = SdkRadrootsdListingPublishOptions::from_signer_session_ref(&session); - if let Some(idempotency_key) = idempotency_key { - options = options.with_idempotency_key(idempotency_key.to_owned()); - } - if let Some(signer_authority) = signer_authority { - options = options.with_signer_authority(sdk_signer_authority(signer_authority)); - } - - let receipt = block_on_sdk( - sdk.listing() - .publish_listing_via_radrootsd_with_options(listing, &options), - )? - .map_err(map_sdk_publish_error)?; - - map_listing_publish_receipt(receipt, idempotency_key) -} - -pub fn bridge_profile_publish( - config: &RuntimeConfig, - profile: &RadrootsProfile, - profile_type: Option<RadrootsProfileType>, - idempotency_key: Option<&str>, - signer_session_id: Option<&str>, - signer_authority: Option<&ActorWriteSignerAuthority>, -) -> Result<BridgeEventPublishResult, DaemonRpcError> { - let Some(signer_session_id) = signer_session_id else { - return Err(DaemonRpcError::Unconfigured( - "profile publish requires a signer session id".to_owned(), - )); - }; - - let sdk = actor_write_sdk_client(config)?; - let session = SdkRadrootsdSignerSessionRef::from_session_id(signer_session_id.to_owned()); - let mut options = SdkRadrootsdProfilePublishOptions::from_signer_session_ref(&session); - if let Some(idempotency_key) = idempotency_key { - options = options.with_idempotency_key(idempotency_key.to_owned()); - } - if let Some(signer_authority) = signer_authority { - options = options.with_signer_authority(sdk_signer_authority(signer_authority)); - } - - let receipt = block_on_sdk(sdk.profile().publish_profile_via_radrootsd_with_options( - profile, - profile_type, - &options, - ))? - .map_err(map_sdk_publish_error)?; - - map_event_publish_receipt(receipt, idempotency_key, "profile") -} - -pub fn bridge_farm_publish( - config: &RuntimeConfig, - farm: &RadrootsFarm, - idempotency_key: Option<&str>, - signer_session_id: Option<&str>, - signer_authority: Option<&ActorWriteSignerAuthority>, -) -> Result<BridgeEventPublishResult, DaemonRpcError> { - let Some(signer_session_id) = signer_session_id else { - return Err(DaemonRpcError::Unconfigured( - "farm publish requires a signer session id".to_owned(), - )); - }; - - let sdk = actor_write_sdk_client(config)?; - let session = SdkRadrootsdSignerSessionRef::from_session_id(signer_session_id.to_owned()); - let mut options = SdkRadrootsdFarmPublishOptions::from_signer_session_ref(&session); - if let Some(idempotency_key) = idempotency_key { - options = options.with_idempotency_key(idempotency_key.to_owned()); - } - if let Some(signer_authority) = signer_authority { - options = options.with_signer_authority(sdk_signer_authority(signer_authority)); - } - - let receipt = block_on_sdk( - sdk.farm() - .publish_farm_via_radrootsd_with_options(farm, &options), - )? - .map_err(map_sdk_publish_error)?; - - map_event_publish_receipt(receipt, idempotency_key, "farm") -} - -pub fn bridge_order_request( - config: &RuntimeConfig, - order: &RadrootsTradeOrder, - idempotency_key: Option<&str>, - signer_session_id: Option<&str>, - signer_authority: Option<&ActorWriteSignerAuthority>, -) -> Result<BridgeOrderRequestResult, DaemonRpcError> { - let Some(signer_session_id) = signer_session_id else { - return Err(DaemonRpcError::Unconfigured( - "order publish requires a signer session id".to_owned(), - )); - }; - - let sdk = actor_write_sdk_client(config)?; - let session = SdkRadrootsdSignerSessionRef::from_session_id(signer_session_id.to_owned()); - let mut options = - radroots_sdk::SdkRadrootsdOrderRequestPublishOptions::from_signer_session_ref(&session); - if let Some(idempotency_key) = idempotency_key { - options = options.with_idempotency_key(idempotency_key.to_owned()); - } - if let Some(signer_authority) = signer_authority { - options = options.with_signer_authority(sdk_signer_authority(signer_authority)); - } - - let receipt = block_on_sdk( - sdk.trade() - .publish_order_request_via_radrootsd_with_options(order, &options), - )? - .map_err(map_sdk_publish_error)?; - - map_order_request_receipt(receipt, idempotency_key) -} - -fn bridge_jobs(config: &RuntimeConfig) -> Result<Vec<BridgeJobRemote>, DaemonRpcError> { - call( - &default_target(config), - "bridge.job.list", - None, - RpcAuthMode::BridgeBearer, - ) -} - -fn bridge_job_status( - config: &RuntimeConfig, - job_id: &str, -) -> Result<BridgeJobRemote, DaemonRpcError> { - call( - &default_target(config), - "bridge.job.status", - Some(serde_json::json!({ "job_id": job_id })), - RpcAuthMode::BridgeBearer, - ) -} - -fn nip46_sessions_with_target( - target: &RpcTarget, -) -> Result<Vec<Nip46SessionRemote>, DaemonRpcError> { - call(target, "nip46.session.list", None, RpcAuthMode::None) -} - -fn hydrate_nip46_session_user_pubkeys( - target: &RpcTarget, - sessions: Vec<Nip46SessionRemote>, -) -> Result<Vec<Nip46SessionRemote>, DaemonRpcError> { - if sessions - .iter() - .all(|session| session_user_pubkey(session).is_some()) - { - return Ok(sessions); - } - - let sdk = radrootsd_sdk_client(target)?; - let signer_sessions = sdk.radrootsd().signer_sessions(); - sessions - .into_iter() - .map(|mut session| { - if session_user_pubkey(&session).is_none() { - let session_ref = - SdkRadrootsdSignerSessionRef::from_session_id(session.session_id.clone()); - let public_key = block_on_sdk(signer_sessions.get_public_key(&session_ref))? - .map_err(|error| { - DaemonRpcError::Remote(format!( - "failed to hydrate signer session `{}` user pubkey: {error}", - session.session_id - )) - })?; - let pubkey = public_key.pubkey.trim().to_owned(); - if pubkey.is_empty() { - return Err(DaemonRpcError::InvalidResponse(format!( - "hydrated signer session `{}` reported an empty user pubkey", - session.session_id - ))); - } - session.user_pubkey = Some(pubkey); - } - Ok(session) - }) - .collect() -} - -fn actor_write_target(config: &RuntimeConfig) -> Result<RpcTarget, DaemonRpcError> { - let resolved = - provider::resolve_actor_write_plane_target(config).map_err(DaemonRpcError::Unconfigured)?; - Ok(RpcTarget { - url: resolved.url, - bridge_bearer_token: Some(resolved.bridge_bearer_token), - }) -} - -fn default_target(config: &RuntimeConfig) -> RpcTarget { - RpcTarget { - url: config.rpc.url.clone(), - bridge_bearer_token: config.rpc.bridge_bearer_token.clone(), - } -} - -fn actor_write_sdk_client( - config: &RuntimeConfig, -) -> Result<radroots_sdk::RadrootsSdkClient, DaemonRpcError> { - let target = actor_write_target(config)?; - radrootsd_sdk_client(&target) -} - -fn radrootsd_sdk_client( - target: &RpcTarget, -) -> Result<radroots_sdk::RadrootsSdkClient, DaemonRpcError> { - let mut sdk_config = RadrootsSdkConfig::custom(); - sdk_config.transport = SdkTransportMode::Radrootsd; - sdk_config.signer = SignerConfig::Nip46; - sdk_config.radrootsd.endpoint = Some(target.url.clone()); - let Some(bridge_bearer_token) = target.bridge_bearer_token.clone() else { - return Err(DaemonRpcError::Unconfigured( - "actor write plane target is missing a bridge bearer token".to_owned(), - )); - }; - sdk_config.radrootsd.auth = RadrootsdAuth::BearerToken(bridge_bearer_token); - radroots_sdk::RadrootsSdkClient::from_config(sdk_config) - .map_err(|err| DaemonRpcError::Unconfigured(err.to_string())) -} - -fn sdk_signer_authority(value: &ActorWriteSignerAuthority) -> SdkRadrootsdSignerAuthority { - SdkRadrootsdSignerAuthority { - provider_runtime_id: value.provider_runtime_id.clone(), - account_identity_id: value.account_identity_id.clone(), - provider_signer_session_id: value.provider_signer_session_id.clone(), - } -} - -fn map_sdk_publish_error(error: SdkPublishError) -> DaemonRpcError { - match error { - SdkPublishError::Config(err) => DaemonRpcError::Unconfigured(err.to_string()), - SdkPublishError::Radrootsd(message) => DaemonRpcError::Remote(message), - other => DaemonRpcError::External(other.to_string()), - } -} - -fn map_listing_publish_receipt( - receipt: radroots_sdk::SdkPublishReceipt, - idempotency_key: Option<&str>, -) -> Result<BridgeListingPublishResult, DaemonRpcError> { - let radroots_sdk::SdkTransportReceipt::Radrootsd(transport_receipt) = receipt.transport_receipt - else { - return Err(DaemonRpcError::InvalidResponse( - "sdk listing publish returned a non-radrootsd transport receipt".to_owned(), - )); - }; - let Some(job_id) = transport_receipt.job_id else { - return Err(DaemonRpcError::InvalidResponse( - "sdk listing publish did not return a job id".to_owned(), - )); - }; - let Some(status) = transport_receipt.status else { - return Err(DaemonRpcError::InvalidResponse( - "sdk listing publish did not return a job status".to_owned(), - )); - }; - let Some(signer_mode) = transport_receipt.signer_mode else { - return Err(DaemonRpcError::InvalidResponse( - "sdk listing publish did not return a signer mode".to_owned(), - )); - }; - Ok(BridgeListingPublishResult { - deduplicated: transport_receipt.deduplicated, - job_id, - idempotency_key: idempotency_key.map(str::to_owned), - status, - signer_mode, - signer_session_id: transport_receipt.signer_session_id, - event_kind: receipt.event_kind, - event_id: receipt.event_id, - event_addr: transport_receipt.event_addr, - }) -} - -fn map_event_publish_receipt( - receipt: radroots_sdk::SdkPublishReceipt, - idempotency_key: Option<&str>, - label: &str, -) -> Result<BridgeEventPublishResult, DaemonRpcError> { - let radroots_sdk::SdkTransportReceipt::Radrootsd(transport_receipt) = receipt.transport_receipt - else { - return Err(DaemonRpcError::InvalidResponse(format!( - "sdk {label} publish returned a non-radrootsd transport receipt" - ))); - }; - let Some(job_id) = transport_receipt.job_id else { - return Err(DaemonRpcError::InvalidResponse(format!( - "sdk {label} publish did not return a job id" - ))); - }; - let Some(status) = transport_receipt.status else { - return Err(DaemonRpcError::InvalidResponse(format!( - "sdk {label} publish did not return a job status" - ))); - }; - let Some(signer_mode) = transport_receipt.signer_mode else { - return Err(DaemonRpcError::InvalidResponse(format!( - "sdk {label} publish did not return a signer mode" - ))); - }; - Ok(BridgeEventPublishResult { - deduplicated: transport_receipt.deduplicated, - job_id, - idempotency_key: idempotency_key.map(str::to_owned), - status, - signer_mode, - signer_session_id: transport_receipt.signer_session_id, - event_kind: receipt.event_kind, - event_id: receipt.event_id, - event_addr: transport_receipt.event_addr, - }) -} - -fn map_order_request_receipt( - receipt: radroots_sdk::SdkPublishReceipt, - idempotency_key: Option<&str>, -) -> Result<BridgeOrderRequestResult, DaemonRpcError> { - let radroots_sdk::SdkTransportReceipt::Radrootsd(transport_receipt) = receipt.transport_receipt - else { - return Err(DaemonRpcError::InvalidResponse( - "sdk order publish returned a non-radrootsd transport receipt".to_owned(), - )); - }; - let Some(job_id) = transport_receipt.job_id else { - return Err(DaemonRpcError::InvalidResponse( - "sdk order publish did not return a job id".to_owned(), - )); - }; - let Some(status) = transport_receipt.status else { - return Err(DaemonRpcError::InvalidResponse( - "sdk order publish did not return a job status".to_owned(), - )); - }; - let Some(signer_mode) = transport_receipt.signer_mode else { - return Err(DaemonRpcError::InvalidResponse( - "sdk order publish did not return a signer mode".to_owned(), - )); - }; - Ok(BridgeOrderRequestResult { - deduplicated: transport_receipt.deduplicated, - job_id, - idempotency_key: idempotency_key.map(str::to_owned), - status, - signer_mode, - signer_session_id: transport_receipt.signer_session_id, - event_id: receipt.event_id, - event_addr: transport_receipt.event_addr, - }) -} - -fn block_on_sdk<F, T>(future: F) -> Result<T, DaemonRpcError> -where - F: std::future::Future<Output = T>, -{ - let runtime = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .map_err(|err| DaemonRpcError::External(format!("build sdk runtime: {err}")))?; - Ok(runtime.block_on(future)) -} - -pub fn resolve_signer_session_id( - config: &RuntimeConfig, - actor_role: &str, - actor_pubkey: &str, - event_kind: u32, - requested_session_id: Option<&str>, - signer_authority: Option<&ActorWriteSignerAuthority>, -) -> Result<String, DaemonRpcError> { - let target = actor_write_target(config)?; - let sessions = - hydrate_nip46_session_user_pubkeys(&target, nip46_sessions_with_target(&target)?)?; - - if let Some(session_id) = requested_session_id { - let Some(session) = sessions - .into_iter() - .find(|session| session.session_id == session_id) - else { - return Err(DaemonRpcError::Unconfigured(format!( - "requested signer session `{session_id}` was not found" - ))); - }; - validate_signer_session( - &session, - actor_role, - actor_pubkey, - event_kind, - signer_authority, - )?; - return Ok(session.session_id); - } - - let mut matches = sessions - .into_iter() - .filter(|session| { - session_matches_actor(session, actor_pubkey, event_kind, signer_authority) - }) - .map(|session| session.session_id) - .collect::<Vec<_>>(); - - match matches.len() { - 1 => Ok(matches.pop().expect("exactly one signer session")), - 0 => Err(DaemonRpcError::Unconfigured(format!( - "no authorized signer session matched {actor_role} pubkey `{actor_pubkey}` for sign_event:{event_kind}; configure exactly one signer session" - ))), - _ => Err(DaemonRpcError::Unconfigured(format!( - "multiple authorized signer sessions matched {actor_role} pubkey `{actor_pubkey}` for sign_event:{event_kind}; configure exactly one signer session" - ))), - } -} - -fn validate_signer_session( - session: &Nip46SessionRemote, - actor_role: &str, - actor_pubkey: &str, - event_kind: u32, - signer_authority: Option<&ActorWriteSignerAuthority>, -) -> Result<(), DaemonRpcError> { - if !session.authorized { - return Err(DaemonRpcError::Unconfigured(format!( - "requested signer session `{}` is not authorized", - session.session_id - ))); - } - let Some(user_pubkey) = session_user_pubkey(session) else { - return Err(DaemonRpcError::Unconfigured(format!( - "requested signer session `{}` did not report a user pubkey", - session.session_id - ))); - }; - if !user_pubkey.eq_ignore_ascii_case(actor_pubkey) { - return Err(DaemonRpcError::Unconfigured(format!( - "requested signer session `{}` user pubkey `{}` does not match {actor_role} pubkey `{actor_pubkey}`", - session.session_id, user_pubkey - ))); - } - if !sign_event_allowed(&session.permissions, event_kind) { - return Err(DaemonRpcError::Unconfigured(format!( - "requested signer session `{}` is not approved for sign_event:{event_kind}", - session.session_id - ))); - } - validate_signer_authority(session, signer_authority)?; - Ok(()) -} - -fn session_matches_actor( - session: &Nip46SessionRemote, - actor_pubkey: &str, - event_kind: u32, - signer_authority: Option<&ActorWriteSignerAuthority>, -) -> bool { - session.authorized - && session_user_pubkey(session) - .is_some_and(|user_pubkey| user_pubkey.eq_ignore_ascii_case(actor_pubkey)) - && sign_event_allowed(&session.permissions, event_kind) - && signer_authority_matches(session, signer_authority) -} - -fn session_user_pubkey(session: &Nip46SessionRemote) -> Option<&str> { - session - .user_pubkey - .as_deref() - .map(str::trim) - .filter(|value| !value.is_empty()) -} - -fn validate_signer_authority( - session: &Nip46SessionRemote, - signer_authority: Option<&ActorWriteSignerAuthority>, -) -> Result<(), DaemonRpcError> { - let Some(expected) = signer_authority else { - return Ok(()); - }; - let Some(actual) = session.signer_authority.as_ref() else { - return Err(DaemonRpcError::Unconfigured(format!( - "requested signer session `{}` is missing signer authority continuity metadata", - session.session_id - ))); - }; - if actual.provider_runtime_id != expected.provider_runtime_id { - return Err(DaemonRpcError::Unconfigured(format!( - "requested signer session `{}` provider `{}` does not match required provider `{}`", - session.session_id, actual.provider_runtime_id, expected.provider_runtime_id - ))); - } - if actual.account_identity_id != expected.account_identity_id { - return Err(DaemonRpcError::Unconfigured(format!( - "requested signer session `{}` account identity `{}` does not match required account `{}`", - session.session_id, actual.account_identity_id, expected.account_identity_id - ))); - } - if actual.provider_signer_session_id != expected.provider_signer_session_id { - return Err(DaemonRpcError::Unconfigured(format!( - "requested signer session `{}` provider signer session `{}` does not match required provider session `{}`", - session.session_id, - actual - .provider_signer_session_id - .as_deref() - .unwrap_or("<none>"), - expected - .provider_signer_session_id - .as_deref() - .unwrap_or("<none>") - ))); - } - Ok(()) -} - -fn signer_authority_matches( - session: &Nip46SessionRemote, - signer_authority: Option<&ActorWriteSignerAuthority>, -) -> bool { - validate_signer_authority(session, signer_authority).is_ok() -} - -fn sign_event_allowed(perms: &[String], kind: u32) -> bool { - perms.iter().any(|entry| entry == "sign_event") - || perms - .iter() - .any(|entry| entry == &format!("sign_event:{kind}")) -} - -fn call<T: DeserializeOwned>( - target: &RpcTarget, - method: &str, - params: Option<Value>, - auth_mode: RpcAuthMode, -) -> Result<T, DaemonRpcError> { - let client = Client::builder() - .timeout(Duration::from_secs(RPC_TIMEOUT_SECS)) - .build() - .map_err(|error| DaemonRpcError::InvalidResponse(format!("build rpc client: {error}")))?; - - let mut request = client.post(target.url.as_str()).json(&JsonRpcRequest { - jsonrpc: "2.0", - id: 1, - method, - params, - }); - - if matches!(auth_mode, RpcAuthMode::BridgeBearer) { - let Some(token) = target.bridge_bearer_token.as_deref() else { - return Err(DaemonRpcError::Unconfigured( - "bridge bearer token is not configured".to_owned(), - )); - }; - request = request.bearer_auth(token); - } - - let response = request.send().map_err(|error| { - DaemonRpcError::External(format!( - "failed to reach daemon rpc at {}: {error}", - target.url - )) - })?; - let status = response.status(); - let body = response.text().map_err(|error| { - DaemonRpcError::InvalidResponse(format!("read daemon rpc response: {error}")) - })?; - if !status.is_success() { - return Err(DaemonRpcError::External(format!( - "daemon rpc returned http {}", - status.as_u16() - ))); - } - - let envelope: JsonRpcResponse<T> = serde_json::from_str(body.as_str()).map_err(|error| { - DaemonRpcError::InvalidResponse(format!("parse daemon rpc response: {error}")) - })?; - if let Some(result) = envelope.result { - return Ok(result); - } - let Some(error) = envelope.error else { - return Err(DaemonRpcError::InvalidResponse( - "daemon rpc response did not include a result".to_owned(), - )); - }; - Err(map_rpc_error(method, error)) -} - -fn map_rpc_error(method: &str, error: JsonRpcResponseError) -> DaemonRpcError { - match error.code { - -32601 => DaemonRpcError::MethodUnavailable(error.message), - -32001 => DaemonRpcError::Unauthorized(error.message), - -32000 - if method == "bridge.job.status" - && error.message.starts_with("unknown bridge job:") => - { - DaemonRpcError::UnknownJob(error.message) - } - -32000 => DaemonRpcError::Remote(error.message), - _ => DaemonRpcError::InvalidResponse(format!( - "daemon rpc returned unexpected error {}: {}", - error.code, error.message - )), - } -} - -fn map_job_command(command: String) -> String { - match command.as_str() { - "bridge.profile.publish" => "profile.publish".to_owned(), - "bridge.farm.publish" => "farm.publish".to_owned(), - "bridge.listing.publish" => "listing.publish".to_owned(), - "bridge.order.request" => "order.submit".to_owned(), - other => other.to_owned(), - } -} - -fn map_job_summary_view(job: BridgeJobRemote) -> JobSummaryView { - JobSummaryView { - id: job.job_id, - command: map_job_command(job.command), - state: job.status, - terminal: job.terminal, - signer: job.signer_mode, - signer_session_id: job.signer_session_id, - requested_at_unix: job.requested_at_unix, - completed_at_unix: job.completed_at_unix, - recovered_after_restart: job.recovered_after_restart, - } -} - -fn map_job_detail_view(job: BridgeJobRemote) -> JobDetailView { - JobDetailView { - id: job.job_id, - command: map_job_command(job.command), - state: job.status, - terminal: job.terminal, - signer: job.signer_mode, - signer_session_id: job.signer_session_id, - requested_at_unix: job.requested_at_unix, - completed_at_unix: job.completed_at_unix, - recovered_after_restart: job.recovered_after_restart, - event_id: job.event_id, - event_addr: job.event_addr, - delivery_policy: job.delivery_policy, - delivery_quorum: job.delivery_quorum, - relay_count: job.relay_count, - acknowledged_relay_count: job.acknowledged_relay_count, - required_acknowledged_relay_count: job.required_acknowledged_relay_count, - attempt_count: job.attempt_count, - relay_outcome_summary: job.relay_outcome_summary, - attempt_summaries: job.attempt_summaries, - } -} - -pub fn bridge_source() -> &'static str { - BRIDGE_SOURCE -} diff --git a/src/runtime/management.rs b/src/runtime/management.rs @@ -1,1128 +0,0 @@ -use std::fs; -use std::path::{Path, PathBuf}; - -use chrono::Utc; -use getrandom::getrandom; -use radroots_runtime_distribution::{RadrootsRuntimeDistributionResolver, RuntimeArtifactRequest}; -use radroots_runtime_manager::{ - ManagedRuntimeActionInspection, ManagedRuntimeConfigInspection, - ManagedRuntimeContext as RuntimeManagementContext, ManagedRuntimeGroup as RuntimeGroup, - ManagedRuntimeInspection, ManagedRuntimeInspectionAvailability, ManagedRuntimeInstallState, - ManagedRuntimeInstancePaths, ManagedRuntimeInstanceRecord, ManagedRuntimeLifecycleAction, - ManagedRuntimeLogsInspection, ManagedRuntimeStatusInspection, - ManagedRuntimeTarget as RuntimeTarget, extract_binary_archive, - inspect_runtime_action as inspect_target_action, - inspect_runtime_config as inspect_target_config, inspect_runtime_logs as inspect_target_logs, - inspect_runtime_status as inspect_target_status, load_management_context_with_selection, - parse_contract_str, process_running as managed_process_running, remove_instance, - remove_instance_artifacts, resolve_runtime_target, save_registry, start_process, stop_process, - upsert_instance, write_instance_metadata, write_managed_file, write_secret_file, -}; -use radroots_runtime_paths::{RadrootsPathResolver, RadrootsRuntimePathSelection}; -use serde::{Deserialize, Serialize}; - -use crate::domain::runtime::{ - RuntimeActionView, RuntimeInstancePathsView, RuntimeInstanceRecordView, RuntimeLogsView, - RuntimeManagedConfigView, RuntimeStatusView, -}; -use crate::runtime::{RuntimeError, config::RuntimeConfig}; - -const MANAGEMENT_CONTRACT_RAW: &str = include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../../../foundation/contracts/runtime/management.toml" -)); -const DISTRIBUTION_CONTRACT_RAW: &str = include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../../../foundation/contracts/runtime/distribution.toml" -)); -const RADROOTSD_RUNTIME_ID: &str = "radrootsd"; -const RADROOTSD_BINARY_NAME: &str = "radrootsd"; -const RADROOTSD_ARTIFACT_CHANNEL: &str = "stable"; -const RADROOTSD_DEFAULT_RPC_ADDR: &str = "127.0.0.1:7070"; -const RADROOTSD_DEFAULT_METADATA_NAME: &str = "radrootsd"; -const RADROOTSD_BRIDGE_TOKEN_FILE: &str = "bridge-bearer-token.txt"; -const RADROOTSD_IDENTITY_FILE: &str = "identity.secret.json"; - -#[derive(Debug, Clone, Serialize, Deserialize)] -struct ManagedRadrootsdSettingsFile { - metadata: ManagedRadrootsdMetadata, - config: ManagedRadrootsdConfig, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -struct ManagedRadrootsdMetadata { - name: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -struct ManagedRadrootsdConfig { - #[serde(default, skip_serializing_if = "Vec::is_empty")] - relays: Vec<String>, - #[serde(skip_serializing_if = "Option::is_none")] - logs_dir: Option<String>, - rpc: ManagedRadrootsdRpc, - bridge: ManagedRadrootsdBridge, - #[serde(default)] - nip46: ManagedRadrootsdNip46, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -struct ManagedRadrootsdRpc { - addr: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -struct ManagedRadrootsdBridge { - enabled: bool, - #[serde(skip_serializing_if = "Option::is_none")] - bearer_token: Option<String>, - delivery_policy: String, - publish_max_attempts: usize, - #[serde(skip_serializing_if = "Option::is_none")] - state_path: Option<String>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -struct ManagedRadrootsdNip46 { - public_jsonrpc_enabled: bool, - session_ttl_secs: u64, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - perms: Vec<String>, -} - -impl Default for ManagedRadrootsdNip46 { - fn default() -> Self { - Self { - public_jsonrpc_enabled: false, - session_ttl_secs: 900, - perms: Vec::new(), - } - } -} - -#[derive(Debug, Clone)] -pub struct RuntimeConfigMutationRequest { - pub runtime_id: String, - pub instance_id: Option<String>, - pub key: String, - pub value: String, -} - -pub type RuntimeCommandAvailability = ManagedRuntimeInspectionAvailability; -pub type RuntimeLifecycleAction = ManagedRuntimeLifecycleAction; - -type RuntimeInspection<T> = ManagedRuntimeInspection<T>; - -pub fn inspect_status( - config: &RuntimeConfig, - runtime_id: &str, - instance_id: Option<&str>, -) -> Result<RuntimeInspection<RuntimeStatusView>, RuntimeError> { - let context = load_management_context(config)?; - let target = resolve_runtime_target(&context, runtime_id, instance_id); - let inspection = inspect_target_status(&target, &context.contract.lifecycle.actions); - Ok(RuntimeInspection { - availability: inspection.availability, - view: runtime_status_view(inspection.view), - }) -} - -pub fn inspect_logs( - config: &RuntimeConfig, - runtime_id: &str, - instance_id: Option<&str>, -) -> Result<RuntimeInspection<RuntimeLogsView>, RuntimeError> { - let context = load_management_context(config)?; - let target = resolve_runtime_target(&context, runtime_id, instance_id); - let inspection = inspect_target_logs(&target); - Ok(RuntimeInspection { - availability: inspection.availability, - view: runtime_logs_view(inspection.view), - }) -} - -pub fn inspect_config_show( - config: &RuntimeConfig, - runtime_id: &str, - instance_id: Option<&str>, -) -> Result<RuntimeInspection<RuntimeManagedConfigView>, RuntimeError> { - let context = load_management_context(config)?; - let target = resolve_runtime_target(&context, runtime_id, instance_id); - let inspection = inspect_target_config(&target); - Ok(RuntimeInspection { - availability: inspection.availability, - view: runtime_config_view(inspection.view), - }) -} - -pub fn inspect_action( - config: &RuntimeConfig, - runtime_id: &str, - instance_id: Option<&str>, - action: RuntimeLifecycleAction, - dry_run: bool, -) -> Result<RuntimeInspection<RuntimeActionView>, RuntimeError> { - let mut context = load_management_context(config)?; - let target = resolve_runtime_target(&context, runtime_id, instance_id); - if target.runtime_group == RuntimeGroup::ActiveManagedTarget && !dry_run { - return execute_action(config, &mut context, target, action); - } - let inspection = inspect_target_action(&target, action, None); - Ok(RuntimeInspection { - availability: inspection.availability, - view: runtime_action_view(inspection.view), - }) -} - -pub fn inspect_config_set( - config: &RuntimeConfig, - request: &RuntimeConfigMutationRequest, -) -> Result<RuntimeInspection<RuntimeActionView>, RuntimeError> { - let mut context = load_management_context(config)?; - let target = resolve_runtime_target( - &context, - request.runtime_id.as_str(), - request.instance_id.as_deref(), - ); - if target.runtime_group == RuntimeGroup::ActiveManagedTarget { - return execute_config_set(config, &mut context, target, request); - } - let detail = Some(format!( - "requested managed config mutation {}={} for runtime `{}` instance `{}`; runtime `{}` is not an active managed target in this wave", - request.key, request.value, target.runtime_id, target.instance_id, target.runtime_id - )); - let inspection = inspect_target_action(&target, RuntimeLifecycleAction::ConfigSet, detail); - Ok(RuntimeInspection { - availability: inspection.availability, - view: runtime_action_view(inspection.view), - }) -} - -fn load_management_context( - config: &RuntimeConfig, -) -> Result<RuntimeManagementContext, RuntimeError> { - let contract = parse_contract_str(MANAGEMENT_CONTRACT_RAW)?; - let selection = RadrootsRuntimePathSelection::from_profile_value( - config.paths.profile.as_str(), - config.paths.repo_local_root.clone(), - ) - .map_err(|error| RuntimeError::Config(error.to_string()))?; - let resolver = RadrootsPathResolver::current(); - load_management_context_with_selection(contract, &resolver, &selection) - .map_err(RuntimeError::from) -} - -fn runtime_status_view(view: ManagedRuntimeStatusInspection) -> RuntimeStatusView { - RuntimeStatusView { - runtime_id: view.runtime_id, - instance_id: view.instance_id, - instance_source: view.instance_source, - runtime_group: view.runtime_group, - management_posture: view.management_posture, - state: view.state, - source: view.source, - detail: view.detail, - management_mode: view.management_mode, - service_manager_integration: view.service_manager_integration, - uses_absolute_binary_paths: view.uses_absolute_binary_paths, - preferred_cli_binding: view.preferred_cli_binding, - install_state: view.install_state, - health_state: view.health_state, - health_source: view.health_source, - registry_path: view.registry_path.display().to_string(), - lifecycle_actions: view.lifecycle_actions, - instance_paths: view.instance_paths.map(runtime_instance_paths_view), - instance_record: view.instance_record.map(runtime_instance_record_view), - } -} - -fn runtime_logs_view(view: ManagedRuntimeLogsInspection) -> RuntimeLogsView { - RuntimeLogsView { - runtime_id: view.runtime_id, - instance_id: view.instance_id, - instance_source: view.instance_source, - runtime_group: view.runtime_group, - state: view.state, - source: view.source, - detail: view.detail, - stdout_log_path: view.stdout_log_path.map(|path| path.display().to_string()), - stderr_log_path: view.stderr_log_path.map(|path| path.display().to_string()), - stdout_log_present: view.stdout_log_present, - stderr_log_present: view.stderr_log_present, - } -} - -fn runtime_config_view(view: ManagedRuntimeConfigInspection) -> RuntimeManagedConfigView { - RuntimeManagedConfigView { - runtime_id: view.runtime_id, - instance_id: view.instance_id, - instance_source: view.instance_source, - runtime_group: view.runtime_group, - state: view.state, - source: view.source, - detail: view.detail, - config_format: view.config_format, - config_path: view.config_path.map(|path| path.display().to_string()), - config_present: view.config_present, - requires_bootstrap_secret: view.requires_bootstrap_secret, - requires_config_bootstrap: view.requires_config_bootstrap, - requires_signer_provider: view.requires_signer_provider, - } -} - -fn runtime_action_view(view: ManagedRuntimeActionInspection) -> RuntimeActionView { - RuntimeActionView { - action: view.action, - runtime_id: view.runtime_id, - instance_id: view.instance_id, - instance_source: view.instance_source, - runtime_group: view.runtime_group, - state: view.state, - source: view.source, - detail: view.detail, - mutates_bindings: view.mutates_bindings, - next_step: view.next_step, - } -} - -fn execute_action( - config: &RuntimeConfig, - context: &mut RuntimeManagementContext, - target: RuntimeTarget, - action: RuntimeLifecycleAction, -) -> Result<RuntimeInspection<RuntimeActionView>, RuntimeError> { - if target.runtime_id != RADROOTSD_RUNTIME_ID { - let inspection = inspect_target_action( - &target, - action, - Some(format!( - "runtime `{}` is not admitted as an active managed implementation in this wave", - target.runtime_id - )), - ); - return Ok(RuntimeInspection { - availability: inspection.availability, - view: runtime_action_view(inspection.view), - }); - } - - match action { - RuntimeLifecycleAction::Install => install_managed_radrootsd(config, context, target), - RuntimeLifecycleAction::Start => start_managed_radrootsd(config, context, target), - RuntimeLifecycleAction::Stop => stop_managed_radrootsd(context, target), - RuntimeLifecycleAction::Restart => restart_managed_radrootsd(config, context, target), - RuntimeLifecycleAction::Uninstall => uninstall_managed_radrootsd(context, target), - RuntimeLifecycleAction::ConfigSet => unreachable!("config set is handled separately"), - } -} - -fn execute_config_set( - _config: &RuntimeConfig, - context: &mut RuntimeManagementContext, - target: RuntimeTarget, - request: &RuntimeConfigMutationRequest, -) -> Result<RuntimeInspection<RuntimeActionView>, RuntimeError> { - if target.runtime_id != RADROOTSD_RUNTIME_ID { - let inspection = inspect_target_action( - &target, - RuntimeLifecycleAction::ConfigSet, - Some(format!( - "runtime `{}` is not admitted as an active managed implementation in this wave", - target.runtime_id - )), - ); - return Ok(RuntimeInspection { - availability: inspection.availability, - view: runtime_action_view(inspection.view), - }); - } - - let Some(predicted_paths) = target.predicted_paths.as_ref() else { - return Ok(runtime_action_unconfigured( - &target, - RuntimeLifecycleAction::ConfigSet, - "active managed runtime is missing predicted instance paths".to_owned(), - )); - }; - let Some(mut record) = target.instance_record.clone() else { - return Ok(runtime_action_unconfigured( - &target, - RuntimeLifecycleAction::ConfigSet, - format!( - "managed runtime `{}` instance `{}` is not installed in local runtime state; run `radroots runtime status get` for current status", - target.runtime_id, target.instance_id - ), - )); - }; - - let mut settings = load_managed_radrootsd_settings(&record.config_path)?; - let token_path = managed_radrootsd_token_path(predicted_paths); - let identity_path = managed_radrootsd_identity_path(predicted_paths); - apply_managed_radrootsd_config_mutation( - &mut settings, - &mut record, - predicted_paths, - request.key.as_str(), - request.value.as_str(), - token_path.as_path(), - )?; - write_secret_material_state(&settings, &mut record, token_path.as_path())?; - save_managed_radrootsd_settings(record.config_path.as_path(), &settings)?; - write_instance_metadata(predicted_paths, &record)?; - upsert_instance(&mut context.registry, record.clone()); - save_registry( - &context.shared_paths.instance_registry_path, - &context.registry, - )?; - - Ok(RuntimeInspection { - availability: RuntimeCommandAvailability::Success, - view: RuntimeActionView { - action: RuntimeLifecycleAction::ConfigSet.as_str().to_owned(), - runtime_id: target.runtime_id, - instance_id: target.instance_id, - instance_source: target.instance_source, - runtime_group: target.runtime_group.as_str().to_owned(), - state: "configured".to_owned(), - source: "generic runtime-management command family".to_owned(), - detail: format!( - "updated managed {} instance `{}` config key `{}`; config path = {}, identity path = {}", - RADROOTSD_RUNTIME_ID, - record.instance_id, - request.key, - record.config_path.display(), - identity_path.display() - ), - mutates_bindings: false, - next_step: None, - }, - }) -} - -fn install_managed_radrootsd( - _config: &RuntimeConfig, - context: &mut RuntimeManagementContext, - target: RuntimeTarget, -) -> Result<RuntimeInspection<RuntimeActionView>, RuntimeError> { - let Some(predicted_paths) = target.predicted_paths.as_ref() else { - return Ok(runtime_action_unconfigured( - &target, - RuntimeLifecycleAction::Install, - "active managed runtime is missing predicted instance paths".to_owned(), - )); - }; - - let artifact = resolve_radrootsd_artifact(&context.shared_paths)?; - let binary_path = extract_binary_archive( - artifact.archive_path.as_path(), - artifact.archive_format.as_str(), - predicted_paths, - artifact.binary_name.as_str(), - )?; - - let rpc_addr = RADROOTSD_DEFAULT_RPC_ADDR.to_owned(); - let health_endpoint = rpc_addr_to_http_url(rpc_addr.as_str())?; - let token_path = managed_radrootsd_token_path(predicted_paths); - let bridge_token = generate_bridge_token()?; - let config_path = predicted_paths.state_dir.join("config.toml"); - let settings = bootstrap_managed_radrootsd_settings( - predicted_paths, - rpc_addr.as_str(), - bridge_token.as_str(), - ); - write_secret_file(token_path.as_path(), bridge_token.as_str())?; - save_managed_radrootsd_settings(config_path.as_path(), &settings)?; - - let record = ManagedRuntimeInstanceRecord { - runtime_id: target.runtime_id.clone(), - instance_id: target.instance_id.clone(), - management_mode: target - .management_mode - .clone() - .unwrap_or_else(|| "interactive_user_managed".to_owned()), - install_state: ManagedRuntimeInstallState::Configured, - binary_path: binary_path.clone(), - config_path: config_path.clone(), - logs_path: predicted_paths.logs_dir.clone(), - run_path: predicted_paths.run_dir.clone(), - installed_version: artifact.version.clone(), - health_endpoint: Some(health_endpoint.clone()), - secret_material_ref: Some(token_path.display().to_string()), - last_started_at: None, - last_stopped_at: None, - notes: Some(format!( - "installed from artifact cache {}", - artifact.archive_path.display() - )), - }; - write_instance_metadata(predicted_paths, &record)?; - upsert_instance(&mut context.registry, record.clone()); - save_registry( - &context.shared_paths.instance_registry_path, - &context.registry, - )?; - - let identity_path = managed_radrootsd_identity_path(predicted_paths); - Ok(RuntimeInspection { - availability: RuntimeCommandAvailability::Success, - view: RuntimeActionView { - action: RuntimeLifecycleAction::Install.as_str().to_owned(), - runtime_id: target.runtime_id, - instance_id: target.instance_id, - instance_source: target.instance_source, - runtime_group: target.runtime_group.as_str().to_owned(), - state: "configured".to_owned(), - source: "generic runtime-management command family".to_owned(), - detail: format!( - "installed managed {RADROOTSD_RUNTIME_ID} instance `{}` from artifact {} to {}; config = {}; identity bootstrap path = {}; health endpoint = {}", - record.instance_id, - artifact.archive_path.display(), - binary_path.display(), - config_path.display(), - identity_path.display(), - health_endpoint - ), - mutates_bindings: false, - next_step: None, - }, - }) -} - -fn start_managed_radrootsd( - config: &RuntimeConfig, - context: &mut RuntimeManagementContext, - target: RuntimeTarget, -) -> Result<RuntimeInspection<RuntimeActionView>, RuntimeError> { - let Some(predicted_paths) = target.predicted_paths.as_ref() else { - return Ok(runtime_action_unconfigured( - &target, - RuntimeLifecycleAction::Start, - "active managed runtime is missing predicted instance paths".to_owned(), - )); - }; - let Some(mut record) = target.instance_record.clone() else { - return Ok(runtime_action_unconfigured( - &target, - RuntimeLifecycleAction::Start, - format!( - "managed runtime `{}` instance `{}` is not installed in local runtime state; run `radroots runtime status get` for current status", - target.runtime_id, target.instance_id - ), - )); - }; - - if managed_process_running(predicted_paths)? { - return Ok(RuntimeInspection { - availability: RuntimeCommandAvailability::Success, - view: RuntimeActionView { - action: RuntimeLifecycleAction::Start.as_str().to_owned(), - runtime_id: target.runtime_id, - instance_id: target.instance_id, - instance_source: target.instance_source, - runtime_group: target.runtime_group.as_str().to_owned(), - state: "running".to_owned(), - source: "generic runtime-management command family".to_owned(), - detail: format!( - "managed {} instance `{}` is already running from {}", - RADROOTSD_RUNTIME_ID, - record.instance_id, - record.binary_path.display() - ), - mutates_bindings: false, - next_step: None, - }, - }); - } - - let args = vec![ - "--config".to_owned(), - record.config_path.display().to_string(), - "--identity".to_owned(), - managed_radrootsd_identity_path(predicted_paths) - .display() - .to_string(), - "--allow-generate-identity".to_owned(), - ]; - let envs = managed_radrootsd_start_envs(config); - let pid = start_process(record.binary_path.as_path(), &args, &envs, predicted_paths)?; - record.last_started_at = Some(Utc::now().to_rfc3339()); - write_instance_metadata(predicted_paths, &record)?; - upsert_instance(&mut context.registry, record.clone()); - save_registry( - &context.shared_paths.instance_registry_path, - &context.registry, - )?; - - Ok(RuntimeInspection { - availability: RuntimeCommandAvailability::Success, - view: RuntimeActionView { - action: RuntimeLifecycleAction::Start.as_str().to_owned(), - runtime_id: target.runtime_id, - instance_id: target.instance_id, - instance_source: target.instance_source, - runtime_group: target.runtime_group.as_str().to_owned(), - state: "running".to_owned(), - source: "generic runtime-management command family".to_owned(), - detail: format!( - "started managed {} instance `{}` with pid {} using config {}", - RADROOTSD_RUNTIME_ID, - record.instance_id, - pid, - record.config_path.display() - ), - mutates_bindings: false, - next_step: None, - }, - }) -} - -fn stop_managed_radrootsd( - context: &mut RuntimeManagementContext, - target: RuntimeTarget, -) -> Result<RuntimeInspection<RuntimeActionView>, RuntimeError> { - let Some(predicted_paths) = target.predicted_paths.as_ref() else { - return Ok(runtime_action_unconfigured( - &target, - RuntimeLifecycleAction::Stop, - "active managed runtime is missing predicted instance paths".to_owned(), - )); - }; - let Some(mut record) = target.instance_record.clone() else { - return Ok(runtime_action_unconfigured( - &target, - RuntimeLifecycleAction::Stop, - format!( - "managed runtime `{}` instance `{}` is not installed", - target.runtime_id, target.instance_id - ), - )); - }; - - let stopped = stop_process(predicted_paths)?; - record.last_stopped_at = Some(Utc::now().to_rfc3339()); - write_instance_metadata(predicted_paths, &record)?; - upsert_instance(&mut context.registry, record.clone()); - save_registry( - &context.shared_paths.instance_registry_path, - &context.registry, - )?; - - Ok(RuntimeInspection { - availability: RuntimeCommandAvailability::Success, - view: RuntimeActionView { - action: RuntimeLifecycleAction::Stop.as_str().to_owned(), - runtime_id: target.runtime_id, - instance_id: target.instance_id, - instance_source: target.instance_source, - runtime_group: target.runtime_group.as_str().to_owned(), - state: "stopped".to_owned(), - source: "generic runtime-management command family".to_owned(), - detail: if stopped { - format!( - "stopped managed {} instance `{}`", - RADROOTSD_RUNTIME_ID, record.instance_id - ) - } else { - format!( - "managed {} instance `{}` was already stopped", - RADROOTSD_RUNTIME_ID, record.instance_id - ) - }, - mutates_bindings: false, - next_step: None, - }, - }) -} - -fn restart_managed_radrootsd( - config: &RuntimeConfig, - context: &mut RuntimeManagementContext, - target: RuntimeTarget, -) -> Result<RuntimeInspection<RuntimeActionView>, RuntimeError> { - let stop_result = stop_managed_radrootsd(context, target.clone())?; - if stop_result.availability != RuntimeCommandAvailability::Success { - return Ok(stop_result); - } - let refreshed_target = resolve_runtime_target( - context, - RADROOTSD_RUNTIME_ID, - Some(target.instance_id.as_str()), - ); - let start_result = start_managed_radrootsd(config, context, refreshed_target)?; - Ok(RuntimeInspection { - availability: start_result.availability, - view: RuntimeActionView { - action: RuntimeLifecycleAction::Restart.as_str().to_owned(), - runtime_id: start_result.view.runtime_id, - instance_id: start_result.view.instance_id, - instance_source: start_result.view.instance_source, - runtime_group: start_result.view.runtime_group, - state: start_result.view.state, - source: start_result.view.source, - detail: format!( - "restarted managed {} instance `{}`", - RADROOTSD_RUNTIME_ID, target.instance_id - ), - mutates_bindings: false, - next_step: None, - }, - }) -} - -fn uninstall_managed_radrootsd( - context: &mut RuntimeManagementContext, - target: RuntimeTarget, -) -> Result<RuntimeInspection<RuntimeActionView>, RuntimeError> { - let Some(predicted_paths) = target.predicted_paths.as_ref() else { - return Ok(runtime_action_unconfigured( - &target, - RuntimeLifecycleAction::Uninstall, - "active managed runtime is missing predicted instance paths".to_owned(), - )); - }; - let Some(record) = target.instance_record.clone() else { - return Ok(runtime_action_unconfigured( - &target, - RuntimeLifecycleAction::Uninstall, - format!( - "managed runtime `{}` instance `{}` is not installed", - target.runtime_id, target.instance_id - ), - )); - }; - - let _ = stop_process(predicted_paths); - remove_instance_artifacts(predicted_paths)?; - remove_instance( - &mut context.registry, - record.runtime_id.as_str(), - record.instance_id.as_str(), - ); - save_registry( - &context.shared_paths.instance_registry_path, - &context.registry, - )?; - - Ok(RuntimeInspection { - availability: RuntimeCommandAvailability::Success, - view: RuntimeActionView { - action: RuntimeLifecycleAction::Uninstall.as_str().to_owned(), - runtime_id: target.runtime_id, - instance_id: target.instance_id, - instance_source: target.instance_source, - runtime_group: target.runtime_group.as_str().to_owned(), - state: "uninstalled".to_owned(), - source: "generic runtime-management command family".to_owned(), - detail: format!( - "uninstalled managed {} instance `{}` and removed {}", - RADROOTSD_RUNTIME_ID, - record.instance_id, - predicted_paths - .install_dir - .parent() - .unwrap_or(predicted_paths.install_dir.as_path()) - .display() - ), - mutates_bindings: false, - next_step: None, - }, - }) -} - -fn runtime_action_unconfigured( - target: &RuntimeTarget, - action: RuntimeLifecycleAction, - detail: String, -) -> RuntimeInspection<RuntimeActionView> { - RuntimeInspection { - availability: RuntimeCommandAvailability::Unconfigured, - view: RuntimeActionView { - action: action.as_str().to_owned(), - runtime_id: target.runtime_id.clone(), - instance_id: target.instance_id.clone(), - instance_source: target.instance_source.clone(), - runtime_group: target.runtime_group.as_str().to_owned(), - state: "not_installed".to_owned(), - source: "generic runtime-management command family".to_owned(), - detail, - mutates_bindings: false, - next_step: None, - }, - } -} - -#[derive(Debug, Clone)] -struct ResolvedManagedArtifact { - archive_path: PathBuf, - archive_format: String, - binary_name: String, - version: String, -} - -fn resolve_radrootsd_artifact( - shared_paths: &radroots_runtime_manager::ManagedRuntimeSharedPaths, -) -> Result<ResolvedManagedArtifact, RuntimeError> { - let resolver = RadrootsRuntimeDistributionResolver::parse_str(DISTRIBUTION_CONTRACT_RAW)?; - let request = RuntimeArtifactRequest { - runtime_id: RADROOTSD_RUNTIME_ID, - os: current_distribution_os(), - arch: current_distribution_arch(), - version: "0.0.0", - channel: Some(RADROOTSD_ARTIFACT_CHANNEL), - }; - let artifact = resolver.resolve_artifact(&request)?; - let search_root = shared_paths.artifact_cache_dir.join(RADROOTSD_RUNTIME_ID); - let matches = find_cached_artifacts( - search_root.as_path(), - RADROOTSD_RUNTIME_ID, - artifact.target_id.as_str(), - artifact.archive_extension.as_str(), - )?; - match matches.as_slice() { - [] => Err(RuntimeError::Config(format!( - "no cached {RADROOTSD_RUNTIME_ID} artifact found under {} for target {}{}", - search_root.display(), - artifact.target_id, - artifact.archive_extension - ))), - [found] => Ok(found.clone()), - _ => Err(RuntimeError::Config(format!( - "multiple cached {RADROOTSD_RUNTIME_ID} artifacts found under {}; keep exactly one matching target {}{}", - search_root.display(), - artifact.target_id, - artifact.archive_extension - ))), - } -} - -fn find_cached_artifacts( - root: &Path, - runtime_id: &str, - target_id: &str, - extension: &str, -) -> Result<Vec<ResolvedManagedArtifact>, RuntimeError> { - let mut matches = Vec::new(); - if !root.exists() { - return Ok(matches); - } - collect_cached_artifacts(root, runtime_id, target_id, extension, &mut matches)?; - Ok(matches) -} - -fn collect_cached_artifacts( - root: &Path, - runtime_id: &str, - target_id: &str, - extension: &str, - matches: &mut Vec<ResolvedManagedArtifact>, -) -> Result<(), RuntimeError> { - for entry in fs::read_dir(root)? { - let entry = entry?; - let path = entry.path(); - if path.is_dir() { - collect_cached_artifacts(path.as_path(), runtime_id, target_id, extension, matches)?; - continue; - } - let Some(file_name) = path.file_name().and_then(|value| value.to_str()) else { - continue; - }; - let prefix = format!("{runtime_id}-"); - let suffix = format!("-{target_id}{extension}"); - if !file_name.starts_with(prefix.as_str()) || !file_name.ends_with(suffix.as_str()) { - continue; - } - let version = file_name - .strip_prefix(prefix.as_str()) - .and_then(|value| value.strip_suffix(suffix.as_str())) - .ok_or_else(|| { - RuntimeError::Config(format!( - "invalid cached artifact name `{file_name}` under {}", - root.display() - )) - })?; - matches.push(ResolvedManagedArtifact { - archive_path: path.clone(), - archive_format: archive_format_from_extension(extension).to_owned(), - binary_name: RADROOTSD_BINARY_NAME.to_owned(), - version: version.to_owned(), - }); - } - Ok(()) -} - -fn archive_format_from_extension(extension: &str) -> &str { - match extension { - ".tar.gz" => "tar.gz", - other => other.trim_start_matches('.'), - } -} - -fn bootstrap_managed_radrootsd_settings( - predicted_paths: &radroots_runtime_manager::ManagedRuntimeInstancePaths, - rpc_addr: &str, - bridge_token: &str, -) -> ManagedRadrootsdSettingsFile { - ManagedRadrootsdSettingsFile { - metadata: ManagedRadrootsdMetadata { - name: RADROOTSD_DEFAULT_METADATA_NAME.to_owned(), - }, - config: ManagedRadrootsdConfig { - relays: Vec::new(), - logs_dir: Some(predicted_paths.logs_dir.display().to_string()), - rpc: ManagedRadrootsdRpc { - addr: rpc_addr.to_owned(), - }, - bridge: ManagedRadrootsdBridge { - enabled: true, - bearer_token: Some(bridge_token.to_owned()), - delivery_policy: "any".to_owned(), - publish_max_attempts: 2, - state_path: Some( - predicted_paths - .state_dir - .join("bridge/bridge-jobs.json") - .display() - .to_string(), - ), - }, - nip46: ManagedRadrootsdNip46::default(), - }, - } -} - -fn load_managed_radrootsd_settings( - path: &Path, -) -> Result<ManagedRadrootsdSettingsFile, RuntimeError> { - let raw = fs::read_to_string(path)?; - toml::from_str(raw.as_str()).map_err(|err| { - RuntimeError::Config(format!( - "parse managed {RADROOTSD_RUNTIME_ID} config {}: {err}", - path.display() - )) - }) -} - -fn save_managed_radrootsd_settings( - path: &Path, - settings: &ManagedRadrootsdSettingsFile, -) -> Result<(), RuntimeError> { - let raw = toml::to_string_pretty(settings).map_err(|err| { - RuntimeError::Config(format!( - "serialize managed {RADROOTSD_RUNTIME_ID} config {}: {err}", - path.display() - )) - })?; - write_managed_file(path, raw.as_str())?; - Ok(()) -} - -fn apply_managed_radrootsd_config_mutation( - settings: &mut ManagedRadrootsdSettingsFile, - record: &mut ManagedRuntimeInstanceRecord, - predicted_paths: &radroots_runtime_manager::ManagedRuntimeInstancePaths, - key: &str, - value: &str, - token_path: &Path, -) -> Result<(), RuntimeError> { - match key { - "metadata.name" => { - settings.metadata.name = non_empty_value(key, value)?; - } - "config.logs_dir" => { - settings.config.logs_dir = Some(non_empty_value(key, value)?); - } - "config.rpc.addr" => { - let rpc_addr = non_empty_value(key, value)?; - settings.config.rpc.addr = rpc_addr.clone(); - record.health_endpoint = Some(rpc_addr_to_http_url(rpc_addr.as_str())?); - } - "config.bridge.enabled" => { - let enabled = parse_bool(value, key)?; - settings.config.bridge.enabled = enabled; - if !enabled { - settings.config.bridge.bearer_token = None; - if token_path.exists() { - fs::remove_file(token_path)?; - } - record.secret_material_ref = None; - } - } - "config.bridge.bearer_token" => { - let token = non_empty_value(key, value)?; - settings.config.bridge.enabled = true; - settings.config.bridge.bearer_token = Some(token.clone()); - write_secret_file(token_path, token.as_str())?; - record.secret_material_ref = Some(token_path.display().to_string()); - } - "config.bridge.state_path" => { - settings.config.bridge.state_path = Some(non_empty_value(key, value)?); - } - other => { - return Err(RuntimeError::Config(format!( - "unsupported managed {RADROOTSD_RUNTIME_ID} config key `{other}`; supported keys: metadata.name, config.logs_dir, config.rpc.addr, config.bridge.enabled, config.bridge.bearer_token, config.bridge.state_path" - ))); - } - } - - if settings.config.logs_dir.is_none() { - settings.config.logs_dir = Some(predicted_paths.logs_dir.display().to_string()); - } - if settings.config.bridge.state_path.is_none() { - settings.config.bridge.state_path = Some( - predicted_paths - .state_dir - .join("bridge/bridge-jobs.json") - .display() - .to_string(), - ); - } - Ok(()) -} - -fn write_secret_material_state( - settings: &ManagedRadrootsdSettingsFile, - record: &mut ManagedRuntimeInstanceRecord, - token_path: &Path, -) -> Result<(), RuntimeError> { - if settings.config.bridge.enabled { - let token = settings - .config - .bridge - .bearer_token - .as_deref() - .ok_or_else(|| { - RuntimeError::Config(format!( - "managed {RADROOTSD_RUNTIME_ID} bridge is enabled but bearer_token is missing" - )) - })?; - write_secret_file(token_path, token)?; - record.secret_material_ref = Some(token_path.display().to_string()); - } else { - record.secret_material_ref = None; - } - Ok(()) -} - -fn managed_radrootsd_start_envs(config: &RuntimeConfig) -> Vec<(String, String)> { - let mut envs = Vec::new(); - envs.push(( - "RADROOTSD_PATHS_PROFILE".to_owned(), - config.paths.profile.clone(), - )); - if config.paths.profile == "repo_local" { - if let Some(root) = &config.paths.repo_local_root { - envs.push(( - "RADROOTSD_PATHS_REPO_LOCAL_ROOT".to_owned(), - root.display().to_string(), - )); - } - } - envs -} - -fn managed_radrootsd_token_path( - predicted_paths: &radroots_runtime_manager::ManagedRuntimeInstancePaths, -) -> PathBuf { - predicted_paths - .secrets_dir - .join(RADROOTSD_BRIDGE_TOKEN_FILE) -} - -fn managed_radrootsd_identity_path( - predicted_paths: &radroots_runtime_manager::ManagedRuntimeInstancePaths, -) -> PathBuf { - predicted_paths.secrets_dir.join(RADROOTSD_IDENTITY_FILE) -} - -fn current_distribution_os() -> &'static str { - match std::env::consts::OS { - "macos" => "macos", - "linux" => "linux", - "windows" => "windows", - other => other, - } -} - -fn current_distribution_arch() -> &'static str { - match std::env::consts::ARCH { - "x86_64" => "amd64", - "aarch64" => "arm64", - other => other, - } -} - -fn non_empty_value(key: &str, value: &str) -> Result<String, RuntimeError> { - let trimmed = value.trim(); - if trimmed.is_empty() { - return Err(RuntimeError::Config(format!( - "managed config key `{key}` must not be empty" - ))); - } - Ok(trimmed.to_owned()) -} - -fn parse_bool(value: &str, key: &str) -> Result<bool, RuntimeError> { - match value.trim() { - "true" => Ok(true), - "false" => Ok(false), - other => Err(RuntimeError::Config(format!( - "managed config key `{key}` must be `true` or `false`, got `{other}`" - ))), - } -} - -fn rpc_addr_to_http_url(value: &str) -> Result<String, RuntimeError> { - let trimmed = value.trim(); - if trimmed.is_empty() { - return Err(RuntimeError::Config( - "managed rpc addr must not be empty".to_owned(), - )); - } - if trimmed.contains("://") { - return Ok(trimmed.to_owned()); - } - Ok(format!("http://{trimmed}")) -} - -fn generate_bridge_token() -> Result<String, RuntimeError> { - let mut bytes = [0_u8; 24]; - getrandom(&mut bytes) - .map_err(|err| RuntimeError::Config(format!("generate bridge token: {err}")))?; - Ok(bytes.iter().map(|byte| format!("{byte:02x}")).collect()) -} - -fn runtime_instance_paths_view(paths: ManagedRuntimeInstancePaths) -> RuntimeInstancePathsView { - RuntimeInstancePathsView { - install_dir: paths.install_dir.display().to_string(), - state_dir: paths.state_dir.display().to_string(), - logs_dir: paths.logs_dir.display().to_string(), - run_dir: paths.run_dir.display().to_string(), - secrets_dir: paths.secrets_dir.display().to_string(), - pid_file_path: paths.pid_file_path.display().to_string(), - stdout_log_path: paths.stdout_log_path.display().to_string(), - stderr_log_path: paths.stderr_log_path.display().to_string(), - metadata_path: paths.metadata_path.display().to_string(), - } -} - -fn runtime_instance_record_view(record: ManagedRuntimeInstanceRecord) -> RuntimeInstanceRecordView { - RuntimeInstanceRecordView { - management_mode: record.management_mode, - install_state: match record.install_state { - ManagedRuntimeInstallState::NotInstalled => "not_installed".to_owned(), - ManagedRuntimeInstallState::Installed => "installed".to_owned(), - ManagedRuntimeInstallState::Configured => "configured".to_owned(), - ManagedRuntimeInstallState::Failed => "failed".to_owned(), - }, - binary_path: record.binary_path.display().to_string(), - config_path: record.config_path.display().to_string(), - logs_path: record.logs_path.display().to_string(), - run_path: record.run_path.display().to_string(), - installed_version: record.installed_version, - health_endpoint: record.health_endpoint, - secret_material_ref: record.secret_material_ref, - last_started_at: record.last_started_at, - last_stopped_at: record.last_stopped_at, - notes: record.notes, - } -} diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs @@ -1,6 +1,5 @@ pub mod accounts; pub mod config; -pub mod daemon; pub mod farm; pub mod farm_config; pub mod find; @@ -8,7 +7,6 @@ pub mod hyf; pub mod listing; pub mod local; pub mod logging; -pub mod management; pub mod network; pub mod order; pub mod paths; @@ -34,10 +32,6 @@ pub enum RuntimeError { Json(#[from] serde_json::Error), #[error("failed to write output: {0}")] Io(#[from] std::io::Error), - #[error("runtime manager error: {0}")] - RuntimeManager(#[from] radroots_runtime_manager::RadrootsRuntimeManagerError), - #[error("runtime distribution error: {0}")] - RuntimeDistribution(#[from] radroots_runtime_distribution::RadrootsRuntimeDistributionError), } impl RuntimeError { @@ -49,9 +43,7 @@ impl RuntimeError { | Self::Sql(_) | Self::ReplicaSync(_) | Self::Json(_) - | Self::Io(_) - | Self::RuntimeManager(_) - | Self::RuntimeDistribution(_) => ExitCode::from(1), + | Self::Io(_) => ExitCode::from(1), } } }