From d86943ba7d5c2d537b13b0c661a11e153f040823 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Fri, 25 Aug 2023 10:35:04 -0600 Subject: [PATCH 01/89] the only way to begin is by beginning --- backend/Cargo.lock | 312 +++++++---------------- backend/src/db/mod.rs | 131 ++-------- backend/src/db/model.rs | 297 +++++++++++----------- backend/src/db/package.rs | 80 +----- backend/src/db/prelude.rs | 361 +++++++++++++++++++++++++++ backend/src/lib.rs | 1 + backend/src/prelude.rs | 5 + libs/Cargo.lock | 297 ++++++---------------- libs/models/Cargo.toml | 7 +- libs/models/src/data_url.rs | 163 ++++++++++++ libs/models/src/errors.rs | 42 +++- libs/models/src/id/action.rs | 43 ++++ libs/models/src/id/address.rs | 59 +++++ libs/models/src/id/health_check.rs | 31 +++ libs/models/src/id/image.rs | 38 +++ libs/models/src/id/interface.rs | 59 +++++ libs/models/src/id/invalid_id.rs | 3 + libs/models/src/{id.rs => id/mod.rs} | 55 +++- libs/models/src/id/package.rs | 88 +++++++ libs/models/src/id/volume.rs | 58 +++++ libs/models/src/lib.rs | 19 +- libs/models/src/mime.rs | 47 ++++ libs/models/src/version.rs | 4 - patch-db | 2 +- 24 files changed, 1385 insertions(+), 817 deletions(-) create mode 100644 backend/src/db/prelude.rs create mode 100644 backend/src/prelude.rs create mode 100644 libs/models/src/data_url.rs create mode 100644 libs/models/src/id/action.rs create mode 100644 libs/models/src/id/address.rs create mode 100644 libs/models/src/id/health_check.rs create mode 100644 libs/models/src/id/image.rs create mode 100644 libs/models/src/id/interface.rs create mode 100644 libs/models/src/id/invalid_id.rs rename libs/models/src/{id.rs => id/mod.rs} (54%) create mode 100644 libs/models/src/id/package.rs create mode 100644 libs/models/src/id/volume.rs create mode 100644 libs/models/src/mime.rs diff --git a/backend/Cargo.lock b/backend/Cargo.lock index ec983eaf4..c466b9e5d 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -58,6 +58,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ "cfg-if 1.0.0", + "getrandom 0.2.10", "once_cell", "version_check", ] @@ -374,15 +375,6 @@ version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" -[[package]] -name = "bitmaps" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" -dependencies = [ - "typenum", -] - [[package]] name = "bitmaps" version = "3.2.0" @@ -437,44 +429,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" -[[package]] -name = "bollard" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82e7850583ead5f8bbef247e2a3c37a19bd576e8420cd262a6711921827e1e5" -dependencies = [ - "base64 0.13.1", - "bollard-stubs", - "bytes", - "futures-core", - "futures-util", - "hex", - "http", - "hyper", - "hyperlocal", - "log", - "pin-project-lite", - "serde", - "serde_derive", - "serde_json", - "serde_urlencoded", - "thiserror", - "tokio", - "tokio-util", - "url", - "winapi", -] - -[[package]] -name = "bollard-stubs" -version = "1.42.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed59b5c00048f48d7af971b71f800fdf23e858844a6f9e4d32ca72e9399e7864" -dependencies = [ - "serde", - "serde_with 1.14.0", -] - [[package]] name = "brotli" version = "3.3.4" @@ -664,7 +618,7 @@ dependencies = [ "indenter", "once_cell", "owo-colors", - "tracing-error 0.2.0", + "tracing-error", ] [[package]] @@ -676,7 +630,7 @@ dependencies = [ "once_cell", "owo-colors", "tracing-core", - "tracing-error 0.2.0", + "tracing-error", ] [[package]] @@ -912,38 +866,14 @@ dependencies = [ "zeroize", ] -[[package]] -name = "darling" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" -dependencies = [ - "darling_core 0.13.4", - "darling_macro 0.13.4", -] - [[package]] name = "darling" version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" dependencies = [ - "darling_core 0.20.3", - "darling_macro 0.20.3", -] - -[[package]] -name = "darling_core" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2 1.0.66", - "quote 1.0.31", - "strsim 0.10.0", - "syn 1.0.109", + "darling_core", + "darling_macro", ] [[package]] @@ -960,24 +890,13 @@ dependencies = [ "syn 2.0.18", ] -[[package]] -name = "darling_macro" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" -dependencies = [ - "darling_core 0.13.4", - "quote 1.0.31", - "syn 1.0.109", -] - [[package]] name = "darling_macro" version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ - "darling_core 0.20.3", + "darling_core", "quote 1.0.31", "syn 2.0.18", ] @@ -1339,7 +1258,7 @@ dependencies = [ "color-eyre", "futures", "helpers", - "imbl 2.0.0", + "imbl", "nix 0.25.1", "procfs", "serde", @@ -1347,9 +1266,9 @@ dependencies = [ "tokio", "tokio-stream", "tracing", - "tracing-error 0.2.0", + "tracing-error", "tracing-futures", - "tracing-subscriber 0.3.17", + "tracing-subscriber", "yajrc 0.1.0 (git+https://github.com/dr-bonez/yajrc.git?branch=develop)", ] @@ -1400,7 +1319,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" dependencies = [ - "heck 0.4.1", + "heck", "proc-macro2 1.0.66", "quote 1.0.31", "syn 1.0.109", @@ -1821,6 +1740,15 @@ dependencies = [ "ahash 0.7.6", ] +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.3", +] + [[package]] name = "hashbrown" version = "0.14.0" @@ -1840,15 +1768,6 @@ dependencies = [ "hashbrown 0.14.0", ] -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "heck" version = "0.4.1" @@ -2033,19 +1952,6 @@ dependencies = [ "tokio-tungstenite", ] -[[package]] -name = "hyperlocal" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fafdf7b2b2de7c9784f76e02c0935e65a8117ec3b768644379983ab333ac98c" -dependencies = [ - "futures-util", - "hex", - "hyper", - "pin-project", - "tokio", -] - [[package]] name = "iana-time-zone" version = "0.1.57" @@ -2112,30 +2018,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" -[[package]] -name = "imbl" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "543682c9082b25e63d03b5acbd65ad111fd49dd93e70843e5175db4ff81d606b" -dependencies = [ - "bitmaps 2.1.0", - "rand_core 0.6.4", - "rand_xoshiro", - "sized-chunks", - "typenum", - "version_check", -] - [[package]] name = "imbl" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2806b69cd9f4664844027b64465eacb444c67c1db9c778e341adff0c25cdb0d" dependencies = [ - "bitmaps 3.2.0", + "bitmaps", "imbl-sized-chunks", "rand_core 0.6.4", "rand_xoshiro", + "serde", "version_check", ] @@ -2145,7 +2038,19 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6957ea0b2541c5ca561d3ef4538044af79f8a05a1eb3a3b148936aaceaa1076" dependencies = [ - "bitmaps 3.2.0", + "bitmaps", +] + +[[package]] +name = "imbl-value" +version = "0.1.0" +source = "git+https://github.com/Start9Labs/imbl-value.git#929395141c3a882ac366c12ac9402d0ebaa2201b" +dependencies = [ + "imbl", + "serde", + "serde_json", + "treediff", + "yasi", ] [[package]] @@ -2203,20 +2108,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "internment" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "161079c3ad892faa215fcfcf3fd7a6a3c9288df2b06a2c2bad7fbfad4f01d69d" -dependencies = [ - "ahash 0.7.6", - "dashmap", - "hashbrown 0.12.3", - "once_cell", - "parking_lot 0.12.1", - "serde", -] - [[package]] name = "io-lifetimes" version = "1.0.11" @@ -2395,9 +2286,9 @@ dependencies = [ name = "json-patch" version = "0.2.7-alpha.0" dependencies = [ + "imbl-value", "json-ptr", "serde", - "serde_json", "treediff", ] @@ -2405,8 +2296,9 @@ dependencies = [ name = "json-ptr" version = "0.1.0" dependencies = [ + "imbl", + "imbl-value", "serde", - "serde_json", "thiserror", ] @@ -2701,6 +2593,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.17" @@ -2737,11 +2638,10 @@ dependencies = [ name = "models" version = "0.1.0" dependencies = [ - "bollard", + "base64 0.21.2", "color-eyre", "ed25519-dalek", "emver", - "internment", "ipnet", "lazy_static", "mbrman", @@ -2749,6 +2649,7 @@ dependencies = [ "patch-db", "rand 0.8.5", "regex", + "reqwest", "rpc-toolkit", "serde", "serde_json", @@ -2758,6 +2659,7 @@ dependencies = [ "tokio", "torut", "tracing", + "yasi", ] [[package]] @@ -2805,41 +2707,42 @@ dependencies = [ [[package]] name = "nix" -version = "0.23.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" dependencies = [ "bitflags 1.3.2", - "cc", "cfg-if 1.0.0", "libc", - "memoffset", + "memoffset 0.6.5", ] [[package]] name = "nix" -version = "0.24.3" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" dependencies = [ + "autocfg", "bitflags 1.3.2", "cfg-if 1.0.0", "libc", - "memoffset", + "memoffset 0.6.5", + "pin-utils", ] [[package]] name = "nix" -version = "0.25.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ - "autocfg", "bitflags 1.3.2", "cfg-if 1.0.0", "libc", - "memoffset", + "memoffset 0.7.1", "pin-utils", + "static_assertions", ] [[package]] @@ -3211,19 +3114,19 @@ dependencies = [ "async-trait", "fd-lock-rs", "futures", - "imbl 1.0.1", + "imbl", + "imbl-value", "json-patch", "json-ptr", "lazy_static", - "nix 0.23.2", + "nix 0.26.2", "patch-db-macro", "serde", "serde_cbor 0.11.1", - "serde_json", "thiserror", "tokio", "tracing", - "tracing-error 0.1.2", + "tracing-error", ] [[package]] @@ -3239,7 +3142,7 @@ dependencies = [ name = "patch-db-macro-internals" version = "0.1.0" dependencies = [ - "heck 0.3.3", + "heck", "proc-macro2 1.0.66", "quote 1.0.31", "syn 1.0.109", @@ -4237,16 +4140,6 @@ dependencies = [ "v8", ] -[[package]] -name = "serde_with" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" -dependencies = [ - "serde", - "serde_with_macros 1.5.2", -] - [[package]] name = "serde_with" version = "2.3.3" @@ -4259,29 +4152,17 @@ dependencies = [ "indexmap 1.9.3", "serde", "serde_json", - "serde_with_macros 2.3.3", + "serde_with_macros", "time 0.3.23", ] -[[package]] -name = "serde_with_macros" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" -dependencies = [ - "darling 0.13.4", - "proc-macro2 1.0.66", - "quote 1.0.31", - "syn 1.0.109", -] - [[package]] name = "serde_with_macros" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" dependencies = [ - "darling 0.20.3", + "darling", "proc-macro2 1.0.66", "quote 1.0.31", "syn 2.0.18", @@ -4419,16 +4300,6 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" -[[package]] -name = "sized-chunks" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" -dependencies = [ - "bitmaps 2.1.0", - "typenum", -] - [[package]] name = "slab" version = "0.4.8" @@ -4579,7 +4450,7 @@ checksum = "9966e64ae989e7e575b19d7265cb79d7fc3cbbdf179835cb0d716f294c2049c9" dependencies = [ "dotenvy", "either", - "heck 0.4.1", + "heck", "hex", "once_cell", "proc-macro2 1.0.66", @@ -4690,7 +4561,7 @@ dependencies = [ "http", "hyper", "hyper-ws-listener", - "imbl 2.0.0", + "imbl", "include_dir", "indexmap 1.9.3", "ipnet", @@ -4733,7 +4604,7 @@ dependencies = [ "scopeguard", "serde", "serde_json", - "serde_with 2.3.3", + "serde_with", "serde_yaml", "sha2 0.10.7", "sha2 0.9.9", @@ -4753,9 +4624,9 @@ dependencies = [ "toml", "torut", "tracing", - "tracing-error 0.2.0", + "tracing-error", "tracing-futures", - "tracing-subscriber 0.3.17", + "tracing-subscriber", "trust-dns-server", "typed-builder", "url", @@ -4859,7 +4730,7 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ - "heck 0.4.1", + "heck", "proc-macro2 1.0.66", "quote 1.0.31", "rustversion", @@ -5627,16 +5498,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-error" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24" -dependencies = [ - "tracing", - "tracing-subscriber 0.2.25", -] - [[package]] name = "tracing-error" version = "0.2.0" @@ -5644,7 +5505,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" dependencies = [ "tracing", - "tracing-subscriber 0.3.17", + "tracing-subscriber", ] [[package]] @@ -5668,17 +5529,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-subscriber" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" -dependencies = [ - "sharded-slab", - "thread_local", - "tracing-core", -] - [[package]] name = "tracing-subscriber" version = "0.3.17" @@ -6390,6 +6240,18 @@ dependencies = [ "thiserror", ] +[[package]] +name = "yasi" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f355ab62ebe30b758c1f4ab096a306722c4b7dbfb9d8c07d18c70d71a945588" +dependencies = [ + "ahash 0.8.3", + "hashbrown 0.13.2", + "lazy_static", + "serde", +] + [[package]] name = "zeroize" version = "1.6.0" diff --git a/backend/src/db/mod.rs b/backend/src/db/mod.rs index 6823c398d..9a945620c 100644 --- a/backend/src/db/mod.rs +++ b/backend/src/db/mod.rs @@ -1,13 +1,13 @@ pub mod model; pub mod package; +pub mod prelude; use std::future::Future; use std::sync::Arc; -use color_eyre::eyre::eyre; use futures::{FutureExt, SinkExt, StreamExt}; use patch_db::json_ptr::JsonPointer; -use patch_db::{DbHandle, Dump, LockType, Revision}; +use patch_db::{Dump, Revision}; use rpc_toolkit::command; use rpc_toolkit::hyper::upgrade::Upgraded; use rpc_toolkit::hyper::{Body, Error as HyperError, Request, Response}; @@ -22,12 +22,10 @@ use tokio_tungstenite::tungstenite::Message; use tokio_tungstenite::WebSocketStream; use tracing::instrument; -pub use self::model::DatabaseModel; use crate::context::RpcContext; use crate::middleware::auth::{HasValidSession, HashSessionToken}; -use crate::util::display_none; +use crate::prelude::*; use crate::util::serde::{display_serializable, IoFormat}; -use crate::{Error, ResultExt}; #[instrument(skip_all)] async fn ws_handler< @@ -40,8 +38,8 @@ async fn ws_handler< let (dump, sub) = ctx.db.dump_and_sub().await?; let mut stream = ws_fut .await - .with_kind(crate::ErrorKind::Network)? - .with_kind(crate::ErrorKind::Unknown)?; + .with_kind(ErrorKind::Network)? + .with_kind(ErrorKind::Unknown)?; if let Some((session, token)) = session { let kill = subscribe_to_session_kill(&ctx, token).await; @@ -55,7 +53,7 @@ async fn ws_handler< reason: "UNAUTHORIZED".into(), })) .await - .with_kind(crate::ErrorKind::Network)?; + .with_kind(ErrorKind::Network)?; } Ok(()) @@ -82,7 +80,6 @@ async fn deal_with_messages( mut sub: patch_db::Subscriber, mut stream: WebSocketStream, ) -> Result<(), Error> { - let mut timer = tokio::time::interval(tokio::time::Duration::from_secs(5)); loop { futures::select! { _ = (&mut kill).fuse() => { @@ -93,31 +90,26 @@ async fn deal_with_messages( reason: "UNAUTHORIZED".into(), })) .await - .with_kind(crate::ErrorKind::Network)?; + .with_kind(ErrorKind::Network)?; return Ok(()) } new_rev = sub.recv().fuse() => { let rev = new_rev.expect("UNREACHABLE: patch-db is dropped"); stream - .send(Message::Text(serde_json::to_string(&rev).with_kind(crate::ErrorKind::Serialization)?)) + .send(Message::Text(serde_json::to_string(&rev).with_kind(ErrorKind::Serialization)?)) .await - .with_kind(crate::ErrorKind::Network)?; + .with_kind(ErrorKind::Network)?; } message = stream.next().fuse() => { - let message = message.transpose().with_kind(crate::ErrorKind::Network)?; - if message.is_none() { - tracing::info!("Closing WebSocket: Stream Finished"); - return Ok(()) + let message = message.transpose().with_kind(ErrorKind::Network)?; + match message { + None => { + tracing::info!("Closing WebSocket: Stream Finished"); + return Ok(()) + } + _ => (), } } - // This is trying to give a health checks to the home to keep the ui alive. - _ = timer.tick().fuse() => { - stream - .send(Message::Ping(vec![])) - .await - .with_kind(crate::ErrorKind::Network)?; - } - } } } @@ -129,10 +121,10 @@ async fn send_dump( ) -> Result<(), Error> { stream .send(Message::Text( - serde_json::to_string(&dump).with_kind(crate::ErrorKind::Serialization)?, + serde_json::to_string(&dump).with_kind(ErrorKind::Serialization)?, )) .await - .with_kind(crate::ErrorKind::Network)?; + .with_kind(ErrorKind::Network)?; Ok(()) } @@ -147,7 +139,7 @@ pub async fn subscribe(ctx: RpcContext, req: Request) -> Result Some(a), Err(e) => { - if e.kind != crate::ErrorKind::Authorization { + if e.kind != ErrorKind::Authorization { tracing::error!("Error Authenticating Websocket: {}", e); tracing::debug!("{:?}", e); } @@ -155,7 +147,7 @@ pub async fn subscribe(ctx: RpcContext, req: Request) -> Result) -> Result Result<(), RpcError> { Ok(()) } @@ -207,85 +199,6 @@ pub async fn dump( Ok(ctx.db.dump().await?) } -fn apply_expr(input: jaq_core::Val, expr: &str) -> Result { - let (expr, errs) = jaq_core::parse::parse(expr, jaq_core::parse::main()); - - let Some(expr) = expr else { - return Err(Error::new( - eyre!("Failed to parse expression: {:?}", errs), - crate::ErrorKind::InvalidRequest, - )); - }; - - let mut errs = Vec::new(); - - let mut defs = jaq_core::Definitions::core(); - for def in jaq_std::std() { - defs.insert(def, &mut errs); - } - - let filter = defs.finish(expr, Vec::new(), &mut errs); - - if !errs.is_empty() { - return Err(Error::new( - eyre!("Failed to compile expression: {:?}", errs), - crate::ErrorKind::InvalidRequest, - )); - }; - - let inputs = jaq_core::RcIter::new(std::iter::empty()); - let mut res_iter = filter.run(jaq_core::Ctx::new([], &inputs), input); - - let Some(res) = res_iter - .next() - .transpose() - .map_err(|e| eyre!("{e}")) - .with_kind(crate::ErrorKind::Deserialization)? - else { - return Err(Error::new( - eyre!("expr returned no results"), - crate::ErrorKind::InvalidRequest, - )); - }; - - if res_iter.next().is_some() { - return Err(Error::new( - eyre!("expr returned too many results"), - crate::ErrorKind::InvalidRequest, - )); - } - - Ok(res) -} - -#[command(display(display_none))] -pub async fn apply(#[context] ctx: RpcContext, #[arg] expr: String) -> Result<(), Error> { - let mut db = ctx.db.handle(); - - DatabaseModel::new().lock(&mut db, LockType::Write).await?; - - let root_ptr = JsonPointer::::default(); - - let input = db.get_value(&root_ptr, None).await?; - - let res = (|| { - let res = apply_expr(input.into(), &expr)?; - - serde_json::from_value::(res.clone().into()).with_ctx(|_| { - ( - crate::ErrorKind::Deserialization, - "result does not match database model", - ) - })?; - - Ok::(res.into()) - })()?; - - db.put_value(&root_ptr, &res).await?; - - Ok(()) -} - #[command(subcommands(ui))] pub fn put() -> Result<(), RpcError> { Ok(()) @@ -303,7 +216,7 @@ pub async fn ui( ) -> Result<(), Error> { let ptr = "/ui" .parse::() - .with_kind(crate::ErrorKind::Database)? + .with_kind(ErrorKind::Database)? + &pointer; ctx.db.put(&ptr, &value).await?; Ok(()) diff --git a/backend/src/db/model.rs b/backend/src/db/model.rs index 0024f76c6..235fcffef 100644 --- a/backend/src/db/model.rs +++ b/backend/src/db/model.rs @@ -1,38 +1,34 @@ use std::collections::{BTreeMap, BTreeSet}; use std::net::{Ipv4Addr, Ipv6Addr}; -use std::sync::Arc; use chrono::{DateTime, Utc}; use emver::VersionRange; use ipnet::{Ipv4Net, Ipv6Net}; use isocountry::CountryCode; use itertools::Itertools; +use models::{AddressId, DataUrl, HealthCheckId}; use openssl::hash::MessageDigest; -use patch_db::json_ptr::JsonPointer; -use patch_db::{HasModel, Map, MapModel, OptionModel}; +use patch_db::{HasModel, Value}; use reqwest::Url; use serde::{Deserialize, Serialize}; -use serde_json::Value; use ssh_key::public::Ed25519PublicKey; use crate::account::AccountInfo; -use crate::config::spec::{PackagePointerSpec, SystemPointerSpec}; +use crate::action::Actions; use crate::install::progress::InstallProgress; -use crate::net::interface::InterfaceId; use crate::net::utils::{get_iface_ipv4_addr, get_iface_ipv6_addr}; -use crate::s9pk::manifest::{Manifest, ManifestModel, PackageId}; -use crate::status::health_check::HealthCheckId; +use crate::prelude::*; +use crate::s9pk::manifest::{Manifest, PackageId}; use crate::status::Status; use crate::util::Version; use crate::version::{Current, VersionT}; -use crate::Error; #[derive(Debug, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] +#[model = "Model"] +// #[macro_debug] pub struct Database { - #[model] pub server_info: ServerInfo, - #[model] pub package_data: AllPackageData, pub ui: Value, } @@ -44,12 +40,12 @@ impl Database { server_info: ServerInfo { id: account.server_id.clone(), version: Current::new().semver().into(), - hostname: Some(account.hostname.no_dot_host_name()), + hostname: account.hostname.no_dot_host_name(), last_backup: None, last_wifi_region: None, eos_version_compat: Current::new().compat().clone(), lan_address, - tor_address: format!("https://{}", account.key.tor_address()) + tor_address: format!("http://{}", account.key.tor_address()) .parse() .unwrap(), ip_info: BTreeMap::new(), @@ -88,17 +84,15 @@ impl Database { } } } -impl DatabaseModel { - pub fn new() -> Self { - Self::from(JsonPointer::default()) - } -} + +pub type DatabaseModel = Model; #[derive(Debug, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] +#[model = "Model"] pub struct ServerInfo { pub id: String, - pub hostname: Option, + pub hostname: String, pub version: Version, pub last_backup: Option>, /// Used in the wifi to determine the region to set the system to @@ -106,10 +100,7 @@ pub struct ServerInfo { pub eos_version_compat: VersionRange, pub lan_address: Url, pub tor_address: Url, - #[model] - #[serde(default)] pub ip_info: BTreeMap, - #[model] #[serde(default)] pub status_info: ServerStatus, pub wifi: WifiInfo, @@ -125,6 +116,7 @@ pub struct ServerInfo { #[derive(Debug, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] +#[model = "Model"] pub struct IpInfo { pub ipv4_range: Option, pub ipv4: Option, @@ -145,29 +137,31 @@ impl IpInfo { } #[derive(Debug, Default, Deserialize, Serialize, HasModel)] +#[model = "Model"] pub struct BackupProgress { pub complete: bool, } #[derive(Debug, Default, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] +#[model = "Model"] pub struct ServerStatus { - #[model] pub backup_progress: Option>, pub updated: bool, - #[model] pub update_progress: Option, } #[derive(Debug, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] +#[model = "Model"] pub struct UpdateProgress { pub size: Option, pub downloaded: u64, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] +#[model = "Model"] pub struct WifiInfo { pub ssids: Vec, pub selected: Option, @@ -194,16 +188,11 @@ pub struct AllPackageData(pub BTreeMap); impl Map for AllPackageData { type Key = PackageId; type Value = PackageDataEntry; - fn get(&self, key: &Self::Key) -> Option<&Self::Value> { - self.0.get(key) - } -} -impl HasModel for AllPackageData { - type Model = MapModel; } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] +#[model = "Model"] pub struct StaticFiles { license: String, instructions: String, @@ -219,120 +208,156 @@ impl StaticFiles { } } +#[derive(Debug, Deserialize, Serialize, HasModel)] +#[serde(rename_all = "kebab-case")] +#[model = "Model"] +pub struct PackageDataEntryInstalling { + pub static_files: StaticFiles, + pub manifest: Manifest, + pub install_progress: InstallProgress, +} + +#[derive(Debug, Deserialize, Serialize, HasModel)] +#[serde(rename_all = "kebab-case")] +#[model = "Model"] +pub struct PackageDataEntryUpdating { + pub static_files: StaticFiles, + pub manifest: Manifest, + pub installed: InstalledPackageInfo, + pub install_progress: InstallProgress, +} + +#[derive(Debug, Deserialize, Serialize, HasModel)] +#[serde(rename_all = "kebab-case")] +#[model = "Model"] +pub struct PackageDataEntryRestoring { + pub static_files: StaticFiles, + pub manifest: Manifest, + pub install_progress: Arc, +} + +#[derive(Debug, Deserialize, Serialize, HasModel)] +#[serde(rename_all = "kebab-case")] +#[model = "Model"] +pub struct PackageDataEntryRemoving { + pub static_files: StaticFiles, + pub manifest: Manifest, + pub removing: InstalledPackageInfo, +} + +#[derive(Debug, Deserialize, Serialize, HasModel)] +#[serde(rename_all = "kebab-case")] +#[model = "Model"] +pub struct PackageDataEntryInstalled { + pub static_files: StaticFiles, + pub manifest: Manifest, + pub installed: InstalledPackageInfo, +} + #[derive(Debug, Deserialize, Serialize, HasModel)] #[serde(tag = "state")] #[serde(rename_all = "kebab-case")] +#[model = "Model"] +// #[macro_debug] pub enum PackageDataEntry { - #[serde(rename_all = "kebab-case")] - Installing { - static_files: StaticFiles, - manifest: Manifest, - install_progress: Arc, - }, - #[serde(rename_all = "kebab-case")] - Updating { - static_files: StaticFiles, - manifest: Manifest, - installed: InstalledPackageDataEntry, - install_progress: Arc, - }, - #[serde(rename_all = "kebab-case")] - Restoring { - static_files: StaticFiles, - manifest: Manifest, - install_progress: Arc, - }, - #[serde(rename_all = "kebab-case")] - Removing { - static_files: StaticFiles, - manifest: Manifest, - removing: InstalledPackageDataEntry, - }, - #[serde(rename_all = "kebab-case")] - Installed { - static_files: StaticFiles, - manifest: Manifest, - installed: InstalledPackageDataEntry, - }, -} -impl PackageDataEntry { - pub fn installed(&self) -> Option<&InstalledPackageDataEntry> { - match self { - Self::Installing { .. } | Self::Restoring { .. } | Self::Removing { .. } => None, - Self::Updating { installed, .. } | Self::Installed { installed, .. } => Some(installed), + Installing(PackageDataEntryInstalling), + Updating(PackageDataEntryUpdating), + Restoring(PackageDataEntryRestoring), + Removing(PackageDataEntryRemoving), + Installed(PackageDataEntryInstalled), +} +impl Model { + pub fn expect_into_installed(self) -> Result, Error> { + if let PackageDataEntryMatchModel::Installed(a) = self.into_match() { + Ok(a) + } else { + Err(Error::new( + eyre!("package is not in installed state"), + ErrorKind::InvalidRequest, + )) } } - pub fn installed_mut(&mut self) -> Option<&mut InstalledPackageDataEntry> { - match self { - Self::Installing { .. } | Self::Restoring { .. } | Self::Removing { .. } => None, - Self::Updating { installed, .. } | Self::Installed { installed, .. } => Some(installed), + pub fn expect_as_installed(&self) -> Result<&Model, Error> { + if let PackageDataEntryMatchModelRef::Installed(a) = self.as_match() { + Ok(a) + } else { + Err(Error::new( + eyre!("package is not in installed state"), + ErrorKind::InvalidRequest, + )) } } - pub fn into_installed(self) -> Option { - match self { - Self::Installing { .. } | Self::Restoring { .. } | Self::Removing { .. } => None, - Self::Updating { installed, .. } | Self::Installed { installed, .. } => Some(installed), + pub fn expect_as_installed_mut( + &mut self, + ) -> Result<&mut Model, Error> { + if let PackageDataEntryMatchModelMut::Installed(a) = self.as_match_mut() { + Ok(a) + } else { + Err(Error::new( + eyre!("package is not in installed state"), + ErrorKind::InvalidRequest, + )) } } - pub fn manifest(self) -> Manifest { - match self { - PackageDataEntry::Installing { manifest, .. } => manifest, - PackageDataEntry::Updating { manifest, .. } => manifest, - PackageDataEntry::Restoring { manifest, .. } => manifest, - PackageDataEntry::Removing { manifest, .. } => manifest, - PackageDataEntry::Installed { manifest, .. } => manifest, + pub fn into_manifest(self) -> Model { + match self.into_match() { + PackageDataEntryMatchModel::Installing(a) => a.into_manifest(), + PackageDataEntryMatchModel::Updating(a) => a.into_old_manifest(), + PackageDataEntryMatchModel::Restoring(a) => a.into_manifest(), + PackageDataEntryMatchModel::Removing(a) => a.into_manifest(), + PackageDataEntryMatchModel::NeedsUpdate(a) => a.into_manifest(), + PackageDataEntryMatchModel::Installed(a) => a.into_manifest(), + PackageDataEntryMatchModel::Error(_) => Model::from(Value::Null), } } - pub fn manifest_borrow(&self) -> &Manifest { - match self { - PackageDataEntry::Installing { manifest, .. } => manifest, - PackageDataEntry::Updating { manifest, .. } => manifest, - PackageDataEntry::Restoring { manifest, .. } => manifest, - PackageDataEntry::Removing { manifest, .. } => manifest, - PackageDataEntry::Installed { manifest, .. } => manifest, + pub fn as_manifest(&self) -> Result<&Model, Error> { + match self.as_match() { + PackageDataEntryMatchModelRef::Installing(a) => Ok(a.as_manifest()), + PackageDataEntryMatchModelRef::Updating(a) => Ok(a.as_old_manifest()), + PackageDataEntryMatchModelRef::Restoring(a) => Ok(a.as_manifest()), + PackageDataEntryMatchModelRef::Removing(a) => Ok(a.as_manifest()), + PackageDataEntryMatchModelRef::NeedsUpdate(a) => Ok(a.as_manifest()), + PackageDataEntryMatchModelRef::Installed(a) => Ok(a.as_manifest()), + PackageDataEntryMatchModelRef::Error(a) => Err(Error::new( + eyre!("unknown variant of PackageDataEntry"), + ErrorKind::Deserialization, + )), } } -} -impl PackageDataEntryModel { - pub fn installed(self) -> OptionModel { - self.0.child("installed").into() - } - pub fn removing(self) -> OptionModel { - self.0.child("removing").into() - } - pub fn install_progress(self) -> OptionModel { - self.0.child("install-progress").into() - } - pub fn manifest(self) -> ManifestModel { - self.0.child("manifest").into() + pub fn as_icon(&self) -> Result<&Model, Error> { + match self.as_match() { + PackageDataEntryMatchModelRef::Installing(a) => Ok(a.as_icon()), + PackageDataEntryMatchModelRef::Updating(a) => Ok(a.as_icon()), + PackageDataEntryMatchModelRef::Restoring(a) => Ok(a.as_icon()), + PackageDataEntryMatchModelRef::Removing(a) => Ok(a.as_icon()), + PackageDataEntryMatchModelRef::NeedsUpdate(a) => Ok(a.as_icon()), + PackageDataEntryMatchModelRef::Installed(a) => Ok(a.as_icon()), + PackageDataEntryMatchModelRef::Error(a) => Err(Error::new( + eyre!("unknown variant of PackageDataEntry"), + ErrorKind::Deserialization, + )), + } } } #[derive(Debug, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] -pub struct InstalledPackageDataEntry { - #[model] +#[model = "Model"] +pub struct InstalledPackageInfo { pub status: Status, pub marketplace_url: Option, #[serde(default)] #[serde(with = "crate::util::serde::ed25519_pubkey")] pub developer_key: ed25519_dalek::PublicKey, - #[model] pub manifest: Manifest, pub last_backup: Option>, - #[model] - pub system_pointers: Vec, - #[model] pub dependency_info: BTreeMap, - #[model] pub current_dependents: CurrentDependents, - #[model] pub current_dependencies: CurrentDependencies, - #[model] pub interface_addresses: InterfaceAddressMap, } -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Debug, Clone, Default, Deserialize, Serialize)] pub struct CurrentDependents(pub BTreeMap); impl CurrentDependents { pub fn map( @@ -348,12 +373,6 @@ impl CurrentDependents { impl Map for CurrentDependents { type Key = PackageId; type Value = CurrentDependencyInfo; - fn get(&self, key: &Self::Key) -> Option<&Self::Value> { - self.0.get(key) - } -} -impl HasModel for CurrentDependents { - type Model = MapModel; } #[derive(Debug, Clone, Default, Deserialize, Serialize)] @@ -372,54 +391,34 @@ impl CurrentDependencies { impl Map for CurrentDependencies { type Key = PackageId; type Value = CurrentDependencyInfo; - fn get(&self, key: &Self::Key) -> Option<&Self::Value> { - self.0.get(key) - } -} -impl HasModel for CurrentDependencies { - type Model = MapModel; } -#[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel)] +#[derive(Debug, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] +#[model = "Model"] pub struct StaticDependencyInfo { - pub manifest: Option, - pub icon: String, + pub title: String, + pub icon: DataUrl<'static>, } #[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] +#[model = "Model"] pub struct CurrentDependencyInfo { - pub pointers: Vec, pub health_checks: BTreeSet, } #[derive(Debug, Deserialize, Serialize)] -pub struct InterfaceAddressMap(pub BTreeMap); +pub struct InterfaceAddressMap(pub BTreeMap); impl Map for InterfaceAddressMap { type Key = InterfaceId; - type Value = InterfaceAddresses; - fn get(&self, key: &Self::Key) -> Option<&Self::Value> { - self.0.get(key) - } -} -impl HasModel for InterfaceAddressMap { - type Model = MapModel; + type Value = InterfaceAdresses; } #[derive(Debug, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] -pub struct InterfaceAddresses { - #[model] +#[model = "Model"] +pub struct InterfaceAdresses { pub tor_address: Option, - #[model] pub lan_address: Option, } - -#[derive(Debug, Deserialize, Serialize, HasModel)] -#[serde(rename_all = "kebab-case")] -pub struct RecoveredPackageInfo { - pub title: String, - pub icon: String, - pub version: Version, -} diff --git a/backend/src/db/package.rs b/backend/src/db/package.rs index dd167c160..bcc670e19 100644 --- a/backend/src/db/package.rs +++ b/backend/src/db/package.rs @@ -1,75 +1,15 @@ -use patch_db::{DbHandle, LockReceipt, LockTargetId, LockType, Verifier}; - +use crate::prelude::*; use crate::s9pk::manifest::{Manifest, PackageId}; -use crate::Error; - -pub struct PackageReceipts { - package_data: LockReceipt, -} - -impl PackageReceipts { - pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result { - let mut locks = Vec::new(); - - let setup = Self::setup(&mut locks); - Ok(setup(&db.lock_all(locks).await?)?) - } - - pub fn setup(locks: &mut Vec) -> impl FnOnce(&Verifier) -> Result { - let package_data = crate::db::DatabaseModel::new() - .package_data() - .make_locker(LockType::Read) - .add_to_keys(locks); - move |skeleton_key| { - Ok(Self { - package_data: package_data.verify(&skeleton_key)?, - }) - } - } -} - -pub async fn get_packages( - db: &mut Db, - receipts: &PackageReceipts, -) -> Result, Error> { - let packages = receipts.package_data.get(db).await?; - Ok(packages.0.keys().cloned().collect()) -} - -pub struct ManifestReceipts { - manifest: LockReceipt, -} - -impl ManifestReceipts { - pub async fn new<'a>(db: &'a mut impl DbHandle, id: &PackageId) -> Result { - let mut locks = Vec::new(); - - let setup = Self::setup(&mut locks, id); - Ok(setup(&db.lock_all(locks).await?)?) - } - pub fn setup( - locks: &mut Vec, - _id: &PackageId, - ) -> impl FnOnce(&Verifier) -> Result { - let manifest = crate::db::DatabaseModel::new() - .package_data() - .star() - .manifest() - .make_locker(LockType::Read) - .add_to_keys(locks); - move |skeleton_key| { - Ok(Self { - manifest: manifest.verify(&skeleton_key)?, - }) - } - } +pub async fn get_packages(db: &PatchDb) -> Result, Error> { + Ok(db.peek().await?.as_package_data().keys()?) } -pub async fn get_manifest( - db: &mut Db, - pkg: &PackageId, - receipts: &ManifestReceipts, -) -> Result, Error> { - Ok(receipts.manifest.get(db, pkg).await?) +pub async fn get_manifest(db: &PatchDb, pkg: &PackageId) -> Result, Error> { + db.peek() + .await? + .into_package_data() + .into_idx(pkg) + .map(|pde| pde.into_manifest().de()) + .transpose() } diff --git a/backend/src/db/prelude.rs b/backend/src/db/prelude.rs new file mode 100644 index 000000000..f295a0dd8 --- /dev/null +++ b/backend/src/db/prelude.rs @@ -0,0 +1,361 @@ +use std::marker::PhantomData; +use std::panic::UnwindSafe; + +use patch_db::value::InternedString; +pub use patch_db::{HasModel, PatchDb, Value}; +use serde::de::DeserializeOwned; +use serde::Serialize; + +use crate::db::model::DatabaseModel; +use crate::prelude::*; + +pub fn to_value(value: &T) -> Result +where + T: Serialize, +{ + patch_db::value::to_value(value).with_kind(ErrorKind::Serialization) +} + +pub fn from_value(value: Value) -> Result +where + T: DeserializeOwned, +{ + patch_db::value::from_value(value).with_kind(ErrorKind::Deserialization) +} + +#[async_trait::async_trait] +pub trait PatchDbExt { + async fn peek(&self) -> Result; + async fn mutate( + &self, + f: impl FnOnce(&mut DatabaseModel) -> Result + UnwindSafe + Send, + ) -> Result; + async fn map_mutate( + &self, + f: impl FnOnce(DatabaseModel) -> Result + UnwindSafe + Send, + ) -> Result; +} +#[async_trait::async_trait] +impl PatchDbExt for PatchDb { + async fn peek(&self) -> Result { + Ok(DatabaseModel::from(self.dump().await?.value)) + } + async fn mutate( + &self, + f: impl FnOnce(&mut DatabaseModel) -> Result + UnwindSafe + Send, + ) -> Result { + Ok(self + .apply_function(|v| { + let mut model = <&mut DatabaseModel>::from(&mut v); + let res = f(&mut model)?; + Ok::<_, Error>((v, res)) + }) + .await? + .1) + } + async fn map_mutate( + &self, + f: impl FnOnce(DatabaseModel) -> Result + UnwindSafe + Send, + ) -> Result { + Ok(DatabaseModel::from( + self.apply_function(|v| f(DatabaseModel::from(v)).map(|a| (a.into(), ()))) + .await? + .0, + )) + } +} + +/// &mut Model <=> &mut Value +#[repr(transparent)] +#[derive(Debug)] +pub struct Model { + value: Value, + phantom: PhantomData, +} +impl Model { + pub fn de(self) -> Result { + from_value(self.value) + } +} +impl Model { + pub fn new(value: &T) -> Result { + Ok(Self::from(to_value(value)?)) + } + pub fn ser(&mut self, value: &T) -> Result<(), Error> { + self.value = to_value(value)?; + Ok(()) + } +} +impl Clone for Model { + fn clone(&self) -> Self { + Self { + value: self.value.clone(), + phantom: PhantomData, + } + } +} +impl From for Model { + fn from(value: Value) -> Self { + Self { + value, + phantom: PhantomData, + } + } +} +impl From> for Value { + fn from(value: Model) -> Self { + value.value + } +} +impl<'a, T> From<&'a Value> for &'a Model { + fn from(value: &'a Value) -> Self { + unsafe { std::mem::transmute(value) } + } +} +impl<'a, T> From<&'a Model> for &'a Value { + fn from(value: &'a Model) -> Self { + unsafe { std::mem::transmute(value) } + } +} +impl<'a, T> From<&'a mut Value> for &mut Model { + fn from(value: &'a mut Value) -> Self { + unsafe { std::mem::transmute(value) } + } +} +impl<'a, T> From<&'a mut Model> for &mut Value { + fn from(value: &'a mut Model) -> Self { + unsafe { std::mem::transmute(value) } + } +} +impl patch_db::Model for Model { + type Model = Model; +} + +impl Model> { + pub fn transpose(self) -> Option> { + use patch_db::ModelExt; + if self.value.is_null() { + None + } else { + Some(self.transmute(|a| a)) + } + } + pub fn transpose_ref(&self) -> Option<&Model> { + use patch_db::ModelExt; + if self.value.is_null() { + None + } else { + Some(self.transmute_ref(|a| a)) + } + } + pub fn transpose_mut(&mut self) -> Option<&mut Model> { + use patch_db::ModelExt; + if self.value.is_null() { + None + } else { + Some(self.transmute_mut(|a| a)) + } + } + pub fn from_option(opt: Option>) -> Self { + use patch_db::ModelExt; + match opt { + Some(a) => a.transmute(|a| a), + None => Self::from_value(Value::Null), + } + } +} + +pub trait Map: DeserializeOwned + Serialize { + type Key; + type Value; +} + +impl Model +where + T::Key: AsRef, + T::Value: Serialize, +{ + pub fn insert(&mut self, key: &T::Key, value: &T::Value) -> Result<(), Error> { + use serde::ser::Error; + let v = patch_db::value::to_value(value)?; + match &mut self.value { + Value::Object(o) => { + o.insert(InternedString::intern(key.as_ref()), v); + Ok(()) + } + v => Err(patch_db::value::Error { + source: patch_db::value::ErrorSource::custom(format!("expected object found {v}")), + kind: patch_db::value::ErrorKind::Serialization, + } + .into()), + } + } + pub fn insert_model(&mut self, key: &T::Key, value: Model) -> Result<(), Error> { + use patch_db::ModelExt; + use serde::ser::Error; + let v = value.into_value(); + match &mut self.value { + Value::Object(o) => { + o.insert(InternedString::intern(key.as_ref()), v); + Ok(()) + } + v => Err(patch_db::value::Error { + source: patch_db::value::ErrorSource::custom(format!("expected object found {v}")), + kind: patch_db::value::ErrorKind::Serialization, + } + .into()), + } + } +} + +impl Model +where + T::Key: DeserializeOwned + Ord + Clone, +{ + pub fn keys(&self) -> Result, Error> { + use serde::de::Error; + use serde::Deserialize; + match &self.value { + Value::Object(o) => o + .keys() + .cloned() + .map(|k| { + T::Key::deserialize(patch_db::value::de::InternedStringDeserializer::from(k)) + .map_err(|e| { + patch_db::value::Error { + kind: patch_db::value::ErrorKind::Deserialization, + source: e, + } + .into() + }) + }) + .collect(), + v => Err(patch_db::value::Error { + source: patch_db::value::ErrorSource::custom(format!("expected object found {v}")), + kind: patch_db::value::ErrorKind::Deserialization, + } + .into()), + } + } + pub fn into_entries(self) -> Result)>, Error> { + use patch_db::ModelExt; + use serde::de::Error; + use serde::Deserialize; + match self.value { + Value::Object(o) => o + .into_iter() + .map(|(k, v)| { + Ok(( + T::Key::deserialize(patch_db::value::de::InternedStringDeserializer::from( + k, + )) + .with_kind(ErrorKind::Deserialization)?, + Model::from_value(v), + )) + }) + .collect(), + v => Err(patch_db::value::Error { + source: patch_db::value::ErrorSource::custom(format!("expected object found {v}")), + kind: patch_db::value::ErrorKind::Deserialization, + } + .into()), + } + } + pub fn as_entries(&self) -> Result)>, Error> { + use patch_db::ModelExt; + use serde::de::Error; + use serde::Deserialize; + match &self.value { + Value::Object(o) => o + .iter() + .map(|(k, v)| { + Ok(( + T::Key::deserialize(patch_db::value::de::InternedStringDeserializer::from( + k.clone(), + )) + .with_kind(ErrorKind::Deserialization)?, + Model::value_as(v), + )) + }) + .collect(), + v => Err(patch_db::value::Error { + source: patch_db::value::ErrorSource::custom(format!("expected object found {v}")), + kind: patch_db::value::ErrorKind::Deserialization, + } + .into()), + } + } + pub fn as_entries_mut(&mut self) -> Result)>, Error> { + use patch_db::ModelExt; + use serde::de::Error; + use serde::Deserialize; + match &self.value { + Value::Object(o) => o + .iter_mut() + .map(|(k, v)| { + Ok(( + T::Key::deserialize(patch_db::value::de::InternedStringDeserializer::from( + k.clone(), + )) + .with_kind(ErrorKind::Deserialization)?, + Model::value_as_mut(v), + )) + }) + .collect(), + v => Err(patch_db::value::Error { + source: patch_db::value::ErrorSource::custom(format!("expected object found {v}")), + kind: patch_db::value::ErrorKind::Deserialization, + } + .into()), + } + } +} +impl Model +where + T::Key: AsRef, +{ + pub fn into_idx(self, key: &T::Key) -> Option> { + use patch_db::ModelExt; + match &self.value { + Value::Object(o) if o.contains_key(key.as_ref()) => Some(self.transmute(|v| { + use patch_db::value::index::Index; + key.as_ref().index_into_owned(v).unwrap() + })), + _ => None, + } + } + pub fn as_idx<'a>(&'a self, key: &T::Key) -> Option<&'a Model> { + use patch_db::ModelExt; + match &self.value { + Value::Object(o) if o.contains_key(key.as_ref()) => Some(self.transmute_ref(|v| { + use patch_db::value::index::Index; + key.as_ref().index_into(v).unwrap() + })), + _ => None, + } + } + pub fn as_idx_mut<'a>(&'a mut self, key: &T::Key) -> Option<&'a mut Model> { + use patch_db::ModelExt; + match &mut self.value { + Value::Object(o) if o.contains_key(key.as_ref()) => Some(self.transmute_mut(|v| { + use patch_db::value::index::Index; + key.as_ref().index_or_insert(v) + })), + _ => None, + } + } + pub fn remove(&mut self, key: &T::Key) -> Result>, Error> { + use serde::ser::Error; + match &mut self.value { + Value::Object(o) => { + let v = o.remove(key.as_ref()); + Ok(v.map(patch_db::ModelExt::from_value)) + } + v => Err(patch_db::value::Error { + source: patch_db::value::ErrorSource::custom(format!("expected object found {v}")), + kind: patch_db::value::ErrorKind::Serialization, + } + .into()), + } + } +} diff --git a/backend/src/lib.rs b/backend/src/lib.rs index 4e4b1619a..f20de5d73 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -40,6 +40,7 @@ pub mod migration; pub mod net; pub mod notifications; pub mod os_install; +pub mod prelude; pub mod procedure; pub mod properties; pub mod s9pk; diff --git a/backend/src/prelude.rs b/backend/src/prelude.rs new file mode 100644 index 000000000..eff08ecd0 --- /dev/null +++ b/backend/src/prelude.rs @@ -0,0 +1,5 @@ +pub use color_eyre::eyre::eyre; + +pub use crate::db::prelude::*; +pub use crate::ensure_code; +pub use crate::error::{Error, ErrorCollection, ErrorKind, OptionExt, ResultExt}; diff --git a/libs/Cargo.lock b/libs/Cargo.lock index 55ea76110..49db9a61e 100644 --- a/libs/Cargo.lock +++ b/libs/Cargo.lock @@ -45,6 +45,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ "cfg-if", + "getrandom 0.2.10", "once_cell", "version_check", ] @@ -243,15 +244,6 @@ version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" -[[package]] -name = "bitmaps" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" -dependencies = [ - "typenum", -] - [[package]] name = "bitmaps" version = "3.2.0" @@ -295,44 +287,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" -[[package]] -name = "bollard" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82e7850583ead5f8bbef247e2a3c37a19bd576e8420cd262a6711921827e1e5" -dependencies = [ - "base64 0.13.1", - "bollard-stubs", - "bytes", - "futures-core", - "futures-util", - "hex", - "http", - "hyper", - "hyperlocal", - "log", - "pin-project-lite", - "serde", - "serde_derive", - "serde_json", - "serde_urlencoded", - "thiserror", - "tokio", - "tokio-util", - "url", - "winapi", -] - -[[package]] -name = "bollard-stubs" -version = "1.42.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed59b5c00048f48d7af971b71f800fdf23e858844a6f9e4d32ca72e9399e7864" -dependencies = [ - "serde", - "serde_with", -] - [[package]] name = "bumpalo" version = "3.13.0" @@ -411,7 +365,7 @@ dependencies = [ "indenter", "once_cell", "owo-colors", - "tracing-error 0.2.0", + "tracing-error", ] [[package]] @@ -423,7 +377,7 @@ dependencies = [ "once_cell", "owo-colors", "tracing-core", - "tracing-error 0.2.0", + "tracing-error", ] [[package]] @@ -560,41 +514,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "darling" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 1.0.109", -] - -[[package]] -name = "darling_macro" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" -dependencies = [ - "darling_core", - "quote", - "syn 1.0.109", -] - [[package]] name = "dashmap" version = "5.5.0" @@ -898,7 +817,7 @@ dependencies = [ "color-eyre", "futures", "helpers", - "imbl 2.0.0", + "imbl", "nix 0.25.1", "procfs", "serde", @@ -906,9 +825,9 @@ dependencies = [ "tokio", "tokio-stream", "tracing", - "tracing-error 0.2.0", + "tracing-error", "tracing-futures", - "tracing-subscriber 0.3.17", + "tracing-subscriber", "yajrc 0.1.0 (git+https://github.com/dr-bonez/yajrc.git?branch=develop)", ] @@ -1259,8 +1178,14 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.7.6", + "ahash 0.8.3", ] [[package]] @@ -1282,15 +1207,6 @@ dependencies = [ "hashbrown 0.14.0", ] -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "heck" version = "0.4.1" @@ -1437,19 +1353,6 @@ dependencies = [ "tokio-native-tls", ] -[[package]] -name = "hyperlocal" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fafdf7b2b2de7c9784f76e02c0935e65a8117ec3b768644379983ab333ac98c" -dependencies = [ - "futures-util", - "hex", - "hyper", - "pin-project", - "tokio", -] - [[package]] name = "iana-time-zone" version = "0.1.57" @@ -1473,12 +1376,6 @@ dependencies = [ "cc", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" version = "0.4.0" @@ -1495,30 +1392,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" -[[package]] -name = "imbl" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "543682c9082b25e63d03b5acbd65ad111fd49dd93e70843e5175db4ff81d606b" -dependencies = [ - "bitmaps 2.1.0", - "rand_core 0.6.4", - "rand_xoshiro", - "sized-chunks", - "typenum", - "version_check", -] - [[package]] name = "imbl" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2806b69cd9f4664844027b64465eacb444c67c1db9c778e341adff0c25cdb0d" dependencies = [ - "bitmaps 3.2.0", + "bitmaps", "imbl-sized-chunks", "rand_core 0.6.4", "rand_xoshiro", + "serde", "version_check", ] @@ -1528,7 +1412,19 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6957ea0b2541c5ca561d3ef4538044af79f8a05a1eb3a3b148936aaceaa1076" dependencies = [ - "bitmaps 3.2.0", + "bitmaps", +] + +[[package]] +name = "imbl-value" +version = "0.1.0" +source = "git+https://github.com/Start9Labs/imbl-value.git#929395141c3a882ac366c12ac9402d0ebaa2201b" +dependencies = [ + "imbl", + "serde", + "serde_json", + "treediff", + "yasi", ] [[package]] @@ -1566,20 +1462,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "internment" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "161079c3ad892faa215fcfcf3fd7a6a3c9288df2b06a2c2bad7fbfad4f01d69d" -dependencies = [ - "ahash 0.7.6", - "dashmap", - "hashbrown 0.12.3", - "once_cell", - "parking_lot 0.12.1", - "serde", -] - [[package]] name = "io-lifetimes" version = "1.0.11" @@ -1668,9 +1550,9 @@ dependencies = [ name = "json-patch" version = "0.2.7-alpha.0" dependencies = [ + "imbl-value", "json-ptr", "serde", - "serde_json", "treediff", ] @@ -1678,8 +1560,9 @@ dependencies = [ name = "json-ptr" version = "0.1.0" dependencies = [ + "imbl", + "imbl-value", "serde", - "serde_json", "thiserror", ] @@ -1893,6 +1776,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.17" @@ -1929,11 +1821,10 @@ dependencies = [ name = "models" version = "0.1.0" dependencies = [ - "bollard", + "base64 0.21.2", "color-eyre", "ed25519-dalek", "emver", - "internment", "ipnet", "lazy_static", "mbrman", @@ -1941,6 +1832,7 @@ dependencies = [ "patch-db", "rand 0.8.5", "regex", + "reqwest", "rpc-toolkit", "serde", "serde_json", @@ -1950,6 +1842,7 @@ dependencies = [ "tokio", "torut", "tracing", + "yasi", ] [[package]] @@ -1978,41 +1871,42 @@ checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" [[package]] name = "nix" -version = "0.23.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" dependencies = [ "bitflags 1.3.2", - "cc", "cfg-if", "libc", - "memoffset", + "memoffset 0.6.5", ] [[package]] name = "nix" -version = "0.24.3" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" dependencies = [ + "autocfg", "bitflags 1.3.2", "cfg-if", "libc", - "memoffset", + "memoffset 0.6.5", + "pin-utils", ] [[package]] name = "nix" -version = "0.25.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ - "autocfg", "bitflags 1.3.2", "cfg-if", "libc", - "memoffset", + "memoffset 0.7.1", "pin-utils", + "static_assertions", ] [[package]] @@ -2282,19 +2176,19 @@ dependencies = [ "async-trait", "fd-lock-rs", "futures", - "imbl 1.0.1", + "imbl", + "imbl-value", "json-patch", "json-ptr", "lazy_static", - "nix 0.23.2", + "nix 0.26.2", "patch-db-macro", "serde", "serde_cbor 0.11.1", - "serde_json", "thiserror", "tokio", "tracing", - "tracing-error 0.1.2", + "tracing-error", ] [[package]] @@ -2310,7 +2204,7 @@ dependencies = [ name = "patch-db-macro-internals" version = "0.1.0" dependencies = [ - "heck 0.3.3", + "heck", "proc-macro2", "quote", "syn 1.0.109", @@ -3085,28 +2979,6 @@ dependencies = [ "v8", ] -[[package]] -name = "serde_with" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" -dependencies = [ - "serde", - "serde_with_macros", -] - -[[package]] -name = "serde_with_macros" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "sha-1" version = "0.10.0" @@ -3199,16 +3071,6 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" -[[package]] -name = "sized-chunks" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" -dependencies = [ - "bitmaps 2.1.0", - "typenum", -] - [[package]] name = "slab" version = "0.4.8" @@ -3368,7 +3230,7 @@ checksum = "9966e64ae989e7e575b19d7265cb79d7fc3cbbdf179835cb0d716f294c2049c9" dependencies = [ "dotenvy", "either", - "heck 0.4.1", + "heck", "hex", "once_cell", "proc-macro2", @@ -3517,7 +3379,7 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ - "heck 0.4.1", + "heck", "proc-macro2", "quote", "rustversion", @@ -4136,16 +3998,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-error" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24" -dependencies = [ - "tracing", - "tracing-subscriber 0.2.25", -] - [[package]] name = "tracing-error" version = "0.2.0" @@ -4153,7 +4005,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" dependencies = [ "tracing", - "tracing-subscriber 0.3.17", + "tracing-subscriber", ] [[package]] @@ -4177,17 +4029,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-subscriber" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" -dependencies = [ - "sharded-slab", - "thread_local", - "tracing-core", -] - [[package]] name = "tracing-subscriber" version = "0.3.17" @@ -4695,6 +4536,18 @@ dependencies = [ "thiserror", ] +[[package]] +name = "yasi" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f355ab62ebe30b758c1f4ab096a306722c4b7dbfb9d8c07d18c70d71a945588" +dependencies = [ + "ahash 0.8.3", + "hashbrown 0.13.2", + "lazy_static", + "serde", +] + [[package]] name = "zeroize" version = "1.6.0" diff --git a/libs/models/Cargo.toml b/libs/models/Cargo.toml index 6362919f6..9a05f98b6 100644 --- a/libs/models/Cargo.toml +++ b/libs/models/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bollard = "0.13.0" +base64 = "0.21.0" color-eyre = "0.6.1" ed25519-dalek = { version = "1.0.1", features = ["serde"] } lazy_static = "1.4" @@ -14,7 +14,6 @@ mbrman = "0.5.0" emver = { version = "0.1", git = "https://github.com/Start9Labs/emver-rs.git", features = [ "serde", ] } -internment = { version = "0.7.0", features = ["arc", "serde"] } ipnet = "2.7.1" openssl = { version = "0.10.41", features = ["vendored"] } patch-db = { version = "*", path = "../../patch-db/patch-db", features = [ @@ -22,9 +21,10 @@ patch-db = { version = "*", path = "../../patch-db/patch-db", features = [ ] } rand = "0.8" regex = "1.7.1" +reqwest = "0.11.14" rpc-toolkit = "0.2.1" serde = { version = "1.0", features = ["derive", "rc"] } -serde_json = "1.0.82" +serde_json = "1.0" sqlx = { version = "0.6.0", features = [ "chrono", "offline", @@ -36,3 +36,4 @@ thiserror = "1.0" tokio = { version = "1", features = ["full"] } torut = "0.2.1" tracing = "0.1.35" +yasi = "0.1.2" diff --git a/libs/models/src/data_url.rs b/libs/models/src/data_url.rs new file mode 100644 index 000000000..5661d262b --- /dev/null +++ b/libs/models/src/data_url.rs @@ -0,0 +1,163 @@ +use std::borrow::Cow; +use std::path::Path; + +use base64::Engine; +use color_eyre::eyre::eyre; +use reqwest::header::CONTENT_TYPE; +use serde::{Deserialize, Serialize}; +use tokio::io::{AsyncRead, AsyncReadExt}; +use yasi::InternedString; + +use crate::{mime, Error, ErrorKind, ResultExt}; + +pub struct DataUrl<'a> { + mime: InternedString, + data: Cow<'a, [u8]>, +} +impl<'a> DataUrl<'a> { + pub const DEFAULT_MIME: &'static str = "application/octet-stream"; + pub const MAX_SIZE: u64 = 100 * 1024; + + // data:{mime};base64,{data} + pub fn to_string(&self) -> String { + use std::fmt::Write; + let mut res = String::with_capacity(self.data_url_len_without_mime() + self.mime.len()); + let _ = write!(res, "data:{};base64,", self.mime); + base64::engine::general_purpose::URL_SAFE.encode_string(&self.data, &mut res); + res + } + + fn data_url_len_without_mime(&self) -> usize { + 5 + 8 + (4 * self.data.len() / 3) + 3 + } + + pub fn data_url_len(&self) -> usize { + self.data_url_len_without_mime() + self.mime.len() + } + + pub fn from_slice(mime: &str, data: &'a [u8]) -> Self { + Self { + mime: InternedString::intern(mime), + data: Cow::Borrowed(data), + } + } +} +impl DataUrl<'static> { + pub async fn from_reader( + mime: &str, + rdr: impl AsyncRead + Unpin, + size_est: Option, + ) -> Result { + let check_size = |s| { + if s > Self::MAX_SIZE { + Err(Error::new( + eyre!("Data URLs must be smaller than 100KiB"), + ErrorKind::Filesystem, + )) + } else { + Ok(s) + } + }; + let mut buf = size_est + .map(check_size) + .transpose()? + .map(|s| Vec::with_capacity(s as usize)) + .unwrap_or_default(); + rdr.take(Self::MAX_SIZE + 1).read_to_end(&mut buf).await?; + check_size(buf.len() as u64)?; + + Ok(Self { + mime: InternedString::intern(mime), + data: Cow::Owned(buf), + }) + } + + pub async fn from_path(path: impl AsRef) -> Result { + let path = path.as_ref(); + let f = tokio::fs::File::open(path).await?; + let m = f.metadata().await?; + let mime = path + .extension() + .and_then(|s| s.to_str()) + .and_then(mime) + .unwrap_or(Self::DEFAULT_MIME); + Self::from_reader(mime, f, Some(m.len())).await + } + + pub async fn from_response(res: reqwest::Response) -> Result { + let mime = InternedString::intern( + res.headers() + .get(CONTENT_TYPE) + .and_then(|h| h.to_str().ok()) + .unwrap_or(Self::DEFAULT_MIME), + ); + let data = res.bytes().await.with_kind(ErrorKind::Network)?.to_vec(); + Ok(Self { + mime, + data: Cow::Owned(data), + }) + } +} + +impl<'a> std::fmt::Debug for DataUrl<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.to_string()) + } +} + +impl<'de> Deserialize<'de> for DataUrl<'static> { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct Visitor; + impl<'de> serde::de::Visitor<'de> for Visitor { + type Value = DataUrl<'static>; + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(formatter, "a valid base64 data url") + } + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + v.strip_prefix("data:") + .and_then(|v| v.split_once(";base64")) + .and_then(|(mime, data)| { + Some(DataUrl { + mime: InternedString::intern(mime), + data: Cow::Owned( + base64::engine::general_purpose::URL_SAFE + .decode(data) + .ok()?, + ), + }) + }) + .ok_or_else(|| { + E::invalid_value(serde::de::Unexpected::Str(v), &"a valid base64 data url") + }) + } + } + deserializer.deserialize_str(Visitor) + } +} + +impl<'a> Serialize for DataUrl<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +#[test] +fn doesnt_reallocate() { + let random: [u8; 10] = rand::random(); + for i in 0..10 { + let icon = DataUrl { + mime: InternedString::intern("png"), + data: Cow::Borrowed(&random[..i]), + }; + assert_eq!(dbg!(icon.to_string()).capacity(), icon.data_url_len()); + } +} diff --git a/libs/models/src/errors.rs b/libs/models/src/errors.rs index c429ccd7c..b0d786830 100644 --- a/libs/models/src/errors.rs +++ b/libs/models/src/errors.rs @@ -235,19 +235,14 @@ impl From for Error { Error::new(e, ErrorKind::InvalidSignature) } } -impl From for Error { - fn from(e: bollard::errors::Error) -> Self { - Error::new(e, ErrorKind::Docker) +impl From for Error { + fn from(e: std::net::AddrParseError) -> Self { + Error::new(e, ErrorKind::ParseNetAddress) } } impl From for Error { fn from(e: torut::control::ConnError) -> Self { - Error::new(eyre!("{:?}", e), ErrorKind::Tor) - } -} -impl From for Error { - fn from(e: std::net::AddrParseError) -> Self { - Error::new(e, ErrorKind::ParseNetAddress) + Error::new(e, ErrorKind::Tor) } } impl From for Error { @@ -275,6 +270,23 @@ impl From for Error { Error::new(e, ErrorKind::OpenSsh) } } +impl From for Error { + fn from(e: reqwest::Error) -> Self { + Error::new(e, ErrorKind::Network) + } +} +impl From for Error { + fn from(value: patch_db::value::Error) -> Self { + match value.kind { + patch_db::value::ErrorKind::Serialization => { + Error::new(value.source, ErrorKind::Serialization) + } + patch_db::value::ErrorKind::Deserialization => { + Error::new(value.source, ErrorKind::Deserialization) + } + } + } +} impl From for RpcError { fn from(e: Error) -> Self { @@ -388,6 +400,18 @@ where } } +pub trait OptionExt +where + Self: Sized, +{ + fn or_not_found(self, message: impl std::fmt::Display) -> Result; +} +impl OptionExt for Option { + fn or_not_found(self, message: impl std::fmt::Display) -> Result { + self.ok_or_else(|| Error::new(eyre!("{}", message), ErrorKind::NotFound)) + } +} + #[macro_export] macro_rules! ensure_code { ($x:expr, $c:expr, $fmt:expr $(, $arg:expr)*) => { diff --git a/libs/models/src/id/action.rs b/libs/models/src/id/action.rs new file mode 100644 index 000000000..9b814a98a --- /dev/null +++ b/libs/models/src/id/action.rs @@ -0,0 +1,43 @@ +use std::path::Path; +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; + +use crate::{Id, InvalidId}; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] +pub struct ActionId(Id); +impl FromStr for ActionId { + type Err = InvalidId; + fn from_str(s: &str) -> Result { + Ok(ActionId(Id::try_from(s.to_owned())?)) + } +} +impl AsRef for ActionId { + fn as_ref(&self) -> &ActionId { + self + } +} +impl std::fmt::Display for ActionId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", &self.0) + } +} +impl AsRef for ActionId { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} +impl AsRef for ActionId { + fn as_ref(&self) -> &Path { + self.0.as_ref().as_ref() + } +} +impl<'de> Deserialize<'de> for ActionId { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + Ok(ActionId(serde::Deserialize::deserialize(deserializer)?)) + } +} diff --git a/libs/models/src/id/address.rs b/libs/models/src/id/address.rs new file mode 100644 index 000000000..1bd670525 --- /dev/null +++ b/libs/models/src/id/address.rs @@ -0,0 +1,59 @@ +use std::path::Path; + +use serde::{Deserialize, Deserializer, Serialize}; + +use crate::Id; + +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] +pub struct AddressId(Id); +impl From for AddressId { + fn from(id: Id) -> Self { + Self(id) + } +} +impl std::fmt::Display for AddressId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", &self.0) + } +} +impl std::ops::Deref for AddressId { + type Target = str; + fn deref(&self) -> &Self::Target { + &*self.0 + } +} +impl AsRef for AddressId { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} +impl<'de> Deserialize<'de> for AddressId { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(AddressId(Deserialize::deserialize(deserializer)?)) + } +} +impl AsRef for AddressId { + fn as_ref(&self) -> &Path { + self.0.as_ref().as_ref() + } +} +impl<'q> sqlx::Encode<'q, sqlx::Postgres> for AddressId { + fn encode_by_ref( + &self, + buf: &mut >::ArgumentBuffer, + ) -> sqlx::encode::IsNull { + <&str as sqlx::Encode<'q, sqlx::Postgres>>::encode_by_ref(&&**self, buf) + } +} +impl sqlx::Type for AddressId { + fn type_info() -> sqlx::postgres::PgTypeInfo { + <&str as sqlx::Type>::type_info() + } + + fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool { + <&str as sqlx::Type>::compatible(ty) + } +} diff --git a/libs/models/src/id/health_check.rs b/libs/models/src/id/health_check.rs new file mode 100644 index 000000000..dc643c912 --- /dev/null +++ b/libs/models/src/id/health_check.rs @@ -0,0 +1,31 @@ +use std::path::Path; + +use serde::{Deserialize, Deserializer, Serialize}; + +use crate::Id; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] +pub struct HealthCheckId(Id); +impl std::fmt::Display for HealthCheckId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", &self.0) + } +} +impl AsRef for HealthCheckId { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} +impl<'de> Deserialize<'de> for HealthCheckId { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(HealthCheckId(Deserialize::deserialize(deserializer)?)) + } +} +impl AsRef for HealthCheckId { + fn as_ref(&self) -> &Path { + self.0.as_ref().as_ref() + } +} diff --git a/libs/models/src/id/image.rs b/libs/models/src/id/image.rs new file mode 100644 index 000000000..10ef0451d --- /dev/null +++ b/libs/models/src/id/image.rs @@ -0,0 +1,38 @@ +use std::fmt::Debug; +use std::str::FromStr; + +use serde::{Deserialize, Deserializer, Serialize}; + +use crate::{Id, InvalidId, PackageId, Version}; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] +pub struct ImageId(Id); +impl std::fmt::Display for ImageId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", &self.0) + } +} +impl ImageId { + pub fn for_package(&self, pkg_id: &PackageId, pkg_version: Option<&Version>) -> String { + format!( + "start9/{}/{}:{}", + pkg_id, + self.0, + pkg_version.map(|v| { v.as_str() }).unwrap_or("latest") + ) + } +} +impl FromStr for ImageId { + type Err = InvalidId; + fn from_str(s: &str) -> Result { + Ok(ImageId(Id::try_from(s.to_owned())?)) + } +} +impl<'de> Deserialize<'de> for ImageId { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(ImageId(Deserialize::deserialize(deserializer)?)) + } +} diff --git a/libs/models/src/id/interface.rs b/libs/models/src/id/interface.rs new file mode 100644 index 000000000..d062a3648 --- /dev/null +++ b/libs/models/src/id/interface.rs @@ -0,0 +1,59 @@ +use std::path::Path; + +use serde::{Deserialize, Deserializer, Serialize}; + +use crate::Id; + +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] +pub struct InterfaceId(Id); +impl From for InterfaceId { + fn from(id: Id) -> Self { + Self(id) + } +} +impl std::fmt::Display for InterfaceId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", &self.0) + } +} +impl std::ops::Deref for InterfaceId { + type Target = str; + fn deref(&self) -> &Self::Target { + &*self.0 + } +} +impl AsRef for InterfaceId { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} +impl<'de> Deserialize<'de> for InterfaceId { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(InterfaceId(Deserialize::deserialize(deserializer)?)) + } +} +impl AsRef for InterfaceId { + fn as_ref(&self) -> &Path { + self.0.as_ref().as_ref() + } +} +impl<'q> sqlx::Encode<'q, sqlx::Postgres> for InterfaceId { + fn encode_by_ref( + &self, + buf: &mut >::ArgumentBuffer, + ) -> sqlx::encode::IsNull { + <&str as sqlx::Encode<'q, sqlx::Postgres>>::encode_by_ref(&&**self, buf) + } +} +impl sqlx::Type for InterfaceId { + fn type_info() -> sqlx::postgres::PgTypeInfo { + <&str as sqlx::Type>::type_info() + } + + fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool { + <&str as sqlx::Type>::compatible(ty) + } +} diff --git a/libs/models/src/id/invalid_id.rs b/libs/models/src/id/invalid_id.rs new file mode 100644 index 000000000..d2cc82bd5 --- /dev/null +++ b/libs/models/src/id/invalid_id.rs @@ -0,0 +1,3 @@ +#[derive(Debug, thiserror::Error)] +#[error("Invalid ID")] +pub struct InvalidId; diff --git a/libs/models/src/id.rs b/libs/models/src/id/mod.rs similarity index 54% rename from libs/models/src/id.rs rename to libs/models/src/id/mod.rs index 30a1818ec..20816cb1a 100644 --- a/libs/models/src/id.rs +++ b/libs/models/src/id/mod.rs @@ -1,21 +1,37 @@ use std::borrow::Borrow; -use internment::ArcIntern; use regex::Regex; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use yasi::InternedString; -use crate::invalid_id::InvalidId; +mod action; +mod address; +mod health_check; +mod image; +mod interface; +mod invalid_id; +mod package; +mod volume; + +pub use action::ActionId; +pub use address::AddressId; +pub use health_check::HealthCheckId; +pub use image::ImageId; +pub use interface::InterfaceId; +pub use invalid_id::InvalidId; +pub use package::PackageId; +pub use volume::VolumeId; lazy_static::lazy_static! { static ref ID_REGEX: Regex = Regex::new("^[a-z]+(-[a-z]+)*$").unwrap(); - pub static ref SYSTEM_ID: Id = Id(ArcIntern::from_ref("x_system")); + pub static ref SYSTEM_ID: Id = Id(InternedString::intern("x_system")); } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] -pub struct Id(ArcIntern); -impl TryFrom> for Id { +pub struct Id(InternedString); +impl TryFrom for Id { type Error = InvalidId; - fn try_from(value: ArcIntern) -> Result { + fn try_from(value: InternedString) -> Result { if ID_REGEX.is_match(&*value) { Ok(Id(value)) } else { @@ -27,7 +43,7 @@ impl TryFrom for Id { type Error = InvalidId; fn try_from(value: String) -> Result { if ID_REGEX.is_match(&value) { - Ok(Id(ArcIntern::new(value))) + Ok(Id(InternedString::intern(value))) } else { Err(InvalidId) } @@ -37,14 +53,14 @@ impl TryFrom<&str> for Id { type Error = InvalidId; fn try_from(value: &str) -> Result { if ID_REGEX.is_match(&value) { - Ok(Id(ArcIntern::from_ref(value))) + Ok(Id(InternedString::intern(value))) } else { Err(InvalidId) } } } impl std::ops::Deref for Id { - type Target = String; + type Target = str; fn deref(&self) -> &Self::Target { &*self.0 } @@ -69,7 +85,7 @@ impl<'de> Deserialize<'de> for Id { where D: Deserializer<'de>, { - let unchecked: String = Deserialize::deserialize(deserializer)?; + let unchecked: InternedString = Deserialize::deserialize(deserializer)?; Id::try_from(unchecked).map_err(serde::de::Error::custom) } } @@ -78,6 +94,23 @@ impl Serialize for Id { where Ser: Serializer, { - serializer.serialize_str(self.as_ref()) + serializer.serialize_str(&*self) + } +} +impl<'q> sqlx::Encode<'q, sqlx::Postgres> for Id { + fn encode_by_ref( + &self, + buf: &mut >::ArgumentBuffer, + ) -> sqlx::encode::IsNull { + <&str as sqlx::Encode<'q, sqlx::Postgres>>::encode_by_ref(&&**self, buf) + } +} +impl sqlx::Type for Id { + fn type_info() -> sqlx::postgres::PgTypeInfo { + <&str as sqlx::Type>::type_info() + } + + fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool { + <&str as sqlx::Type>::compatible(ty) } } diff --git a/libs/models/src/id/package.rs b/libs/models/src/id/package.rs new file mode 100644 index 000000000..14c29d88b --- /dev/null +++ b/libs/models/src/id/package.rs @@ -0,0 +1,88 @@ +use std::borrow::Borrow; +use std::path::Path; +use std::str::FromStr; + +use serde::{Deserialize, Serialize, Serializer}; + +use crate::{Id, InvalidId, SYSTEM_ID}; + +lazy_static::lazy_static! { + pub static ref SYSTEM_PACKAGE_ID: PackageId = PackageId(SYSTEM_ID.clone()); +} +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct PackageId(Id); +impl FromStr for PackageId { + type Err = InvalidId; + fn from_str(s: &str) -> Result { + Ok(PackageId(Id::try_from(s.to_owned())?)) + } +} +impl From for PackageId { + fn from(id: Id) -> Self { + PackageId(id) + } +} +impl std::ops::Deref for PackageId { + type Target = str; + fn deref(&self) -> &Self::Target { + &*self.0 + } +} +impl AsRef for PackageId { + fn as_ref(&self) -> &PackageId { + self + } +} +impl std::fmt::Display for PackageId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", &self.0) + } +} +impl AsRef for PackageId { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} +impl Borrow for PackageId { + fn borrow(&self) -> &str { + self.0.as_ref() + } +} +impl AsRef for PackageId { + fn as_ref(&self) -> &Path { + self.0.as_ref().as_ref() + } +} +impl<'de> Deserialize<'de> for PackageId { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + Ok(PackageId(Deserialize::deserialize(deserializer)?)) + } +} +impl Serialize for PackageId { + fn serialize(&self, serializer: Ser) -> Result + where + Ser: Serializer, + { + Serialize::serialize(&self.0, serializer) + } +} +impl<'q> sqlx::Encode<'q, sqlx::Postgres> for PackageId { + fn encode_by_ref( + &self, + buf: &mut >::ArgumentBuffer, + ) -> sqlx::encode::IsNull { + <&str as sqlx::Encode<'q, sqlx::Postgres>>::encode_by_ref(&&**self, buf) + } +} +impl sqlx::Type for PackageId { + fn type_info() -> sqlx::postgres::PgTypeInfo { + <&str as sqlx::Type>::type_info() + } + + fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool { + <&str as sqlx::Type>::compatible(ty) + } +} diff --git a/libs/models/src/id/volume.rs b/libs/models/src/id/volume.rs new file mode 100644 index 000000000..16821a3cf --- /dev/null +++ b/libs/models/src/id/volume.rs @@ -0,0 +1,58 @@ +use std::borrow::Borrow; +use std::path::Path; + +use serde::{Deserialize, Deserializer, Serialize}; + +use crate::Id; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum VolumeId { + Backup, + Custom(Id), +} +impl std::fmt::Display for VolumeId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + VolumeId::Backup => write!(f, "BACKUP"), + VolumeId::Custom(id) => write!(f, "{}", id), + } + } +} +impl AsRef for VolumeId { + fn as_ref(&self) -> &str { + match self { + VolumeId::Backup => "BACKUP", + VolumeId::Custom(id) => id.as_ref(), + } + } +} +impl Borrow for VolumeId { + fn borrow(&self) -> &str { + self.as_ref() + } +} +impl AsRef for VolumeId { + fn as_ref(&self) -> &Path { + AsRef::::as_ref(self).as_ref() + } +} +impl<'de> Deserialize<'de> for VolumeId { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let unchecked: String = Deserialize::deserialize(deserializer)?; + Ok(match unchecked.as_ref() { + "BACKUP" => VolumeId::Backup, + _ => VolumeId::Custom(Id::try_from(unchecked).map_err(serde::de::Error::custom)?), + }) + } +} +impl Serialize for VolumeId { + fn serialize(&self, serializer: Ser) -> Result + where + Ser: serde::Serializer, + { + serializer.serialize_str(self.as_ref()) + } +} diff --git a/libs/models/src/lib.rs b/libs/models/src/lib.rs index 01edd65e8..9778a0728 100644 --- a/libs/models/src/lib.rs +++ b/libs/models/src/lib.rs @@ -1,23 +1,14 @@ -mod action_id; +mod data_url; mod errors; -mod health_check_id; mod id; -mod image_id; -mod interface_id; -mod invalid_id; -mod package_id; +mod mime; mod procedure_name; mod version; -mod volume_id; -pub use action_id::*; +pub use data_url::*; +pub use data_url::*; pub use errors::*; -pub use health_check_id::*; pub use id::*; -pub use image_id::*; -pub use interface_id::*; -pub use invalid_id::*; -pub use package_id::*; +pub use mime::*; pub use procedure_name::*; pub use version::*; -pub use volume_id::*; diff --git a/libs/models/src/mime.rs b/libs/models/src/mime.rs new file mode 100644 index 000000000..ea02473d7 --- /dev/null +++ b/libs/models/src/mime.rs @@ -0,0 +1,47 @@ +pub fn mime(extension: &str) -> Option<&'static str> { + match extension { + "apng" => Some("image/apng"), + "avif" => Some("image/avif"), + "flif" => Some("image/flif"), + "gif" => Some("image/gif"), + "jpg" | "jpeg" | "jfif" | "pjpeg" | "pjp" => Some("image/jpeg"), + "jxl" => Some("image/jxl"), + "png" => Some("image/png"), + "svg" => Some("image/svg+xml"), + "webp" => Some("image/webp"), + "mng" | "x-mng" => Some("image/x-mng"), + "css" => Some("text/css"), + "csv" => Some("text/csv"), + "html" => Some("text/html"), + "php" => Some("text/php"), + "plain" | "md" | "txt" => Some("text/plain"), + "xml" => Some("text/xml"), + "js" => Some("text/javascript"), + "wasm" => Some("application/wasm"), + _ => None, + } +} + +pub fn unmime(mime: &str) -> Option<&'static str> { + match mime { + "image/apng" => Some("apng"), + "image/avif" => Some("avif"), + "image/flif" => Some("flif"), + "image/gif" => Some("gif"), + "jpg" | "jpeg" | "jfif" | "pjpeg" | "image/jpeg" => Some("pjp"), + "image/jxl" => Some("jxl"), + "image/png" => Some("png"), + "image/svg+xml" => Some("svg"), + "image/webp" => Some("webp"), + "mng" | "image/x-mng" => Some("x-mng"), + "text/css" => Some("css"), + "text/csv" => Some("csv"), + "text/html" => Some("html"), + "text/php" => Some("php"), + "plain" | "md" | "text/plain" => Some("txt"), + "text/xml" => Some("xml"), + "text/javascript" => Some("js"), + "application/wasm" => Some("wasm"), + _ => None, + } +} diff --git a/libs/models/src/version.rs b/libs/models/src/version.rs index 82b577820..1e4798ba1 100644 --- a/libs/models/src/version.rs +++ b/libs/models/src/version.rs @@ -2,7 +2,6 @@ use std::hash::{Hash, Hasher}; use std::ops::Deref; use std::str::FromStr; -use patch_db::{HasModel, Model}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; #[derive(Debug, Clone)] @@ -105,6 +104,3 @@ impl Serialize for Version { self.string.serialize(serializer) } } -impl HasModel for Version { - type Model = Model; -} diff --git a/patch-db b/patch-db index 85959db11..b589a40a8 160000 --- a/patch-db +++ b/patch-db @@ -1 +1 @@ -Subproject commit 85959db110dfdddefdc9bef1e8f2013acf32d23f +Subproject commit b589a40a8be6904d46dc26f5c75b2b0241d3e8a8 From e6f8602772fccb1fef1e26c0481a577c52165ea5 Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Fri, 25 Aug 2023 13:11:57 -0600 Subject: [PATCH 02/89] chore: Convert over 3444 migration --- backend/src/version/v0_3_4_4.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/backend/src/version/v0_3_4_4.rs b/backend/src/version/v0_3_4_4.rs index 1876ff700..6870834a2 100644 --- a/backend/src/version/v0_3_4_4.rs +++ b/backend/src/version/v0_3_4_4.rs @@ -23,16 +23,17 @@ impl VersionT for Version { &*V0_3_0_COMPAT } async fn up(&self, db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { - let mut tor_addr = crate::db::DatabaseModel::new() - .server_info() - .tor_address() - .get_mut(db) + let mut tor_addr = db + .mutate(|v| { + let mut tor_address_lens = v.as_server_info_mut().as_tor_address_mut(); + let mut tor_addr = tor_address_lens.de(); + tor_addr + .set_scheme("https") + .map_err(|_| eyre!("unable to update url scheme to https")) + .with_kind(crate::ErrorKind::ParseUrl)?; + tor_address_lens.ser(tor_addr); + }) .await?; - tor_addr - .set_scheme("https") - .map_err(|_| eyre!("unable to update url scheme to https")) - .with_kind(crate::ErrorKind::ParseUrl)?; - tor_addr.save(db).await?; Ok(()) } async fn down(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { From aa6fa41b083db1f064974f931cff97f0a57f7aaa Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Mon, 28 Aug 2023 11:05:13 -0600 Subject: [PATCH 03/89] fix imports --- backend/src/db/model.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/db/model.rs b/backend/src/db/model.rs index 235fcffef..1f525c932 100644 --- a/backend/src/db/model.rs +++ b/backend/src/db/model.rs @@ -1,12 +1,13 @@ use std::collections::{BTreeMap, BTreeSet}; use std::net::{Ipv4Addr, Ipv6Addr}; +use std::sync::Arc; use chrono::{DateTime, Utc}; use emver::VersionRange; use ipnet::{Ipv4Net, Ipv6Net}; use isocountry::CountryCode; use itertools::Itertools; -use models::{AddressId, DataUrl, HealthCheckId}; +use models::{AddressId, DataUrl, HealthCheckId, InterfaceId}; use openssl::hash::MessageDigest; use patch_db::{HasModel, Value}; use reqwest::Url; From c2854b5b308826612dcc526ef84e918cd44b41a8 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Mon, 28 Aug 2023 12:06:09 -0600 Subject: [PATCH 04/89] wip --- backend/src/backup/backup_bulk.rs | 227 ++++++++++----------------- backend/src/backup/mod.rs | 245 ++++++++---------------------- backend/src/backup/os.rs | 10 +- backend/src/backup/restore.rs | 120 +++++---------- backend/src/backup/target/cifs.rs | 10 +- backend/src/backup/target/mod.rs | 74 +-------- backend/src/context/rpc.rs | 229 +++++++++++----------------- backend/src/db/model.rs | 32 ++++ libs/js_engine/Cargo.toml | 4 +- 9 files changed, 321 insertions(+), 630 deletions(-) diff --git a/backend/src/backup/backup_bulk.rs b/backend/src/backup/backup_bulk.rs index c97eeb0d6..6465fc9f4 100644 --- a/backend/src/backup/backup_bulk.rs +++ b/backend/src/backup/backup_bulk.rs @@ -1,14 +1,16 @@ -use std::collections::{BTreeMap, BTreeSet}; -use std::path::{Path, PathBuf}; +use std::collections::BTreeMap; +use std::panic::UnwindSafe; +use std::path::PathBuf; use std::sync::Arc; use chrono::Utc; use clap::ArgMatches; use color_eyre::eyre::eyre; use helpers::AtomicFile; -use patch_db::{DbHandle, LockType, PatchDbHandle}; +use imbl::OrdSet; use rpc_toolkit::command; -use tokio::{io::AsyncWriteExt, sync::Mutex}; +use tokio::io::AsyncWriteExt; +use tokio::sync::Mutex; use tracing::instrument; use super::target::BackupTargetId; @@ -18,26 +20,26 @@ use crate::backup::os::OsBackup; use crate::backup::{BackupReport, ServerBackupReport}; use crate::context::RpcContext; use crate::db::model::BackupProgress; +use crate::db::package::get_packages; use crate::disk::mount::backup::BackupMountGuard; use crate::disk::mount::filesystem::ReadWrite; use crate::disk::mount::guard::TmpMountGuard; use crate::manager::BackupReturn; use crate::notifications::NotificationLevel; +use crate::prelude::*; use crate::s9pk::manifest::PackageId; use crate::util::display_none; -use crate::util::io::dir_copy; use crate::util::serde::IoFormat; use crate::version::VersionT; -use crate::{Error, ErrorKind, ResultExt}; -fn parse_comma_separated(arg: &str, _: &ArgMatches) -> Result, Error> { +fn parse_comma_separated(arg: &str, _: &ArgMatches) -> Result, Error> { arg.split(',') - .map(|s| s.trim().parse().map_err(Error::from)) + .map(|s| s.trim().parse::().map_err(Error::from)) .collect() } #[command(rename = "create", display(display_none))] -#[instrument(skip_all)] +#[instrument(skip(ctx, old_password, password))] pub async fn backup_all( #[context] ctx: RpcContext, #[arg(rename = "target-id")] target_id: BackupTargetId, @@ -49,10 +51,9 @@ pub async fn backup_all( long = "package-ids", parse(parse_comma_separated) )] - package_ids: Option>, + package_ids: Option>, #[arg] password: crate::auth::PasswordType, ) -> Result<(), Error> { - let mut db = ctx.db.handle(); let old_password_decrypted = old_password .as_ref() .unwrap_or(&password) @@ -68,36 +69,22 @@ pub async fn backup_all( &old_password_decrypted, ) .await?; - let all_packages = crate::db::DatabaseModel::new() - .package_data() - .get(&mut db) - .await? - .0 - .keys() - .into_iter() - .cloned() - .collect(); - let package_ids = package_ids.unwrap_or(all_packages); + let package_ids = if let Some(ids) = package_ids { + ids + } else { + get_packages(&ctx.db).await?.into_iter().collect() + }; if old_password.is_some() { backup_guard.change_password(&password)?; } - assure_backing_up(&mut db, &package_ids).await?; + assure_backing_up(&ctx.db, &package_ids).await?; tokio::task::spawn(async move { - let backup_res = perform_backup(&ctx, &mut db, backup_guard, &package_ids).await; - let backup_progress = crate::db::DatabaseModel::new() - .server_info() - .status_info() - .backup_progress(); - backup_progress - .clone() - .lock(&mut db, LockType::Write) - .await - .expect("failed to lock server status"); + let backup_res = perform_backup(&ctx, backup_guard, &package_ids).await; match backup_res { Ok(report) if report.iter().all(|(_, rep)| rep.error.is_none()) => ctx .notification_manager .notify( - &mut db, + &ctx.db, None, NotificationLevel::Success, "Backup Complete".to_owned(), @@ -116,7 +103,7 @@ pub async fn backup_all( Ok(report) => ctx .notification_manager .notify( - &mut db, + &ctx.db, None, NotificationLevel::Warning, "Backup Complete".to_owned(), @@ -137,7 +124,7 @@ pub async fn backup_all( tracing::debug!("{:?}", e); ctx.notification_manager .notify( - &mut db, + &ctx.db, None, NotificationLevel::Error, "Backup Failed".to_owned(), @@ -155,106 +142,85 @@ pub async fn backup_all( .expect("failed to send notification"); } } - backup_progress - .delete(&mut db) - .await - .expect("failed to change server status"); + ctx.db + .mutate(|v| { + v.as_server_info_mut() + .as_status_info_mut() + .as_backup_progress_mut() + .ser(&None) + }) + .await?; + backup_res }); Ok(()) } -#[instrument(skip_all)] +#[instrument(skip(db, packages))] async fn assure_backing_up( - db: &mut PatchDbHandle, - packages: impl IntoIterator, + db: &PatchDb, + packages: impl IntoIterator + UnwindSafe + Send, ) -> Result<(), Error> { - let mut tx = db.begin().await?; - let mut backing_up = crate::db::DatabaseModel::new() - .server_info() - .status_info() - .backup_progress() - .get_mut(&mut tx) - .await?; - - if backing_up - .iter() - .flat_map(|x| x.values()) - .fold(false, |acc, x| { - if !x.complete { - return true; - } - acc - }) - { - return Err(Error::new( - eyre!("Server is already backing up!"), - crate::ErrorKind::InvalidRequest, - )); - } - *backing_up = Some( - packages - .into_iter() - .map(|x| (x.clone(), BackupProgress { complete: false })) - .collect(), - ); - backing_up.save(&mut tx).await?; - tx.commit().await?; - Ok(()) + db.mutate(|v| { + let backing_up = v + .as_server_info_mut() + .as_status_info_mut() + .as_backup_progress_mut(); + if backing_up + .clone() + .de()? + .iter() + .flat_map(|x| x.values()) + .fold(false, |acc, x| { + if !x.complete { + return true; + } + acc + }) + { + return Err(Error::new( + eyre!("Server is already backing up!"), + ErrorKind::InvalidRequest, + )); + } + backing_up.ser(&Some( + packages + .into_iter() + .map(|x| (x.clone(), BackupProgress { complete: false })) + .collect(), + ))?; + Ok(()) + }) + .await } -#[instrument(skip_all)] -async fn perform_backup( +#[instrument(skip(ctx, backup_guard))] +async fn perform_backup( ctx: &RpcContext, - mut db: Db, backup_guard: BackupMountGuard, - package_ids: &BTreeSet, + package_ids: &OrdSet, ) -> Result, Error> { let mut backup_report = BTreeMap::new(); let backup_guard = Arc::new(Mutex::new(backup_guard)); - for package_id in crate::db::DatabaseModel::new() - .package_data() - .keys(&mut db) - .await? - .into_iter() - .filter(|id| package_ids.contains(id)) - { - let mut tx = db.begin().await?; // for lock scope - let installed_model = if let Some(installed_model) = crate::db::DatabaseModel::new() - .package_data() - .idx_model(&package_id) - .and_then(|m| m.installed()) - .check(&mut tx) - .await? - { - installed_model - } else { - continue; - }; - let main_status_model = installed_model.clone().status().main(); - - let manifest = installed_model.clone().manifest().get(&mut tx).await?; - + for package_id in package_ids { let (response, report) = match ctx .managers - .get(&(manifest.id.clone(), manifest.version.clone())) + .get(package_id) .await - .ok_or_else(|| { - Error::new(eyre!("Manager not found"), crate::ErrorKind::InvalidRequest) - })? + .ok_or_else(|| Error::new(eyre!("Manager not found"), ErrorKind::InvalidRequest))? .backup(backup_guard.clone()) .await { BackupReturn::Ran { report, res } => (res, report), BackupReturn::AlreadyRunning(report) => { - backup_report.insert(package_id, report); + backup_report.insert(package_id.clone(), report); continue; } BackupReturn::Error(error) => { tracing::warn!("Backup thread error"); tracing::debug!("{error:?}"); backup_report.insert( - package_id, + package_id.clone(), PackageBackupReport { error: Some("Backup thread error".to_owned()), }, @@ -270,10 +236,6 @@ async fn perform_backup( ); if let Ok(pkg_meta) = response { - installed_model - .last_backup() - .put(&mut tx, &Some(pkg_meta.timestamp)) - .await?; backup_guard .lock() .await @@ -282,30 +244,10 @@ async fn perform_backup( .insert(package_id.clone(), pkg_meta); } - let mut backup_progress = crate::db::DatabaseModel::new() - .server_info() - .status_info() - .backup_progress() - .get_mut(&mut tx) - .await?; - if backup_progress.is_none() { - *backup_progress = Some(Default::default()); - } - if let Some(mut backup_progress) = backup_progress - .as_mut() - .and_then(|bp| bp.get_mut(&package_id)) - { - (*backup_progress).complete = true; - } - backup_progress.save(&mut tx).await?; - tx.save().await?; + todo!("Manager backup fn should handle updating progress, since it needs to happen atomically with updating the status"); } - let ui = crate::db::DatabaseModel::new() - .ui() - .get(&mut db) - .await? - .into_owned(); + let ui = ctx.db.peek().await?.into_ui().de()?; let mut os_backup_file = AtomicFile::new( backup_guard.lock().await.as_ref().join("os-backup.cbor"), @@ -324,19 +266,6 @@ async fn perform_backup( .await .with_kind(ErrorKind::Filesystem)?; - let luks_folder_old = backup_guard.lock().await.as_ref().join("luks.old"); - if tokio::fs::metadata(&luks_folder_old).await.is_ok() { - tokio::fs::remove_dir_all(&luks_folder_old).await?; - } - let luks_folder_bak = backup_guard.lock().await.as_ref().join("luks"); - if tokio::fs::metadata(&luks_folder_bak).await.is_ok() { - tokio::fs::rename(&luks_folder_bak, &luks_folder_old).await?; - } - let luks_folder = Path::new("/media/embassy/config/luks"); - if tokio::fs::metadata(&luks_folder).await.is_ok() { - dir_copy(&luks_folder, &luks_folder_bak, None).await?; - } - let timestamp = Some(Utc::now()); let mut backup_guard = Arc::try_unwrap(backup_guard) .map_err(|_err| { @@ -354,10 +283,8 @@ async fn perform_backup( backup_guard.save_and_unmount().await?; - crate::db::DatabaseModel::new() - .server_info() - .last_backup() - .put(&mut db, ×tamp) + ctx.db + .mutate(|v| v.as_server_info_mut().as_last_backup_mut().ser(×tamp)) .await?; Ok(backup_report) } diff --git a/backend/src/backup/mod.rs b/backend/src/backup/mod.rs index ba890645b..163d1fe22 100644 --- a/backend/src/backup/mod.rs +++ b/backend/src/backup/mod.rs @@ -1,11 +1,9 @@ -use std::collections::{BTreeMap, BTreeSet}; use std::path::{Path, PathBuf}; +use std::{collections::BTreeMap, sync::Arc}; use chrono::{DateTime, Utc}; -use color_eyre::eyre::eyre; use helpers::AtomicFile; -use models::ImageId; -use patch_db::{DbHandle, HasModel}; +use models::{InterfaceId, ProcedureName}; use reqwest::Url; use rpc_toolkit::command; use serde::{Deserialize, Serialize}; @@ -14,19 +12,15 @@ use tokio::io::AsyncWriteExt; use tracing::instrument; use self::target::PackageBackupInfo; -use crate::context::RpcContext; -use crate::dependencies::reconfigure_dependents_with_live_pointers; use crate::install::PKG_ARCHIVE_DIR; -use crate::net::interface::{InterfaceId, Interfaces}; +use crate::manager::Manager; use crate::net::keys::Key; -use crate::procedure::docker::DockerContainers; -use crate::procedure::{NoOutput, PackageProcedure, ProcedureName}; +use crate::prelude::*; use crate::s9pk::manifest::PackageId; +use crate::script::NoOutput; use crate::util::serde::{Base32, Base64, IoFormat}; -use crate::util::Version; use crate::version::{Current, VersionT}; -use crate::volume::{backup_dir, Volume, VolumeId, Volumes, BACKUP_DIR}; -use crate::{Error, ErrorKind, ResultExt}; +use crate::volume::BACKUP_DIR; pub mod backup_bulk; pub mod os; @@ -70,59 +64,15 @@ struct BackupMetadata { pub marketplace_url: Option, } -#[derive(Clone, Debug, Deserialize, Serialize, HasModel)] -pub struct BackupActions { - pub create: PackageProcedure, - pub restore: PackageProcedure, -} -impl BackupActions { - pub fn validate( - &self, - container: &Option, - eos_version: &Version, - volumes: &Volumes, - image_ids: &BTreeSet, - ) -> Result<(), Error> { - self.create - .validate(eos_version, volumes, image_ids, false) - .with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Backup Create"))?; - self.restore - .validate(eos_version, volumes, image_ids, false) - .with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Backup Restore"))?; - Ok(()) - } +#[instrument(skip(manager))] +pub async fn create(manager: Arc) -> Result { + manager + .clone() + .run_procedure::<(), NoOutput>(ProcedureName::Main, None, None) + .await?; - #[instrument(skip_all)] - pub async fn create( - &self, - ctx: &RpcContext, - db: &mut Db, - pkg_id: &PackageId, - pkg_title: &str, - pkg_version: &Version, - interfaces: &Interfaces, - volumes: &Volumes, - ) -> Result { - let mut volumes = volumes.to_readonly(); - volumes.insert(VolumeId::Backup, Volume::Backup { readonly: false }); - let backup_dir = backup_dir(pkg_id); - if tokio::fs::metadata(&backup_dir).await.is_err() { - tokio::fs::create_dir_all(&backup_dir).await? - } - self.create - .execute::<(), NoOutput>( - ctx, - pkg_id, - pkg_version, - ProcedureName::CreateBackup, - &volumes, - None, - None, - ) - .await? - .map_err(|e| eyre!("{}", e.1)) - .with_kind(crate::ErrorKind::Backup)?; - let (network_keys, tor_keys) = Key::for_package(&ctx.secret_store, pkg_id) + let (network_keys, tor_keys) = + Key::for_package(&manager.seed.ctx.secret_store, &manager.seed.manifest.id) .await? .into_iter() .filter_map(|k| { @@ -133,122 +83,59 @@ impl BackupActions { )) }) .unzip(); - let marketplace_url = crate::db::DatabaseModel::new() - .package_data() - .idx_model(pkg_id) - .expect(db) - .await? - .installed() - .expect(db) - .await? - .marketplace_url() - .get(db) - .await? - .into_owned(); - let tmp_path = Path::new(BACKUP_DIR) - .join(pkg_id) - .join(format!("{}.s9pk", pkg_id)); - let s9pk_path = ctx - .datadir - .join(PKG_ARCHIVE_DIR) - .join(pkg_id) - .join(pkg_version.as_str()) - .join(format!("{}.s9pk", pkg_id)); - let mut infile = File::open(&s9pk_path).await?; - let mut outfile = AtomicFile::new(&tmp_path, None::) - .await - .with_kind(ErrorKind::Filesystem)?; - tokio::io::copy(&mut infile, &mut *outfile) - .await - .with_ctx(|_| { - ( - crate::ErrorKind::Filesystem, - format!("cp {} -> {}", s9pk_path.display(), tmp_path.display()), - ) - })?; - outfile.save().await.with_kind(ErrorKind::Filesystem)?; - let timestamp = Utc::now(); - let metadata_path = Path::new(BACKUP_DIR).join(pkg_id).join("metadata.cbor"); - let mut outfile = AtomicFile::new(&metadata_path, None::) - .await - .with_kind(ErrorKind::Filesystem)?; - outfile - .write_all(&IoFormat::Cbor.to_vec(&BackupMetadata { - timestamp, - network_keys, - tor_keys, - marketplace_url, - })?) - .await?; - outfile.save().await.with_kind(ErrorKind::Filesystem)?; - Ok(PackageBackupInfo { - os_version: Current::new().semver().into(), - title: pkg_title.to_owned(), - version: pkg_version.clone(), - timestamp, - }) - } - - #[instrument(skip_all)] - pub async fn restore( - &self, - ctx: &RpcContext, - db: &mut Db, - pkg_id: &PackageId, - pkg_version: &Version, - interfaces: &Interfaces, - volumes: &Volumes, - ) -> Result<(), Error> { - let mut volumes = volumes.clone(); - volumes.insert(VolumeId::Backup, Volume::Backup { readonly: true }); - self.restore - .execute::<(), NoOutput>( - ctx, - pkg_id, - pkg_version, - ProcedureName::RestoreBackup, - &volumes, - None, - None, + let tmp_path = Path::new(BACKUP_DIR) + .join(&manager.seed.manifest.id) + .join(format!("{}.s9pk", &manager.seed.manifest.id)); + let s9pk_path = manager + .seed + .ctx + .datadir + .join(PKG_ARCHIVE_DIR) + .join(&manager.seed.manifest.id) + .join(&manager.seed.manifest.version.as_str()) + .join(format!("{}.s9pk", &manager.seed.manifest.id)); + let mut infile = File::open(&s9pk_path).await?; + let mut outfile = AtomicFile::new(&tmp_path, None::) + .await + .with_kind(ErrorKind::Filesystem)?; + tokio::io::copy(&mut infile, &mut *outfile) + .await + .with_ctx(|_| { + ( + ErrorKind::Filesystem, + format!("cp {} -> {}", s9pk_path.display(), tmp_path.display()), ) - .await? - .map_err(|e| eyre!("{}", e.1)) - .with_kind(crate::ErrorKind::Restore)?; - let metadata_path = Path::new(BACKUP_DIR).join(pkg_id).join("metadata.cbor"); - let metadata: BackupMetadata = IoFormat::Cbor.from_slice( - &tokio::fs::read(&metadata_path).await.with_ctx(|_| { - ( - crate::ErrorKind::Filesystem, - metadata_path.display().to_string(), - ) - })?, - )?; - let pde = crate::db::DatabaseModel::new() - .package_data() - .idx_model(pkg_id) - .expect(db) - .await? - .installed() - .expect(db) - .await?; - pde.marketplace_url() - .put(db, &metadata.marketplace_url) - .await?; - - let entry = crate::db::DatabaseModel::new() - .package_data() - .idx_model(pkg_id) - .expect(db) - .await? - .installed() - .expect(db) - .await? - .get(db) - .await?; + })?; + outfile.save().await.with_kind(ErrorKind::Filesystem)?; + let timestamp = Utc::now(); + let metadata_path = Path::new(BACKUP_DIR) + .join(&manager.seed.manifest.id) + .join("metadata.cbor"); + let mut outfile = AtomicFile::new(&metadata_path, None::) + .await + .with_kind(ErrorKind::Filesystem)?; + outfile + .write_all(&IoFormat::Cbor.to_vec(&BackupMetadata { + timestamp, + network_keys, + tor_keys, + marketplace_url: manager.seed.marketplace_url.clone(), + })?) + .await?; + outfile.save().await.with_kind(ErrorKind::Filesystem)?; + Ok(PackageBackupInfo { + os_version: Current::new().semver().into(), + title: manager.seed.manifest.title.clone(), + version: manager.seed.manifest.version.clone(), + timestamp, + }) +} - let receipts = crate::config::ConfigReceipts::new(db).await?; - reconfigure_dependents_with_live_pointers(ctx, db, &receipts, &entry).await?; +#[instrument(skip(manager))] +pub async fn restore(manager: Arc) -> Result<(), Error> { + manager + .run_procedure::<(), NoOutput>(ProcedureName::RestoreBackup, None, None) + .await?; - Ok(()) - } + Ok(()) } diff --git a/backend/src/backup/os.rs b/backend/src/backup/os.rs index 74498452c..5ab8bd12e 100644 --- a/backend/src/backup/os.rs +++ b/backend/src/backup/os.rs @@ -1,13 +1,13 @@ use openssl::pkey::PKey; use openssl::x509::X509; +use patch_db::Value; use serde::{Deserialize, Serialize}; -use serde_json::Value; use crate::account::AccountInfo; use crate::hostname::{generate_hostname, generate_id, Hostname}; use crate::net::keys::Key; +use crate::prelude::*; use crate::util::serde::Base64; -use crate::Error; pub struct OsBackup { pub account: AccountInfo, @@ -20,11 +20,11 @@ impl<'de> Deserialize<'de> for OsBackup { { let tagged = OsBackupSerDe::deserialize(deserializer)?; match tagged.version { - 0 => serde_json::from_value::(tagged.rest) + 0 => patch_db::value::from_value::(tagged.rest) .map_err(serde::de::Error::custom)? .project() .map_err(serde::de::Error::custom), - 1 => serde_json::from_value::(tagged.rest) + 1 => patch_db::value::from_value::(tagged.rest) .map_err(serde::de::Error::custom)? .project() .map_err(serde::de::Error::custom), @@ -41,7 +41,7 @@ impl Serialize for OsBackup { { OsBackupSerDe { version: 1, - rest: serde_json::to_value( + rest: patch_db::value::to_value( &OsBackupV1::unproject(self).map_err(serde::ser::Error::custom)?, ) .map_err(serde::ser::Error::custom)?, diff --git a/backend/src/backup/restore.rs b/backend/src/backup/restore.rs index 1589aabe6..dd3c4dd5e 100644 --- a/backend/src/backup/restore.rs +++ b/backend/src/backup/restore.rs @@ -5,14 +5,13 @@ use std::sync::Arc; use std::time::Duration; use clap::ArgMatches; -use color_eyre::eyre::eyre; use futures::future::BoxFuture; use futures::{stream, FutureExt, StreamExt}; use openssl::x509::X509; -use patch_db::{DbHandle, PatchDbHandle}; use rpc_toolkit::command; use sqlx::Connection; use tokio::fs::File; +use tokio::sync::watch; use torut::onion::OnionAddressV3; use tracing::instrument; @@ -21,7 +20,6 @@ use crate::backup::os::OsBackup; use crate::backup::BackupMetadata; use crate::context::rpc::RpcContextConfig; use crate::context::{RpcContext, SetupContext}; -use crate::db::model::{PackageDataEntry, StaticFiles}; use crate::disk::mount::backup::{BackupMountGuard, PackageBackupMountGuard}; use crate::disk::mount::filesystem::ReadWrite; use crate::disk::mount::guard::TmpMountGuard; @@ -30,6 +28,7 @@ use crate::init::init; use crate::install::progress::InstallProgress; use crate::install::{download_install_s9pk, PKG_PUBLIC_DIR}; use crate::notifications::NotificationLevel; +use crate::prelude::*; use crate::s9pk::manifest::{Manifest, PackageId}; use crate::s9pk::reader::S9pkReader; use crate::setup::SetupStatus; @@ -37,7 +36,6 @@ use crate::util::display_none; use crate::util::io::dir_size; use crate::util::serde::IoFormat; use crate::volume::{backup_dir, BACKUP_DIR, PKG_VOLUME_DIR}; -use crate::{Error, ResultExt}; fn parse_comma_separated(arg: &str, _: &ArgMatches) -> Result, Error> { arg.split(',') @@ -46,33 +44,31 @@ fn parse_comma_separated(arg: &str, _: &ArgMatches) -> Result, Er } #[command(rename = "restore", display(display_none))] -#[instrument(skip_all)] +#[instrument(skip(ctx, password))] pub async fn restore_packages_rpc( #[context] ctx: RpcContext, #[arg(parse(parse_comma_separated))] ids: Vec, #[arg(rename = "target-id")] target_id: BackupTargetId, #[arg] password: String, ) -> Result<(), Error> { - let mut db = ctx.db.handle(); let fs = target_id .load(&mut ctx.secret_store.acquire().await?) .await?; let backup_guard = BackupMountGuard::mount(TmpMountGuard::mount(&fs, ReadWrite).await?, &password).await?; - let (backup_guard, tasks, _) = restore_packages(&ctx, &mut db, backup_guard, ids).await?; + let (backup_guard, tasks, _) = restore_packages(&ctx, backup_guard, ids).await?; tokio::spawn(async move { stream::iter(tasks.into_iter().map(|x| (x, ctx.clone()))) .for_each_concurrent(5, |(res, ctx)| async move { - let mut db = ctx.db.handle(); match res.await { (Ok(_), _) => (), (Err(err), package_id) => { if let Err(err) = ctx .notification_manager .notify( - &mut db, + &ctx.db, Some(package_id.clone()), NotificationLevel::Error, "Restoration Failure".to_string(), @@ -109,7 +105,7 @@ async fn approximate_progress( if tokio::fs::metadata(&dir).await.is_err() { *size = 0; } else { - *size = dir_size(&dir, None).await?; + *size = dir_size(&dir).await?; } } Ok(()) @@ -133,7 +129,7 @@ async fn approximate_progress_loop( #[derive(Debug, Default)] struct ProgressInfo { - package_installs: BTreeMap>, + package_installs: BTreeMap>, src_volume_size: BTreeMap, target_volume_size: BTreeMap, } @@ -169,7 +165,7 @@ impl ProgressInfo { } } -#[instrument(skip_all)] +#[instrument(skip(ctx))] pub async fn recover_full_embassy( ctx: SetupContext, disk_guid: Arc, @@ -184,20 +180,18 @@ pub async fn recover_full_embassy( .await?; let os_backup_path = backup_guard.as_ref().join("os-backup.cbor"); - let mut os_backup: OsBackup = - IoFormat::Cbor.from_slice(&tokio::fs::read(&os_backup_path).await.with_ctx(|_| { - ( - crate::ErrorKind::Filesystem, - os_backup_path.display().to_string(), - ) - })?)?; + let mut os_backup: OsBackup = IoFormat::Cbor.from_slice( + &tokio::fs::read(&os_backup_path) + .await + .with_ctx(|_| (ErrorKind::Filesystem, os_backup_path.display().to_string()))?, + )?; os_backup.account.password = argon2::hash_encoded( embassy_password.as_bytes(), &rand::random::<[u8; 16]>()[..], &argon2::Config::default(), ) - .with_kind(crate::ErrorKind::PasswordHashGeneration)?; + .with_kind(ErrorKind::PasswordHashGeneration)?; let secret_store = ctx.secret_store().await?; @@ -211,8 +205,6 @@ pub async fn recover_full_embassy( let rpc_ctx = RpcContext::init(ctx.config_path.clone(), disk_guid.clone()).await?; - let mut db = rpc_ctx.db.handle(); - let ids = backup_guard .metadata .package_backups @@ -220,18 +212,17 @@ pub async fn recover_full_embassy( .cloned() .collect(); let (backup_guard, tasks, progress_info) = - restore_packages(&rpc_ctx, &mut db, backup_guard, ids).await?; + restore_packages(&rpc_ctx, backup_guard, ids).await?; let task_consumer_rpc_ctx = rpc_ctx.clone(); tokio::select! { _ = async move { stream::iter(tasks.into_iter().map(|x| (x, task_consumer_rpc_ctx.clone()))) .for_each_concurrent(5, |(res, ctx)| async move { - let mut db = ctx.db.handle(); match res.await { (Ok(_), _) => (), (Err(err), package_id) => { if let Err(err) = ctx.notification_manager.notify( - &mut db, + &ctx.db, Some(package_id.clone()), NotificationLevel::Error, "Restoration Failure".to_string(), format!("Error restoring package {}: {}", package_id,err), (), None).await{ @@ -263,7 +254,6 @@ pub async fn recover_full_embassy( async fn restore_packages( ctx: &RpcContext, - db: &mut PatchDbHandle, backup_guard: BackupMountGuard, ids: Vec, ) -> Result< @@ -274,7 +264,7 @@ async fn restore_packages( ), Error, > { - let guards = assure_restoring(ctx, db, ids, &backup_guard).await?; + let guards = assure_restoring(ctx, ids, &backup_guard).await?; let mut progress_info = ProgressInfo::default(); @@ -285,7 +275,7 @@ async fn restore_packages( progress_info.package_installs.insert(id.clone(), progress); progress_info .src_volume_size - .insert(id.clone(), dir_size(backup_dir(&id), None).await?); + .insert(id.clone(), dir_size(backup_dir(&id)).await?); progress_info.target_volume_size.insert(id.clone(), 0); let package_id = id.clone(); tasks.push( @@ -306,31 +296,15 @@ async fn restore_packages( Ok((backup_guard, tasks, progress_info)) } -#[instrument(skip_all)] +#[instrument(skip(ctx, backup_guard))] async fn assure_restoring( ctx: &RpcContext, - db: &mut PatchDbHandle, ids: Vec, backup_guard: &BackupMountGuard, ) -> Result, Error> { - let mut tx = db.begin().await?; - let mut guards = Vec::with_capacity(ids.len()); for id in ids { - let mut model = crate::db::DatabaseModel::new() - .package_data() - .idx_model(&id) - .get_mut(&mut tx) - .await?; - - if !model.is_none() { - return Err(Error::new( - eyre!("Can't restore over existing package: {}", id), - crate::ErrorKind::InvalidRequest, - )); - } - let guard = backup_guard.mount_package_backup(&id).await?; let s9pk_path = Path::new(BACKUP_DIR).join(&id).join(format!("{}.s9pk", id)); let mut rdr = S9pkReader::open(&s9pk_path, false).await?; @@ -339,6 +313,13 @@ async fn assure_restoring( let version = manifest.version.clone(); let progress = InstallProgress::new(Some(tokio::fs::metadata(&s9pk_path).await?.len())); + // ctx.db.apply_fn(|mut v| v.package_data().idx()) * model = + // Some(PackageDataEntry::Restoring { + // install_progress: progress.clone(), + // static_files: StaticFiles::local(&id, &version, manifest.assets.icon_type()), + // manifest: manifest.clone(), + // }); + let public_dir_path = ctx .datadir .join(PKG_PUBLIC_DIR) @@ -362,21 +343,13 @@ async fn assure_restoring( tokio::io::copy(&mut rdr.icon().await?, &mut dst).await?; dst.sync_all().await?; - *model = Some(PackageDataEntry::Restoring { - install_progress: progress.clone(), - static_files: StaticFiles::local(&id, &version, manifest.assets.icon_type()), - manifest: manifest.clone(), - }); - model.save(&mut tx).await?; - guards.push((manifest, guard)); } - tx.commit().await?; Ok(guards) } -#[instrument(skip_all)] +#[instrument(skip(ctx, guard))] async fn restore_package<'a>( ctx: RpcContext, manifest: Manifest, @@ -388,13 +361,11 @@ async fn restore_package<'a>( .join(format!("{}.s9pk", id)); let metadata_path = Path::new(BACKUP_DIR).join(&id).join("metadata.cbor"); - let metadata: BackupMetadata = - IoFormat::Cbor.from_slice(&tokio::fs::read(&metadata_path).await.with_ctx(|_| { - ( - crate::ErrorKind::Filesystem, - metadata_path.display().to_string(), - ) - })?)?; + let metadata: BackupMetadata = IoFormat::Cbor.from_slice( + &tokio::fs::read(&metadata_path) + .await + .with_ctx(|_| (ErrorKind::Filesystem, metadata_path.display().to_string()))?, + )?; let mut secrets = ctx.secret_store.acquire().await?; let mut secrets_tx = secrets.begin().await?; @@ -402,8 +373,8 @@ async fn restore_package<'a>( let k = key.0.as_slice(); sqlx::query!( "INSERT INTO network_keys (package, interface, key) VALUES ($1, $2, $3) ON CONFLICT (package, interface) DO NOTHING", - *id, - *iface, + id, + iface, k, ) .execute(&mut secrets_tx).await?; @@ -413,8 +384,8 @@ async fn restore_package<'a>( let k = key.0.as_slice(); sqlx::query!( "INSERT INTO tor (package, interface, key) VALUES ($1, $2, $3) ON CONFLICT (package, interface) DO NOTHING", - *id, - *iface, + id, + iface, k, ) .execute(&mut secrets_tx).await?; @@ -424,26 +395,19 @@ async fn restore_package<'a>( let len = tokio::fs::metadata(&s9pk_path) .await - .with_ctx(|_| { - ( - crate::ErrorKind::Filesystem, - s9pk_path.display().to_string(), - ) - })? + .with_ctx(|_| (ErrorKind::Filesystem, s9pk_path.display().to_string()))? .len(); - let file = File::open(&s9pk_path).await.with_ctx(|_| { - ( - crate::ErrorKind::Filesystem, - s9pk_path.display().to_string(), - ) - })?; + let file = File::open(&s9pk_path) + .await + .with_ctx(|_| (ErrorKind::Filesystem, s9pk_path.display().to_string()))?; let progress = InstallProgress::new(Some(len)); + let marketplace_url = metadata.marketplace_url; Ok(( progress.clone(), async move { - download_install_s9pk(&ctx, &manifest, None, progress, file, None).await?; + download_install_s9pk(&ctx, &manifest, marketplace_url, progress, file).await?; guard.unmount().await?; diff --git a/backend/src/backup/target/cifs.rs b/backend/src/backup/target/cifs.rs index 3c683ad1f..3f3251535 100644 --- a/backend/src/backup/target/cifs.rs +++ b/backend/src/backup/target/cifs.rs @@ -12,9 +12,9 @@ use crate::disk::mount::filesystem::cifs::Cifs; use crate::disk::mount::filesystem::ReadOnly; use crate::disk::mount::guard::TmpMountGuard; use crate::disk::util::{recovery_info, EmbassyOsRecoveryInfo}; +use crate::prelude::*; use crate::util::display_none; use crate::util::serde::KeyVal; -use crate::Error; #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] @@ -84,7 +84,7 @@ pub async fn update( } else { return Err(Error::new( eyre!("Backup Target ID {} Not Found", id), - crate::ErrorKind::NotFound, + ErrorKind::NotFound, )); }; let cifs = Cifs { @@ -112,7 +112,7 @@ pub async fn update( { return Err(Error::new( eyre!("Backup Target ID {} Not Found", BackupTargetId::Cifs { id }), - crate::ErrorKind::NotFound, + ErrorKind::NotFound, )); }; Ok(KeyVal { @@ -134,7 +134,7 @@ pub async fn remove(#[context] ctx: RpcContext, #[arg] id: BackupTargetId) -> Re } else { return Err(Error::new( eyre!("Backup Target ID {} Not Found", id), - crate::ErrorKind::NotFound, + ErrorKind::NotFound, )); }; if sqlx::query!("DELETE FROM cifs_shares WHERE id = $1", id) @@ -145,7 +145,7 @@ pub async fn remove(#[context] ctx: RpcContext, #[arg] id: BackupTargetId) -> Re { return Err(Error::new( eyre!("Backup Target ID {} Not Found", BackupTargetId::Cifs { id }), - crate::ErrorKind::NotFound, + ErrorKind::NotFound, )); }; Ok(()) diff --git a/backend/src/backup/target/mod.rs b/backend/src/backup/target/mod.rs index a17cf8d62..dbd1ab93f 100644 --- a/backend/src/backup/target/mod.rs +++ b/backend/src/backup/target/mod.rs @@ -7,12 +7,10 @@ use clap::ArgMatches; use color_eyre::eyre::eyre; use digest::generic_array::GenericArray; use digest::OutputSizeUser; -use lazy_static::lazy_static; use rpc_toolkit::command; use serde::{Deserialize, Serialize}; use sha2::Sha256; use sqlx::{Executor, Postgres}; -use tokio::sync::Mutex; use tracing::instrument; use self::cifs::CifsBackupTarget; @@ -23,10 +21,10 @@ use crate::disk::mount::filesystem::cifs::Cifs; use crate::disk::mount::filesystem::{FileSystem, MountType, ReadWrite}; use crate::disk::mount::guard::TmpMountGuard; use crate::disk::util::PartitionInfo; +use crate::prelude::*; use crate::s9pk::manifest::PackageId; use crate::util::serde::{deserialize_from_str, display_serializable, serialize_display}; -use crate::util::{display_none, Version}; -use crate::Error; +use crate::util::Version; pub mod cifs; @@ -44,7 +42,7 @@ pub enum BackupTarget { Cifs(CifsBackupTarget), } -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum BackupTargetId { Disk { logicalname: PathBuf }, Cifs { id: i32 }, @@ -80,7 +78,7 @@ impl std::str::FromStr for BackupTargetId { Some(("cifs", id)) => Ok(BackupTargetId::Cifs { id: id.parse()? }), _ => Err(Error::new( eyre!("Invalid Backup Target ID"), - crate::ErrorKind::InvalidBackupTargetId, + ErrorKind::InvalidBackupTargetId, )), } } @@ -131,7 +129,7 @@ impl FileSystem for BackupTargetFS { } } -#[command(subcommands(cifs::cifs, list, info, mount, umount))] +#[command(subcommands(cifs::cifs, list, info))] pub fn target() -> Result<(), Error> { Ok(()) } @@ -214,7 +212,7 @@ fn display_backup_info(info: BackupInfo, matches: &ArgMatches) { ]); for (id, info) in info.package_backups { let row = row![ - id.as_str(), + &*id, info.version.as_str(), info.os_version.as_str(), &info.timestamp.to_string(), @@ -225,7 +223,7 @@ fn display_backup_info(info: BackupInfo, matches: &ArgMatches) { } #[command(display(display_backup_info))] -#[instrument(skip_all)] +#[instrument(skip(ctx, password))] pub async fn info( #[context] ctx: RpcContext, #[arg(rename = "target-id")] target_id: BackupTargetId, @@ -249,61 +247,3 @@ pub async fn info( Ok(res) } - -lazy_static! { - static ref USER_MOUNTS: Mutex>> = - Mutex::new(BTreeMap::new()); -} - -#[command] -#[instrument(skip_all)] -pub async fn mount( - #[context] ctx: RpcContext, - #[arg(rename = "target-id")] target_id: BackupTargetId, - #[arg] password: String, -) -> Result { - let mut mounts = USER_MOUNTS.lock().await; - - if let Some(existing) = mounts.get(&target_id) { - return Ok(existing.as_ref().display().to_string()); - } - - let guard = BackupMountGuard::mount( - TmpMountGuard::mount( - &target_id - .clone() - .load(&mut ctx.secret_store.acquire().await?) - .await?, - ReadWrite, - ) - .await?, - &password, - ) - .await?; - - let res = guard.as_ref().display().to_string(); - - mounts.insert(target_id, guard); - - Ok(res) -} - -#[command(display(display_none))] -#[instrument(skip_all)] -pub async fn umount( - #[context] ctx: RpcContext, - #[arg(rename = "target-id")] target_id: Option, -) -> Result<(), Error> { - let mut mounts = USER_MOUNTS.lock().await; - if let Some(target_id) = target_id { - if let Some(existing) = mounts.remove(&target_id) { - existing.unmount().await?; - } - } else { - for (_, existing) in std::mem::take(&mut *mounts) { - existing.unmount().await?; - } - } - - Ok(()) -} diff --git a/backend/src/context/rpc.rs b/backend/src/context/rpc.rs index 119393fb0..04bf0f4af 100644 --- a/backend/src/context/rpc.rs +++ b/backend/src/context/rpc.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use helpers::to_tmp_path; use josekit::jwk::Jwk; use patch_db::json_ptr::JsonPointer; -use patch_db::{DbHandle, LockReceipt, LockType, PatchDb}; +use patch_db::PatchDb; use reqwest::{Client, Proxy, Url}; use rpc_toolkit::Context; use serde::Deserialize; @@ -20,10 +20,11 @@ use tracing::instrument; use super::setup::CURRENT_SECRET; use crate::account::AccountInfo; use crate::core::rpc_continuations::{RequestGuid, RestHandler, RpcContinuation}; -use crate::db::model::{CurrentDependents, Database, InstalledPackageDataEntry, PackageDataEntry}; +use crate::db::model::{CurrentDependents, Database, PackageDataEntryMatchModelRef}; +use crate::db::prelude::PatchDbExt; use crate::disk::OsPartitionInfo; use crate::init::init_postgres; -use crate::install::cleanup::{cleanup_failed, uninstall, CleanupFailedReceipts}; +use crate::install::cleanup::{cleanup_failed, uninstall}; use crate::manager::ManagerMap; use crate::middleware::auth::HashSessionToken; use crate::net::net_controller::NetController; @@ -31,7 +32,7 @@ use crate::net::ssl::SslManager; use crate::net::wifi::WpaCli; use crate::notifications::NotificationManager; use crate::shutdown::Shutdown; -use crate::status::{MainStatus, Status}; +use crate::status::MainStatus; use crate::system::get_mem_info; use crate::util::config::load_config_from_paths; use crate::util::lshw::{lshw, LshwDevice}; @@ -128,44 +129,6 @@ pub struct Hardware { pub ram: u64, } -pub struct RpcCleanReceipts { - cleanup_receipts: CleanupFailedReceipts, - packages: LockReceipt, - package: LockReceipt, -} - -impl RpcCleanReceipts { - pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result { - let mut locks = Vec::new(); - - let setup = Self::setup(&mut locks); - Ok(setup(&db.lock_all(locks).await?)?) - } - - pub fn setup( - locks: &mut Vec, - ) -> impl FnOnce(&patch_db::Verifier) -> Result { - let cleanup_receipts = CleanupFailedReceipts::setup(locks); - - let packages = crate::db::DatabaseModel::new() - .package_data() - .make_locker(LockType::Write) - .add_to_keys(locks); - let package = crate::db::DatabaseModel::new() - .package_data() - .star() - .make_locker(LockType::Write) - .add_to_keys(locks); - move |skeleton_key| { - Ok(Self { - cleanup_receipts: cleanup_receipts(skeleton_key)?, - packages: packages.verify(skeleton_key)?, - package: package.verify(skeleton_key)?, - }) - } - } -} - #[derive(Clone)] pub struct RpcContext(Arc); impl RpcContext { @@ -277,118 +240,96 @@ impl RpcContext { Ok(()) } - #[instrument(skip_all)] + #[instrument(skip(self))] pub async fn cleanup(&self) -> Result<(), Error> { - let mut db = self.db.handle(); - let receipts = RpcCleanReceipts::new(&mut db).await?; - let packages = receipts.packages.get(&mut db).await?.0; - let mut current_dependents = packages - .keys() - .map(|k| (k.clone(), BTreeMap::new())) - .collect::>(); - for (package_id, package) in packages { - for (k, v) in package - .into_installed() - .into_iter() - .flat_map(|i| i.current_dependencies.0) - { - let mut entry: BTreeMap<_, _> = current_dependents.remove(&k).unwrap_or_default(); - entry.insert(package_id.clone(), v); - current_dependents.insert(k, entry); - } - } - for (package_id, current_dependents) in current_dependents { - if let Some(deps) = crate::db::DatabaseModel::new() - .package_data() - .idx_model(&package_id) - .and_then(|pde| pde.installed()) - .map::<_, CurrentDependents>(|i| i.current_dependents()) - .check(&mut db) - .await? - { - deps.put(&mut db, &CurrentDependents(current_dependents)) - .await?; - } else if let Some(deps) = crate::db::DatabaseModel::new() - .package_data() - .idx_model(&package_id) - .and_then(|pde| pde.removing()) - .map::<_, CurrentDependents>(|i| i.current_dependents()) - .check(&mut db) - .await? - { - deps.put(&mut db, &CurrentDependents(current_dependents)) - .await?; - } - } - for (package_id, package) in receipts.packages.get(&mut db).await?.0 { - if let Err(e) = async { - match package { - PackageDataEntry::Installing { .. } - | PackageDataEntry::Restoring { .. } - | PackageDataEntry::Updating { .. } => { - cleanup_failed(self, &mut db, &package_id, &receipts.cleanup_receipts) - .await?; + self.db + .mutate(|f| { + let mut current_dependents = f + .as_package_data() + .keys() + .map(|k| (k.clone(), BTreeMap::new())) + .collect::>(); + for (package_id, package) in f.as_package_data() { + for (k, v) in package + .into_installed() + .into_iter() + .flat_map(|i| i.current_dependencies.0) + { + let mut entry: BTreeMap<_, _> = + current_dependents.remove(&k).unwrap_or_default(); + entry.insert(package_id.clone(), v); + current_dependents.insert(k, entry); } - PackageDataEntry::Removing { .. } => { - uninstall( - self, - &mut db, - &mut self.secret_store.acquire().await?, - &package_id, - ) - .await?; + } + for (package_id, current_dependents) in current_dependents { + if let Some(deps) = f + .as_package_data_mut() + .as_idx_mut(&package_id) + .and_then(|pde| pde.expect_as_installed_mut().ok()) + .map(|i| i.as_installed_mut().as_current_dependents_mut()) + { + deps.ser(&CurrentDependents(current_dependents))?; + } else if let Some(deps) = f + .as_package_data_mut() + .as_idx_mut(&package_id) + .and_then(|pde| pde.expect_as_removing_mut().ok()) + .map(|i| i.as_removing_mut().as_current_dependents_mut()) + { + deps.ser(&CurrentDependents(current_dependents))?; } - PackageDataEntry::Installed { - installed, - static_files, - manifest, - } => { - for (volume_id, volume_info) in &*manifest.volumes { - let tmp_path = to_tmp_path(volume_info.path_for( - &self.datadir, - &package_id, - &manifest.version, - &volume_id, - )) - .with_kind(ErrorKind::Filesystem)?; - if tokio::fs::metadata(&tmp_path).await.is_ok() { - tokio::fs::remove_dir_all(&tmp_path).await?; - } + } + }) + .await?; + let peek = self.db.peek().await?; + for (package_id, package) in peek.as_package_data().as_entries()?.into_iter() { + let action = match package.as_match() { + PackageDataEntryMatchModelRef::Installing(_) + | PackageDataEntryMatchModelRef::Restoring(_) + | PackageDataEntryMatchModelRef::Updating(_) => { + cleanup_failed(self, &package_id).await + } + PackageDataEntryMatchModelRef::Removing(_) => uninstall(self, &package_id).await, + PackageDataEntryMatchModelRef::Installed(m) => { + let version = m.as_manifest().as_version().clone().de()?; + for (volume_id, volume_info) in &*m.as_manifest().as_volumes().clone().de()? { + let tmp_path = to_tmp_path(volume_info.path_for( + &self.datadir, + &package_id, + &version, + &volume_id, + )) + .with_kind(ErrorKind::Filesystem)?; + if tokio::fs::metadata(&tmp_path).await.is_ok() { + tokio::fs::remove_dir_all(&tmp_path).await?; } - let status = installed.status; - let main = match status.main { - MainStatus::BackingUp { started, .. } => { - if let Some(_) = started { - MainStatus::Starting - } else { - MainStatus::Stopped - } - } - MainStatus::Running { .. } => MainStatus::Starting, - a => a.clone(), - }; - let new_package = PackageDataEntry::Installed { - installed: InstalledPackageDataEntry { - status: Status { main, ..status }, - ..installed - }, - static_files, - manifest, - }; - receipts - .package - .set(&mut db, new_package, &package_id) - .await?; } + Ok(()) } - Ok::<_, Error>(()) - } - .await - { + _ => continue, + }; + if let Err(e) = action { tracing::error!("Failed to clean up package {}: {}", package_id, e); tracing::debug!("{:?}", e); } } + self.db + .mutate(|v| { + for (_, pde) in v.as_package_data_mut().as_entries_mut()? { + let status = pde + .expect_as_installed_mut()? + .as_installed_mut() + .as_status_mut() + .as_main_mut(); + let running = status.clone().de()?.running(); + status.ser(&if running { + MainStatus::Starting + } else { + MainStatus::Stopped + })?; + } + Ok(()) + }) + .await?; Ok(()) } diff --git a/backend/src/db/model.rs b/backend/src/db/model.rs index 1f525c932..4be241d4b 100644 --- a/backend/src/db/model.rs +++ b/backend/src/db/model.rs @@ -300,6 +300,38 @@ impl Model { )) } } + pub fn expect_into_removing(self) -> Result, Error> { + if let PackageDataEntryMatchModel::Removing(a) = self.into_match() { + Ok(a) + } else { + Err(Error::new( + eyre!("package is not in removing state"), + ErrorKind::InvalidRequest, + )) + } + } + pub fn expect_as_removing(&self) -> Result<&Model, Error> { + if let PackageDataEntryMatchModelRef::Removing(a) = self.as_match() { + Ok(a) + } else { + Err(Error::new( + eyre!("package is not in removing state"), + ErrorKind::InvalidRequest, + )) + } + } + pub fn expect_as_removing_mut( + &mut self, + ) -> Result<&mut Model, Error> { + if let PackageDataEntryMatchModelMut::Removing(a) = self.as_match_mut() { + Ok(a) + } else { + Err(Error::new( + eyre!("package is not in removing state"), + ErrorKind::InvalidRequest, + )) + } + } pub fn into_manifest(self) -> Model { match self.into_match() { PackageDataEntryMatchModel::Installing(a) => a.into_manifest(), diff --git a/libs/js_engine/Cargo.toml b/libs/js_engine/Cargo.toml index 7bf8c49f2..904a63fdb 100644 --- a/libs/js_engine/Cargo.toml +++ b/libs/js_engine/Cargo.toml @@ -8,8 +8,8 @@ edition = "2021" [dependencies] async-trait = "0.1.56" dashmap = "5.3.4" -deno_core = "0.195.0" -deno_ast = { version = "0.27.2", features = ["transpiling"] } +deno_core = "=0.195.0" +deno_ast = { version = "=0.27.2", features = ["transpiling"] } embassy_container_init = { path = "../embassy_container_init" } reqwest = { version = "0.11.11" } sha2 = "0.10.2" From 1af37ce329a20b63d67374cb620280495befa665 Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Mon, 28 Aug 2023 12:14:08 -0600 Subject: [PATCH 05/89] feat: convert volume --- backend/src/volume.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/backend/src/volume.rs b/backend/src/volume.rs index 594217980..030c7ed22 100644 --- a/backend/src/volume.rs +++ b/backend/src/volume.rs @@ -4,13 +4,14 @@ use std::path::{Path, PathBuf}; pub use helpers::script_dir; pub use models::VolumeId; -use patch_db::{HasModel, Map, MapModel}; +use patch_db::HasModel; use serde::{Deserialize, Serialize}; use tracing::instrument; use crate::context::RpcContext; use crate::net::interface::{InterfaceId, Interfaces}; use crate::net::PACKAGE_CERT_PATH; +use crate::prelude::*; use crate::s9pk::manifest::PackageId; use crate::util::Version; use crate::{Error, ResultExt}; @@ -82,13 +83,6 @@ impl DerefMut for Volumes { impl Map for Volumes { type Key = VolumeId; type Value = Volume; - fn get(&self, key: &Self::Key) -> Option<&Self::Value> { - self.0.get(key) - } -} -pub type VolumesModel = MapModel; -impl HasModel for Volumes { - type Model = MapModel; } pub fn data_dir>(datadir: P, pkg_id: &PackageId, volume_id: &VolumeId) -> PathBuf { @@ -117,7 +111,7 @@ pub fn cert_dir(pkg_id: &PackageId, interface_id: &InterfaceId) -> PathBuf { Path::new(PACKAGE_CERT_PATH).join(pkg_id).join(interface_id) } -#[derive(Clone, Debug, Deserialize, Serialize, HasModel)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(tag = "type")] #[serde(rename_all = "kebab-case")] pub enum Volume { From 6675505ae1d9cfda7fdb9dffe2b52f70c134021b Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Mon, 28 Aug 2023 12:25:31 -0600 Subject: [PATCH 06/89] convert: system.rs --- backend/src/system.rs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/backend/src/system.rs b/backend/src/system.rs index 9e8e0e049..7e1e51776 100644 --- a/backend/src/system.rs +++ b/backend/src/system.rs @@ -17,6 +17,7 @@ use crate::logs::{ cli_logs_generic_follow, cli_logs_generic_nofollow, fetch_logs, follow_logs, LogFollowResponse, LogResponse, LogSource, }; +use crate::prelude::*; use crate::shutdown::Shutdown; use crate::util::serde::{display_serializable, IoFormat}; use crate::util::{display_none, Invoke}; @@ -59,16 +60,17 @@ pub async fn enable_zram() -> Result<(), Error> { #[command(display(display_none))] pub async fn zram(#[context] ctx: RpcContext, #[arg] enable: bool) -> Result<(), Error> { - let mut db = ctx.db.handle(); - let mut zram = crate::db::DatabaseModel::new() - .server_info() - .zram() - .get_mut(&mut db) - .await?; - if enable == *zram { + let db = ctx.db.peek().await?; + + let zram = db.as_server_info().as_zram().de()?; + // ::db::DatabaseModel::new() + // .server_info() + // .zram() + // .get_mut(&mut db) + // .await?; + if enable == zram { return Ok(()); } - *zram = enable; if enable { enable_zram().await?; } else { @@ -80,7 +82,12 @@ pub async fn zram(#[context] ctx: RpcContext, #[arg] enable: bool) -> Result<(), .await .with_kind(ErrorKind::Zram)?; } - zram.save(&mut db).await?; + ctx.db + .mutate(|v| { + v.as_server_info_mut().as_zram_mut().ser(&enable)?; + Ok(()) + }) + .await?; Ok(()) } From 20dd5545d6cd447fe6786917511cc57c6058d933 Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Mon, 28 Aug 2023 12:29:34 -0600 Subject: [PATCH 07/89] wip(convert): Setup --- backend/src/setup.rs | 11 +++-------- backend/src/system.rs | 5 ----- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/backend/src/setup.rs b/backend/src/setup.rs index a404d9f1d..a95ff7b98 100644 --- a/backend/src/setup.rs +++ b/backend/src/setup.rs @@ -5,7 +5,6 @@ use std::time::Duration; use color_eyre::eyre::eyre; use josekit::jwk::Jwk; use openssl::x509::X509; -use patch_db::DbHandle; use rpc_toolkit::command; use rpc_toolkit::yajrc::RpcError; use serde::{Deserialize, Serialize}; @@ -32,6 +31,7 @@ use crate::disk::REPAIR_DISK_PATH; use crate::hostname::Hostname; use crate::init::{init, InitResult}; use crate::middleware::encrypt::EncryptedWire; +use crate::prelude::*; use crate::util::io::{dir_copy, dir_size, Counter}; use crate::{Error, ErrorKind, ResultExt}; @@ -57,23 +57,18 @@ async fn setup_init( let InitResult { secret_store, db } = init(&RpcContextConfig::load(ctx.config_path.clone()).await?).await?; let mut secrets_handle = secret_store.acquire().await?; - let mut db_handle = db.handle(); + let peek = db.peek().await?; let mut secrets_tx = secrets_handle.begin().await?; - let mut db_tx = db_handle.begin().await?; let mut account = AccountInfo::load(&mut secrets_tx).await?; if let Some(password) = password { account.set_password(&password)?; account.save(&mut secrets_tx).await?; - crate::db::DatabaseModel::new() - .server_info() - .password_hash() - .put(&mut db_tx, &account.password) + db.mutate(|m| m.server_info().password_hash().ser(&account.password)) .await?; } - db_tx.commit().await?; secrets_tx.commit().await?; Ok(( diff --git a/backend/src/system.rs b/backend/src/system.rs index 7e1e51776..6f05b6b73 100644 --- a/backend/src/system.rs +++ b/backend/src/system.rs @@ -63,11 +63,6 @@ pub async fn zram(#[context] ctx: RpcContext, #[arg] enable: bool) -> Result<(), let db = ctx.db.peek().await?; let zram = db.as_server_info().as_zram().de()?; - // ::db::DatabaseModel::new() - // .server_info() - // .zram() - // .get_mut(&mut db) - // .await?; if enable == zram { return Ok(()); } From 25c1472dcacdf7d65aea1544748d765a58ad1397 Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Mon, 28 Aug 2023 12:39:46 -0600 Subject: [PATCH 08/89] wip properties --- backend/src/properties.rs | 21 ++++++++++----------- backend/src/setup.rs | 1 - backend/src/sound.rs | 2 +- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/backend/src/properties.rs b/backend/src/properties.rs index 22e57aa2d..da90a7370 100644 --- a/backend/src/properties.rs +++ b/backend/src/properties.rs @@ -5,8 +5,9 @@ use serde_json::Value; use tracing::instrument; use crate::context::RpcContext; +use crate::prelude::*; use crate::procedure::ProcedureName; -use crate::s9pk::manifest::{Manifest, PackageId}; +use crate::s9pk::manifest::PackageId; use crate::{Error, ErrorKind}; pub fn display_properties(response: Value, _: &ArgMatches) { @@ -20,17 +21,15 @@ pub async fn properties(#[context] ctx: RpcContext, #[arg] id: PackageId) -> Res #[instrument(skip_all)] pub async fn fetch_properties(ctx: RpcContext, id: PackageId) -> Result { - let mut db = ctx.db.handle(); + let peek = ctx.db.peek().await?; - let manifest: Manifest = crate::db::DatabaseModel::new() - .package_data() - .idx_model(&id) - .and_then(|p| p.installed()) - .map(|m| m.manifest()) - .get(&mut db) - .await? - .to_owned() - .ok_or_else(|| Error::new(eyre!("{} is not installed", id), ErrorKind::NotFound))?; + let manifest = peek + .as_package_data() + .as_idx(&id) + .ok_or_else(|| Error::new(eyre!("{} is not installed", id), ErrorKind::NotFound))? + .expect_as_installed()? + .as_manifest() + .de()?; if let Some(props) = manifest.properties { props .execute::<(), Value>( diff --git a/backend/src/setup.rs b/backend/src/setup.rs index a95ff7b98..88cfaa817 100644 --- a/backend/src/setup.rs +++ b/backend/src/setup.rs @@ -57,7 +57,6 @@ async fn setup_init( let InitResult { secret_store, db } = init(&RpcContextConfig::load(ctx.config_path.clone()).await?).await?; let mut secrets_handle = secret_store.acquire().await?; - let peek = db.peek().await?; let mut secrets_tx = secrets_handle.begin().await?; let mut account = AccountInfo::load(&mut secrets_tx).await?; diff --git a/backend/src/sound.rs b/backend/src/sound.rs index ca010c7c3..8dc78357c 100644 --- a/backend/src/sound.rs +++ b/backend/src/sound.rs @@ -15,7 +15,7 @@ lazy_static::lazy_static! { static ref C_0: f64 = *A_4 / SEMITONE_K.powf(9f64) / 2f64.powf(4f64); } -pub const SOUND_LOCK_FILE: &'static str = "/etc/embassy/sound.lock"; +pub const SOUND_LOCK_FILE: &str = "/etc/embassy/sound.lock"; struct SoundInterface { guard: Option, From 4906cb95ed0881b335057ddd30bd079e900d2a6a Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Mon, 28 Aug 2023 12:50:10 -0600 Subject: [PATCH 09/89] wip notifications --- backend/src/notifications.rs | 29 ++++++++++++++--------------- backend/src/prelude.rs | 2 +- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/backend/src/notifications.rs b/backend/src/notifications.rs index 962927e90..40699e0f1 100644 --- a/backend/src/notifications.rs +++ b/backend/src/notifications.rs @@ -4,7 +4,6 @@ use std::str::FromStr; use chrono::{DateTime, Utc}; use color_eyre::eyre::eyre; -use patch_db::{DbHandle, LockType}; use rpc_toolkit::command; use sqlx::PgPool; use tokio::sync::Mutex; @@ -12,6 +11,7 @@ use tracing::instrument; use crate::backup::BackupReport; use crate::context::RpcContext; +use crate::prelude::*; use crate::s9pk::manifest::PackageId; use crate::util::display_none; use crate::util::serde::display_serializable; @@ -30,13 +30,9 @@ pub async fn list( #[arg] limit: Option, ) -> Result, Error> { let limit = limit.unwrap_or(40); - let mut handle = ctx.db.handle(); + let peek = ctx.db.peek().await?; match before { None => { - let model = crate::db::DatabaseModel::new() - .server_info() - .unread_notification_count(); - model.lock(&mut handle, LockType::Write).await?; let records = sqlx::query!( "SELECT id, package_id, created_at, code, level, title, message, data FROM notifications ORDER BY id DESC LIMIT $1", limit as i64 @@ -70,8 +66,14 @@ pub async fn list( }) }) .collect::, Error>>()?; - // set notification count to zero - model.put(&mut handle, &0).await?; + + ctx.db + .mutate(|d| { + d.as_server_info_mut() + .as_unread_notification_count() + .ser(&0)? + }) + .await?; Ok(notifs) } Some(before) => { @@ -233,9 +235,9 @@ impl NotificationManager { } } #[instrument(skip_all)] - pub async fn notify( + pub async fn notify( &self, - db: &mut Db, + db: PatchDb, package_id: Option, level: NotificationLevel, title: String, @@ -243,17 +245,14 @@ impl NotificationManager { subtype: T, debounce_interval: Option, ) -> Result<(), Error> { + let peek = db.peek().await?; if !self .should_notify(&package_id, &level, &title, debounce_interval) .await { return Ok(()); } - let mut count = crate::db::DatabaseModel::new() - .server_info() - .unread_notification_count() - .get_mut(db) - .await?; + let mut count = peek.as_server_info().as_unread_notification_count().de()?; let sql_package_id = package_id.as_ref().map(|p| &**p); let sql_code = T::CODE; let sql_level = format!("{}", level); diff --git a/backend/src/prelude.rs b/backend/src/prelude.rs index eff08ecd0..83d821c31 100644 --- a/backend/src/prelude.rs +++ b/backend/src/prelude.rs @@ -2,4 +2,4 @@ pub use color_eyre::eyre::eyre; pub use crate::db::prelude::*; pub use crate::ensure_code; -pub use crate::error::{Error, ErrorCollection, ErrorKind, OptionExt, ResultExt}; +pub use crate::error::{Error, ErrorCollection, ErrorKind, ResultExt}; From c57a4f502cfc39cd7c2b48dcd7b61bfa0a0720c9 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Tue, 29 Aug 2023 09:38:12 -0600 Subject: [PATCH 10/89] wip --- backend/src/backup/mod.rs | 240 +++++++++++---- backend/src/db/model.rs | 53 ++-- backend/src/dependencies.rs | 510 +++++++++----------------------- backend/src/error.rs | 4 +- backend/src/manager/health.rs | 148 +++------ backend/src/procedure/docker.rs | 4 +- backend/src/s9pk/manifest.rs | 12 +- backend/src/status/mod.rs | 12 +- 8 files changed, 391 insertions(+), 592 deletions(-) diff --git a/backend/src/backup/mod.rs b/backend/src/backup/mod.rs index 163d1fe22..f403dac19 100644 --- a/backend/src/backup/mod.rs +++ b/backend/src/backup/mod.rs @@ -1,9 +1,10 @@ +use std::collections::{BTreeMap, BTreeSet}; use std::path::{Path, PathBuf}; -use std::{collections::BTreeMap, sync::Arc}; use chrono::{DateTime, Utc}; +use color_eyre::eyre::eyre; use helpers::AtomicFile; -use models::{InterfaceId, ProcedureName}; +use models::{ImageId, OptionExt}; use reqwest::Url; use rpc_toolkit::command; use serde::{Deserialize, Serialize}; @@ -12,15 +13,20 @@ use tokio::io::AsyncWriteExt; use tracing::instrument; use self::target::PackageBackupInfo; +use crate::context::RpcContext; +use crate::dependencies::reconfigure_dependents_with_live_pointers; use crate::install::PKG_ARCHIVE_DIR; -use crate::manager::Manager; +use crate::net::interface::{InterfaceId, Interfaces}; use crate::net::keys::Key; use crate::prelude::*; +use crate::procedure::docker::DockerContainers; +use crate::procedure::{NoOutput, PackageProcedure, ProcedureName}; use crate::s9pk::manifest::PackageId; -use crate::script::NoOutput; use crate::util::serde::{Base32, Base64, IoFormat}; +use crate::util::Version; use crate::version::{Current, VersionT}; -use crate::volume::BACKUP_DIR; +use crate::volume::{backup_dir, Volume, VolumeId, Volumes, BACKUP_DIR}; +use crate::{Error, ErrorKind, ResultExt}; pub mod backup_bulk; pub mod os; @@ -64,15 +70,59 @@ struct BackupMetadata { pub marketplace_url: Option, } -#[instrument(skip(manager))] -pub async fn create(manager: Arc) -> Result { - manager - .clone() - .run_procedure::<(), NoOutput>(ProcedureName::Main, None, None) - .await?; +#[derive(Clone, Debug, Deserialize, Serialize, HasModel)] +#[model = "Model"] +pub struct BackupActions { + pub create: PackageProcedure, + pub restore: PackageProcedure, +} +impl BackupActions { + pub fn validate( + &self, + container: &Option, + eos_version: &Version, + volumes: &Volumes, + image_ids: &BTreeSet, + ) -> Result<(), Error> { + self.create + .validate(eos_version, volumes, image_ids, false) + .with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Backup Create"))?; + self.restore + .validate(eos_version, volumes, image_ids, false) + .with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Backup Restore"))?; + Ok(()) + } - let (network_keys, tor_keys) = - Key::for_package(&manager.seed.ctx.secret_store, &manager.seed.manifest.id) + #[instrument(skip_all)] + pub async fn create( + &self, + ctx: &RpcContext, + pkg_id: &PackageId, + pkg_title: &str, + pkg_version: &Version, + interfaces: &Interfaces, + volumes: &Volumes, + ) -> Result { + let mut volumes = volumes.to_readonly(); + volumes.insert(VolumeId::Backup, Volume::Backup { readonly: false }); + let backup_dir = backup_dir(pkg_id); + if tokio::fs::metadata(&backup_dir).await.is_err() { + tokio::fs::create_dir_all(&backup_dir).await? + } + self.create + .execute::<(), NoOutput>( + ctx, + pkg_id, + pkg_version, + ProcedureName::CreateBackup, + &volumes, + None, + None, + ) + .await? + .map_err(|e| eyre!("{}", e.1)) + .with_kind(crate::ErrorKind::Backup)?; + let (network_keys, tor_keys) = Key::for_package(&ctx.secret_store, pkg_id) .await? .into_iter() .filter_map(|k| { @@ -83,59 +133,119 @@ pub async fn create(manager: Arc) -> Result { )) }) .unzip(); - let tmp_path = Path::new(BACKUP_DIR) - .join(&manager.seed.manifest.id) - .join(format!("{}.s9pk", &manager.seed.manifest.id)); - let s9pk_path = manager - .seed - .ctx - .datadir - .join(PKG_ARCHIVE_DIR) - .join(&manager.seed.manifest.id) - .join(&manager.seed.manifest.version.as_str()) - .join(format!("{}.s9pk", &manager.seed.manifest.id)); - let mut infile = File::open(&s9pk_path).await?; - let mut outfile = AtomicFile::new(&tmp_path, None::) - .await - .with_kind(ErrorKind::Filesystem)?; - tokio::io::copy(&mut infile, &mut *outfile) - .await - .with_ctx(|_| { - ( - ErrorKind::Filesystem, - format!("cp {} -> {}", s9pk_path.display(), tmp_path.display()), - ) - })?; - outfile.save().await.with_kind(ErrorKind::Filesystem)?; - let timestamp = Utc::now(); - let metadata_path = Path::new(BACKUP_DIR) - .join(&manager.seed.manifest.id) - .join("metadata.cbor"); - let mut outfile = AtomicFile::new(&metadata_path, None::) - .await - .with_kind(ErrorKind::Filesystem)?; - outfile - .write_all(&IoFormat::Cbor.to_vec(&BackupMetadata { + let marketplace_url = ctx + .db + .peek() + .await? + .as_package_data() + .as_idx(pkg_id) + .or_not_found(pkg_id)? + .expect_as_installed()? + .as_installed_mut() + .as_marketplace_url() + .de()?; + let tmp_path = Path::new(BACKUP_DIR) + .join(pkg_id) + .join(format!("{}.s9pk", pkg_id)); + let s9pk_path = ctx + .datadir + .join(PKG_ARCHIVE_DIR) + .join(pkg_id) + .join(pkg_version.as_str()) + .join(format!("{}.s9pk", pkg_id)); + let mut infile = File::open(&s9pk_path).await?; + let mut outfile = AtomicFile::new(&tmp_path, None::) + .await + .with_kind(ErrorKind::Filesystem)?; + tokio::io::copy(&mut infile, &mut *outfile) + .await + .with_ctx(|_| { + ( + crate::ErrorKind::Filesystem, + format!("cp {} -> {}", s9pk_path.display(), tmp_path.display()), + ) + })?; + outfile.save().await.with_kind(ErrorKind::Filesystem)?; + let timestamp = Utc::now(); + let metadata_path = Path::new(BACKUP_DIR).join(pkg_id).join("metadata.cbor"); + let mut outfile = AtomicFile::new(&metadata_path, None::) + .await + .with_kind(ErrorKind::Filesystem)?; + outfile + .write_all(&IoFormat::Cbor.to_vec(&BackupMetadata { + timestamp, + network_keys, + tor_keys, + marketplace_url, + })?) + .await?; + outfile.save().await.with_kind(ErrorKind::Filesystem)?; + Ok(PackageBackupInfo { + os_version: Current::new().semver().into(), + title: pkg_title.to_owned(), + version: pkg_version.clone(), timestamp, - network_keys, - tor_keys, - marketplace_url: manager.seed.marketplace_url.clone(), - })?) - .await?; - outfile.save().await.with_kind(ErrorKind::Filesystem)?; - Ok(PackageBackupInfo { - os_version: Current::new().semver().into(), - title: manager.seed.manifest.title.clone(), - version: manager.seed.manifest.version.clone(), - timestamp, - }) -} + }) + } + + #[instrument(skip_all)] + pub async fn restore( + &self, + ctx: &RpcContext, + pkg_id: &PackageId, + pkg_version: &Version, + interfaces: &Interfaces, + volumes: &Volumes, + ) -> Result<(), Error> { + let mut volumes = volumes.clone(); + volumes.insert(VolumeId::Backup, Volume::Backup { readonly: true }); + self.restore + .execute::<(), NoOutput>( + ctx, + pkg_id, + pkg_version, + ProcedureName::RestoreBackup, + &volumes, + None, + None, + ) + .await? + .map_err(|e| eyre!("{}", e.1)) + .with_kind(crate::ErrorKind::Restore)?; + let metadata_path = Path::new(BACKUP_DIR).join(pkg_id).join("metadata.cbor"); + let metadata: BackupMetadata = IoFormat::Cbor.from_slice( + &tokio::fs::read(&metadata_path).await.with_ctx(|_| { + ( + crate::ErrorKind::Filesystem, + metadata_path.display().to_string(), + ) + })?, + )?; + ctx.db + .mutate(|v| { + v.as_package_data_mut() + .as_idx_mut(pkg_id) + .or_not_found(pkg_id)? + .expect_as_restoring_mut()? // TODO: Restoring? + .as_marketplace_url_mut() + .ser(metadata.marketplace_url) + }) + .await?; -#[instrument(skip(manager))] -pub async fn restore(manager: Arc) -> Result<(), Error> { - manager - .run_procedure::<(), NoOutput>(ProcedureName::RestoreBackup, None, None) - .await?; + let entry = crate::db::DatabaseModel::new() + .package_data() + .idx_model(pkg_id) + .expect(db) + .await? + .installed() + .expect(db) + .await? + .get(db) + .await?; - Ok(()) + let receipts = crate::config::ConfigReceipts::new(db).await?; + reconfigure_dependents_with_live_pointers(ctx, db, &receipts, &entry).await?; + + Ok(()) + } } diff --git a/backend/src/db/model.rs b/backend/src/db/model.rs index 4be241d4b..53fcd2a25 100644 --- a/backend/src/db/model.rs +++ b/backend/src/db/model.rs @@ -335,40 +335,41 @@ impl Model { pub fn into_manifest(self) -> Model { match self.into_match() { PackageDataEntryMatchModel::Installing(a) => a.into_manifest(), - PackageDataEntryMatchModel::Updating(a) => a.into_old_manifest(), + PackageDataEntryMatchModel::Updating(a) => a.into_installed().into_manifest(), PackageDataEntryMatchModel::Restoring(a) => a.into_manifest(), PackageDataEntryMatchModel::Removing(a) => a.into_manifest(), - PackageDataEntryMatchModel::NeedsUpdate(a) => a.into_manifest(), PackageDataEntryMatchModel::Installed(a) => a.into_manifest(), PackageDataEntryMatchModel::Error(_) => Model::from(Value::Null), } } - pub fn as_manifest(&self) -> Result<&Model, Error> { - match self.as_match() { - PackageDataEntryMatchModelRef::Installing(a) => Ok(a.as_manifest()), - PackageDataEntryMatchModelRef::Updating(a) => Ok(a.as_old_manifest()), - PackageDataEntryMatchModelRef::Restoring(a) => Ok(a.as_manifest()), - PackageDataEntryMatchModelRef::Removing(a) => Ok(a.as_manifest()), - PackageDataEntryMatchModelRef::NeedsUpdate(a) => Ok(a.as_manifest()), - PackageDataEntryMatchModelRef::Installed(a) => Ok(a.as_manifest()), - PackageDataEntryMatchModelRef::Error(a) => Err(Error::new( - eyre!("unknown variant of PackageDataEntry"), - ErrorKind::Deserialization, - )), + pub fn into_installed(self) -> Option> { + match self.into_match() { + PackageDataEntryMatchModel::Installing(_) => None, + PackageDataEntryMatchModel::Updating(a) => Some(a.into_installed()), + PackageDataEntryMatchModel::Restoring(_) => None, + PackageDataEntryMatchModel::Removing(_) => None, + PackageDataEntryMatchModel::Installed(a) => Some(a.into_installed()), + PackageDataEntryMatchModel::Error(_) => None, } } - pub fn as_icon(&self) -> Result<&Model, Error> { - match self.as_match() { - PackageDataEntryMatchModelRef::Installing(a) => Ok(a.as_icon()), - PackageDataEntryMatchModelRef::Updating(a) => Ok(a.as_icon()), - PackageDataEntryMatchModelRef::Restoring(a) => Ok(a.as_icon()), - PackageDataEntryMatchModelRef::Removing(a) => Ok(a.as_icon()), - PackageDataEntryMatchModelRef::NeedsUpdate(a) => Ok(a.as_icon()), - PackageDataEntryMatchModelRef::Installed(a) => Ok(a.as_icon()), - PackageDataEntryMatchModelRef::Error(a) => Err(Error::new( - eyre!("unknown variant of PackageDataEntry"), - ErrorKind::Deserialization, - )), + pub fn as_installed(&self) -> Option<&Model> { + match self.into_match() { + PackageDataEntryMatchModel::Installing(_) => None, + PackageDataEntryMatchModel::Updating(a) => Some(a.as_installed()), + PackageDataEntryMatchModel::Restoring(_) => None, + PackageDataEntryMatchModel::Removing(_) => None, + PackageDataEntryMatchModel::Installed(a) => Some(a.as_installed()), + PackageDataEntryMatchModel::Error(_) => None, + } + } + pub fn as_installed_mut(&mut self) -> Option<&mut Model> { + match self.into_match() { + PackageDataEntryMatchModel::Installing(_) => None, + PackageDataEntryMatchModel::Updating(mut a) => Some(a.as_installed_mut()), + PackageDataEntryMatchModel::Restoring(_) => None, + PackageDataEntryMatchModel::Removing(_) => None, + PackageDataEntryMatchModel::Installed(mut a) => Some(a.as_installed_mut()), + PackageDataEntryMatchModel::Error(_) => None, } } } diff --git a/backend/src/dependencies.rs b/backend/src/dependencies.rs index 99e146944..2042949d0 100644 --- a/backend/src/dependencies.rs +++ b/backend/src/dependencies.rs @@ -6,9 +6,7 @@ use color_eyre::eyre::eyre; use emver::VersionRange; use futures::future::BoxFuture; use futures::FutureExt; -use patch_db::{ - DbHandle, HasModel, LockReceipt, LockTargetId, LockType, Map, MapModel, PatchDbHandle, Verifier, -}; +use imbl::OrdSet; use rand::SeedableRng; use rpc_toolkit::command; use serde::{Deserialize, Serialize}; @@ -18,7 +16,8 @@ use crate::config::action::{ConfigActions, ConfigRes}; use crate::config::spec::PackagePointerSpec; use crate::config::{not_found, Config, ConfigReceipts, ConfigSpec, ConfigureContext}; use crate::context::RpcContext; -use crate::db::model::{CurrentDependencies, CurrentDependents, InstalledPackageDataEntry}; +use crate::db::model::{CurrentDependencies, CurrentDependents, Database, InstalledPackageInfo}; +use crate::prelude::*; use crate::procedure::docker::DockerContainers; use crate::procedure::{NoOutput, PackageProcedure, ProcedureName}; use crate::s9pk::manifest::{Manifest, PackageId}; @@ -57,81 +56,6 @@ pub enum DependencyError { Transitive, // { "type": "transitive" } } -#[derive(Clone)] -pub struct TryHealReceipts { - status: LockReceipt, - manifest: LockReceipt, - manifest_version: LockReceipt, - current_dependencies: LockReceipt, - dependency_errors: LockReceipt, - docker_containers: LockReceipt, -} - -impl TryHealReceipts { - pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result { - let mut locks = Vec::new(); - - let setup = Self::setup(&mut locks); - Ok(setup(&db.lock_all(locks).await?)?) - } - - pub fn setup(locks: &mut Vec) -> impl FnOnce(&Verifier) -> Result { - let manifest_version = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .map(|x| x.manifest().version()) - .make_locker(LockType::Write) - .add_to_keys(locks); - let status = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .map(|x| x.status()) - .make_locker(LockType::Write) - .add_to_keys(locks); - let manifest = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .map(|x| x.manifest()) - .make_locker(LockType::Write) - .add_to_keys(locks); - - let current_dependencies = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .map(|x| x.current_dependencies()) - .make_locker(LockType::Write) - .add_to_keys(locks); - let dependency_errors = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .map(|x| x.status().dependency_errors()) - .make_locker(LockType::Write) - .add_to_keys(locks); - let docker_containers = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .and_then(|x| x.manifest().containers()) - .make_locker(LockType::Write) - .add_to_keys(locks); - move |skeleton_key| { - Ok(Self { - status: status.verify(skeleton_key)?, - manifest_version: manifest_version.verify(skeleton_key)?, - current_dependencies: current_dependencies.verify(skeleton_key)?, - manifest: manifest.verify(skeleton_key)?, - dependency_errors: dependency_errors.verify(skeleton_key)?, - docker_containers: docker_containers.verify(skeleton_key)?, - }) - } - } -} - impl DependencyError { pub fn cmp_priority(&self, other: &DependencyError) -> std::cmp::Ordering { use std::cmp::Ordering::*; @@ -192,200 +116,157 @@ impl DependencyError { } } #[instrument(skip_all)] - pub fn try_heal<'a, Db: DbHandle>( + pub fn try_heal( self, - ctx: &'a RpcContext, - db: &'a mut Db, - id: &'a PackageId, - dependency: &'a PackageId, - mut dependency_config: Option, - info: &'a DepInfo, - receipts: &'a TryHealReceipts, - ) -> BoxFuture<'a, Result, Error>> { - async move { - let container = receipts.docker_containers.get(db, id).await?; - Ok(match self { - DependencyError::NotInstalled => { - if receipts.status.get(db, dependency).await?.is_some() { - DependencyError::IncorrectVersion { - expected: info.version.clone(), - received: Default::default(), - } - .try_heal(ctx, db, id, dependency, dependency_config, info, receipts) - .await? - } else { - Some(DependencyError::NotInstalled) + db: &Model, + id: &PackageId, + dependency: &PackageId, + mut dependency_config_error: Option, // config error + info: &DepInfo, + ) -> Result, Error> { + let installed_info = db + .as_package_data() + .as_idx(id) + .or_not_found(id)? + .as_installed() + .or_not_found(id)?; + let dependency_installed_info = db + .as_package_data() + .as_idx(dependency) + .and_then(|d| d.as_installed()); + let container = installed_info.as_manifest()?.as_containers().de()?; + Ok(match self { + DependencyError::NotInstalled => { + if dependency_installed_info.is_some() { + DependencyError::IncorrectVersion { + expected: info.version.clone(), + received: Default::default(), } + .try_heal( + db, + id, + dependency, + dependency_config_error, + info, + )? + } else { + Some(DependencyError::NotInstalled) } - DependencyError::IncorrectVersion { expected, .. } => { - let version: Version = receipts - .manifest_version - .get(db, dependency) - .await? - .unwrap_or_default(); - if version.satisfies(&expected) { - DependencyError::ConfigUnsatisfied { - error: String::new(), - } - .try_heal(ctx, db, id, dependency, dependency_config, info, receipts) - .await? - } else { - Some(DependencyError::IncorrectVersion { - expected, - received: version, - }) + } + DependencyError::IncorrectVersion { expected, .. } => { + let version: Version = dependency_installed_info + .or_not_found(dependency)? + .as_manifest() + .as_version() + .de()?; + if version.satisfies(&expected) { + DependencyError::ConfigUnsatisfied { + error: String::new(), } + .try_heal( + db, + id, + dependency, + dependency_config_error, + info, + )? + } else { + Some(DependencyError::IncorrectVersion { + expected, + received: version, + }) } - DependencyError::ConfigUnsatisfied { .. } => { - let dependent_manifest = receipts - .manifest - .get(db, id) - .await? - .ok_or_else(|| not_found!(id))?; - let dependency_manifest = receipts - .manifest - .get(db, dependency) - .await? - .ok_or_else(|| not_found!(dependency))?; - - let dependency_config = if let Some(cfg) = dependency_config.take() { - cfg - } else if let Some(cfg_info) = &dependency_manifest.config { - cfg_info - .get( - ctx, - dependency, - &dependency_manifest.version, - &dependency_manifest.volumes, - ) - .await? - .config - .unwrap_or_default() - } else { - Config::default() - }; - if let Some(cfg_req) = &info.config { - if let Err(error) = cfg_req - .check( - ctx, - &container, - id, - &dependent_manifest.version, - &dependent_manifest.volumes, - dependency, - &dependency_config, - ) - .await? - { - return Ok(Some(DependencyError::ConfigUnsatisfied { error })); - } - } - DependencyError::NotRunning - .try_heal( - ctx, - db, - id, - dependency, - Some(dependency_config), - info, - receipts, - ) - .await? + } + DependencyError::ConfigUnsatisfied { .. } => { + if let Some(error) = dependency_config_error { + Some(DependencyError::ConfigUnsatisfied { error }) + } else { + DependencyError::NotRunning.try_heal(db, id, dependency, None, info)? } - DependencyError::NotRunning => { - let status = receipts - .status - .get(db, dependency) - .await? - .ok_or_else(|| not_found!(dependency))?; - if status.main.running() { - DependencyError::HealthChecksFailed { - failures: BTreeMap::new(), - } - .try_heal(ctx, db, id, dependency, dependency_config, info, receipts) - .await? - } else { - Some(DependencyError::NotRunning) + } + DependencyError::NotRunning => { + let status = dependency_installed_info + .or_not_found(dependency)? + .as_status() + .as_main() + .de()?; + if status.running() { + DependencyError::HealthChecksFailed { + failures: BTreeMap::new(), } + .try_heal( + db, + id, + dependency, + dependency_config_error, + info, + )? + } else { + Some(DependencyError::NotRunning) } - DependencyError::HealthChecksFailed { .. } => { - let status = receipts - .status - .get(db, dependency) - .await? - .ok_or_else(|| not_found!(dependency))?; - match status.main { - MainStatus::BackingUp { - started: Some(_), - health, - } - | MainStatus::Running { health, .. } => { - let mut failures = BTreeMap::new(); - for (check, res) in health { - if !matches!(res, HealthCheckResult::Success) - && receipts - .current_dependencies - .get(db, id) - .await? - .ok_or_else(|| not_found!(id))? - .get(dependency) - .map(|x| x.health_checks.contains(&check)) - .unwrap_or(false) - { - failures.insert(check.clone(), res.clone()); - } - } - if !failures.is_empty() { - Some(DependencyError::HealthChecksFailed { failures }) - } else { - DependencyError::Transitive - .try_heal( - ctx, - db, - id, - dependency, - dependency_config, - info, - receipts, - ) - .await? + } + DependencyError::HealthChecksFailed { .. } => { + let status = dependency_installed_info + .or_not_found(dependency)? + .as_status() + .as_main() + .de()?; + match status { + MainStatus::BackingUp { + started: Some(_), + health, + } + | MainStatus::Running { health, .. } => { + let mut failures = BTreeMap::new(); + for (check, res) in health { + if !matches!(res, HealthCheckResult::Success) + && installed_info + .as_current_dependencies() + .as_idx(dependency) + .or_not_found(dependency)? + .de()? + .health_checks + .contains(&check) + { + failures.insert(check.clone(), res.clone()); } } - MainStatus::Starting { .. } | MainStatus::Restarting => { - DependencyError::Transitive - .try_heal( - ctx, - db, - id, - dependency, - dependency_config, - info, - receipts, - ) - .await? + if !failures.is_empty() { + Some(DependencyError::HealthChecksFailed { failures }) + } else { + DependencyError::Transitive.try_heal(db, id, dependency, None, info)? } - _ => return Ok(Some(DependencyError::NotRunning)), } - } - DependencyError::Transitive => { - if receipts - .dependency_errors - .get(db, dependency) - .await? - .unwrap_or_default() - .0 - .is_empty() - { - None - } else { - Some(DependencyError::Transitive) + MainStatus::Starting { .. } | MainStatus::Restarting => { + DependencyError::Transitive.try_heal(db, id, dependency, None, info)? } + _ => return Ok(Some(DependencyError::NotRunning)), } - }) - } - .boxed() + } + DependencyError::Transitive => { + if dependency_installed_info + .or_not_found(dependency)? + .as_status() + .as_dependency_errors() + .de()? + .unwrap_or_default() + .0 + .is_empty() + { + None + } else { + Some(DependencyError::Transitive) + } + } + }) + } +} +impl PartialOrd for DependencyError { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp_priority(other)) } } +impl Ord for DependencyError {} impl std::fmt::Display for DependencyError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -431,17 +312,12 @@ pub struct TaggedDependencyError { #[serde(rename_all = "kebab-case")] pub struct BreakageRes(pub BTreeMap); -#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel)] +#[model = "Model"] pub struct Dependencies(pub BTreeMap); impl Map for Dependencies { type Key = PackageId; type Value = DepInfo; - fn get(&self, key: &Self::Key) -> Option<&Self::Value> { - self.0.get(key) - } -} -impl HasModel for Dependencies { - type Model = MapModel; } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -460,12 +336,12 @@ impl DependencyRequirement { #[derive(Clone, Debug, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] +#[model = "Model"] pub struct DepInfo { pub version: VersionRange, pub requirement: DependencyRequirement, pub description: Option, #[serde(default)] - #[model] pub config: Option, } impl DepInfo { @@ -501,6 +377,7 @@ impl DepInfo { #[derive(Clone, Debug, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] +#[model = "Model"] pub struct DependencyConfig { check: PackageProcedure, auto_configure: PackageProcedure, @@ -554,96 +431,6 @@ impl DependencyConfig { } } -pub struct DependencyConfigReceipts { - dependencies: LockReceipt, - dependency_volumes: LockReceipt, - dependency_version: LockReceipt, - dependency_config_action: LockReceipt, - package_volumes: LockReceipt, - package_version: LockReceipt, - docker_containers: LockReceipt, -} - -impl DependencyConfigReceipts { - pub async fn new<'a>( - db: &'a mut impl DbHandle, - package_id: &PackageId, - dependency_id: &PackageId, - ) -> Result { - let mut locks = Vec::new(); - - let setup = Self::setup(&mut locks, package_id, dependency_id); - Ok(setup(&db.lock_all(locks).await?)?) - } - - pub fn setup( - locks: &mut Vec, - package_id: &PackageId, - dependency_id: &PackageId, - ) -> impl FnOnce(&Verifier) -> Result { - let dependencies = crate::db::DatabaseModel::new() - .package_data() - .idx_model(package_id) - .and_then(|x| x.installed()) - .map(|x| x.manifest().dependencies()) - .make_locker(LockType::Write) - .add_to_keys(locks); - let dependency_volumes = crate::db::DatabaseModel::new() - .package_data() - .idx_model(dependency_id) - .and_then(|x| x.installed()) - .map(|x| x.manifest().volumes()) - .make_locker(LockType::Write) - .add_to_keys(locks); - let dependency_version = crate::db::DatabaseModel::new() - .package_data() - .idx_model(dependency_id) - .and_then(|x| x.installed()) - .map(|x| x.manifest().version()) - .make_locker(LockType::Write) - .add_to_keys(locks); - let dependency_config_action = crate::db::DatabaseModel::new() - .package_data() - .idx_model(dependency_id) - .and_then(|x| x.installed()) - .and_then(|x| x.manifest().config()) - .make_locker(LockType::Write) - .add_to_keys(locks); - let package_volumes = crate::db::DatabaseModel::new() - .package_data() - .idx_model(package_id) - .and_then(|x| x.installed()) - .map(|x| x.manifest().volumes()) - .make_locker(LockType::Write) - .add_to_keys(locks); - let package_version = crate::db::DatabaseModel::new() - .package_data() - .idx_model(package_id) - .and_then(|x| x.installed()) - .map(|x| x.manifest().version()) - .make_locker(LockType::Write) - .add_to_keys(locks); - let docker_containers = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .and_then(|x| x.manifest().containers()) - .make_locker(LockType::Write) - .add_to_keys(locks); - move |skeleton_key| { - Ok(Self { - dependencies: dependencies.verify(&skeleton_key)?, - dependency_volumes: dependency_volumes.verify(&skeleton_key)?, - dependency_version: dependency_version.verify(&skeleton_key)?, - dependency_config_action: dependency_config_action.verify(&skeleton_key)?, - package_volumes: package_volumes.verify(&skeleton_key)?, - package_version: package_version.verify(&skeleton_key)?, - docker_containers: docker_containers.verify(&skeleton_key)?, - }) - } - } -} - #[command( subcommands(self(configure_impl(async)), configure_dry), display(display_none) @@ -801,17 +588,12 @@ pub async fn add_dependent_to_current_dependents_lists<'a, Db: DbHandle>( Ok(()) } -#[derive(Debug, Clone, Default, Deserialize, Serialize)] -pub struct DependencyErrors(pub BTreeMap); +#[derive(Debug, Clone, Default, Deserialize, Serialize, HasModel)] +#[model = "Model"] +pub struct DependencyErrors(pub BTreeMap>); impl Map for DependencyErrors { type Key = PackageId; - type Value = DependencyError; - fn get(&self, key: &Self::Key) -> Option<&Self::Value> { - self.0.get(key) - } -} -impl HasModel for DependencyErrors { - type Model = MapModel; + type Value = OrdSet; } impl DependencyErrors { pub async fn init( diff --git a/backend/src/error.rs b/backend/src/error.rs index d4286339d..2b769b03a 100644 --- a/backend/src/error.rs +++ b/backend/src/error.rs @@ -1,5 +1,5 @@ use color_eyre::eyre::eyre; -pub use models::{Error, ErrorKind, ResultExt}; +pub use models::{Error, ErrorKind, OptionExt, ResultExt}; #[derive(Debug, Default)] pub struct ErrorCollection(Vec); @@ -54,7 +54,7 @@ impl std::fmt::Display for ErrorCollection { macro_rules! ensure_code { ($x:expr, $c:expr, $fmt:expr $(, $arg:expr)*) => { if !($x) { - return Err(crate::Error::new(color_eyre::eyre::eyre!($fmt, $($arg, )*), $c)); + return Err(crate::error::Error::new(color_eyre::eyre::eyre!($fmt, $($arg, )*), $c)); } }; } diff --git a/backend/src/manager/health.rs b/backend/src/manager/health.rs index a70a19f37..01c0d6bf8 100644 --- a/backend/src/manager/health.rs +++ b/backend/src/manager/health.rs @@ -1,111 +1,34 @@ use std::collections::BTreeMap; -use patch_db::{DbHandle, LockReceipt, LockType}; +use models::OptionExt; use tracing::instrument; use crate::context::RpcContext; use crate::db::model::CurrentDependents; +use crate::db::prelude::*; use crate::dependencies::{break_transitive, heal_transitive, DependencyError}; use crate::s9pk::manifest::{Manifest, PackageId}; use crate::status::health_check::{HealthCheckId, HealthCheckResult}; use crate::status::MainStatus; use crate::Error; -struct HealthCheckPreInformationReceipt { - status_model: LockReceipt, - manifest: LockReceipt, -} -impl HealthCheckPreInformationReceipt { - pub async fn new(db: &'_ mut impl DbHandle, id: &PackageId) -> Result { - let mut locks = Vec::new(); - - let setup = Self::setup(&mut locks, id); - setup(&db.lock_all(locks).await?) - } - - pub fn setup( - locks: &mut Vec, - id: &PackageId, - ) -> impl FnOnce(&patch_db::Verifier) -> Result { - let status_model = crate::db::DatabaseModel::new() - .package_data() - .idx_model(id) - .and_then(|x| x.installed()) - .map(|x| x.status().main()) - .make_locker(LockType::Read) - .add_to_keys(locks); - let manifest = crate::db::DatabaseModel::new() - .package_data() - .idx_model(id) - .and_then(|x| x.installed()) - .map(|x| x.manifest()) - .make_locker(LockType::Read) - .add_to_keys(locks); - move |skeleton_key| { - Ok(Self { - status_model: status_model.verify(skeleton_key)?, - manifest: manifest.verify(skeleton_key)?, - }) - } - } -} - -struct HealthCheckStatusReceipt { - status: LockReceipt, - current_dependents: LockReceipt, -} -impl HealthCheckStatusReceipt { - pub async fn new(db: &'_ mut impl DbHandle, id: &PackageId) -> Result { - let mut locks = Vec::new(); - - let setup = Self::setup(&mut locks, id); - setup(&db.lock_all(locks).await?) - } - - pub fn setup( - locks: &mut Vec, - id: &PackageId, - ) -> impl FnOnce(&patch_db::Verifier) -> Result { - let status = crate::db::DatabaseModel::new() - .package_data() - .idx_model(id) - .and_then(|x| x.installed()) - .map(|x| x.status().main()) - .make_locker(LockType::Write) - .add_to_keys(locks); - let current_dependents = crate::db::DatabaseModel::new() - .package_data() - .idx_model(id) - .and_then(|x| x.installed()) - .map(|x| x.current_dependents()) - .make_locker(LockType::Read) - .add_to_keys(locks); - move |skeleton_key| { - Ok(Self { - status: status.verify(skeleton_key)?, - current_dependents: current_dependents.verify(skeleton_key)?, - }) - } - } -} - /// So, this is used for a service to run a health check cycle, go out and run the health checks, and store those in the db #[instrument(skip_all)] -pub async fn check( - ctx: &RpcContext, - db: &mut Db, - id: &PackageId, -) -> Result<(), Error> { - let mut tx = db.begin().await?; +pub async fn check(ctx: &RpcContext, id: &PackageId) -> Result<(), Error> { let (manifest, started) = { - let mut checkpoint = tx.begin().await?; - let receipts = HealthCheckPreInformationReceipt::new(&mut checkpoint, id).await?; + let pde = ctx + .db + .peek() + .await? + .as_package_data() + .as_idx(id) + .or_not_found(id)? + .expect_as_installed()?; - let manifest = receipts.manifest.get(&mut checkpoint).await?; + let manifest = pde.as_installed().as_manifest().de()?; - let started = receipts.status_model.get(&mut checkpoint).await?.started(); + let started = pde.as_installed().as_status().as_main().de()?.started(); - checkpoint.save().await?; (manifest, started) }; @@ -119,31 +42,26 @@ pub async fn check( return Ok(()); }; - let current_dependents = { - let mut checkpoint = tx.begin().await?; - let receipts = HealthCheckStatusReceipt::new(&mut checkpoint, id).await?; - - let status = receipts.status.get(&mut checkpoint).await?; - - if let MainStatus::Running { health: _, started } = status { - receipts - .status - .set( - &mut checkpoint, - MainStatus::Running { - health: health_results.clone(), - started, - }, - ) - .await?; - } - let current_dependents = receipts.current_dependents.get(&mut checkpoint).await?; - - checkpoint.save().await?; - current_dependents - }; - - let receipts = crate::dependencies::BreakTransitiveReceipts::new(&mut tx).await?; + let current_dependents = ctx + .db + .mutate(|v| { + let mut pde = v + .as_package_data_mut() + .as_idx_mut(id) + .or_not_found(id)? + .expect_as_installed_mut()?; + let mut status = pde.as_installed_mut().as_status_mut().as_main_mut(); + + if let MainStatus::Running { health: _, started } = status.de()? { + status.ser(&MainStatus::Running { + health: health_results.clone(), + started, + })?; + } + + pde.as_installed().as_current_dependents().de() + }) + .await?; for (dependent, info) in (current_dependents).0.iter() { let failures: BTreeMap = health_results diff --git a/backend/src/procedure/docker.rs b/backend/src/procedure/docker.rs index 8a43eea19..3539bab76 100644 --- a/backend/src/procedure/docker.rs +++ b/backend/src/procedure/docker.rs @@ -24,6 +24,7 @@ use tracing::instrument; use super::ProcedureName; use crate::context::RpcContext; +use crate::prelude::*; use crate::s9pk::manifest::{PackageId, SYSTEM_PACKAGE_ID}; use crate::util::docker::{remove_container, CONTAINER_TOOL}; use crate::util::serde::{Duration as SerdeDuration, IoFormat}; @@ -44,8 +45,9 @@ lazy_static::lazy_static! { }; } -#[derive(Clone, Debug, Deserialize, Serialize, patch_db::HasModel)] +#[derive(Clone, Debug, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] +#[model = "Model"] pub struct DockerContainers { pub main: DockerContainer, // #[serde(default)] diff --git a/backend/src/s9pk/manifest.rs b/backend/src/s9pk/manifest.rs index 52b499375..c33d61cb1 100644 --- a/backend/src/s9pk/manifest.rs +++ b/backend/src/s9pk/manifest.rs @@ -3,7 +3,6 @@ use std::path::{Path, PathBuf}; use color_eyre::eyre::eyre; pub use models::{PackageId, SYSTEM_PACKAGE_ID}; -use patch_db::HasModel; use serde::{Deserialize, Serialize}; use url::Url; @@ -14,6 +13,7 @@ use crate::config::action::ConfigActions; use crate::dependencies::Dependencies; use crate::migration::Migrations; use crate::net::interface::Interfaces; +use crate::prelude::*; use crate::procedure::docker::DockerContainers; use crate::procedure::PackageProcedure; use crate::status::health_check::HealthChecks; @@ -29,6 +29,7 @@ fn current_version() -> Version { #[derive(Clone, Debug, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] +#[model = "Model"] pub struct Manifest { #[serde(default = "current_version")] pub eos_version: Version, @@ -36,7 +37,6 @@ pub struct Manifest { #[serde(default)] pub git_hash: Option, pub title: String, - #[model] pub version: Version, pub description: Description, #[serde(default)] @@ -52,31 +52,23 @@ pub struct Manifest { pub donation_url: Option, #[serde(default)] pub alerts: Alerts, - #[model] pub main: PackageProcedure, pub health_checks: HealthChecks, - #[model] pub config: Option, - #[model] pub properties: Option, - #[model] pub volumes: Volumes, // #[serde(default)] pub interfaces: Interfaces, // #[serde(default)] - #[model] pub backup: BackupActions, #[serde(default)] - #[model] pub migrations: Migrations, #[serde(default)] pub actions: Actions, // #[serde(default)] // pub permissions: Permissions, #[serde(default)] - #[model] pub dependencies: Dependencies, - #[model] pub containers: Option, #[serde(default)] diff --git a/backend/src/status/mod.rs b/backend/src/status/mod.rs index eba367580..412de2991 100644 --- a/backend/src/status/mod.rs +++ b/backend/src/status/mod.rs @@ -1,25 +1,24 @@ use std::collections::BTreeMap; use chrono::{DateTime, Utc}; -use patch_db::{HasModel, Model}; use serde::{Deserialize, Serialize}; use self::health_check::HealthCheckId; use crate::dependencies::DependencyErrors; +use crate::prelude::*; use crate::status::health_check::HealthCheckResult; pub mod health_check; #[derive(Clone, Debug, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] +#[model = "Model"] pub struct Status { pub configured: bool, - #[model] pub main: MainStatus, - #[model] pub dependency_errors: DependencyErrors, } -#[derive(Debug, Clone, Deserialize, Serialize, HasModel)] +#[derive(Debug, Clone, Deserialize, Serialize)] #[serde(tag = "status")] #[serde(rename_all = "kebab-case")] pub enum MainStatus { @@ -83,8 +82,3 @@ impl MainStatus { MainStatus::BackingUp { started, health } } } -impl MainStatusModel { - pub fn started(self) -> Model>> { - self.0.child("started") - } -} From f25eb01adf1da38b450a8ff4466d29a1fce9f3bf Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Tue, 29 Aug 2023 10:06:28 -0600 Subject: [PATCH 11/89] wip migration --- backend/src/migration.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/src/migration.rs b/backend/src/migration.rs index 3961907e7..548943edb 100644 --- a/backend/src/migration.rs +++ b/backend/src/migration.rs @@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize}; use tracing::instrument; use crate::context::RpcContext; +use crate::prelude::*; use crate::procedure::docker::DockerContainers; use crate::procedure::{PackageProcedure, ProcedureName}; use crate::s9pk::manifest::PackageId; @@ -19,6 +20,7 @@ use crate::{Error, ResultExt}; #[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] +#[model = "Model"] pub struct Migrations { pub from: IndexMap, pub to: IndexMap, @@ -133,6 +135,7 @@ impl Migrations { #[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] +#[model = "Model"] pub struct MigrationRes { pub configured: bool, } From 38f4b4c9177fc1066eecdf497330525c909c8dad Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Tue, 29 Aug 2023 10:45:44 -0600 Subject: [PATCH 12/89] wip init --- backend/src/dependencies.rs | 10 +-- backend/src/init.rs | 54 +++--------- backend/src/install/cleanup.rs | 4 +- backend/src/version/mod.rs | 151 ++++++++++----------------------- 4 files changed, 60 insertions(+), 159 deletions(-) diff --git a/backend/src/dependencies.rs b/backend/src/dependencies.rs index 2042949d0..52058b523 100644 --- a/backend/src/dependencies.rs +++ b/backend/src/dependencies.rs @@ -567,7 +567,7 @@ pub async fn configure_logic( }) } #[instrument(skip_all)] -pub async fn add_dependent_to_current_dependents_lists<'a, Db: DbHandle>( +pub async fn add_dependent_to_current_dependents_lists<'a>( db: &mut Db, dependent_id: &PackageId, current_dependencies: &CurrentDependencies, @@ -635,7 +635,7 @@ impl std::fmt::Display for DependencyErrors { } } -pub async fn break_all_dependents_transitive<'a, Db: DbHandle>( +pub async fn break_all_dependents_transitive<'a>( db: &'a mut Db, id: &'a PackageId, error: DependencyError, @@ -697,7 +697,7 @@ impl BreakTransitiveReceipts { } #[instrument(skip_all)] -pub fn break_transitive<'a, Db: DbHandle>( +pub fn break_transitive<'a>( db: &'a mut Db, id: &'a PackageId, dependency: &'a PackageId, @@ -764,7 +764,7 @@ pub fn break_transitive<'a, Db: DbHandle>( } #[instrument(skip_all)] -pub async fn heal_all_dependents_transitive<'a, Db: DbHandle>( +pub async fn heal_all_dependents_transitive<'a>( ctx: &'a RpcContext, db: &'a mut Db, id: &'a PackageId, @@ -782,7 +782,7 @@ pub async fn heal_all_dependents_transitive<'a, Db: DbHandle>( } #[instrument(skip_all)] -pub fn heal_transitive<'a, Db: DbHandle>( +pub fn heal_transitive<'a>( ctx: &'a RpcContext, db: &'a mut Db, id: &'a PackageId, diff --git a/backend/src/init.rs b/backend/src/init.rs index 2dbcbb977..6bf0158da 100644 --- a/backend/src/init.rs +++ b/backend/src/init.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::fs::Permissions; use std::os::unix::fs::PermissionsExt; use std::path::Path; @@ -7,17 +6,17 @@ use std::time::Duration; use color_eyre::eyre::eyre; use helpers::NonDetachingJoinHandle; use models::ResultExt; -use patch_db::{DbHandle, LockReceipt, LockType}; use rand::random; use sqlx::{Pool, Postgres}; use tokio::process::Command; use crate::account::AccountInfo; use crate::context::rpc::RpcContextConfig; -use crate::db::model::{ServerInfo, ServerStatus}; +use crate::db::model::ServerStatus; use crate::disk::mount::util::unmount; use crate::install::PKG_ARCHIVE_DIR; use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH; +use crate::prelude::*; use crate::sound::BEP; use crate::system::time; use crate::util::docker::{create_bridge_network, CONTAINER_DATADIR, CONTAINER_TOOL}; @@ -40,39 +39,6 @@ pub async fn check_time_is_synchronized() -> Result { == "NTPSynchronized=yes") } -pub struct InitReceipts { - pub server_info: LockReceipt, - pub server_version: LockReceipt, - pub version_range: LockReceipt, -} -impl InitReceipts { - pub async fn new(db: &mut impl DbHandle) -> Result { - let mut locks = Vec::new(); - - let server_info = crate::db::DatabaseModel::new() - .server_info() - .make_locker(LockType::Write) - .add_to_keys(&mut locks); - let server_version = crate::db::DatabaseModel::new() - .server_info() - .version() - .make_locker(LockType::Write) - .add_to_keys(&mut locks); - let version_range = crate::db::DatabaseModel::new() - .server_info() - .eos_version_compat() - .make_locker(LockType::Write) - .add_to_keys(&mut locks); - - let skeleton_key = db.lock_all(locks).await?; - Ok(Self { - server_info: server_info.verify(&skeleton_key)?, - server_version: server_version.verify(&skeleton_key)?, - version_range: version_range.verify(&skeleton_key)?, - }) - } -} - // must be idempotent pub async fn init_postgres(datadir: impl AsRef) -> Result<(), Error> { let db_dir = datadir.as_ref().join("main/postgresql"); @@ -224,12 +190,8 @@ pub async fn init(cfg: &RpcContextConfig) -> Result { let account = AccountInfo::load(&secret_store).await?; let db = cfg.db(&account).await?; tracing::info!("Opened PatchDB"); - let mut handle = db.handle(); - let mut server_info = crate::db::DatabaseModel::new() - .server_info() - .get_mut(&mut handle) - .await?; - let receipts = InitReceipts::new(&mut handle).await?; + let peek = db.peek().await?; + let mut server_info = peek.as_server_info().de()?; // write to ca cert store tokio::fs::write( @@ -388,9 +350,13 @@ pub async fn init(cfg: &RpcContextConfig) -> Result { server_info.system_start_time = time().await?; - server_info.save(&mut handle).await?; + db.mutate(|v| { + v.as_server_info_mut().ser(&server_info)?; + Ok(()) + }) + .await?; - crate::version::init(&mut handle, &secret_store, &receipts).await?; + crate::version::init(&mut db, &secret_store).await?; if should_rebuild { match tokio::fs::remove_file(SYSTEM_REBUILD_PATH).await { diff --git a/backend/src/install/cleanup.rs b/backend/src/install/cleanup.rs index 0cc4cbbef..4bbb28127 100644 --- a/backend/src/install/cleanup.rs +++ b/backend/src/install/cleanup.rs @@ -61,7 +61,7 @@ impl UpdateDependencyReceipts { } #[instrument(skip_all)] -pub async fn update_dependency_errors_of_dependents<'a, Db: DbHandle>( +pub async fn update_dependency_errors_of_dependents<'a>( ctx: &RpcContext, db: &mut Db, id: &PackageId, @@ -237,7 +237,7 @@ pub async fn cleanup_failed( } #[instrument(skip_all)] -pub async fn remove_from_current_dependents_lists<'a, Db: DbHandle>( +pub async fn remove_from_current_dependents_lists<'a>( db: &mut Db, id: &'a PackageId, current_dependencies: &'a CurrentDependencies, diff --git a/backend/src/version/mod.rs b/backend/src/version/mod.rs index 797d8fb8d..fcd4c521e 100644 --- a/backend/src/version/mod.rs +++ b/backend/src/version/mod.rs @@ -2,11 +2,10 @@ use std::cmp::Ordering; use async_trait::async_trait; use color_eyre::eyre::eyre; -use patch_db::DbHandle; use rpc_toolkit::command; use sqlx::PgPool; -use crate::init::InitReceipts; +use crate::prelude::*; use crate::Error; mod v0_3_0; @@ -89,55 +88,43 @@ where fn new() -> Self; fn semver(&self) -> emver::Version; fn compat(&self) -> &'static emver::VersionRange; - async fn up(&self, db: &mut Db, secrets: &PgPool) -> Result<(), Error>; - async fn down(&self, db: &mut Db, secrets: &PgPool) -> Result<(), Error>; - async fn commit( - &self, - db: &mut Db, - receipts: &InitReceipts, - ) -> Result<(), Error> { - receipts - .version_range - .set(db, self.compat().clone()) - .await?; - receipts - .server_version - .set(db, self.semver().into()) - .await?; - + async fn up(&self, db: PatchDb, secrets: &PgPool) -> Result<(), Error>; + async fn down(&self, db: PatchDb, secrets: &PgPool) -> Result<(), Error>; + async fn commit(&self, db: PatchDb) -> Result<(), Error> { + db.mutate(|d| { + db.as_server_info_mut() + .as_server_version_mut() + .ser(&self.semver().into())?; + db.as_server_info_mut() + .as_eos_version_compat() + .ser(&self.compat().clone())?; + Ok(()) + }) + .await?; Ok(()) } - async fn migrate_to( + async fn migrate_to( &self, version: &V, - db: &mut Db, + db: PatchDb, secrets: &PgPool, - receipts: &InitReceipts, ) -> Result<(), Error> { match self.semver().cmp(&version.semver()) { - Ordering::Greater => { - self.rollback_to_unchecked(version, db, secrets, receipts) - .await - } - Ordering::Less => { - version - .migrate_from_unchecked(self, db, secrets, receipts) - .await - } + Ordering::Greater => self.rollback_to_unchecked(version, db, secrets).await, + Ordering::Less => version.migrate_from_unchecked(self, db, secrets).await, Ordering::Equal => Ok(()), } } - async fn migrate_from_unchecked( + async fn migrate_from_unchecked( &self, version: &V, - db: &mut Db, + db: PatchDb, secrets: &PgPool, - receipts: &InitReceipts, ) -> Result<(), Error> { let previous = Self::Previous::new(); if version.semver() < previous.semver() { previous - .migrate_from_unchecked(version, db, secrets, receipts) + .migrate_from_unchecked(version, db, secrets) .await?; } else if version.semver() > previous.semver() { return Err(Error::new( @@ -150,24 +137,21 @@ where } tracing::info!("{} -> {}", previous.semver(), self.semver(),); self.up(db, secrets).await?; - self.commit(db, receipts).await?; + self.commit(db).await?; Ok(()) } - async fn rollback_to_unchecked( + async fn rollback_to_unchecked( &self, version: &V, - db: &mut Db, + db: PatchDb, secrets: &PgPool, - receipts: &InitReceipts, ) -> Result<(), Error> { let previous = Self::Previous::new(); tracing::info!("{} -> {}", self.semver(), previous.semver(),); self.down(db, secrets).await?; - previous.commit(db, receipts).await?; + previous.commit(db).await?; if version.semver() < previous.semver() { - previous - .rollback_to_unchecked(version, db, secrets, receipts) - .await?; + previous.rollback_to_unchecked(version, db, secrets).await?; } else if version.semver() > previous.semver() { return Err(Error::new( eyre!( @@ -205,73 +189,24 @@ where } } -pub async fn init( - db: &mut Db, - secrets: &PgPool, - receipts: &crate::init::InitReceipts, -) -> Result<(), Error> { - let version = Version::from_util_version(receipts.server_version.get(db).await?); +pub async fn init(db: &PatchDb, secrets: &PgPool) -> Result<(), Error> { + let version = Version::from_util_version(db.peek().await?.as_server_version().de()); match version { - Version::V0_3_0(v) => { - v.0.migrate_to(&Current::new(), db, secrets, receipts) - .await? - } - Version::V0_3_0_1(v) => { - v.0.migrate_to(&Current::new(), db, secrets, receipts) - .await? - } - Version::V0_3_0_2(v) => { - v.0.migrate_to(&Current::new(), db, secrets, receipts) - .await? - } - Version::V0_3_0_3(v) => { - v.0.migrate_to(&Current::new(), db, secrets, receipts) - .await? - } - Version::V0_3_1(v) => { - v.0.migrate_to(&Current::new(), db, secrets, receipts) - .await? - } - Version::V0_3_1_1(v) => { - v.0.migrate_to(&Current::new(), db, secrets, receipts) - .await? - } - Version::V0_3_1_2(v) => { - v.0.migrate_to(&Current::new(), db, secrets, receipts) - .await? - } - Version::V0_3_2(v) => { - v.0.migrate_to(&Current::new(), db, secrets, receipts) - .await? - } - Version::V0_3_2_1(v) => { - v.0.migrate_to(&Current::new(), db, secrets, receipts) - .await? - } - Version::V0_3_3(v) => { - v.0.migrate_to(&Current::new(), db, secrets, receipts) - .await? - } - Version::V0_3_4(v) => { - v.0.migrate_to(&Current::new(), db, secrets, receipts) - .await? - } - Version::V0_3_4_1(v) => { - v.0.migrate_to(&Current::new(), db, secrets, receipts) - .await? - } - Version::V0_3_4_2(v) => { - v.0.migrate_to(&Current::new(), db, secrets, receipts) - .await? - } - Version::V0_3_4_3(v) => { - v.0.migrate_to(&Current::new(), db, secrets, receipts) - .await? - } - Version::V0_3_4_4(v) => { - v.0.migrate_to(&Current::new(), db, secrets, receipts) - .await? - } + Version::V0_3_0(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, + Version::V0_3_0_1(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, + Version::V0_3_0_2(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, + Version::V0_3_0_3(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, + Version::V0_3_1(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, + Version::V0_3_1_1(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, + Version::V0_3_1_2(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, + Version::V0_3_2(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, + Version::V0_3_2_1(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, + Version::V0_3_3(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, + Version::V0_3_4(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, + Version::V0_3_4_1(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, + Version::V0_3_4_2(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, + Version::V0_3_4_3(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, + Version::V0_3_4_4(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, Version::Other(_) => { return Err(Error::new( eyre!("Cannot downgrade"), From 53748cfc445cbe6b98073ab1114e596b2aaca4ec Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Tue, 29 Aug 2023 11:41:54 -0600 Subject: [PATCH 13/89] wip auth/control --- backend/src/auth.rs | 33 ++--- backend/src/control.rs | 252 +++++++++++++++++--------------------- backend/src/db/prelude.rs | 8 ++ 3 files changed, 128 insertions(+), 165 deletions(-) diff --git a/backend/src/auth.rs b/backend/src/auth.rs index 1ccbf81df..2819fe77e 100644 --- a/backend/src/auth.rs +++ b/backend/src/auth.rs @@ -5,7 +5,6 @@ use chrono::{DateTime, Utc}; use clap::ArgMatches; use color_eyre::eyre::eyre; use josekit::jwk::Jwk; -use patch_db::{DbHandle, LockReceipt}; use rpc_toolkit::command; use rpc_toolkit::command_helpers::prelude::{RequestParts, ResponseParts}; use rpc_toolkit::yajrc::RpcError; @@ -17,6 +16,7 @@ use tracing::instrument; use crate::context::{CliContext, RpcContext}; use crate::middleware::auth::{AsLogoutSessionId, HasLoggedOutSessions, HashSessionToken}; use crate::middleware::encrypt::EncryptedWire; +use crate::prelude::*; use crate::util::display_none; use crate::util::serde::{display_serializable, IoFormat}; use crate::{ensure_code, Error, ResultExt}; @@ -343,27 +343,6 @@ async fn cli_reset_password( Ok(()) } -pub struct SetPasswordReceipt(LockReceipt); -impl SetPasswordReceipt { - pub async fn new(db: &mut Db) -> Result { - let mut locks = Vec::new(); - - let setup = Self::setup(&mut locks); - Ok(setup(&db.lock_all(locks).await?)?) - } - - pub fn setup( - locks: &mut Vec, - ) -> impl FnOnce(&patch_db::Verifier) -> Result { - let password_hash = crate::db::DatabaseModel::new() - .server_info() - .password_hash() - .make_locker(patch_db::LockType::Write) - .add_to_keys(locks); - move |skeleton_key| Ok(Self(password_hash.verify(skeleton_key)?)) - } -} - #[command( rename = "reset-password", custom_cli(cli_reset_password(async, context(CliContext))), @@ -389,10 +368,12 @@ pub async fn reset_password( } account.set_password(&new_password)?; account.save(&ctx.secret_store).await?; - crate::db::DatabaseModel::new() - .server_info() - .password_hash() - .put(&mut ctx.db.handle(), &account.password) + ctx.db + .mutate(|d| { + d.as_server_info_mut() + .as_password_hash_mut() + .ser(&account.password) + }) .await?; Ok(()) diff --git a/backend/src/control.rs b/backend/src/control.rs index 794afc64a..4356f327a 100644 --- a/backend/src/control.rs +++ b/backend/src/control.rs @@ -1,7 +1,6 @@ use std::collections::BTreeMap; use color_eyre::eyre::eyre; -use patch_db::{DbHandle, LockReceipt, LockType}; use rpc_toolkit::command; use tracing::instrument; @@ -10,68 +9,64 @@ use crate::dependencies::{ break_all_dependents_transitive, heal_all_dependents_transitive, BreakageRes, DependencyError, DependencyReceipt, TaggedDependencyError, }; +use crate::prelude::*; use crate::s9pk::manifest::PackageId; use crate::status::MainStatus; use crate::util::display_none; use crate::util::serde::display_serializable; use crate::Error; -#[derive(Clone)] -pub struct StartReceipts { - dependency_receipt: DependencyReceipt, - status: LockReceipt, - version: LockReceipt, -} - -impl StartReceipts { - pub async fn new(db: &mut impl DbHandle, id: &PackageId) -> Result { - let mut locks = Vec::new(); - - let setup = Self::setup(&mut locks, id); - setup(&db.lock_all(locks).await?) - } - - pub fn setup( - locks: &mut Vec, - id: &PackageId, - ) -> impl FnOnce(&patch_db::Verifier) -> Result { - let dependency_receipt = DependencyReceipt::setup(locks); - let status = crate::db::DatabaseModel::new() - .package_data() - .idx_model(id) - .and_then(|x| x.installed()) - .map(|x| x.status().main()) - .make_locker(LockType::Write) - .add_to_keys(locks); - let version = crate::db::DatabaseModel::new() - .package_data() - .idx_model(id) - .and_then(|x| x.installed()) - .map(|x| x.manifest().version()) - .make_locker(LockType::Read) - .add_to_keys(locks); - move |skeleton_key| { - Ok(Self { - dependency_receipt: dependency_receipt(skeleton_key)?, - status: status.verify(skeleton_key)?, - version: version.verify(skeleton_key)?, - }) - } - } -} +// impl StartReceipts { +// pub async fn new(db: PatchDb, id: &PackageId) -> Result { +// let mut locks = Vec::new(); + +// let setup = Self::setup(&mut locks, id); +// setup(&db.lock_all(locks).await?) +// } + +// pub fn setup( +// locks: &mut Vec, +// id: &PackageId, +// ) -> impl FnOnce(&patch_db::Verifier) -> Result { +// let dependency_receipt = DependencyReceipt::setup(locks); +// let status = crate::db::DatabaseModel::new() +// .package_data() +// .idx_model(id) +// .and_then(|x| x.installed()) +// .map(|x| x.status().main()) +// .make_locker(LockType::Write) +// .add_to_keys(locks); +// let version = crate::db::DatabaseModel::new() +// .package_data() +// .idx_model(id) +// .and_then(|x| x.installed()) +// .map(|x| x.manifest().version()) +// .make_locker(LockType::Read) +// .add_to_keys(locks); +// move |skeleton_key| { +// Ok(Self { +// dependency_receipt: dependency_receipt(skeleton_key)?, +// status: status.verify(skeleton_key)?, +// version: version.verify(skeleton_key)?, +// }) +// } +// } +// } #[command(display(display_none), metadata(sync_db = true))] #[instrument(skip_all)] pub async fn start(#[context] ctx: RpcContext, #[arg] id: PackageId) -> Result<(), Error> { - let mut db = ctx.db.handle(); - let mut tx = db.begin().await?; - let receipts = StartReceipts::new(&mut tx, &id).await?; - let version = receipts.version.get(&mut tx).await?; - receipts.status.set(&mut tx, MainStatus::Starting).await?; - heal_all_dependents_transitive(&ctx, &mut tx, &id, &receipts.dependency_receipt).await?; - - tx.commit().await?; - drop(receipts); + let peek = ctx.db.peek().await?; + let version = peek + .as_package_data() + .as_idx(&id) + .or_not_found(&id)? + .as_installed() + .or_not_found(&id)? + .as_manifest() + .as_version() + .de()?; + heal_all_dependents_transitive(&ctx, &mut tx, &id).await?; ctx.managers .get(&(id, version)) @@ -81,61 +76,53 @@ pub async fn start(#[context] ctx: RpcContext, #[arg] id: PackageId) -> Result<( Ok(()) } -#[derive(Clone)] -pub struct StopReceipts { - breaks: crate::dependencies::BreakTransitiveReceipts, - status: LockReceipt, -} - -impl StopReceipts { - pub async fn new<'a>(db: &'a mut impl DbHandle, id: &PackageId) -> Result { - let mut locks = Vec::new(); - - let setup = Self::setup(&mut locks, id); - setup(&db.lock_all(locks).await?) - } - - pub fn setup( - locks: &mut Vec, - id: &PackageId, - ) -> impl FnOnce(&patch_db::Verifier) -> Result { - let breaks = crate::dependencies::BreakTransitiveReceipts::setup(locks); - let status = crate::db::DatabaseModel::new() - .package_data() - .idx_model(id) - .and_then(|x| x.installed()) - .map(|x| x.status().main()) - .make_locker(LockType::Write) - .add_to_keys(locks); - move |skeleton_key| { - Ok(Self { - breaks: breaks(skeleton_key)?, - status: status.verify(skeleton_key)?, - }) - } - } -} +// impl StopReceipts { +// pub async fn new<'a>(db: &'a mut impl DbHandle, id: &PackageId) -> Result { +// let mut locks = Vec::new(); + +// let setup = Self::setup(&mut locks, id); +// setup(&db.lock_all(locks).await?) +// } + +// pub fn setup( +// locks: &mut Vec, +// id: &PackageId, +// ) -> impl FnOnce(&patch_db::Verifier) -> Result { +// let breaks = crate::dependencies::BreakTransitiveReceipts::setup(locks); +// let status = crate::db::DatabaseModel::new() +// .package_data() +// .idx_model(id) +// .and_then(|x| x.installed()) +// .map(|x| x.status().main()) +// .make_locker(LockType::Write) +// .add_to_keys(locks); +// move |skeleton_key| { +// Ok(Self { +// breaks: breaks(skeleton_key)?, +// status: status.verify(skeleton_key)?, +// }) +// } +// } +// } #[instrument(skip_all)] -pub async fn stop_common( - db: &mut Db, +pub async fn stop_common( + db: PatchDb, id: &PackageId, breakages: &mut BTreeMap, ) -> Result { - let mut tx = db.begin().await?; - let receipts = StopReceipts::new(&mut tx, id).await?; - let last_status = receipts.status.get(&mut tx).await?; - receipts.status.set(&mut tx, MainStatus::Stopping).await?; - - tx.save().await?; - break_all_dependents_transitive( - db, - id, - DependencyError::NotRunning, - breakages, - &receipts.breaks, - ) - .await?; + let last_status = db + .mutate(|v| { + v.as_package_data_mut() + .as_idx_mut(id) + .and_then(|x| x.as_installed_mut()) + .ok_or_else(|| Error::new(eyre!("{} is not installed", id), ErrorKind::NotFound))? + .as_status_mut() + .as_main_mut() + .replace(&MainStatus::Stopping) + }) + .await?; + break_all_dependents_transitive(db, id, DependencyError::NotRunning, breakages).await?; Ok(last_status) } @@ -168,25 +155,19 @@ pub async fn stop_dry( #[instrument(skip_all)] pub async fn stop_impl(ctx: RpcContext, id: PackageId) -> Result { - let mut db = ctx.db.handle(); - let mut tx = db.begin().await?; - let version = crate::db::DatabaseModel::new() - .package_data() - .idx_model(&id) - .expect(&mut tx) - .await? - .installed() - .expect(&mut tx) - .await? - .manifest() - .version() - .get(&mut tx) - .await? - .clone(); - - let last_statuts = stop_common(&mut tx, &id, &mut BTreeMap::new()).await?; - - tx.commit().await?; + let peek = ctx.db.peek().await?; + let version = peek + .as_package_data() + .as_idx(&id) + .or_not_found(&id)? + .as_installed() + .or_not_found(&id)? + .as_manifest() + .as_version() + .de()?; + + let last_statuts = stop_common(ctx.db.clone(), &id, &mut BTreeMap::new()).await?; + ctx.managers .get(&(id, version)) .await @@ -198,23 +179,16 @@ pub async fn stop_impl(ctx: RpcContext, id: PackageId) -> Result Result<(), Error> { - let mut db = ctx.db.handle(); - let mut tx = db.begin().await?; - let version = crate::db::DatabaseModel::new() - .package_data() - .idx_model(&id) - .expect(&mut tx) - .await? - .installed() - .expect(&mut tx) - .await? - .manifest() - .version() - .get(&mut tx) - .await? - .clone(); - - tx.commit().await?; + let peek = ctx.db.peek().await?; + let version = peek + .as_package_data() + .as_idx(&id) + .or_not_found(&id)? + .as_installed() + .or_not_found(&id)? + .as_manifest() + .as_version() + .de()?; ctx.managers .get(&(id, version)) diff --git a/backend/src/db/prelude.rs b/backend/src/db/prelude.rs index f295a0dd8..b50e75c86 100644 --- a/backend/src/db/prelude.rs +++ b/backend/src/db/prelude.rs @@ -86,6 +86,14 @@ impl Model { Ok(()) } } + +impl Model { + pub fn replace(&mut self, value: &T) -> Result { + let orig = self.de()?; + self.ser(value)?; + Ok(orig) + } +} impl Clone for Model { fn clone(&self) -> Self { Self { From ee050394ce6ff3cad297c0b88ad361bab69c089d Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Tue, 29 Aug 2023 11:47:27 -0600 Subject: [PATCH 14/89] wip action --- backend/src/action.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/backend/src/action.rs b/backend/src/action.rs index e397e8f2f..263cd07cb 100644 --- a/backend/src/action.rs +++ b/backend/src/action.rs @@ -11,6 +11,7 @@ use tracing::instrument; use crate::config::{Config, ConfigSpec}; use crate::context::RpcContext; +use crate::prelude::*; use crate::procedure::docker::DockerContainers; use crate::procedure::{PackageProcedure, ProcedureName}; use crate::s9pk::manifest::PackageId; @@ -130,18 +131,17 @@ pub async fn action( #[arg(long = "format")] format: Option, ) -> Result { - let mut db = ctx.db.handle(); - let manifest = crate::db::DatabaseModel::new() - .package_data() - .idx_model(&pkg_id) - .and_then(|p| p.installed()) - .expect(&mut db) - .await - .with_kind(crate::ErrorKind::NotFound)? - .manifest() - .get(&mut db) + let manifest = ctx + .db + .peek() .await? - .to_owned(); + .as_package_data() + .as_idx(&pkg_id) + .or_not_found(&pkg_id)? + .as_installed() + .or_not_found(&pkg_id)? + .as_manifest() + .de()?; if let Some(action) = manifest.actions.0.get(&action_id) { action From e4dd60e7c5ee0258dfd3a23aa6b3896e21175c46 Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Tue, 29 Aug 2023 11:52:23 -0600 Subject: [PATCH 15/89] wip control --- backend/src/control.rs | 65 ------------------------------------------ 1 file changed, 65 deletions(-) diff --git a/backend/src/control.rs b/backend/src/control.rs index 4356f327a..a2b5094da 100644 --- a/backend/src/control.rs +++ b/backend/src/control.rs @@ -16,43 +16,6 @@ use crate::util::display_none; use crate::util::serde::display_serializable; use crate::Error; -// impl StartReceipts { -// pub async fn new(db: PatchDb, id: &PackageId) -> Result { -// let mut locks = Vec::new(); - -// let setup = Self::setup(&mut locks, id); -// setup(&db.lock_all(locks).await?) -// } - -// pub fn setup( -// locks: &mut Vec, -// id: &PackageId, -// ) -> impl FnOnce(&patch_db::Verifier) -> Result { -// let dependency_receipt = DependencyReceipt::setup(locks); -// let status = crate::db::DatabaseModel::new() -// .package_data() -// .idx_model(id) -// .and_then(|x| x.installed()) -// .map(|x| x.status().main()) -// .make_locker(LockType::Write) -// .add_to_keys(locks); -// let version = crate::db::DatabaseModel::new() -// .package_data() -// .idx_model(id) -// .and_then(|x| x.installed()) -// .map(|x| x.manifest().version()) -// .make_locker(LockType::Read) -// .add_to_keys(locks); -// move |skeleton_key| { -// Ok(Self { -// dependency_receipt: dependency_receipt(skeleton_key)?, -// status: status.verify(skeleton_key)?, -// version: version.verify(skeleton_key)?, -// }) -// } -// } -// } - #[command(display(display_none), metadata(sync_db = true))] #[instrument(skip_all)] pub async fn start(#[context] ctx: RpcContext, #[arg] id: PackageId) -> Result<(), Error> { @@ -76,34 +39,6 @@ pub async fn start(#[context] ctx: RpcContext, #[arg] id: PackageId) -> Result<( Ok(()) } -// impl StopReceipts { -// pub async fn new<'a>(db: &'a mut impl DbHandle, id: &PackageId) -> Result { -// let mut locks = Vec::new(); - -// let setup = Self::setup(&mut locks, id); -// setup(&db.lock_all(locks).await?) -// } - -// pub fn setup( -// locks: &mut Vec, -// id: &PackageId, -// ) -> impl FnOnce(&patch_db::Verifier) -> Result { -// let breaks = crate::dependencies::BreakTransitiveReceipts::setup(locks); -// let status = crate::db::DatabaseModel::new() -// .package_data() -// .idx_model(id) -// .and_then(|x| x.installed()) -// .map(|x| x.status().main()) -// .make_locker(LockType::Write) -// .add_to_keys(locks); -// move |skeleton_key| { -// Ok(Self { -// breaks: breaks(skeleton_key)?, -// status: status.verify(skeleton_key)?, -// }) -// } -// } -// } #[instrument(skip_all)] pub async fn stop_common( From a1d75005a97eebe90b582aeddcc9ae9b335c8d1a Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Tue, 29 Aug 2023 12:42:28 -0600 Subject: [PATCH 16/89] wiip 034 --- backend/src/version/v0_3_4.rs | 160 +++++++++++++++------------------- 1 file changed, 70 insertions(+), 90 deletions(-) diff --git a/backend/src/version/v0_3_4.rs b/backend/src/version/v0_3_4.rs index dee3222e9..e1a51faf5 100644 --- a/backend/src/version/v0_3_4.rs +++ b/backend/src/version/v0_3_4.rs @@ -8,7 +8,8 @@ use ssh_key::public::Ed25519PublicKey; use super::v0_3_0::V0_3_0_COMPAT; use super::*; use crate::account::AccountInfo; -use crate::hostname::{generate_hostname, sync_hostname, Hostname}; +use crate::hostname::{sync_hostname, Hostname}; +use crate::prelude::*; const V0_3_4: emver::Version = emver::Version::new(0, 3, 4, 0); @@ -42,105 +43,84 @@ impl VersionT for Version { fn compat(&self) -> &'static VersionRange { &*V0_3_0_COMPAT } - async fn up(&self, db: &mut Db, secrets: &PgPool) -> Result<(), Error> { + async fn up(&self, db: PatchDb, secrets: &PgPool) -> Result<(), Error> { let mut account = AccountInfo::load(secrets).await?; - crate::db::DatabaseModel::new() - .server_info() - .pubkey() - .put( - db, - &ssh_key::PublicKey::from(Ed25519PublicKey::from(&account.key.ssh_key())) - .to_openssh()?, - ) - .await?; - crate::db::DatabaseModel::new() - .server_info() - .ca_fingerprint() - .put( - db, - &account - .root_ca_cert - .digest(MessageDigest::sha256()) - .unwrap() - .iter() - .map(|x| format!("{x:X}")) - .join(":"), - ) + let account = db + .mutate(|d| { + d.as_server_info_mut().as_pub_key_mut().ser( + &ssh_key::PublicKey::from(Ed25519PublicKey::from(&account.key.ssh_key())) + .to_openssh()?, + ); + d.as_server_info_mut().as_ca_fingerprint_mut().ser( + &account + .root_ca_cert + .digest(MessageDigest::sha256()) + .unwrap() + .iter() + .map(|x| format!("{x:X}")) + .join(":"), + ); + let server_info = d.as_server_info(); + account.hostname = server_info.as_hostname().de().map(Hostname)?; + account.server_id = server_info.as_id().de()?; + + Ok(account) + }) .await?; - let server_info = crate::db::DatabaseModel::new() - .server_info() - .get(db) - .await? - .into_owned(); - account.hostname = server_info - .hostname - .map(Hostname) - .unwrap_or_else(generate_hostname); - account.server_id = server_info.id; account.save(secrets).await?; + let peek = db.peek().await?; sync_hostname(&account.hostname).await?; let parsed_url = Some(COMMUNITY_URL.parse().unwrap()); - let mut ui = crate::db::DatabaseModel::new().ui().get_mut(db).await?; - ui["marketplace"]["known-hosts"][COMMUNITY_URL] = json!({}); - ui["marketplace"]["known-hosts"][MAIN_REGISTRY] = json!({}); - for package_id in crate::db::DatabaseModel::new() - .package_data() - .keys(db) - .await? - { - if !COMMUNITY_SERVICES.contains(&&*package_id.to_string()) { - continue; + db.mutate(|d| { + let mut ui = d.as_ui().de()?; + ui["marketplace"]["known-hosts"][COMMUNITY_URL] = json!({}); + ui["marketplace"]["known-hosts"][MAIN_REGISTRY] = json!({}); + for package_id in d.as_package_data().keys()? { + if !COMMUNITY_SERVICES.contains(&&*package_id.to_string()) { + continue; + } + d.as_package_data_mut() + .as_idx_model_mut(&package_id) + .or_not_found(&package_id)? + .as_installed_mut() + .or_not_found(&package_id)? + .as_marketplace_url_mut() + .ser(&parsed_url)?; } - crate::db::DatabaseModel::new() - .package_data() - .idx_model(&package_id) - .expect(db) - .await? - .installed() - .expect(db) - .await? - .marketplace_url() - .put(db, &parsed_url) - .await?; - } - ui["theme"] = json!("Dark".to_string()); - ui["widgets"] = json!([]); - ui.save(db).await?; - Ok(()) + ui["theme"] = json!("Dark".to_string()); + ui["widgets"] = json!([]); + + d.as_ui_mut().ser(&ui) + }) + .await } - async fn down(&self, db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { - let mut ui = crate::db::DatabaseModel::new().ui().get_mut(db).await?; - let parsed_url = Some(MAIN_REGISTRY.parse().unwrap()); - for package_id in crate::db::DatabaseModel::new() - .package_data() - .keys(db) - .await? - { - if !COMMUNITY_SERVICES.contains(&&*package_id.to_string()) { - continue; + async fn down(&self, db: PatchDb, _secrets: &PgPool) -> Result<(), Error> { + db.mutate(|d| { + let mut ui = d.as_ui().de()?; + let parsed_url = Some(MAIN_REGISTRY.parse().unwrap()); + for package_id in db.as_package_data().keys()? { + if !COMMUNITY_SERVICES.contains(&&*package_id.to_string()) { + continue; + } + d.as_package_data_mut() + .as_idx_model_mut(&package_id) + .or_not_found(&package_id)? + .as_installed_mut() + .or_not_found(&package_id)? + .as_marketplace_url_mut() + .ser(&parsed_url)?; } - crate::db::DatabaseModel::new() - .package_data() - .idx_model(&package_id) - .expect(db) - .await? - .installed() - .expect(db) - .await? - .marketplace_url() - .put(db, &parsed_url) - .await?; - } - if let Value::Object(ref mut obj) = *ui { - obj.remove("theme"); - obj.remove("widgets"); - } + if let Value::Object(ref mut obj) = *ui { + obj.remove("theme"); + obj.remove("widgets"); + } - ui["marketplace"]["known-hosts"][COMMUNITY_URL].take(); - ui["marketplace"]["known-hosts"][MAIN_REGISTRY].take(); - ui.save(db).await?; - Ok(()) + ui["marketplace"]["known-hosts"][COMMUNITY_URL].take(); + ui["marketplace"]["known-hosts"][MAIN_REGISTRY].take(); + d.as_ui_mut().ser(&ui) + }) + .await } } From 73ebdaae696af0d0297b417d91e3aabaeaf099eb Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Tue, 29 Aug 2023 12:43:29 -0600 Subject: [PATCH 17/89] wip 344 --- backend/src/version/v0_3_4_4.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/src/version/v0_3_4_4.rs b/backend/src/version/v0_3_4_4.rs index 6870834a2..c3c827f64 100644 --- a/backend/src/version/v0_3_4_4.rs +++ b/backend/src/version/v0_3_4_4.rs @@ -5,6 +5,8 @@ use models::ResultExt; use super::v0_3_0::V0_3_0_COMPAT; use super::*; +use crate::prelude::*; + const V0_3_4_4: emver::Version = emver::Version::new(0, 3, 4, 4); #[derive(Clone, Debug)] @@ -22,7 +24,7 @@ impl VersionT for Version { fn compat(&self) -> &'static VersionRange { &*V0_3_0_COMPAT } - async fn up(&self, db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { + async fn up(&self, db: PatchDb, _secrets: &PgPool) -> Result<(), Error> { let mut tor_addr = db .mutate(|v| { let mut tor_address_lens = v.as_server_info_mut().as_tor_address_mut(); @@ -36,7 +38,7 @@ impl VersionT for Version { .await?; Ok(()) } - async fn down(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { + async fn down(&self, _db: PatchDb, _secrets: &PgPool) -> Result<(), Error> { Ok(()) } } From 09bd4b74c6baf9d1657f60a40a5353853993bf28 Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Tue, 29 Aug 2023 12:49:12 -0600 Subject: [PATCH 18/89] wip some more versions converted --- backend/src/version/v0_3_4_1.rs | 6 ++++-- backend/src/version/v0_3_4_2.rs | 6 ++++-- backend/src/version/v0_3_4_3.rs | 12 ++++-------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/backend/src/version/v0_3_4_1.rs b/backend/src/version/v0_3_4_1.rs index 732cf18d9..cda6263b8 100644 --- a/backend/src/version/v0_3_4_1.rs +++ b/backend/src/version/v0_3_4_1.rs @@ -4,6 +4,8 @@ use emver::VersionRange; use super::v0_3_0::V0_3_0_COMPAT; use super::*; +use crate::prelude::*; + const V0_3_4_1: emver::Version = emver::Version::new(0, 3, 4, 1); #[derive(Clone, Debug)] @@ -21,10 +23,10 @@ impl VersionT for Version { fn compat(&self) -> &'static VersionRange { &*V0_3_0_COMPAT } - async fn up(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { + async fn up(&self, _db: PatchDb, _secrets: &PgPool) -> Result<(), Error> { Ok(()) } - async fn down(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { + async fn down(&self, _db: PatchDb, _secrets: &PgPool) -> Result<(), Error> { Ok(()) } } diff --git a/backend/src/version/v0_3_4_2.rs b/backend/src/version/v0_3_4_2.rs index 1fa4f7939..3d8dd8514 100644 --- a/backend/src/version/v0_3_4_2.rs +++ b/backend/src/version/v0_3_4_2.rs @@ -4,6 +4,8 @@ use emver::VersionRange; use super::v0_3_0::V0_3_0_COMPAT; use super::*; +use crate::prelude::*; + const V0_3_4_2: emver::Version = emver::Version::new(0, 3, 4, 2); #[derive(Clone, Debug)] @@ -21,10 +23,10 @@ impl VersionT for Version { fn compat(&self) -> &'static VersionRange { &*V0_3_0_COMPAT } - async fn up(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { + async fn up(&self, _db: PatchDb, _secrets: &PgPool) -> Result<(), Error> { Ok(()) } - async fn down(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { + async fn down(&self, _db: PatchDb, _secrets: &PgPool) -> Result<(), Error> { Ok(()) } } diff --git a/backend/src/version/v0_3_4_3.rs b/backend/src/version/v0_3_4_3.rs index 814ca3cc1..5e31b1f30 100644 --- a/backend/src/version/v0_3_4_3.rs +++ b/backend/src/version/v0_3_4_3.rs @@ -4,6 +4,8 @@ use emver::VersionRange; use super::v0_3_0::V0_3_0_COMPAT; use super::*; +use crate::prelude::*; + const V0_3_4_3: emver::Version = emver::Version::new(0, 3, 4, 3); #[derive(Clone, Debug)] @@ -21,16 +23,10 @@ impl VersionT for Version { fn compat(&self) -> &'static VersionRange { &*V0_3_0_COMPAT } - async fn up(&self, db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { - crate::db::DatabaseModel::new() - .server_info() - .get_mut(db) - .await? - .save(db) - .await?; + async fn up(&self, db: PatchDb, _secrets: &PgPool) -> Result<(), Error> { Ok(()) } - async fn down(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { + async fn down(&self, _db: PatchDb, _secrets: &PgPool) -> Result<(), Error> { Ok(()) } } From 29953686fd5156e19d6c90d10c25967d0546add8 Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Tue, 29 Aug 2023 12:51:42 -0600 Subject: [PATCH 19/89] feat: Reserialize the version of the db --- backend/src/init.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/src/init.rs b/backend/src/init.rs index 6bf0158da..e09ccfad1 100644 --- a/backend/src/init.rs +++ b/backend/src/init.rs @@ -189,6 +189,11 @@ pub async fn init(cfg: &RpcContextConfig) -> Result { let account = AccountInfo::load(&secret_store).await?; let db = cfg.db(&account).await?; + db.mutate(|d| { + let model = d.de()?; + d.ser(&model) + }) + .await?; tracing::info!("Opened PatchDB"); let peek = db.peek().await?; let mut server_info = peek.as_server_info().de()?; From 77b2777a84b6ba04e2eb61bb4dede545849ede95 Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Tue, 29 Aug 2023 12:57:57 -0600 Subject: [PATCH 20/89] wip rest of the versions --- backend/src/version/mod.rs | 30 ------ backend/src/version/v0_3_0.rs | 37 -------- backend/src/version/v0_3_0_1.rs | 27 ------ backend/src/version/v0_3_0_2.rs | 27 ------ backend/src/version/v0_3_0_3.rs | 27 ------ backend/src/version/v0_3_1.rs | 28 ------ backend/src/version/v0_3_1_1.rs | 28 ------ backend/src/version/v0_3_1_2.rs | 28 ------ backend/src/version/v0_3_2.rs | 156 -------------------------------- backend/src/version/v0_3_2_1.rs | 26 ------ backend/src/version/v0_3_3.rs | 156 -------------------------------- backend/src/version/v0_3_4.rs | 13 ++- backend/src/version/v0_3_4_1.rs | 3 +- backend/src/version/v0_3_4_2.rs | 2 +- backend/src/version/v0_3_4_3.rs | 2 +- backend/src/version/v0_3_4_4.rs | 6 +- 16 files changed, 17 insertions(+), 579 deletions(-) delete mode 100644 backend/src/version/v0_3_0.rs delete mode 100644 backend/src/version/v0_3_0_1.rs delete mode 100644 backend/src/version/v0_3_0_2.rs delete mode 100644 backend/src/version/v0_3_0_3.rs delete mode 100644 backend/src/version/v0_3_1.rs delete mode 100644 backend/src/version/v0_3_1_1.rs delete mode 100644 backend/src/version/v0_3_1_2.rs delete mode 100644 backend/src/version/v0_3_2.rs delete mode 100644 backend/src/version/v0_3_2_1.rs delete mode 100644 backend/src/version/v0_3_3.rs diff --git a/backend/src/version/mod.rs b/backend/src/version/mod.rs index fcd4c521e..b3e7e28f5 100644 --- a/backend/src/version/mod.rs +++ b/backend/src/version/mod.rs @@ -8,16 +8,6 @@ use sqlx::PgPool; use crate::prelude::*; use crate::Error; -mod v0_3_0; -mod v0_3_0_1; -mod v0_3_0_2; -mod v0_3_0_3; -mod v0_3_1; -mod v0_3_1_1; -mod v0_3_1_2; -mod v0_3_2; -mod v0_3_2_1; -mod v0_3_3; mod v0_3_4; mod v0_3_4_1; mod v0_3_4_2; @@ -29,16 +19,6 @@ pub type Current = v0_3_4_4::Version; #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] #[serde(untagged)] enum Version { - V0_3_0(Wrapper), - V0_3_0_1(Wrapper), - V0_3_0_2(Wrapper), - V0_3_0_3(Wrapper), - V0_3_1(Wrapper), - V0_3_1_1(Wrapper), - V0_3_1_2(Wrapper), - V0_3_2(Wrapper), - V0_3_2_1(Wrapper), - V0_3_3(Wrapper), V0_3_4(Wrapper), V0_3_4_1(Wrapper), V0_3_4_2(Wrapper), @@ -239,16 +219,6 @@ mod tests { fn versions() -> impl Strategy { prop_oneof![ - Just(Version::V0_3_0(Wrapper(v0_3_0::Version::new()))), - Just(Version::V0_3_0_1(Wrapper(v0_3_0_1::Version::new()))), - Just(Version::V0_3_0_2(Wrapper(v0_3_0_2::Version::new()))), - Just(Version::V0_3_0_3(Wrapper(v0_3_0_3::Version::new()))), - Just(Version::V0_3_1(Wrapper(v0_3_1::Version::new()))), - Just(Version::V0_3_1_1(Wrapper(v0_3_1_1::Version::new()))), - Just(Version::V0_3_1_2(Wrapper(v0_3_1_2::Version::new()))), - Just(Version::V0_3_2(Wrapper(v0_3_2::Version::new()))), - Just(Version::V0_3_2_1(Wrapper(v0_3_2_1::Version::new()))), - Just(Version::V0_3_3(Wrapper(v0_3_3::Version::new()))), Just(Version::V0_3_4(Wrapper(v0_3_4::Version::new()))), Just(Version::V0_3_4_1(Wrapper(v0_3_4_1::Version::new()))), Just(Version::V0_3_4_2(Wrapper(v0_3_4_2::Version::new()))), diff --git a/backend/src/version/v0_3_0.rs b/backend/src/version/v0_3_0.rs deleted file mode 100644 index ccaf59fdd..000000000 --- a/backend/src/version/v0_3_0.rs +++ /dev/null @@ -1,37 +0,0 @@ -use emver::VersionRange; -use lazy_static::lazy_static; - -use super::*; - -const V0_3_0: emver::Version = emver::Version::new(0, 3, 0, 0); -lazy_static! { - pub static ref V0_3_0_COMPAT: VersionRange = VersionRange::Conj( - Box::new(VersionRange::Anchor( - emver::GTE, - emver::Version::new(0, 3, 0, 0), - )), - Box::new(VersionRange::Anchor(emver::LTE, Current::new().semver())), - ); -} - -#[derive(Debug, Clone)] -pub struct Version; -#[async_trait] -impl VersionT for Version { - type Previous = v0_3_0::Version; - fn new() -> Self { - Version - } - fn semver(&self) -> emver::Version { - V0_3_0 - } - fn compat(&self) -> &'static VersionRange { - &*V0_3_0_COMPAT - } - async fn up(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { - Ok(()) - } - async fn down(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { - Ok(()) - } -} diff --git a/backend/src/version/v0_3_0_1.rs b/backend/src/version/v0_3_0_1.rs deleted file mode 100644 index e42593e6b..000000000 --- a/backend/src/version/v0_3_0_1.rs +++ /dev/null @@ -1,27 +0,0 @@ -use emver::VersionRange; - -use super::*; - -const V0_3_0_1: emver::Version = emver::Version::new(0, 3, 0, 1); - -#[derive(Debug, Clone)] -pub struct Version; -#[async_trait] -impl VersionT for Version { - type Previous = v0_3_0::Version; - fn new() -> Self { - Version - } - fn semver(&self) -> emver::Version { - V0_3_0_1 - } - fn compat(&self) -> &'static VersionRange { - &*v0_3_0::V0_3_0_COMPAT - } - async fn up(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { - Ok(()) - } - async fn down(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { - Ok(()) - } -} diff --git a/backend/src/version/v0_3_0_2.rs b/backend/src/version/v0_3_0_2.rs deleted file mode 100644 index 0b56a9e66..000000000 --- a/backend/src/version/v0_3_0_2.rs +++ /dev/null @@ -1,27 +0,0 @@ -use emver::VersionRange; - -use super::*; - -const V0_3_0_2: emver::Version = emver::Version::new(0, 3, 0, 2); - -#[derive(Debug, Clone)] -pub struct Version; -#[async_trait] -impl VersionT for Version { - type Previous = v0_3_0_1::Version; - fn new() -> Self { - Version - } - fn semver(&self) -> emver::Version { - V0_3_0_2 - } - fn compat(&self) -> &'static VersionRange { - &*v0_3_0::V0_3_0_COMPAT - } - async fn up(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { - Ok(()) - } - async fn down(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { - Ok(()) - } -} diff --git a/backend/src/version/v0_3_0_3.rs b/backend/src/version/v0_3_0_3.rs deleted file mode 100644 index 041f0bf13..000000000 --- a/backend/src/version/v0_3_0_3.rs +++ /dev/null @@ -1,27 +0,0 @@ -use emver::VersionRange; - -use super::*; - -const V0_3_0_3: emver::Version = emver::Version::new(0, 3, 0, 3); - -#[derive(Clone, Debug)] -pub struct Version; -#[async_trait] -impl VersionT for Version { - type Previous = v0_3_0_2::Version; - fn new() -> Self { - Version - } - fn semver(&self) -> emver::Version { - V0_3_0_3 - } - fn compat(&self) -> &'static VersionRange { - &*v0_3_0::V0_3_0_COMPAT - } - async fn up(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { - Ok(()) - } - async fn down(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { - Ok(()) - } -} diff --git a/backend/src/version/v0_3_1.rs b/backend/src/version/v0_3_1.rs deleted file mode 100644 index dcb738dda..000000000 --- a/backend/src/version/v0_3_1.rs +++ /dev/null @@ -1,28 +0,0 @@ -use emver::VersionRange; - -use super::v0_3_0::V0_3_0_COMPAT; -use super::*; - -const V0_3_1: emver::Version = emver::Version::new(0, 3, 1, 0); - -#[derive(Clone, Debug)] -pub struct Version; -#[async_trait] -impl VersionT for Version { - type Previous = v0_3_0_3::Version; - fn new() -> Self { - Version - } - fn semver(&self) -> emver::Version { - V0_3_1 - } - fn compat(&self) -> &'static VersionRange { - &*V0_3_0_COMPAT - } - async fn up(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { - Ok(()) - } - async fn down(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { - Ok(()) - } -} diff --git a/backend/src/version/v0_3_1_1.rs b/backend/src/version/v0_3_1_1.rs deleted file mode 100644 index f7b70e4de..000000000 --- a/backend/src/version/v0_3_1_1.rs +++ /dev/null @@ -1,28 +0,0 @@ -use emver::VersionRange; - -use super::v0_3_0::V0_3_0_COMPAT; -use super::*; - -const V0_3_1_1: emver::Version = emver::Version::new(0, 3, 1, 1); - -#[derive(Clone, Debug)] -pub struct Version; -#[async_trait] -impl VersionT for Version { - type Previous = v0_3_1::Version; - fn new() -> Self { - Version - } - fn semver(&self) -> emver::Version { - V0_3_1_1 - } - fn compat(&self) -> &'static VersionRange { - &*V0_3_0_COMPAT - } - async fn up(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { - Ok(()) - } - async fn down(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { - Ok(()) - } -} diff --git a/backend/src/version/v0_3_1_2.rs b/backend/src/version/v0_3_1_2.rs deleted file mode 100644 index 310b7ee87..000000000 --- a/backend/src/version/v0_3_1_2.rs +++ /dev/null @@ -1,28 +0,0 @@ -use emver::VersionRange; - -use super::v0_3_0::V0_3_0_COMPAT; -use super::*; - -const V0_3_1_2: emver::Version = emver::Version::new(0, 3, 1, 2); - -#[derive(Clone, Debug)] -pub struct Version; -#[async_trait] -impl VersionT for Version { - type Previous = v0_3_1_1::Version; - fn new() -> Self { - Version - } - fn semver(&self) -> emver::Version { - V0_3_1_2 - } - fn compat(&self) -> &'static VersionRange { - &*V0_3_0_COMPAT - } - async fn up(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { - Ok(()) - } - async fn down(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { - Ok(()) - } -} diff --git a/backend/src/version/v0_3_2.rs b/backend/src/version/v0_3_2.rs deleted file mode 100644 index c252d1518..000000000 --- a/backend/src/version/v0_3_2.rs +++ /dev/null @@ -1,156 +0,0 @@ -use emver::VersionRange; - -use super::v0_3_0::V0_3_0_COMPAT; -use super::*; - -const V0_3_2: emver::Version = emver::Version::new(0, 3, 2, 0); - -lazy_static::lazy_static! { - static ref DEFAULT_UI: serde_json::Value =serde_json::json!({ - "name": null, - "auto-check-updates": true, - "pkg-order": [], - "ack-welcome": "0.3.2", - "marketplace": { - "selected-id": null, - "known-hosts": {} - }, - "dev": {}, - "gaming": { - "snake": { - "high-score": 0 - } - }, - "ack-instructions": {} - }); - -} - -#[derive(Clone, Debug)] -pub struct Version; -#[async_trait] -impl VersionT for Version { - type Previous = v0_3_1_2::Version; - fn new() -> Self { - Version - } - fn semver(&self) -> emver::Version { - V0_3_2 - } - fn compat(&self) -> &'static VersionRange { - &*V0_3_0_COMPAT - } - async fn up(&self, db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { - let hostname = legacy::hostname::get_hostname(db).await?; - crate::db::DatabaseModel::new() - .server_info() - .hostname() - .put(db, &Some(hostname.0)) - .await?; - crate::db::DatabaseModel::new() - .server_info() - .id() - .put(db, &legacy::hostname::generate_id()) - .await?; - - legacy::hostname::sync_hostname(db).await?; - Ok(()) - } - async fn down(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { - Ok(()) - } -} - -mod legacy { - pub mod hostname { - use patch_db::DbHandle; - use rand::{thread_rng, Rng}; - use tokio::process::Command; - use tracing::instrument; - - use crate::util::Invoke; - use crate::{Error, ErrorKind}; - #[derive(Clone, serde::Deserialize, serde::Serialize, Debug)] - pub struct Hostname(pub String); - - lazy_static::lazy_static! { - static ref ADJECTIVES: Vec = include_str!("../assets/adjectives.txt").lines().map(|x| x.to_string()).collect(); - static ref NOUNS: Vec = include_str!("../assets/nouns.txt").lines().map(|x| x.to_string()).collect(); - } - impl AsRef for Hostname { - fn as_ref(&self) -> &str { - &self.0 - } - } - - pub fn generate_hostname() -> Hostname { - let mut rng = thread_rng(); - let adjective = &ADJECTIVES[rng.gen_range(0..ADJECTIVES.len())]; - let noun = &NOUNS[rng.gen_range(0..NOUNS.len())]; - Hostname(format!("embassy-{adjective}-{noun}")) - } - - pub fn generate_id() -> String { - let id = uuid::Uuid::new_v4(); - id.to_string() - } - - #[instrument(skip_all)] - pub async fn get_current_hostname() -> Result { - let out = Command::new("hostname") - .invoke(ErrorKind::ParseSysInfo) - .await?; - let out_string = String::from_utf8(out)?; - Ok(Hostname(out_string.trim().to_owned())) - } - - #[instrument(skip_all)] - pub async fn set_hostname(hostname: &Hostname) -> Result<(), Error> { - let hostname: &String = &hostname.0; - let _out = Command::new("hostnamectl") - .arg("set-hostname") - .arg(hostname) - .invoke(ErrorKind::ParseSysInfo) - .await?; - Ok(()) - } - - #[instrument(skip_all)] - pub async fn get_id(handle: &mut Db) -> Result { - let id = crate::db::DatabaseModel::new() - .server_info() - .id() - .get(handle) - .await?; - Ok(id.to_string()) - } - - pub async fn get_hostname(handle: &mut Db) -> Result { - if let Ok(hostname) = crate::db::DatabaseModel::new() - .server_info() - .hostname() - .get(handle) - .await - { - if let Some(hostname) = hostname.to_owned() { - return Ok(Hostname(hostname)); - } - } - let id = get_id(handle).await?; - if id.len() != 8 { - return Ok(generate_hostname()); - } - return Ok(Hostname(format!("embassy-{}", id))); - } - #[instrument(skip_all)] - pub async fn sync_hostname(handle: &mut Db) -> Result<(), Error> { - set_hostname(&get_hostname(handle).await?).await?; - Command::new("systemctl") - .arg("restart") - .arg("avahi-daemon") - .invoke(crate::ErrorKind::Network) - .await?; - Ok(()) - } - } -} diff --git a/backend/src/version/v0_3_2_1.rs b/backend/src/version/v0_3_2_1.rs deleted file mode 100644 index 62f36f623..000000000 --- a/backend/src/version/v0_3_2_1.rs +++ /dev/null @@ -1,26 +0,0 @@ -use super::v0_3_0::V0_3_0_COMPAT; -use super::*; - -const V0_3_2_1: emver::Version = emver::Version::new(0, 3, 2, 1); - -#[derive(Clone, Debug)] -pub struct Version; -#[async_trait] -impl VersionT for Version { - type Previous = v0_3_2::Version; - fn new() -> Self { - Version - } - fn semver(&self) -> emver::Version { - V0_3_2_1 - } - fn compat(&self) -> &'static emver::VersionRange { - &*V0_3_0_COMPAT - } - async fn up(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { - Ok(()) - } - async fn down(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { - Ok(()) - } -} diff --git a/backend/src/version/v0_3_3.rs b/backend/src/version/v0_3_3.rs deleted file mode 100644 index b168aaa56..000000000 --- a/backend/src/version/v0_3_3.rs +++ /dev/null @@ -1,156 +0,0 @@ -use async_trait::async_trait; -use emver::VersionRange; -use regex::Regex; -use serde_json::{json, Value}; - -use super::v0_3_0::V0_3_0_COMPAT; -use super::*; -use crate::DEFAULT_MARKETPLACE; - -const V0_3_3: emver::Version = emver::Version::new(0, 3, 3, 0); - -#[derive(Clone, Debug)] -pub struct Version; - -#[async_trait] -impl VersionT for Version { - type Previous = v0_3_2_1::Version; - fn new() -> Self { - Version - } - fn semver(&self) -> emver::Version { - V0_3_3 - } - fn compat(&self) -> &'static VersionRange { - &*V0_3_0_COMPAT - } - async fn up(&self, db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { - let mut ui = crate::db::DatabaseModel::new().ui().get_mut(db).await?; - - if let Some(Value::String(selected_url)) = - ui["marketplace"] - .get("selected-id") - .and_then(|selected_id| { - if let Value::String(selected_id) = selected_id { - return Some(ui["marketplace"]["known-hosts"].get(&selected_id)?); - } - None - }) - { - ui["marketplace"]["selected-url"] = json!(selected_url); - } - if let Value::Object(ref mut obj) = *ui { - obj.remove("pkg-order"); - obj.remove("auto-check-updates"); - } - let known_hosts = ui["marketplace"]["known-hosts"].take(); - ui["marketplace"]["known-hosts"] = json!({}); - if let Value::Object(known_hosts) = known_hosts { - for (_id, value) in known_hosts { - if let Value::String(url) = &value["url"] { - ui["marketplace"]["known-hosts"][ensure_trailing_slashes(url)] = json!({}); - } - } - } - - ui["marketplace"]["known-hosts"]["https://registry.start9.com/"] = json!({}); - - if let Some(Value::Object(ref mut obj)) = ui.get_mut("marketplace") { - obj.remove("selected-id"); - } - if ui["marketplace"]["selected-url"].is_null() { - ui["marketplace"]["selected-url"] = json!(MarketPlaceUrls::Default.url()); - } - ui.save(db).await?; - - Ok(()) - } - async fn down(&self, db: &mut Db, _secrets: &PgPool) -> Result<(), Error> { - let mut ui = crate::db::DatabaseModel::new().ui().get_mut(db).await?; - let selected_url = ui["marketplace"]["selected-url"] - .as_str() - .map(|x| x.to_owned()); - let known_hosts = ui["marketplace"]["known-hosts"].take(); - ui["marketplace"]["known-hosts"] = json!({}); - if let Value::Object(known_hosts) = known_hosts { - for (url, obj) in known_hosts { - if let Value::String(name) = &obj["name"] { - let id = uuid::Uuid::new_v4().to_string(); - if Some(name) == selected_url.as_ref() { - ui["marketplace"]["selected-id"] = Value::String(id.clone()); - } - ui["marketplace"]["known-hosts"][id.as_str()] = json!({ - "name": name, - "url": url - }); - } - } - } - ui["auto-check-updates"] = Value::Bool(true); - ui["pkg-order"] = json!(crate::db::DatabaseModel::new() - .package_data() - .keys(db) - .await? - .iter() - .map(|x| x.to_string()) - .collect::>()); - if let Some(Value::Object(ref mut obj)) = ui.get_mut("marketplace") { - obj.remove("selected-url"); - } - ui.save(db).await?; - Ok(()) - } -} - -fn ensure_trailing_slashes(url: &str) -> String { - lazy_static::lazy_static! { - static ref REG: Regex = Regex::new(r".*/$").unwrap(); - } - if REG.is_match(url) { - return url.to_string(); - } - format!("{url}/") -} - -#[test] -fn test_ensure_trailing_slashed() { - assert_eq!( - &ensure_trailing_slashes("http://start9.com"), - "http://start9.com/" - ); - assert_eq!( - &ensure_trailing_slashes("http://start9.com/"), - "http://start9.com/" - ); - assert_eq!( - &ensure_trailing_slashes("http://start9.com/a"), - "http://start9.com/a/" - ); -} - -#[derive(Debug, Clone, Copy)] -pub enum MarketPlaceUrls { - Default, -} - -impl MarketPlaceUrls { - pub fn url(&self) -> String { - let url_string = match self { - MarketPlaceUrls::Default => DEFAULT_MARKETPLACE, - }; - format!("{url_string}/") - } -} - -#[test] -fn test_that_ui_includes_url() { - let ui: Value = - serde_json::from_str(include_str!("../../../frontend/patchdb-ui-seed.json")).unwrap(); - for market_place in [MarketPlaceUrls::Default] { - let url = market_place.url(); - assert!( - !ui["marketplace"]["known-hosts"][&url].is_null(), - "Should have a market place for {url}" - ); - } -} diff --git a/backend/src/version/v0_3_4.rs b/backend/src/version/v0_3_4.rs index e1a51faf5..bb1d14cb0 100644 --- a/backend/src/version/v0_3_4.rs +++ b/backend/src/version/v0_3_4.rs @@ -5,7 +5,6 @@ use openssl::hash::MessageDigest; use serde_json::{json, Value}; use ssh_key::public::Ed25519PublicKey; -use super::v0_3_0::V0_3_0_COMPAT; use super::*; use crate::account::AccountInfo; use crate::hostname::{sync_hostname, Hostname}; @@ -13,6 +12,16 @@ use crate::prelude::*; const V0_3_4: emver::Version = emver::Version::new(0, 3, 4, 0); +lazy_static::lazy_static! { + pub static ref V0_3_0_COMPAT: VersionRange = VersionRange::Conj( + Box::new(VersionRange::Anchor( + emver::GTE, + emver::Version::new(0, 3, 0, 0), + )), + Box::new(VersionRange::Anchor(emver::LTE, Current::new().semver())), + ); +} + const COMMUNITY_URL: &str = "https://community-registry.start9.com/"; const MAIN_REGISTRY: &str = "https://registry.start9.com/"; const COMMUNITY_SERVICES: &[&str] = &[ @@ -33,7 +42,7 @@ pub struct Version; #[async_trait] impl VersionT for Version { - type Previous = v0_3_3::Version; + type Previous = Self; fn new() -> Self { Version } diff --git a/backend/src/version/v0_3_4_1.rs b/backend/src/version/v0_3_4_1.rs index cda6263b8..2d3e5af56 100644 --- a/backend/src/version/v0_3_4_1.rs +++ b/backend/src/version/v0_3_4_1.rs @@ -1,8 +1,7 @@ use async_trait::async_trait; use emver::VersionRange; -use super::v0_3_0::V0_3_0_COMPAT; -use super::*; +use super::{v0_3_4::V0_3_0_COMPAT, *}; use crate::prelude::*; diff --git a/backend/src/version/v0_3_4_2.rs b/backend/src/version/v0_3_4_2.rs index 3d8dd8514..6cfe2b244 100644 --- a/backend/src/version/v0_3_4_2.rs +++ b/backend/src/version/v0_3_4_2.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; use emver::VersionRange; -use super::v0_3_0::V0_3_0_COMPAT; +use super::v0_3_4::V0_3_0_COMPAT; use super::*; use crate::prelude::*; diff --git a/backend/src/version/v0_3_4_3.rs b/backend/src/version/v0_3_4_3.rs index 5e31b1f30..6f1c1330f 100644 --- a/backend/src/version/v0_3_4_3.rs +++ b/backend/src/version/v0_3_4_3.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; use emver::VersionRange; -use super::v0_3_0::V0_3_0_COMPAT; +use super::v0_3_4::V0_3_0_COMPAT; use super::*; use crate::prelude::*; diff --git a/backend/src/version/v0_3_4_4.rs b/backend/src/version/v0_3_4_4.rs index c3c827f64..dce555f44 100644 --- a/backend/src/version/v0_3_4_4.rs +++ b/backend/src/version/v0_3_4_4.rs @@ -1,12 +1,12 @@ use async_trait::async_trait; use emver::VersionRange; use models::ResultExt; - -use super::v0_3_0::V0_3_0_COMPAT; -use super::*; +use sqlx::PgPool; use crate::prelude::*; +use super::{v0_3_4::V0_3_0_COMPAT, v0_3_4_3, VersionT}; + const V0_3_4_4: emver::Version = emver::Version::new(0, 3, 4, 4); #[derive(Clone, Debug)] From f26a88b8a7eb44e97056e1f947aed337bf44ee54 Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Tue, 29 Aug 2023 13:33:19 -0600 Subject: [PATCH 21/89] wip s9pk/manifest --- backend/src/s9pk/manifest.rs | 2 +- backend/src/s9pk/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/s9pk/manifest.rs b/backend/src/s9pk/manifest.rs index c33d61cb1..9e8e6f050 100644 --- a/backend/src/s9pk/manifest.rs +++ b/backend/src/s9pk/manifest.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use std::path::{Path, PathBuf}; use color_eyre::eyre::eyre; -pub use models::{PackageId, SYSTEM_PACKAGE_ID}; +pub use models::PackageId; use serde::{Deserialize, Serialize}; use url::Url; diff --git a/backend/src/s9pk/mod.rs b/backend/src/s9pk/mod.rs index a5fff7e11..e1bf4caba 100644 --- a/backend/src/s9pk/mod.rs +++ b/backend/src/s9pk/mod.rs @@ -28,7 +28,7 @@ pub mod header; pub mod manifest; pub mod reader; -pub const SIG_CONTEXT: &'static [u8] = b"s9pk"; +pub const SIG_CONTEXT: &[u8] = b"s9pk"; #[command(cli_only, display(display_none))] #[instrument(skip_all)] From c3048b06e08923cd868936d9257f70d1d9585fde Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Tue, 29 Aug 2023 13:40:44 -0600 Subject: [PATCH 22/89] wip wifi --- backend/src/net/wifi.rs | 31 ++++++++++++++++--------------- backend/src/procedure/docker.rs | 3 ++- backend/src/procedure/mod.rs | 2 ++ 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/backend/src/net/wifi.rs b/backend/src/net/wifi.rs index 5ed233856..1fdbc7315 100644 --- a/backend/src/net/wifi.rs +++ b/backend/src/net/wifi.rs @@ -6,7 +6,6 @@ use std::time::Duration; use clap::ArgMatches; use isocountry::CountryCode; use lazy_static::lazy_static; -use patch_db::DbHandle; use regex::Regex; use rpc_toolkit::command; use tokio::process::Command; @@ -14,6 +13,7 @@ use tokio::sync::RwLock; use tracing::instrument; use crate::context::RpcContext; +use crate::prelude::*; use crate::util::serde::{display_serializable, IoFormat}; use crate::util::{display_none, Invoke}; use crate::{Error, ErrorKind}; @@ -69,7 +69,7 @@ pub async fn add( )); } async fn add_procedure( - db: impl DbHandle, + db: PatchDb, wifi_manager: WifiManager, ssid: &Ssid, password: &Psk, @@ -84,7 +84,7 @@ pub async fn add( Ok(()) } if let Err(err) = add_procedure( - &mut ctx.db.handle(), + ctx.db, wifi_manager.clone(), &Ssid(ssid.clone()), &Psk(password.clone()), @@ -113,7 +113,7 @@ pub async fn connect(#[context] ctx: RpcContext, #[arg] ssid: String) -> Result< )); } async fn connect_procedure( - mut db: impl DbHandle, + mut db: PatchDb, wifi_manager: WifiManager, ssid: &Ssid, ) -> Result<(), Error> { @@ -645,13 +645,14 @@ impl WpaCli { Ok(()) } - pub async fn save_config(&mut self, mut db: impl DbHandle) -> Result<(), Error> { - crate::db::DatabaseModel::new() - .server_info() - .last_wifi_region() - .put(&mut db, &Some(self.get_country_low().await?)) - .await?; - Ok(()) + pub async fn save_config(&mut self, mut db: PatchDb) -> Result<(), Error> { + let new_country = Some(self.get_country_low().await?); + db.mutate(|d| { + d.as_server_info_mut() + .as_last_wifi_region_mut() + .ser(&new_country) + }) + .await } async fn check_active_network(&self, ssid: &Ssid) -> Result, Error> { Ok(self @@ -682,7 +683,7 @@ impl WpaCli { .collect()) } #[instrument(skip_all)] - pub async fn select_network(&mut self, db: impl DbHandle, ssid: &Ssid) -> Result { + pub async fn select_network(&mut self, db: PatchDb, ssid: &Ssid) -> Result { let m_id = self.check_active_network(ssid).await?; match m_id { None => Err(Error::new( @@ -734,7 +735,7 @@ impl WpaCli { } } #[instrument(skip_all)] - pub async fn remove_network(&mut self, db: impl DbHandle, ssid: &Ssid) -> Result { + pub async fn remove_network(&mut self, db: PatchDb, ssid: &Ssid) -> Result { let found_networks = self.find_networks(ssid).await?; if found_networks.is_empty() { return Ok(true); @@ -748,7 +749,7 @@ impl WpaCli { #[instrument(skip_all)] pub async fn set_add_network( &mut self, - db: impl DbHandle, + db: PatchDb, ssid: &Ssid, psk: &Psk, priority: isize, @@ -760,7 +761,7 @@ impl WpaCli { #[instrument(skip_all)] pub async fn add_network( &mut self, - db: impl DbHandle, + db: PatchDb, ssid: &Ssid, psk: &Psk, priority: isize, diff --git a/backend/src/procedure/docker.rs b/backend/src/procedure/docker.rs index 3539bab76..960d87bf9 100644 --- a/backend/src/procedure/docker.rs +++ b/backend/src/procedure/docker.rs @@ -25,7 +25,7 @@ use tracing::instrument; use super::ProcedureName; use crate::context::RpcContext; use crate::prelude::*; -use crate::s9pk::manifest::{PackageId, SYSTEM_PACKAGE_ID}; +use crate::s9pk::manifest::PackageId; use crate::util::docker::{remove_container, CONTAINER_TOOL}; use crate::util::serde::{Duration as SerdeDuration, IoFormat}; use crate::util::Version; @@ -59,6 +59,7 @@ pub struct DockerContainers { /// part of this struct by choice. Used for the times that we are creating our own entry points #[derive(Clone, Debug, Deserialize, Serialize, patch_db::HasModel)] #[serde(rename_all = "kebab-case")] +#[model = "Model"] pub struct DockerContainer { pub image: ImageId, #[serde(default)] diff --git a/backend/src/procedure/mod.rs b/backend/src/procedure/mod.rs index e75445fec..4cc259303 100644 --- a/backend/src/procedure/mod.rs +++ b/backend/src/procedure/mod.rs @@ -10,6 +10,7 @@ use tracing::instrument; use self::docker::DockerProcedure; use crate::context::RpcContext; +use crate::prelude::*; use crate::s9pk::manifest::PackageId; use crate::util::Version; use crate::volume::Volumes; @@ -25,6 +26,7 @@ pub use models::ProcedureName; #[derive(Clone, Debug, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] #[serde(tag = "type")] +#[model = "Model"] pub enum PackageProcedure { Docker(DockerProcedure), From b64e6779ab85f30a48a5e672494b59a259274676 Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Tue, 29 Aug 2023 13:41:10 -0600 Subject: [PATCH 23/89] chore: net/keys --- backend/src/net/keys.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/backend/src/net/keys.rs b/backend/src/net/keys.rs index d406df9c0..0d9bbe702 100644 --- a/backend/src/net/keys.rs +++ b/backend/src/net/keys.rs @@ -31,16 +31,14 @@ async fn compat( } else { Ok(None) } + } else if let Some(key) = sqlx::query!("SELECT tor_key FROM account WHERE id = 0") + .fetch_one(secrets) + .await? + .tor_key + { + Ok(Some(ExpandedSecretKey::from_bytes(&key)?)) } else { - if let Some(key) = sqlx::query!("SELECT tor_key FROM account WHERE id = 0") - .fetch_one(secrets) - .await? - .tor_key - { - Ok(Some(ExpandedSecretKey::from_bytes(&key)?)) - } else { - Ok(None) - } + Ok(None) } } From edcca9a2db31f0b51b6cd4c3bb81120e7c6af878 Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Tue, 29 Aug 2023 13:42:16 -0600 Subject: [PATCH 24/89] chore: net/dns --- backend/src/net/dns.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/backend/src/net/dns.rs b/backend/src/net/dns.rs index a8910f698..c2e445ba6 100644 --- a/backend/src/net/dns.rs +++ b/backend/src/net/dns.rs @@ -50,17 +50,15 @@ impl Resolver { } else { None } + } else if let Some(ip) = self.services.read().await.get(&None) { + Some( + ip.iter() + .filter(|(_, rc)| rc.strong_count() > 0) + .map(|(ip, _)| *ip) + .collect(), + ) } else { - if let Some(ip) = self.services.read().await.get(&None) { - Some( - ip.iter() - .filter(|(_, rc)| rc.strong_count() > 0) - .map(|(ip, _)| *ip) - .collect(), - ) - } else { - None - } + None } } _ => None, From 9040931766a63c0b24587203ef5af1bc79346eed Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Tue, 29 Aug 2023 13:44:12 -0600 Subject: [PATCH 25/89] wip net/dhcp --- backend/src/net/dhcp.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/backend/src/net/dhcp.rs b/backend/src/net/dhcp.rs index ccf2b109f..c58b898bb 100644 --- a/backend/src/net/dhcp.rs +++ b/backend/src/net/dhcp.rs @@ -8,6 +8,7 @@ use tokio::sync::RwLock; use crate::context::RpcContext; use crate::db::model::IpInfo; use crate::net::utils::{iface_is_physical, list_interfaces}; +use crate::prelude::*; use crate::util::display_none; use crate::Error; @@ -58,12 +59,15 @@ pub async fn dhcp() -> Result<(), Error> { pub async fn update(#[context] ctx: RpcContext, #[arg] interface: String) -> Result<(), Error> { if iface_is_physical(&interface).await { let ip_info = IpInfo::for_interface(&interface).await?; - crate::db::DatabaseModel::new() - .server_info() - .ip_info() - .idx_model(&interface) - .put(&mut ctx.db.handle(), &ip_info) + ctx.db + .mutate(|db| { + db.as_server_info_mut() + .as_ip_info_mut() + .as_idx_model_mut(&interface) + .ser(&ip_info) + }) .await?; + let mut cached = CACHED_IPS.write().await; if cached.is_empty() { *cached = _ips().await?; From 323598a8406ec911e4cc8073a9e50f413ff6d12a Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Tue, 29 Aug 2023 14:03:06 -0600 Subject: [PATCH 26/89] wip manager manager-map --- backend/src/db/prelude.rs | 2 ++ backend/src/manager/manager_map.rs | 27 ++++++++++----------------- backend/src/manager/mod.rs | 17 +++++------------ 3 files changed, 17 insertions(+), 29 deletions(-) diff --git a/backend/src/db/prelude.rs b/backend/src/db/prelude.rs index b50e75c86..f51863f4f 100644 --- a/backend/src/db/prelude.rs +++ b/backend/src/db/prelude.rs @@ -9,6 +9,8 @@ use serde::Serialize; use crate::db::model::DatabaseModel; use crate::prelude::*; +pub type Peeked = Model; + pub fn to_value(value: &T) -> Result where T: Serialize, diff --git a/backend/src/manager/manager_map.rs b/backend/src/manager/manager_map.rs index 0e4f39db7..a4ef10f38 100644 --- a/backend/src/manager/manager_map.rs +++ b/backend/src/manager/manager_map.rs @@ -2,13 +2,13 @@ use std::collections::BTreeMap; use std::sync::Arc; use color_eyre::eyre::eyre; -use patch_db::DbHandle; use sqlx::{Executor, Postgres}; use tokio::sync::RwLock; use tracing::instrument; use super::Manager; use crate::context::RpcContext; +use crate::prelude::*; use crate::s9pk::manifest::{Manifest, PackageId}; use crate::util::Version; use crate::Error; @@ -18,31 +18,24 @@ use crate::Error; pub struct ManagerMap(RwLock>>); impl ManagerMap { #[instrument(skip_all)] - pub async fn init( + pub async fn init( &self, ctx: &RpcContext, - db: &mut Db, + peeked: &Peeked, secrets: &mut Ex, ) -> Result<(), Error> where for<'a> &'a mut Ex: Executor<'a, Database = Postgres>, { let mut res = BTreeMap::new(); - for package in crate::db::DatabaseModel::new() - .package_data() - .keys(db) - .await? - { - let man: Manifest = if let Some(manifest) = crate::db::DatabaseModel::new() - .package_data() - .idx_model(&package) - .and_then(|pkg| pkg.installed()) - .map(|m| m.manifest()) - .get(db) - .await? - .to_owned() + for package in peeked.as_package_data().keys()? { + let man: Manifest = if let Some(manifest) = peeked + .as_package_data() + .as_idx(&package) + .and_then(|x| x.as_installed()) + .map(|x| x.as_manifest().de()) { - manifest + manifest? } else { continue; }; diff --git a/backend/src/manager/mod.rs b/backend/src/manager/mod.rs index 875e3eeff..644b9aa66 100644 --- a/backend/src/manager/mod.rs +++ b/backend/src/manager/mod.rs @@ -11,7 +11,6 @@ use futures::{Future, FutureExt, TryFutureExt}; use helpers::UnixRpcClient; use models::{ErrorKind, PackageId}; use nix::sys::signal::Signal; -use patch_db::DbHandle; use persistent_container::PersistentContainer; use rand::SeedableRng; use sqlx::Connection; @@ -40,6 +39,7 @@ use crate::disk::mount::guard::TmpMountGuard; use crate::install::cleanup::remove_from_current_dependents_lists; use crate::net::net_controller::NetService; use crate::net::vhost::AlpnInfo; +use crate::prelude::*; use crate::procedure::docker::{DockerContainer, DockerProcedure, LongRunning}; use crate::procedure::{NoOutput, ProcedureName}; use crate::s9pk::manifest::Manifest; @@ -278,7 +278,6 @@ impl Manager { .backup .create( &seed.ctx, - &mut tx, &seed.manifest.id, &seed.manifest.title, &seed.manifest.version, @@ -428,7 +427,6 @@ async fn configure( current_dependencies.0.insert( pkg_ptr.package_id().to_owned(), CurrentDependencyInfo { - pointers: vec![pkg_ptr], health_checks: BTreeSet::new(), }, ); @@ -450,13 +448,9 @@ async fn configure( if let Some(current_dependency) = current_dependencies.0.get_mut(&package_id) { current_dependency.health_checks.extend(health_checks); } else { - current_dependencies.0.insert( - package_id, - CurrentDependencyInfo { - pointers: Vec::new(), - health_checks, - }, - ); + current_dependencies + .0 + .insert(package_id, CurrentDependencyInfo { health_checks }); } } @@ -808,8 +802,7 @@ async fn remove_network_for_main(svc: NetService) -> Result<(), Error> { async fn main_health_check_daemon(seed: Arc) { tokio::time::sleep(Duration::from_secs(HEALTH_CHECK_GRACE_PERIOD_SECONDS)).await; loop { - let mut db = seed.ctx.db.handle(); - if let Err(e) = health::check(&seed.ctx, &mut db, &seed.manifest.id).await { + if let Err(e) = health::check(&seed.ctx, &seed.manifest.id).await { tracing::error!( "Failed to run health check for {}: {}", &seed.manifest.id, From af63412061dc6cbcef7e7f8a427624a00e377ce2 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Tue, 29 Aug 2023 14:08:02 -0600 Subject: [PATCH 27/89] gut dependency errors --- backend/src/dependencies.rs | 748 ++++-------------------------------- backend/src/status/mod.rs | 5 +- backend/src/util/mod.rs | 2 +- 3 files changed, 72 insertions(+), 683 deletions(-) diff --git a/backend/src/dependencies.rs b/backend/src/dependencies.rs index 2042949d0..ec8fa16aa 100644 --- a/backend/src/dependencies.rs +++ b/backend/src/dependencies.rs @@ -1,28 +1,23 @@ -use std::cmp::Ordering; use std::collections::BTreeMap; use std::time::Duration; use color_eyre::eyre::eyre; use emver::VersionRange; -use futures::future::BoxFuture; use futures::FutureExt; -use imbl::OrdSet; +use models::OptionExt; use rand::SeedableRng; use rpc_toolkit::command; use serde::{Deserialize, Serialize}; use tracing::instrument; -use crate::config::action::{ConfigActions, ConfigRes}; -use crate::config::spec::PackagePointerSpec; -use crate::config::{not_found, Config, ConfigReceipts, ConfigSpec, ConfigureContext}; +use crate::config::action::ConfigRes; +use crate::config::{Config, ConfigSpec, ConfigureContext}; use crate::context::RpcContext; -use crate::db::model::{CurrentDependencies, CurrentDependents, Database, InstalledPackageInfo}; +use crate::db::model::{CurrentDependencies, Database, InstalledPackageInfo}; use crate::prelude::*; use crate::procedure::docker::DockerContainers; use crate::procedure::{NoOutput, PackageProcedure, ProcedureName}; -use crate::s9pk::manifest::{Manifest, PackageId}; -use crate::status::health_check::{HealthCheckId, HealthCheckResult}; -use crate::status::{MainStatus, Status}; +use crate::s9pk::manifest::PackageId; use crate::util::serde::display_serializable; use crate::util::{display_none, Version}; use crate::volume::Volumes; @@ -33,285 +28,6 @@ pub fn dependency() -> Result<(), Error> { Ok(()) } -#[derive(Clone, Debug, thiserror::Error, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -#[serde(tag = "type")] -pub enum DependencyError { - NotInstalled, // { "type": "not-installed" } - #[serde(rename_all = "kebab-case")] - IncorrectVersion { - expected: VersionRange, - received: Version, - }, // { "type": "incorrect-version", "expected": "0.1.0", "received": "^0.2.0" } - #[serde(rename_all = "kebab-case")] - ConfigUnsatisfied { - error: String, - }, // { "type": "config-unsatisfied", "error": "Bitcoin Core must have pruning set to manual." } - NotRunning, // { "type": "not-running" } - #[serde(rename_all = "kebab-case")] - HealthChecksFailed { - failures: BTreeMap, - }, // { "type": "health-checks-failed", "checks": { "rpc": { "time": "2021-05-11T18:21:29Z", "result": "starting" } } } - #[serde(rename_all = "kebab-case")] - Transitive, // { "type": "transitive" } -} - -impl DependencyError { - pub fn cmp_priority(&self, other: &DependencyError) -> std::cmp::Ordering { - use std::cmp::Ordering::*; - - use DependencyError::*; - match (self, other) { - (NotInstalled, NotInstalled) => Equal, - (NotInstalled, _) => Greater, - (_, NotInstalled) => Less, - (IncorrectVersion { .. }, IncorrectVersion { .. }) => Equal, - (IncorrectVersion { .. }, _) => Greater, - (_, IncorrectVersion { .. }) => Less, - (ConfigUnsatisfied { .. }, ConfigUnsatisfied { .. }) => Equal, - (ConfigUnsatisfied { .. }, _) => Greater, - (_, ConfigUnsatisfied { .. }) => Less, - (NotRunning, NotRunning) => Equal, - (NotRunning, _) => Greater, - (_, NotRunning) => Less, - (HealthChecksFailed { .. }, HealthChecksFailed { .. }) => Equal, - (HealthChecksFailed { .. }, _) => Greater, - (_, HealthChecksFailed { .. }) => Less, - (Transitive, Transitive) => Equal, - } - } - pub fn merge_with(self, other: DependencyError) -> DependencyError { - match (self, other) { - (DependencyError::NotInstalled, _) | (_, DependencyError::NotInstalled) => { - DependencyError::NotInstalled - } - (DependencyError::IncorrectVersion { expected, received }, _) - | (_, DependencyError::IncorrectVersion { expected, received }) => { - DependencyError::IncorrectVersion { expected, received } - } - ( - DependencyError::ConfigUnsatisfied { error: e0 }, - DependencyError::ConfigUnsatisfied { error: e1 }, - ) => DependencyError::ConfigUnsatisfied { - error: e0 + "\n" + &e1, - }, - (DependencyError::ConfigUnsatisfied { error }, _) - | (_, DependencyError::ConfigUnsatisfied { error }) => { - DependencyError::ConfigUnsatisfied { error } - } - (DependencyError::NotRunning, _) | (_, DependencyError::NotRunning) => { - DependencyError::NotRunning - } - ( - DependencyError::HealthChecksFailed { failures: f0 }, - DependencyError::HealthChecksFailed { failures: f1 }, - ) => DependencyError::HealthChecksFailed { - failures: f0.into_iter().chain(f1.into_iter()).collect(), - }, - (DependencyError::HealthChecksFailed { failures }, _) - | (_, DependencyError::HealthChecksFailed { failures }) => { - DependencyError::HealthChecksFailed { failures } - } - (DependencyError::Transitive, _) => DependencyError::Transitive, - } - } - #[instrument(skip_all)] - pub fn try_heal( - self, - db: &Model, - id: &PackageId, - dependency: &PackageId, - mut dependency_config_error: Option, // config error - info: &DepInfo, - ) -> Result, Error> { - let installed_info = db - .as_package_data() - .as_idx(id) - .or_not_found(id)? - .as_installed() - .or_not_found(id)?; - let dependency_installed_info = db - .as_package_data() - .as_idx(dependency) - .and_then(|d| d.as_installed()); - let container = installed_info.as_manifest()?.as_containers().de()?; - Ok(match self { - DependencyError::NotInstalled => { - if dependency_installed_info.is_some() { - DependencyError::IncorrectVersion { - expected: info.version.clone(), - received: Default::default(), - } - .try_heal( - db, - id, - dependency, - dependency_config_error, - info, - )? - } else { - Some(DependencyError::NotInstalled) - } - } - DependencyError::IncorrectVersion { expected, .. } => { - let version: Version = dependency_installed_info - .or_not_found(dependency)? - .as_manifest() - .as_version() - .de()?; - if version.satisfies(&expected) { - DependencyError::ConfigUnsatisfied { - error: String::new(), - } - .try_heal( - db, - id, - dependency, - dependency_config_error, - info, - )? - } else { - Some(DependencyError::IncorrectVersion { - expected, - received: version, - }) - } - } - DependencyError::ConfigUnsatisfied { .. } => { - if let Some(error) = dependency_config_error { - Some(DependencyError::ConfigUnsatisfied { error }) - } else { - DependencyError::NotRunning.try_heal(db, id, dependency, None, info)? - } - } - DependencyError::NotRunning => { - let status = dependency_installed_info - .or_not_found(dependency)? - .as_status() - .as_main() - .de()?; - if status.running() { - DependencyError::HealthChecksFailed { - failures: BTreeMap::new(), - } - .try_heal( - db, - id, - dependency, - dependency_config_error, - info, - )? - } else { - Some(DependencyError::NotRunning) - } - } - DependencyError::HealthChecksFailed { .. } => { - let status = dependency_installed_info - .or_not_found(dependency)? - .as_status() - .as_main() - .de()?; - match status { - MainStatus::BackingUp { - started: Some(_), - health, - } - | MainStatus::Running { health, .. } => { - let mut failures = BTreeMap::new(); - for (check, res) in health { - if !matches!(res, HealthCheckResult::Success) - && installed_info - .as_current_dependencies() - .as_idx(dependency) - .or_not_found(dependency)? - .de()? - .health_checks - .contains(&check) - { - failures.insert(check.clone(), res.clone()); - } - } - if !failures.is_empty() { - Some(DependencyError::HealthChecksFailed { failures }) - } else { - DependencyError::Transitive.try_heal(db, id, dependency, None, info)? - } - } - MainStatus::Starting { .. } | MainStatus::Restarting => { - DependencyError::Transitive.try_heal(db, id, dependency, None, info)? - } - _ => return Ok(Some(DependencyError::NotRunning)), - } - } - DependencyError::Transitive => { - if dependency_installed_info - .or_not_found(dependency)? - .as_status() - .as_dependency_errors() - .de()? - .unwrap_or_default() - .0 - .is_empty() - { - None - } else { - Some(DependencyError::Transitive) - } - } - }) - } -} -impl PartialOrd for DependencyError { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp_priority(other)) - } -} -impl Ord for DependencyError {} -impl std::fmt::Display for DependencyError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - DependencyError::NotInstalled => write!(f, "Not Installed"), - DependencyError::IncorrectVersion { expected, received } => write!( - f, - "Incorrect Version: Expected {}, Received {}", - expected, - received.as_str() - ), - DependencyError::ConfigUnsatisfied { error } => { - write!(f, "Configuration Requirements Not Satisfied: {}", error) - } - DependencyError::NotRunning => write!(f, "Not Running"), - DependencyError::HealthChecksFailed { failures } => { - write!(f, "Failed Health Check(s): ")?; - let mut comma = false; - for (check, res) in failures { - if !comma { - comma = true; - } else { - write!(f, ", ")?; - } - write!(f, "{}: {}", check, res)?; - } - Ok(()) - } - DependencyError::Transitive => { - write!(f, "Dependency Error(s)") - } - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub struct TaggedDependencyError { - pub dependency: PackageId, - pub error: DependencyError, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub struct BreakageRes(pub BTreeMap); - #[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel)] #[model = "Model"] pub struct Dependencies(pub BTreeMap); @@ -344,36 +60,6 @@ pub struct DepInfo { #[serde(default)] pub config: Option, } -impl DepInfo { - pub async fn satisfied( - &self, - ctx: &RpcContext, - db: &mut Db, - dependency_id: &PackageId, - dependency_config: Option, // fetch if none - dependent_id: &PackageId, - receipts: &TryHealReceipts, - ) -> Result, Error> { - Ok( - if let Some(err) = DependencyError::NotInstalled - .try_heal( - ctx, - db, - dependent_id, - dependency_id, - dependency_config, - self, - receipts, - ) - .await? - { - Err(err) - } else { - Ok(()) - }, - ) - } -} #[derive(Clone, Debug, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] @@ -446,15 +132,13 @@ pub async fn configure_impl( ctx: RpcContext, (pkg_id, dep_id): (PackageId, PackageId), ) -> Result<(), Error> { - let mut db = ctx.db.handle(); let breakages = BTreeMap::new(); let overrides = Default::default(); - let receipts = DependencyConfigReceipts::new(&mut db, &pkg_id, &dep_id).await?; let ConfigDryRes { old_config: _, new_config, spec: _, - } = configure_logic(ctx.clone(), &mut db, (pkg_id, dep_id.clone()), &receipts).await?; + } = configure_logic(ctx.clone(), (pkg_id, dep_id.clone())).await?; let configure_context = ConfigureContext { breakages, @@ -481,50 +165,38 @@ pub async fn configure_dry( #[context] ctx: RpcContext, #[parent_data] (pkg_id, dependency_id): (PackageId, PackageId), ) -> Result { - let mut db = ctx.db.handle(); - let receipts = DependencyConfigReceipts::new(&mut db, &pkg_id, &dependency_id).await?; - configure_logic(ctx, &mut db, (pkg_id, dependency_id), &receipts).await + configure_logic(ctx, (pkg_id, dependency_id)).await } pub async fn configure_logic( ctx: RpcContext, - db: &mut PatchDbHandle, (pkg_id, dependency_id): (PackageId, PackageId), - receipts: &DependencyConfigReceipts, ) -> Result { - let pkg_version = receipts.package_version.get(db).await?; - let pkg_volumes = receipts.package_volumes.get(db).await?; - let dependency_config_action = receipts.dependency_config_action.get(db).await?; - let dependency_version = receipts.dependency_version.get(db).await?; - let dependency_volumes = receipts.dependency_volumes.get(db).await?; - let dependencies = receipts.dependencies.get(db).await?; - let pkg_docker_container = receipts.docker_containers.get(db, &*pkg_id).await?; + let db = ctx.db.peek().await?; + let pkg = db + .as_package_data() + .as_idx(&pkg_id) + .or_not_found(&pkg_id)? + .as_installed() + .or_not_found(&pkg_id)?; + let pkg_version = pkg.as_manifest().as_version().de()?; + let pkg_volumes = pkg.as_manifest().as_volumes().de()?; + let dependency = db + .as_package_data() + .as_idx(&pkg_id) + .or_not_found(&pkg_id)? + .as_installed() + .or_not_found(&pkg_id)?; + let dependency_config_action = dependency.as_manifest().as_config().de()?; + let dependency_version = dependency.as_manifest().as_version().de()?; + let dependency_volumes = dependency.as_manifest().as_volumes().de()?; + let dependency = pkg + .as_manifest() + .as_dependencies() + .as_idx(&dependency_id) + .or_not_found(&dependency_id)?; + let pkg_docker_container = pkg.as_manifest().as_containers().de()?; - let dependency = dependencies - .0 - .get(&dependency_id) - .ok_or_else(|| { - Error::new( - eyre!( - "dependency for {} not found in the manifest for {}", - dependency_id, - pkg_id - ), - crate::ErrorKind::NotFound, - ) - })? - .config - .as_ref() - .ok_or_else(|| { - Error::new( - eyre!( - "dependency config for {} not found on {}", - dependency_id, - pkg_id - ), - crate::ErrorKind::NotFound, - ) - })?; let ConfigRes { config: maybe_config, spec, @@ -547,6 +219,8 @@ pub async fn configure_logic( }; let new_config = dependency + .as_config() + .de()? .auto_configure .sandboxed( &ctx, @@ -566,340 +240,54 @@ pub async fn configure_logic( spec, }) } + #[instrument(skip_all)] -pub async fn add_dependent_to_current_dependents_lists<'a, Db: DbHandle>( - db: &mut Db, +pub fn add_dependent_to_current_dependents_lists( + db: &mut Model, dependent_id: &PackageId, current_dependencies: &CurrentDependencies, - current_dependent_receipt: &LockReceipt, ) -> Result<(), Error> { for (dependency, dep_info) in ¤t_dependencies.0 { - if let Some(mut dependency_dependents) = - current_dependent_receipt.get(db, dependency).await? + if let Some(mut dependency_dependents) = db + .as_package_data_mut() + .as_idx_mut(dependency) + .and_then(|pde| pde.as_installed_mut()) + .map(|i| i.as_current_dependents_mut()) { - dependency_dependents - .0 - .insert(dependent_id.clone(), dep_info.clone()); - current_dependent_receipt - .set(db, dependency_dependents, dependency) - .await?; + dependency_dependents.insert(dependent_id, dep_info)?; } } Ok(()) } -#[derive(Debug, Clone, Default, Deserialize, Serialize, HasModel)] -#[model = "Model"] -pub struct DependencyErrors(pub BTreeMap>); -impl Map for DependencyErrors { - type Key = PackageId; - type Value = OrdSet; -} -impl DependencyErrors { - pub async fn init( - ctx: &RpcContext, - db: &mut Db, - manifest: &Manifest, - current_dependencies: &CurrentDependencies, - receipts: &TryHealReceipts, - ) -> Result { - let mut res = BTreeMap::new(); - for (dependency_id, info) in current_dependencies.0.keys().filter_map(|dependency_id| { - manifest - .dependencies - .0 - .get(dependency_id) - .map(|info| (dependency_id, info)) - }) { - if let Err(e) = info - .satisfied(ctx, db, dependency_id, None, &manifest.id, receipts) - .await? - { - res.insert(dependency_id.clone(), e); - } - } - Ok(DependencyErrors(res)) - } -} -impl std::fmt::Display for DependencyErrors { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{{ ")?; - for (idx, (id, err)) in self.0.iter().enumerate() { - write!(f, "{}: {}", id, err)?; - if idx < self.0.len() - 1 { - // not last - write!(f, ", ")?; - } - } - write!(f, " }}") - } -} - -pub async fn break_all_dependents_transitive<'a, Db: DbHandle>( - db: &'a mut Db, - id: &'a PackageId, - error: DependencyError, - breakages: &'a mut BTreeMap, - receipts: &'a BreakTransitiveReceipts, -) -> Result<(), Error> { - for dependent in receipts - .current_dependents - .get(db, id) - .await? - .iter() - .flat_map(|x| x.0.keys()) - .filter(|dependent| id != *dependent) - { - break_transitive(db, dependent, id, error.clone(), breakages, receipts).await?; - } - Ok(()) -} - -#[derive(Clone)] -pub struct BreakTransitiveReceipts { - pub dependency_receipt: DependencyReceipt, - dependency_errors: LockReceipt, - current_dependents: LockReceipt, -} - -impl BreakTransitiveReceipts { - pub async fn new(db: &'_ mut impl DbHandle) -> Result { - let mut locks = Vec::new(); - - let setup = Self::setup(&mut locks); - Ok(setup(&db.lock_all(locks).await?)?) - } - - pub fn setup(locks: &mut Vec) -> impl FnOnce(&Verifier) -> Result { - let dependency_receipt = DependencyReceipt::setup(locks); - let dependency_errors = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .map(|x| x.status().dependency_errors()) - .make_locker(LockType::Write) - .add_to_keys(locks); - let current_dependents = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .map(|x| x.current_dependents()) - .make_locker(LockType::Exist) - .add_to_keys(locks); - move |skeleton_key| { - Ok(Self { - dependency_receipt: dependency_receipt(skeleton_key)?, - dependency_errors: dependency_errors.verify(skeleton_key)?, - current_dependents: current_dependents.verify(skeleton_key)?, - }) - } - } -} - -#[instrument(skip_all)] -pub fn break_transitive<'a, Db: DbHandle>( - db: &'a mut Db, - id: &'a PackageId, - dependency: &'a PackageId, - error: DependencyError, - breakages: &'a mut BTreeMap, - receipts: &'a BreakTransitiveReceipts, -) -> BoxFuture<'a, Result<(), Error>> { - async move { - let mut tx = db.begin().await?; - let mut dependency_errors = receipts - .dependency_errors - .get(&mut tx, id) - .await? - .ok_or_else(|| not_found!(id))?; - - let old = dependency_errors.0.remove(dependency); - let newly_broken = if let Some(e) = &old { - error.cmp_priority(&e) == Ordering::Greater - } else { - true - }; - dependency_errors.0.insert( - dependency.clone(), - if let Some(old) = old { - old.merge_with(error.clone()) - } else { - error.clone() - }, - ); - if newly_broken { - breakages.insert( - id.clone(), - TaggedDependencyError { - dependency: dependency.clone(), - error: error.clone(), - }, - ); - receipts - .dependency_errors - .set(&mut tx, dependency_errors, id) - .await?; - - tx.save().await?; - break_all_dependents_transitive( - db, - id, - DependencyError::Transitive, - breakages, - receipts, - ) - .await?; - } else { - receipts - .dependency_errors - .set(&mut tx, dependency_errors, id) - .await?; - - tx.save().await?; - } - - Ok(()) - } - .boxed() -} - -#[instrument(skip_all)] -pub async fn heal_all_dependents_transitive<'a, Db: DbHandle>( - ctx: &'a RpcContext, - db: &'a mut Db, - id: &'a PackageId, - locks: &'a DependencyReceipt, -) -> Result<(), Error> { - let dependents = locks - .current_dependents - .get(db, id) - .await? - .ok_or_else(|| not_found!(id))?; - for dependent in dependents.0.keys().filter(|dependent| id != *dependent) { - heal_transitive(ctx, db, dependent, id, locks).await?; - } - Ok(()) -} - -#[instrument(skip_all)] -pub fn heal_transitive<'a, Db: DbHandle>( - ctx: &'a RpcContext, - db: &'a mut Db, - id: &'a PackageId, - dependency: &'a PackageId, - receipts: &'a DependencyReceipt, -) -> BoxFuture<'a, Result<(), Error>> { - async move { - let mut status = receipts - .status - .get(db, id) - .await? - .ok_or_else(|| not_found!(id))?; - - let old = status.dependency_errors.0.remove(dependency); - - if let Some(old) = old { - let info = receipts - .dependency - .get(db, (id, dependency)) - .await? - .ok_or_else(|| not_found!(format!("{id}'s dependency: {dependency}")))?; - if let Some(new) = old - .try_heal(ctx, db, id, dependency, None, &info, &receipts.try_heal) - .await? - { - status.dependency_errors.0.insert(dependency.clone(), new); - receipts.status.set(db, status, id).await?; - } else { - receipts.status.set(db, status, id).await?; - heal_all_dependents_transitive(ctx, db, id, receipts).await?; - } - } - - Ok(()) - } - .boxed() -} - pub async fn reconfigure_dependents_with_live_pointers( ctx: &RpcContext, - tx: impl DbHandle, - receipts: &ConfigReceipts, - pde: &InstalledPackageDataEntry, + i: &InstalledPackageInfo, ) -> Result<(), Error> { - let dependents = &pde.current_dependents; - let me = &pde.manifest.id; - for (dependent_id, dependency_info) in &dependents.0 { - if dependency_info.pointers.iter().any(|ptr| match ptr { - // dependency id matches the package being uninstalled - PackagePointerSpec::TorAddress(ptr) => &ptr.package_id == me && dependent_id != me, - PackagePointerSpec::LanAddress(ptr) => &ptr.package_id == me && dependent_id != me, - // we never need to retarget these - PackagePointerSpec::TorKey(_) => false, - PackagePointerSpec::Config(_) => false, - }) { - let breakages = BTreeMap::new(); - let overrides = Default::default(); - - let configure_context = ConfigureContext { - breakages, - timeout: None, - config: None, - dry_run: false, - overrides, - }; - crate::config::configure(&ctx, dependent_id, configure_context).await?; - } - } + todo!(); + // let dependents = &pde.current_dependents; + // let me = &pde.manifest.id; + // for (dependent_id, dependency_info) in &dependents.0 { + // if dependency_info.pointers.iter().any(|ptr| match ptr { + // // dependency id matches the package being uninstalled + // PackagePointerSpec::TorAddress(ptr) => &ptr.package_id == me && dependent_id != me, + // PackagePointerSpec::LanAddress(ptr) => &ptr.package_id == me && dependent_id != me, + // // we never need to retarget these + // PackagePointerSpec::TorKey(_) => false, + // PackagePointerSpec::Config(_) => false, + // }) { + // let breakages = BTreeMap::new(); + // let overrides = Default::default(); + + // let configure_context = ConfigureContext { + // breakages, + // timeout: None, + // config: None, + // dry_run: false, + // overrides, + // }; + // crate::config::configure(&ctx, dependent_id, configure_context).await?; + // } + // } Ok(()) } - -#[derive(Clone)] -pub struct DependencyReceipt { - pub try_heal: TryHealReceipts, - current_dependents: LockReceipt, - status: LockReceipt, - dependency: LockReceipt, -} - -impl DependencyReceipt { - pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result { - let mut locks = Vec::new(); - - let setup = Self::setup(&mut locks); - Ok(setup(&db.lock_all(locks).await?)?) - } - - pub fn setup(locks: &mut Vec) -> impl FnOnce(&Verifier) -> Result { - let try_heal = TryHealReceipts::setup(locks); - let dependency = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .map(|x| x.manifest().dependencies().star()) - .make_locker(LockType::Read) - .add_to_keys(locks); - let current_dependents = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .map(|x| x.current_dependents()) - .make_locker(LockType::Write) - .add_to_keys(locks); - let status = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .map(|x| x.status()) - .make_locker(LockType::Write) - .add_to_keys(locks); - move |skeleton_key| { - Ok(Self { - try_heal: try_heal(skeleton_key)?, - current_dependents: current_dependents.verify(skeleton_key)?, - status: status.verify(skeleton_key)?, - dependency: dependency.verify(skeleton_key)?, - }) - } - } -} diff --git a/backend/src/status/mod.rs b/backend/src/status/mod.rs index 412de2991..2d9fe4a71 100644 --- a/backend/src/status/mod.rs +++ b/backend/src/status/mod.rs @@ -1,10 +1,11 @@ use std::collections::BTreeMap; use chrono::{DateTime, Utc}; +use imbl::OrdMap; +use models::PackageId; use serde::{Deserialize, Serialize}; use self::health_check::HealthCheckId; -use crate::dependencies::DependencyErrors; use crate::prelude::*; use crate::status::health_check::HealthCheckResult; @@ -15,7 +16,7 @@ pub mod health_check; pub struct Status { pub configured: bool, pub main: MainStatus, - pub dependency_errors: DependencyErrors, + pub dependency_config_errors: OrdMap, } #[derive(Debug, Clone, Deserialize, Serialize)] diff --git a/backend/src/util/mod.rs b/backend/src/util/mod.rs index a9daf76cc..6740f2e30 100644 --- a/backend/src/util/mod.rs +++ b/backend/src/util/mod.rs @@ -31,7 +31,7 @@ pub mod logger; pub mod lshw; pub mod serde; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, ::serde::Deserialize, ::serde::Serialize)] pub enum Never {} impl Never {} impl Never { From 654493f977ce5c976467a5bf60a2a7d1e86f2917 Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Tue, 29 Aug 2023 14:39:16 -0600 Subject: [PATCH 28/89] wip update/mod --- backend/src/control.rs | 6 +- backend/src/update/mod.rs | 117 ++++++++++++++++++-------------------- 2 files changed, 57 insertions(+), 66 deletions(-) diff --git a/backend/src/control.rs b/backend/src/control.rs index a2b5094da..f1c7e2cea 100644 --- a/backend/src/control.rs +++ b/backend/src/control.rs @@ -5,10 +5,6 @@ use rpc_toolkit::command; use tracing::instrument; use crate::context::RpcContext; -use crate::dependencies::{ - break_all_dependents_transitive, heal_all_dependents_transitive, BreakageRes, DependencyError, - DependencyReceipt, TaggedDependencyError, -}; use crate::prelude::*; use crate::s9pk::manifest::PackageId; use crate::status::MainStatus; @@ -29,7 +25,7 @@ pub async fn start(#[context] ctx: RpcContext, #[arg] id: PackageId) -> Result<( .as_manifest() .as_version() .de()?; - heal_all_dependents_transitive(&ctx, &mut tx, &id).await?; + heal_all_dependents_transitive(&ctx, &id).await?; ctx.managers .get(&(id, version)) diff --git a/backend/src/update/mod.rs b/backend/src/update/mod.rs index 3322c403e..15c5fd051 100644 --- a/backend/src/update/mod.rs +++ b/backend/src/update/mod.rs @@ -7,7 +7,7 @@ use color_eyre::eyre::{eyre, Result}; use emver::Version; use helpers::{Rsync, RsyncOptions}; use lazy_static::lazy_static; -use patch_db::{DbHandle, LockType, Revision}; +use patch_db::Revision; use reqwest::Url; use rpc_toolkit::command; use tokio::process::Command; @@ -21,6 +21,7 @@ use crate::disk::mount::filesystem::ReadWrite; use crate::disk::mount::guard::MountGuard; use crate::marketplace::with_query_params; use crate::notifications::NotificationLevel; +use crate::prelude::*; use crate::sound::{ CIRCLE_OF_5THS_SHORT, UPDATE_FAILED_1, UPDATE_FAILED_2, UPDATE_FAILED_3, UPDATE_FAILED_4, }; @@ -76,11 +77,8 @@ fn display_update_result(status: UpdateResult, _: &ArgMatches) { } #[instrument(skip_all)] -async fn maybe_do_update( - ctx: RpcContext, - marketplace_url: Url, -) -> Result>, Error> { - let mut db = ctx.db.handle(); +async fn maybe_do_update(ctx: RpcContext, marketplace_url: Url) -> Result<(), Error> { + let peeked = ctx.db.peek().await?; let latest_version: Version = ctx .client .get(with_query_params( @@ -94,70 +92,68 @@ async fn maybe_do_update( .await .with_kind(ErrorKind::Network)? .version; - crate::db::DatabaseModel::new() - .server_info() - .lock(&mut db, LockType::Write) - .await?; - let current_version = crate::db::DatabaseModel::new() - .server_info() - .version() - .get_mut(&mut db) - .await?; - if &latest_version < ¤t_version { - return Ok(None); - } - let mut tx = db.begin().await?; - let mut status = crate::db::DatabaseModel::new() - .server_info() - .status_info() - .get_mut(&mut tx) - .await?; - if status.update_progress.is_some() { - return Err(Error::new( - eyre!("Server is already updating!"), - crate::ErrorKind::InvalidRequest, - )); - } - if status.updated { + let current_version = peeked.as_server_info().as_version().de()?; + if latest_version < current_version { return Ok(None); } + let status = peeked.as_status_info().as_server_status().de()?; let eos_url = EosUrl { base: marketplace_url, version: latest_version, }; + let status = ctx + .db + .mutate(|db| { + let mut status = peeked.as_status_info().as_server_status().de()?; + if status.update_progress.is_some() { + return Err(Error::new( + eyre!("Server is already updating!"), + crate::ErrorKind::InvalidRequest, + )); + } - status.update_progress = Some(UpdateProgress { - size: None, - downloaded: 0, - }); - status.save(&mut tx).await?; - let rev = tx.commit().await?; + status.update_progress = Some(UpdateProgress { + size: None, + downloaded: 0, + }); + db.as_status_info_mut().as_server_status_mut().ser(&status) + }) + .await?; + + if status.updated { + return Ok(None); + } tokio::spawn(async move { let res = do_update(ctx.clone(), eos_url).await; - let mut db = ctx.db.handle(); - let mut status = crate::db::DatabaseModel::new() - .server_info() - .status_info() - .get_mut(&mut db) - .await - .expect("could not access status"); - status.update_progress = None; + ctx.db + .mutate(|db| { + db.as_server_info_mut() + .as_status_info_mut() + .as_update_progress_mut() + .ser(&None) + }) + .await?; match res { Ok(()) => { - status.updated = true; - status.save(&mut db).await.expect("could not save status"); + ctx.db + .mutate(|db| { + db.as_server_info_mut() + .as_status_info_mut() + .as_updated_mut() + .ser(&true) + }) + .await?; CIRCLE_OF_5THS_SHORT .play() .await .expect("could not play sound"); } Err(e) => { - status.save(&mut db).await.expect("could not save status"); ctx.notification_manager .notify( - &mut db, + ctx.db, None, NotificationLevel::Error, "embassyOS Update Failed".to_owned(), @@ -187,7 +183,7 @@ async fn maybe_do_update( } } }); - Ok(rev) + Ok(()) } #[instrument(skip_all)] @@ -199,17 +195,16 @@ async fn do_update(ctx: RpcContext, eos_url: EosUrl) -> Result<(), Error> { ) .await?; while let Some(progress) = rsync.progress.next().await { - crate::db::DatabaseModel::new() - .server_info() - .status_info() - .update_progress() - .put( - &mut ctx.db.handle(), - &UpdateProgress { - size: Some(100), - downloaded: (100.0 * progress) as u64, - }, - ) + ctx.db + .mutate(|db| { + db.as_server_status_mut() + .as_status_info_mut() + .as_update_progress_mut() + .ser(&UpdateProgress { + size: Some(100), + downloaded: (100.0 * progress) as u64, + }) + }) .await?; } rsync.wait().await?; From 9323e8356df9f2dc30d48357207b49f47a0393dd Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Tue, 29 Aug 2023 14:41:43 -0600 Subject: [PATCH 29/89] detect breakages locally for updates --- .../marketplace-show-controls.component.ts | 58 ++++++---------- .../ui/src/app/pages/updates/updates.page.ts | 67 +++++++------------ .../ui/src/app/services/api/api.types.ts | 3 - .../app/services/api/embassy-api.service.ts | 4 -- .../services/api/embassy-live-api.service.ts | 6 -- .../services/api/embassy-mock-api.service.ts | 16 ----- .../projects/ui/src/app/util/dry-update.ts | 17 +++++ .../ui/src/app/util/get-package-data.ts | 2 +- 8 files changed, 60 insertions(+), 113 deletions(-) create mode 100644 frontend/projects/ui/src/app/util/dry-update.ts diff --git a/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-controls/marketplace-show-controls.component.ts b/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-controls/marketplace-show-controls.component.ts index d9e2d99d8..c2edfc15d 100644 --- a/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-controls/marketplace-show-controls.component.ts +++ b/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-controls/marketplace-show-controls.component.ts @@ -23,11 +23,10 @@ import { import { ClientStorageService } from 'src/app/services/client-storage.service' import { MarketplaceService } from 'src/app/services/marketplace.service' import { hasCurrentDeps } from 'src/app/util/has-deps' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { Breakages } from 'src/app/services/api/api.types' import { PatchDB } from 'patch-db-client' import { getAllPackages } from 'src/app/util/get-package-data' import { firstValueFrom } from 'rxjs' +import { dryUpdate } from 'src/app/util/dry-update' @Component({ selector: 'marketplace-show-controls', @@ -57,7 +56,6 @@ export class MarketplaceShowControlsComponent { private readonly loadingCtrl: LoadingController, private readonly emver: Emver, private readonly errToast: ErrorToastService, - private readonly embassyApi: ApiService, private readonly patch: PatchDB, ) {} @@ -142,30 +140,19 @@ export class MarketplaceShowControlsComponent { } private async dryInstall(url: string) { - const loader = await this.loadingCtrl.create({ - message: 'Checking dependent services...', - }) - await loader.present() - - const { id, version } = this.pkg.manifest - - try { - const breakages = await this.embassyApi.dryUpdatePackage({ - id, - version: `${version}`, - }) + const breakages = dryUpdate( + this.pkg.manifest, + await getAllPackages(this.patch), + this.emver, + ) - if (isEmptyObject(breakages)) { - this.install(url, loader) - } else { - await loader.dismiss() - const proceed = await this.presentAlertBreakages(breakages) - if (proceed) { - this.install(url) - } + if (isEmptyObject(breakages)) { + this.install(url) + } else { + const proceed = await this.presentAlertBreakages(breakages) + if (proceed) { + this.install(url) } - } catch (e: any) { - this.errToast.present(e) } } @@ -194,14 +181,11 @@ export class MarketplaceShowControlsComponent { await alert.present() } - private async install(url: string, loader?: HTMLIonLoadingElement) { - const message = 'Beginning Install...' - if (loader) { - loader.message = message - } else { - loader = await this.loadingCtrl.create({ message }) - await loader.present() - } + private async install(url: string) { + const loader = await this.loadingCtrl.create({ + message: 'Beginning Install...', + }) + await loader.present() const { id, version } = this.pkg.manifest @@ -214,14 +198,10 @@ export class MarketplaceShowControlsComponent { } } - private async presentAlertBreakages(breakages: Breakages): Promise { + private async presentAlertBreakages(breakages: string[]): Promise { let message: string = 'As a result of this update, the following services will no longer work properly and may crash:
    ' - const localPkgs = await getAllPackages(this.patch) - const bullets = Object.keys(breakages).map(id => { - const title = localPkgs[id].manifest.title - return `
  • ${title}
  • ` - }) + const bullets = breakages.map(title => `
  • ${title}
  • `) message = `${message}${bullets.join('')}
` return new Promise(async resolve => { diff --git a/frontend/projects/ui/src/app/pages/updates/updates.page.ts b/frontend/projects/ui/src/app/pages/updates/updates.page.ts index cfa833e67..522b51218 100644 --- a/frontend/projects/ui/src/app/pages/updates/updates.page.ts +++ b/frontend/projects/ui/src/app/pages/updates/updates.page.ts @@ -1,5 +1,4 @@ import { Component, Inject } from '@angular/core' -import { ApiService } from 'src/app/services/api/embassy-api.service' import { PatchDB } from 'patch-db-client' import { DataModel, @@ -16,14 +15,10 @@ import { import { Emver, isEmptyObject } from '@start9labs/shared' import { Pipe, PipeTransform } from '@angular/core' import { combineLatest, Observable } from 'rxjs' -import { - AlertController, - LoadingController, - NavController, -} from '@ionic/angular' +import { AlertController, NavController } from '@ionic/angular' import { hasCurrentDeps } from 'src/app/util/has-deps' import { getAllPackages } from 'src/app/util/get-package-data' -import { Breakages } from 'src/app/services/api/api.types' +import { dryUpdate } from 'src/app/util/dry-update' interface UpdatesData { hosts: StoreIdentity[] @@ -48,11 +43,10 @@ export class UpdatesPage { constructor( @Inject(AbstractMarketplaceService) readonly marketplaceService: MarketplaceService, - private readonly api: ApiService, private readonly patch: PatchDB, private readonly navCtrl: NavController, - private readonly loadingCtrl: LoadingController, private readonly alertCtrl: AlertController, + private readonly emver: Emver, ) {} viewInMarketplace(event: Event, url: string, id: string) { @@ -77,55 +71,40 @@ export class UpdatesPage { this.marketplaceService.updateQueue[id] = true if (hasCurrentDeps(local)) { - this.dryUpdate(manifest, url) + this.dryInstall(manifest, url) } else { - this.update(id, version, url) + this.install(id, version, url) } } - private async dryUpdate(manifest: MarketplaceManifest, url: string) { - const loader = await this.loadingCtrl.create({ - message: 'Checking dependent services...', - }) - await loader.present() - - const { id, version } = manifest + private async dryInstall(manifest: MarketplaceManifest, url: string) { + const { id, version, title } = manifest - try { - const breakages = await this.api.dryUpdatePackage({ - id, - version: `${version}`, - }) - await loader.dismiss() + const breakages = dryUpdate( + manifest, + await getAllPackages(this.patch), + this.emver, + ) - if (isEmptyObject(breakages)) { - this.update(id, version, url) + if (isEmptyObject(breakages)) { + this.install(id, version, url) + } else { + const proceed = await this.presentAlertBreakages(title, breakages) + if (proceed) { + this.install(id, version, url) } else { - const proceed = await this.presentAlertBreakages( - manifest.title, - breakages, - ) - if (proceed) { - this.update(id, version, url) - } else { - delete this.marketplaceService.updateQueue[id] - } + delete this.marketplaceService.updateQueue[id] } - } catch (e: any) { - delete this.marketplaceService.updateQueue[id] - this.marketplaceService.updateErrors[id] = e.message } } private async presentAlertBreakages( title: string, - breakages: Breakages, + breakages: string[], ): Promise { let message: string = `As a result of updating ${title}, the following services will no longer work properly and may crash:
    ` - const localPkgs = await getAllPackages(this.patch) - const bullets = Object.keys(breakages).map(id => { - const title = localPkgs[id].manifest.title - return `
  • ${title}
  • ` + const bullets = breakages.map(depTitle => { + return `
  • ${depTitle}
  • ` }) message = `${message}${bullets.join('')}
` @@ -156,7 +135,7 @@ export class UpdatesPage { }) } - private async update(id: string, version: string, url: string) { + private async install(id: string, version: string, url: string) { try { await this.marketplaceService.installPackage(id, version, url) delete this.marketplaceService.updateQueue[id] diff --git a/frontend/projects/ui/src/app/services/api/api.types.ts b/frontend/projects/ui/src/app/services/api/api.types.ts index b1f3db206..fbce4dc62 100644 --- a/frontend/projects/ui/src/app/services/api/api.types.ts +++ b/frontend/projects/ui/src/app/services/api/api.types.ts @@ -201,9 +201,6 @@ export module RR { } // package.install export type InstallPackageRes = null - export type DryUpdatePackageReq = { id: string; version: string } // package.update.dry - export type DryUpdatePackageRes = Breakages - export type GetPackageConfigReq = { id: string } // package.config.get export type GetPackageConfigRes = { spec: ConfigSpec; config: object } diff --git a/frontend/projects/ui/src/app/services/api/embassy-api.service.ts b/frontend/projects/ui/src/app/services/api/embassy-api.service.ts index 168993c11..941b407fa 100644 --- a/frontend/projects/ui/src/app/services/api/embassy-api.service.ts +++ b/frontend/projects/ui/src/app/services/api/embassy-api.service.ts @@ -210,10 +210,6 @@ export abstract class ApiService { params: RR.InstallPackageReq, ): Promise - abstract dryUpdatePackage( - params: RR.DryUpdatePackageReq, - ): Promise - abstract getPackageConfig( params: RR.GetPackageConfigReq, ): Promise diff --git a/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts b/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts index 002baf607..f5bddd439 100644 --- a/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts +++ b/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts @@ -366,12 +366,6 @@ export class LiveApiService extends ApiService { return this.rpcRequest({ method: 'package.install', params }) } - async dryUpdatePackage( - params: RR.DryUpdatePackageReq, - ): Promise { - return this.rpcRequest({ method: 'package.update.dry', params }) - } - async getPackageConfig( params: RR.GetPackageConfigReq, ): Promise { diff --git a/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts b/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts index 8a18247a7..109fc1255 100644 --- a/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts +++ b/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts @@ -643,22 +643,6 @@ export class MockApiService extends ApiService { return this.withRevision(patch) } - async dryUpdatePackage( - params: RR.DryUpdatePackageReq, - ): Promise { - await pauseFor(2000) - return { - lnd: { - dependency: 'bitcoind', - error: { - type: DependencyErrorType.IncorrectVersion, - expected: '>0.23.0', - received: params.version, - }, - }, - } - } - async getPackageConfig( params: RR.GetPackageConfigReq, ): Promise { diff --git a/frontend/projects/ui/src/app/util/dry-update.ts b/frontend/projects/ui/src/app/util/dry-update.ts new file mode 100644 index 000000000..9b7ba44a3 --- /dev/null +++ b/frontend/projects/ui/src/app/util/dry-update.ts @@ -0,0 +1,17 @@ +import { Emver } from '@start9labs/shared' +import { DataModel } from '../services/patch-db/data-model' + +export function dryUpdate( + { id, version }: { id: string; version: string }, + pkgs: DataModel['package-data'], + emver: Emver, +): string[] { + return Object.values(pkgs) + .filter( + pkg => + Object.keys(pkg.installed?.['current-dependencies'] || {}).some( + pkgId => pkgId === id, + ) && !emver.satisfies(version, pkg.manifest.dependencies[id].version), + ) + .map(pkg => pkg.manifest.title) +} diff --git a/frontend/projects/ui/src/app/util/get-package-data.ts b/frontend/projects/ui/src/app/util/get-package-data.ts index 816471832..0645f3b3c 100644 --- a/frontend/projects/ui/src/app/util/get-package-data.ts +++ b/frontend/projects/ui/src/app/util/get-package-data.ts @@ -14,6 +14,6 @@ export async function getPackage( export async function getAllPackages( patch: PatchDB, -): Promise> { +): Promise { return firstValueFrom(patch.watch$('package-data')) } From 455d9a5294949499c83bc35ddf6a79ab38655691 Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Tue, 29 Aug 2023 14:54:48 -0600 Subject: [PATCH 30/89] wip: manager/mod --- backend/src/manager/manager_container.rs | 80 +++++++++--------------- backend/src/manager/mod.rs | 17 ++--- 2 files changed, 33 insertions(+), 64 deletions(-) diff --git a/backend/src/manager/manager_container.rs b/backend/src/manager/manager_container.rs index 198cf70af..4ab86a50d 100644 --- a/backend/src/manager/manager_container.rs +++ b/backend/src/manager/manager_container.rs @@ -2,13 +2,14 @@ use std::sync::Arc; use std::time::Duration; use futures::FutureExt; -use patch_db::PatchDbHandle; +use models::OptionExt; use tokio::sync::watch; use tokio::sync::watch::Sender; use tracing::instrument; use super::start_stop::StartStop; use super::{manager_seed, run_main, ManagerPersistentContainer, RunMainResult}; +use crate::prelude::*; use crate::procedure::NoOutput; use crate::s9pk::manifest::Manifest; use crate::status::MainStatus; @@ -32,11 +33,9 @@ impl ManageContainer { seed: Arc, persistent_container: ManagerPersistentContainer, ) -> Result { - let mut db = seed.ctx.db.handle(); let current_state = Arc::new(watch::channel(StartStop::Stop).0); - let desired_state = Arc::new( - watch::channel::(get_status(&mut db, &seed.manifest).await.into()).0, - ); + let desired_state = + Arc::new(watch::channel::(get_status(&mut db, &seed.manifest).into()).0); let override_main_status: ManageContainerOverride = Arc::new(watch::channel(None).0); let service = tokio::spawn(create_service_manager( desired_state.clone(), @@ -73,9 +72,9 @@ impl ManageContainer { } /// Set the override, but don't have a guard to revert it. Used only on the mananger to do a shutdown. - pub(super) async fn lock_state_forever(&self, seed: &manager_seed::ManagerSeed) { + pub(super) fn lock_state_forever(&self, seed: &manager_seed::ManagerSeed) { let mut db = seed.ctx.db.handle(); - let current_state = get_status(&mut db, &seed.manifest).await; + let current_state = get_status(&mut db, &seed.manifest); self.override_main_status .send_modify(|x| *x = Some(current_state)); } @@ -289,55 +288,34 @@ async fn run_main_log_result(result: RunMainResult, seed: Arc MainStatus { - async move { - Ok::<_, Error>( - crate::db::DatabaseModel::new() - .package_data() - .idx_model(&manifest.id) - .expect(db) - .await? - .installed() - .expect(db) - .await? - .status() - .main() - .get(db) - .await? - .clone(), - ) - } - .map(|x| x.unwrap_or_else(|_| MainStatus::Stopped)) - .await +pub(super) fn get_status(db: Peeked, manifest: &Manifest) -> MainStatus { + db.as_package_data() + .as_idx(&manifest.id) + .or_not_found(&manifest.id)? + .as_installed() + .or_not_found(&manifest.id)? + .as_status() + .as_main() + .de() + .unwrap_or_else(|_| MainStatus::Stopped) } #[instrument(skip(db, manifest))] async fn set_status( - db: &mut PatchDbHandle, + db: PatchDb, manifest: &Manifest, main_status: &MainStatus, ) -> Result<(), Error> { - if crate::db::DatabaseModel::new() - .package_data() - .idx_model(&manifest.id) - .expect(db) - .await? - .installed() - .exists(db) - .await? - { - crate::db::DatabaseModel::new() - .package_data() - .idx_model(&manifest.id) - .expect(db) - .await? - .installed() - .expect(db) - .await? - .status() - .main() - .put(db, main_status) - .await?; - } - Ok(()) + db.mutate(|db| { + let Some(installed) = db + .as_package_data_mut() + .as_idx_mut(&manifest.id) + .or_not_found(&manifest.id)? + .as_installed_mut() + else { + return Ok(()); + }; + installed.as_status_mut().as_main_main().ser(main_status) + }) + .await } diff --git a/backend/src/manager/mod.rs b/backend/src/manager/mod.rs index 644b9aa66..f0f2162a4 100644 --- a/backend/src/manager/mod.rs +++ b/backend/src/manager/mod.rs @@ -30,10 +30,7 @@ use crate::config::spec::ValueSpecPointer; use crate::config::{not_found, ConfigReceipts, ConfigureContext}; use crate::context::RpcContext; use crate::db::model::{CurrentDependencies, CurrentDependencyInfo}; -use crate::dependencies::{ - add_dependent_to_current_dependents_lists, break_transitive, heal_all_dependents_transitive, - DependencyError, DependencyErrors, TaggedDependencyError, -}; +use crate::dependencies::add_dependent_to_current_dependents_lists; use crate::disk::mount::backup::BackupMountGuard; use crate::disk::mount::guard::TmpMountGuard; use crate::install::cleanup::remove_from_current_dependents_lists; @@ -205,7 +202,7 @@ impl Manager { /// A special exit that is overridden the start state, should only be called in the shutdown, where we remove other containers async fn shutdown(&self) { - self.manage_container.lock_state_forever(&self.seed).await; + self.manage_container.lock_state_forever(&self.seed); self.exit().await; } @@ -268,7 +265,7 @@ impl Manager { let state_reverter = DesiredStateReverter::new(manage_container.clone()); let mut tx = seed.ctx.db.handle(); let override_guard = manage_container - .set_override(Some(get_status(&mut tx, &seed.manifest).await.backing_up())); + .set_override(Some(get_status(&mut tx, &seed.manifest).backing_up())); manage_container.wait_for_desired(StartStop::Stop).await; let backup_guard = backup_guard.lock().await; let guard = backup_guard.mount_package_backup(&seed.manifest.id).await?; @@ -482,13 +479,7 @@ async fn configure( &receipts.current_dependents, ) .await?; // remove previous - add_dependent_to_current_dependents_lists( - db, - id, - ¤t_dependencies, - &receipts.current_dependents, - ) - .await?; // add new + add_dependent_to_current_dependents_lists(db, id, ¤t_dependencies).await?; // add new current_dependencies.0.remove(id); receipts .current_dependencies From 99667e0f57bc0ddff2a406953f380a00bdebcbb4 Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Tue, 29 Aug 2023 14:59:27 -0600 Subject: [PATCH 31/89] wip: manager/health --- backend/src/manager/health.rs | 34 ++---------------------- backend/src/manager/manager_container.rs | 8 ++++-- 2 files changed, 8 insertions(+), 34 deletions(-) diff --git a/backend/src/manager/health.rs b/backend/src/manager/health.rs index 01c0d6bf8..59a1eac86 100644 --- a/backend/src/manager/health.rs +++ b/backend/src/manager/health.rs @@ -1,14 +1,9 @@ -use std::collections::BTreeMap; - use models::OptionExt; use tracing::instrument; use crate::context::RpcContext; -use crate::db::model::CurrentDependents; -use crate::db::prelude::*; -use crate::dependencies::{break_transitive, heal_transitive, DependencyError}; -use crate::s9pk::manifest::{Manifest, PackageId}; -use crate::status::health_check::{HealthCheckId, HealthCheckResult}; +use crate::prelude::*; +use crate::s9pk::manifest::PackageId; use crate::status::MainStatus; use crate::Error; @@ -63,30 +58,5 @@ pub async fn check(ctx: &RpcContext, id: &PackageId) -> Result<(), Error> { }) .await?; - for (dependent, info) in (current_dependents).0.iter() { - let failures: BTreeMap = health_results - .iter() - .filter(|(_, hc_res)| !matches!(hc_res, HealthCheckResult::Success { .. })) - .filter(|(hc_id, _)| info.health_checks.contains(hc_id)) - .map(|(k, v)| (k.clone(), v.clone())) - .collect(); - - if !failures.is_empty() { - break_transitive( - &mut tx, - &dependent, - id, - DependencyError::HealthChecksFailed { failures }, - &mut BTreeMap::new(), - &receipts, - ) - .await?; - } else { - heal_transitive(ctx, &mut tx, &dependent, id, &receipts.dependency_receipt).await?; - } - } - - tx.save().await?; - Ok(()) } diff --git a/backend/src/manager/manager_container.rs b/backend/src/manager/manager_container.rs index 4ab86a50d..ca66076c3 100644 --- a/backend/src/manager/manager_container.rs +++ b/backend/src/manager/manager_container.rs @@ -34,8 +34,12 @@ impl ManageContainer { persistent_container: ManagerPersistentContainer, ) -> Result { let current_state = Arc::new(watch::channel(StartStop::Stop).0); - let desired_state = - Arc::new(watch::channel::(get_status(&mut db, &seed.manifest).into()).0); + let desired_state = Arc::new( + watch::channel::( + get_status(seed.ctx.db.peek().await?, &seed.manifest).into(), + ) + .0, + ); let override_main_status: ManageContainerOverride = Arc::new(watch::channel(None).0); let service = tokio::spawn(create_service_manager( desired_state.clone(), From 25490553fbe3df51d3973418e6ae78cce7921cb7 Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Tue, 29 Aug 2023 15:30:31 -0600 Subject: [PATCH 32/89] wip: backup/target/mod --- backend/src/backup/mod.rs | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/backend/src/backup/mod.rs b/backend/src/backup/mod.rs index f403dac19..1585ff584 100644 --- a/backend/src/backup/mod.rs +++ b/backend/src/backup/mod.rs @@ -221,30 +221,25 @@ impl BackupActions { ) })?, )?; - ctx.db + let entry = ctx + .db .mutate(|v| { v.as_package_data_mut() .as_idx_mut(pkg_id) .or_not_found(pkg_id)? .expect_as_restoring_mut()? // TODO: Restoring? .as_marketplace_url_mut() - .ser(metadata.marketplace_url) + .ser(metadata.marketplace_url)?; + v.as_package_data() + .as_idx(pkg_id) + .or_not_found(pkg_id)? + .as_installed() + .or_not_found(pkg_id)? + .de() }) .await?; - let entry = crate::db::DatabaseModel::new() - .package_data() - .idx_model(pkg_id) - .expect(db) - .await? - .installed() - .expect(db) - .await? - .get(db) - .await?; - - let receipts = crate::config::ConfigReceipts::new(db).await?; - reconfigure_dependents_with_live_pointers(ctx, db, &receipts, &entry).await?; + reconfigure_dependents_with_live_pointers(ctx, &entry).await?; Ok(()) } From 107289e54fee23d35ae95750c997868a3f7288d2 Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Tue, 29 Aug 2023 15:34:17 -0600 Subject: [PATCH 33/89] fix: Typo addresses --- backend/src/db/model.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/db/model.rs b/backend/src/db/model.rs index 53fcd2a25..a38607e0d 100644 --- a/backend/src/db/model.rs +++ b/backend/src/db/model.rs @@ -443,16 +443,16 @@ pub struct CurrentDependencyInfo { } #[derive(Debug, Deserialize, Serialize)] -pub struct InterfaceAddressMap(pub BTreeMap); +pub struct InterfaceAddressMap(pub BTreeMap); impl Map for InterfaceAddressMap { type Key = InterfaceId; - type Value = InterfaceAdresses; + type Value = InterfaceAddresses; } #[derive(Debug, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] #[model = "Model"] -pub struct InterfaceAdresses { +pub struct InterfaceAddresses { pub tor_address: Option, pub lan_address: Option, } From 669dfe8bf803f273f9e77f8ad13cec42b690a043 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Tue, 29 Aug 2023 16:20:11 -0600 Subject: [PATCH 34/89] clean control.rs --- backend/src/control.rs | 79 ++++++++------------------------------ backend/src/manager/mod.rs | 2 +- 2 files changed, 17 insertions(+), 64 deletions(-) diff --git a/backend/src/control.rs b/backend/src/control.rs index a2b5094da..a84fe7283 100644 --- a/backend/src/control.rs +++ b/backend/src/control.rs @@ -1,19 +1,12 @@ -use std::collections::BTreeMap; - use color_eyre::eyre::eyre; use rpc_toolkit::command; use tracing::instrument; use crate::context::RpcContext; -use crate::dependencies::{ - break_all_dependents_transitive, heal_all_dependents_transitive, BreakageRes, DependencyError, - DependencyReceipt, TaggedDependencyError, -}; use crate::prelude::*; use crate::s9pk::manifest::PackageId; use crate::status::MainStatus; use crate::util::display_none; -use crate::util::serde::display_serializable; use crate::Error; #[command(display(display_none), metadata(sync_db = true))] @@ -29,7 +22,6 @@ pub async fn start(#[context] ctx: RpcContext, #[arg] id: PackageId) -> Result<( .as_manifest() .as_version() .de()?; - heal_all_dependents_transitive(&ctx, &mut tx, &id).await?; ctx.managers .get(&(id, version)) @@ -40,56 +32,8 @@ pub async fn start(#[context] ctx: RpcContext, #[arg] id: PackageId) -> Result<( Ok(()) } -#[instrument(skip_all)] -pub async fn stop_common( - db: PatchDb, - id: &PackageId, - breakages: &mut BTreeMap, -) -> Result { - let last_status = db - .mutate(|v| { - v.as_package_data_mut() - .as_idx_mut(id) - .and_then(|x| x.as_installed_mut()) - .ok_or_else(|| Error::new(eyre!("{} is not installed", id), ErrorKind::NotFound))? - .as_status_mut() - .as_main_mut() - .replace(&MainStatus::Stopping) - }) - .await?; - break_all_dependents_transitive(db, id, DependencyError::NotRunning, breakages).await?; - - Ok(last_status) -} - -#[command( - subcommands(self(stop_impl(async)), stop_dry), - display(display_none), - metadata(sync_db = true) -)] -pub fn stop(#[arg] id: PackageId) -> Result { - Ok(id) -} - -#[command(rename = "dry", display(display_serializable))] -#[instrument(skip_all)] -pub async fn stop_dry( - #[context] ctx: RpcContext, - #[parent_data] id: PackageId, -) -> Result { - let mut db = ctx.db.handle(); - let mut tx = db.begin().await?; - - let mut breakages = BTreeMap::new(); - stop_common(&mut tx, &id, &mut breakages).await?; - - tx.abort().await?; - - Ok(BreakageRes(breakages)) -} - -#[instrument(skip_all)] -pub async fn stop_impl(ctx: RpcContext, id: PackageId) -> Result { +#[command(display(display_none), metadata(sync_db = true))] +pub async fn stop(#[context] ctx: RpcContext, #[arg] id: PackageId) -> Result { let peek = ctx.db.peek().await?; let version = peek .as_package_data() @@ -101,7 +45,18 @@ pub async fn stop_impl(ctx: RpcContext, id: PackageId) -> Result Result .as_package_data() .as_idx(&id) .or_not_found(&id)? - .as_installed() - .or_not_found(&id)? + .expect_as_installed()? .as_manifest() .as_version() .de()?; @@ -129,8 +83,7 @@ pub async fn restart(#[context] ctx: RpcContext, #[arg] id: PackageId) -> Result .get(&(id, version)) .await .ok_or_else(|| Error::new(eyre!("Manager not found"), crate::ErrorKind::InvalidRequest))? - .restart() - .await; + .restart(); Ok(()) } diff --git a/backend/src/manager/mod.rs b/backend/src/manager/mod.rs index 644b9aa66..b9ab75e39 100644 --- a/backend/src/manager/mod.rs +++ b/backend/src/manager/mod.rs @@ -152,7 +152,7 @@ impl Manager { self._transition_abort(); self.manage_container.to_desired(StartStop::Stop); } - pub async fn restart(&self) { + pub fn restart(&self) { if self._is_transition_restart() { return; } From 5a0d1b26fc960c2c0ba67fcb24c14dd367211d06 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Tue, 29 Aug 2023 16:50:26 -0600 Subject: [PATCH 35/89] fix system package id --- backend/src/procedure/docker.rs | 2 +- libs/models/src/action_id.rs | 43 ------------------ libs/models/src/health_check_id.rs | 31 ------------- libs/models/src/id/mod.rs | 2 +- libs/models/src/image_id.rs | 38 ---------------- libs/models/src/interface_id.rs | 42 ------------------ libs/models/src/invalid_id.rs | 3 -- libs/models/src/package_id.rs | 71 ------------------------------ libs/models/src/volume_id.rs | 58 ------------------------ 9 files changed, 2 insertions(+), 288 deletions(-) delete mode 100644 libs/models/src/action_id.rs delete mode 100644 libs/models/src/health_check_id.rs delete mode 100644 libs/models/src/image_id.rs delete mode 100644 libs/models/src/interface_id.rs delete mode 100644 libs/models/src/invalid_id.rs delete mode 100644 libs/models/src/package_id.rs delete mode 100644 libs/models/src/volume_id.rs diff --git a/backend/src/procedure/docker.rs b/backend/src/procedure/docker.rs index 960d87bf9..6763d58f6 100644 --- a/backend/src/procedure/docker.rs +++ b/backend/src/procedure/docker.rs @@ -12,7 +12,7 @@ use color_eyre::Report; use futures::future::{BoxFuture, Either as EitherFuture}; use futures::{FutureExt, TryStreamExt}; use helpers::{NonDetachingJoinHandle, UnixRpcClient}; -use models::{Id, ImageId}; +use models::{Id, ImageId, SYSTEM_PACKAGE_ID}; use nix::sys::signal; use nix::unistd::Pid; use serde::de::DeserializeOwned; diff --git a/libs/models/src/action_id.rs b/libs/models/src/action_id.rs deleted file mode 100644 index 9b814a98a..000000000 --- a/libs/models/src/action_id.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::path::Path; -use std::str::FromStr; - -use serde::{Deserialize, Serialize}; - -use crate::{Id, InvalidId}; - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] -pub struct ActionId(Id); -impl FromStr for ActionId { - type Err = InvalidId; - fn from_str(s: &str) -> Result { - Ok(ActionId(Id::try_from(s.to_owned())?)) - } -} -impl AsRef for ActionId { - fn as_ref(&self) -> &ActionId { - self - } -} -impl std::fmt::Display for ActionId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", &self.0) - } -} -impl AsRef for ActionId { - fn as_ref(&self) -> &str { - self.0.as_ref() - } -} -impl AsRef for ActionId { - fn as_ref(&self) -> &Path { - self.0.as_ref().as_ref() - } -} -impl<'de> Deserialize<'de> for ActionId { - fn deserialize(deserializer: D) -> Result - where - D: serde::de::Deserializer<'de>, - { - Ok(ActionId(serde::Deserialize::deserialize(deserializer)?)) - } -} diff --git a/libs/models/src/health_check_id.rs b/libs/models/src/health_check_id.rs deleted file mode 100644 index dc643c912..000000000 --- a/libs/models/src/health_check_id.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::path::Path; - -use serde::{Deserialize, Deserializer, Serialize}; - -use crate::Id; - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] -pub struct HealthCheckId(Id); -impl std::fmt::Display for HealthCheckId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", &self.0) - } -} -impl AsRef for HealthCheckId { - fn as_ref(&self) -> &str { - self.0.as_ref() - } -} -impl<'de> Deserialize<'de> for HealthCheckId { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - Ok(HealthCheckId(Deserialize::deserialize(deserializer)?)) - } -} -impl AsRef for HealthCheckId { - fn as_ref(&self) -> &Path { - self.0.as_ref().as_ref() - } -} diff --git a/libs/models/src/id/mod.rs b/libs/models/src/id/mod.rs index 20816cb1a..ac32ceb22 100644 --- a/libs/models/src/id/mod.rs +++ b/libs/models/src/id/mod.rs @@ -19,7 +19,7 @@ pub use health_check::HealthCheckId; pub use image::ImageId; pub use interface::InterfaceId; pub use invalid_id::InvalidId; -pub use package::PackageId; +pub use package::{PackageId, SYSTEM_PACKAGE_ID}; pub use volume::VolumeId; lazy_static::lazy_static! { diff --git a/libs/models/src/image_id.rs b/libs/models/src/image_id.rs deleted file mode 100644 index 10ef0451d..000000000 --- a/libs/models/src/image_id.rs +++ /dev/null @@ -1,38 +0,0 @@ -use std::fmt::Debug; -use std::str::FromStr; - -use serde::{Deserialize, Deserializer, Serialize}; - -use crate::{Id, InvalidId, PackageId, Version}; - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] -pub struct ImageId(Id); -impl std::fmt::Display for ImageId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", &self.0) - } -} -impl ImageId { - pub fn for_package(&self, pkg_id: &PackageId, pkg_version: Option<&Version>) -> String { - format!( - "start9/{}/{}:{}", - pkg_id, - self.0, - pkg_version.map(|v| { v.as_str() }).unwrap_or("latest") - ) - } -} -impl FromStr for ImageId { - type Err = InvalidId; - fn from_str(s: &str) -> Result { - Ok(ImageId(Id::try_from(s.to_owned())?)) - } -} -impl<'de> Deserialize<'de> for ImageId { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - Ok(ImageId(Deserialize::deserialize(deserializer)?)) - } -} diff --git a/libs/models/src/interface_id.rs b/libs/models/src/interface_id.rs deleted file mode 100644 index b2f82f83c..000000000 --- a/libs/models/src/interface_id.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::path::Path; - -use serde::{Deserialize, Deserializer, Serialize}; - -use crate::Id; - -#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] -pub struct InterfaceId(Id); -impl From for InterfaceId { - fn from(id: Id) -> Self { - Self(id) - } -} -impl std::fmt::Display for InterfaceId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", &self.0) - } -} -impl std::ops::Deref for InterfaceId { - type Target = String; - fn deref(&self) -> &Self::Target { - &*self.0 - } -} -impl AsRef for InterfaceId { - fn as_ref(&self) -> &str { - self.0.as_ref() - } -} -impl<'de> Deserialize<'de> for InterfaceId { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - Ok(InterfaceId(Deserialize::deserialize(deserializer)?)) - } -} -impl AsRef for InterfaceId { - fn as_ref(&self) -> &Path { - self.0.as_ref().as_ref() - } -} diff --git a/libs/models/src/invalid_id.rs b/libs/models/src/invalid_id.rs deleted file mode 100644 index d2cc82bd5..000000000 --- a/libs/models/src/invalid_id.rs +++ /dev/null @@ -1,3 +0,0 @@ -#[derive(Debug, thiserror::Error)] -#[error("Invalid ID")] -pub struct InvalidId; diff --git a/libs/models/src/package_id.rs b/libs/models/src/package_id.rs deleted file mode 100644 index 9b534a4f5..000000000 --- a/libs/models/src/package_id.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::borrow::Borrow; -use std::path::Path; -use std::str::FromStr; - -use serde::{Deserialize, Serialize, Serializer}; - -use crate::{Id, InvalidId, SYSTEM_ID}; - -lazy_static::lazy_static! { - pub static ref SYSTEM_PACKAGE_ID: PackageId = PackageId(SYSTEM_ID.clone()); -} -#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct PackageId(Id); -impl FromStr for PackageId { - type Err = InvalidId; - fn from_str(s: &str) -> Result { - Ok(PackageId(Id::try_from(s.to_owned())?)) - } -} -impl From for PackageId { - fn from(id: Id) -> Self { - PackageId(id) - } -} -impl std::ops::Deref for PackageId { - type Target = String; - fn deref(&self) -> &Self::Target { - &*self.0 - } -} -impl AsRef for PackageId { - fn as_ref(&self) -> &PackageId { - self - } -} -impl std::fmt::Display for PackageId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", &self.0) - } -} -impl AsRef for PackageId { - fn as_ref(&self) -> &str { - self.0.as_ref() - } -} -impl Borrow for PackageId { - fn borrow(&self) -> &str { - self.0.as_ref() - } -} -impl AsRef for PackageId { - fn as_ref(&self) -> &Path { - self.0.as_ref().as_ref() - } -} -impl<'de> Deserialize<'de> for PackageId { - fn deserialize(deserializer: D) -> Result - where - D: serde::de::Deserializer<'de>, - { - Ok(PackageId(Deserialize::deserialize(deserializer)?)) - } -} -impl Serialize for PackageId { - fn serialize(&self, serializer: Ser) -> Result - where - Ser: Serializer, - { - Serialize::serialize(&self.0, serializer) - } -} diff --git a/libs/models/src/volume_id.rs b/libs/models/src/volume_id.rs deleted file mode 100644 index 16821a3cf..000000000 --- a/libs/models/src/volume_id.rs +++ /dev/null @@ -1,58 +0,0 @@ -use std::borrow::Borrow; -use std::path::Path; - -use serde::{Deserialize, Deserializer, Serialize}; - -use crate::Id; - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum VolumeId { - Backup, - Custom(Id), -} -impl std::fmt::Display for VolumeId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - VolumeId::Backup => write!(f, "BACKUP"), - VolumeId::Custom(id) => write!(f, "{}", id), - } - } -} -impl AsRef for VolumeId { - fn as_ref(&self) -> &str { - match self { - VolumeId::Backup => "BACKUP", - VolumeId::Custom(id) => id.as_ref(), - } - } -} -impl Borrow for VolumeId { - fn borrow(&self) -> &str { - self.as_ref() - } -} -impl AsRef for VolumeId { - fn as_ref(&self) -> &Path { - AsRef::::as_ref(self).as_ref() - } -} -impl<'de> Deserialize<'de> for VolumeId { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let unchecked: String = Deserialize::deserialize(deserializer)?; - Ok(match unchecked.as_ref() { - "BACKUP" => VolumeId::Backup, - _ => VolumeId::Custom(Id::try_from(unchecked).map_err(serde::de::Error::custom)?), - }) - } -} -impl Serialize for VolumeId { - fn serialize(&self, serializer: Ser) -> Result - where - Ser: serde::Serializer, - { - serializer.serialize_str(self.as_ref()) - } -} From 6c87e17e5e521a85ac08ab08e975c734f90d5149 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Tue, 29 Aug 2023 16:57:44 -0600 Subject: [PATCH 36/89] switch to btreemap for now --- backend/src/status/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/src/status/mod.rs b/backend/src/status/mod.rs index 2d9fe4a71..6294325a7 100644 --- a/backend/src/status/mod.rs +++ b/backend/src/status/mod.rs @@ -1,7 +1,6 @@ use std::collections::BTreeMap; use chrono::{DateTime, Utc}; -use imbl::OrdMap; use models::PackageId; use serde::{Deserialize, Serialize}; @@ -16,7 +15,7 @@ pub mod health_check; pub struct Status { pub configured: bool, pub main: MainStatus, - pub dependency_config_errors: OrdMap, + pub dependency_config_errors: BTreeMap, } #[derive(Debug, Clone, Deserialize, Serialize)] From 6d0421fedad981ddc3c685493b5ca29269f42d25 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Tue, 29 Aug 2023 18:01:33 -0600 Subject: [PATCH 37/89] config wip --- backend/src/config/mod.rs | 2 +- backend/src/config/spec.rs | 360 ++++++++++--------------------------- backend/src/db/model.rs | 10 ++ backend/src/manager/mod.rs | 60 ++----- 4 files changed, 117 insertions(+), 315 deletions(-) diff --git a/backend/src/config/mod.rs b/backend/src/config/mod.rs index ece30deeb..fd7b5e201 100644 --- a/backend/src/config/mod.rs +++ b/backend/src/config/mod.rs @@ -432,7 +432,7 @@ pub async fn set_dry( } pub struct ConfigureContext { - pub breakages: BTreeMap, + pub breakages: BTreeMap, pub timeout: Option, pub config: Option, pub overrides: BTreeMap, diff --git a/backend/src/config/spec.rs b/backend/src/config/spec.rs index 7aafad1b3..f04c07a31 100644 --- a/backend/src/config/spec.rs +++ b/backend/src/config/spec.rs @@ -12,7 +12,6 @@ use async_trait::async_trait; use indexmap::{IndexMap, IndexSet}; use itertools::Itertools; use jsonpath_lib::Compiled as CompiledJsonPath; -use patch_db::{DbHandle, LockReceipt, LockType}; use rand::{CryptoRng, Rng}; use regex::Regex; use serde::de::{MapAccess, Visitor}; @@ -26,8 +25,8 @@ use crate::config::ConfigurationError; use crate::context::RpcContext; use crate::net::interface::InterfaceId; use crate::net::keys::Key; +use crate::prelude::*; use crate::s9pk::manifest::{Manifest, PackageId}; -use crate::Error; // Config Value Specifications #[async_trait] @@ -39,14 +38,12 @@ pub trait ValueSpec { // since not all inVariant can be checked by the type fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath>; // update is to fill in values for environment pointers recursively - async fn update( + async fn update( &self, ctx: &RpcContext, - db: &mut Db, manifest: &Manifest, config_overrides: &BTreeMap, value: &mut Value, - receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError>; // returns all pointers that are live in the provided config fn pointers(&self, value: &Value) -> Result, NoMatchWithPath>; @@ -156,17 +153,15 @@ where fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { self.inner.validate(manifest) } - async fn update( + async fn update( &self, ctx: &RpcContext, - db: &mut Db, manifest: &Manifest, config_overrides: &BTreeMap, value: &mut Value, - receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { self.inner - .update(ctx, db, manifest, config_overrides, value, receipts) + .update(ctx, manifest, config_overrides, value) .await } fn pointers(&self, value: &Value) -> Result, NoMatchWithPath> { @@ -201,17 +196,15 @@ where fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { self.inner.validate(manifest) } - async fn update( + async fn update( &self, ctx: &RpcContext, - db: &mut Db, manifest: &Manifest, config_overrides: &BTreeMap, value: &mut Value, - receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { self.inner - .update(ctx, db, manifest, config_overrides, value, receipts) + .update(ctx, manifest, config_overrides, value) .await } fn pointers(&self, value: &Value) -> Result, NoMatchWithPath> { @@ -279,17 +272,15 @@ where fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { self.inner.validate(manifest) } - async fn update( + async fn update( &self, ctx: &RpcContext, - db: &mut Db, manifest: &Manifest, config_overrides: &BTreeMap, value: &mut Value, - receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { self.inner - .update(ctx, db, manifest, config_overrides, value, receipts) + .update(ctx, manifest, config_overrides, value) .await } fn pointers(&self, value: &Value) -> Result, NoMatchWithPath> { @@ -394,48 +385,22 @@ impl ValueSpec for ValueSpecAny { ValueSpecAny::Pointer(a) => a.validate(manifest), } } - async fn update( + async fn update( &self, ctx: &RpcContext, - db: &mut Db, manifest: &Manifest, config_overrides: &BTreeMap, value: &mut Value, - receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { match self { - ValueSpecAny::Boolean(a) => { - a.update(ctx, db, manifest, config_overrides, value, receipts) - .await - } - ValueSpecAny::Enum(a) => { - a.update(ctx, db, manifest, config_overrides, value, receipts) - .await - } - ValueSpecAny::List(a) => { - a.update(ctx, db, manifest, config_overrides, value, receipts) - .await - } - ValueSpecAny::Number(a) => { - a.update(ctx, db, manifest, config_overrides, value, receipts) - .await - } - ValueSpecAny::Object(a) => { - a.update(ctx, db, manifest, config_overrides, value, receipts) - .await - } - ValueSpecAny::String(a) => { - a.update(ctx, db, manifest, config_overrides, value, receipts) - .await - } - ValueSpecAny::Union(a) => { - a.update(ctx, db, manifest, config_overrides, value, receipts) - .await - } - ValueSpecAny::Pointer(a) => { - a.update(ctx, db, manifest, config_overrides, value, receipts) - .await - } + ValueSpecAny::Boolean(a) => a.update(ctx, manifest, config_overrides, value).await, + ValueSpecAny::Enum(a) => a.update(ctx, manifest, config_overrides, value).await, + ValueSpecAny::List(a) => a.update(ctx, manifest, config_overrides, value).await, + ValueSpecAny::Number(a) => a.update(ctx, manifest, config_overrides, value).await, + ValueSpecAny::Object(a) => a.update(ctx, manifest, config_overrides, value).await, + ValueSpecAny::String(a) => a.update(ctx, manifest, config_overrides, value).await, + ValueSpecAny::Union(a) => a.update(ctx, manifest, config_overrides, value).await, + ValueSpecAny::Pointer(a) => a.update(ctx, manifest, config_overrides, value).await, } } fn pointers(&self, value: &Value) -> Result, NoMatchWithPath> { @@ -513,14 +478,12 @@ impl ValueSpec for ValueSpecBoolean { fn validate(&self, _manifest: &Manifest) -> Result<(), NoMatchWithPath> { Ok(()) } - async fn update( + async fn update( &self, _ctx: &RpcContext, - _db: &mut Db, _manifest: &Manifest, _config_overrides: &BTreeMap, _value: &mut Value, - _receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { Ok(()) } @@ -603,14 +566,12 @@ impl ValueSpec for ValueSpecEnum { fn validate(&self, _manifest: &Manifest) -> Result<(), NoMatchWithPath> { Ok(()) } - async fn update( + async fn update( &self, _ctx: &RpcContext, - _db: &mut Db, _manifest: &Manifest, _config_overrides: &BTreeMap, _value: &mut Value, - _receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { Ok(()) } @@ -690,22 +651,16 @@ where fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { self.spec.validate(manifest) } - async fn update( + async fn update( &self, ctx: &RpcContext, - db: &mut Db, manifest: &Manifest, config_overrides: &BTreeMap, value: &mut Value, - receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { if let Value::Array(ref mut ls) = value { for (i, val) in ls.into_iter().enumerate() { - match self - .spec - .update(ctx, db, manifest, config_overrides, val, receipts) - .await - { + match self.spec.update(ctx, manifest, config_overrides, val).await { Err(ConfigurationError::NoMatch(e)) => { Err(ConfigurationError::NoMatch(e.prepend(format!("{}", i)))) } @@ -798,36 +753,19 @@ impl ValueSpec for ValueSpecList { ValueSpecList::Union(a) => a.validate(manifest), } } - async fn update( + async fn update( &self, ctx: &RpcContext, - db: &mut Db, manifest: &Manifest, config_overrides: &BTreeMap, value: &mut Value, - receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { match self { - ValueSpecList::Enum(a) => { - a.update(ctx, db, manifest, config_overrides, value, receipts) - .await - } - ValueSpecList::Number(a) => { - a.update(ctx, db, manifest, config_overrides, value, receipts) - .await - } - ValueSpecList::Object(a) => { - a.update(ctx, db, manifest, config_overrides, value, receipts) - .await - } - ValueSpecList::String(a) => { - a.update(ctx, db, manifest, config_overrides, value, receipts) - .await - } - ValueSpecList::Union(a) => { - a.update(ctx, db, manifest, config_overrides, value, receipts) - .await - } + ValueSpecList::Enum(a) => a.update(ctx, manifest, config_overrides, value).await, + ValueSpecList::Number(a) => a.update(ctx, manifest, config_overrides, value).await, + ValueSpecList::Object(a) => a.update(ctx, manifest, config_overrides, value).await, + ValueSpecList::String(a) => a.update(ctx, manifest, config_overrides, value).await, + ValueSpecList::Union(a) => a.update(ctx, manifest, config_overrides, value).await, } } fn pointers(&self, value: &Value) -> Result, NoMatchWithPath> { @@ -941,14 +879,12 @@ impl ValueSpec for ValueSpecNumber { fn validate(&self, _manifest: &Manifest) -> Result<(), NoMatchWithPath> { Ok(()) } - async fn update( + async fn update( &self, _ctx: &RpcContext, - _db: &mut Db, _manifest: &Manifest, _config_overrides: &BTreeMap, _value: &mut Value, - _receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { Ok(()) } @@ -1005,19 +941,15 @@ impl ValueSpec for ValueSpecObject { fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { self.spec.validate(manifest) } - async fn update( + async fn update( &self, ctx: &RpcContext, - db: &mut Db, manifest: &Manifest, config_overrides: &BTreeMap, value: &mut Value, - receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { if let Value::Object(o) = value { - self.spec - .update(ctx, db, manifest, config_overrides, o, receipts) - .await + self.spec.update(ctx, manifest, config_overrides, o).await } else { Err(ConfigurationError::NoMatch(NoMatchWithPath::new( MatchError::InvalidType("object", value.type_of()), @@ -1108,27 +1040,21 @@ impl ConfigSpec { Ok(()) } - pub async fn update( + pub async fn update( &self, ctx: &RpcContext, - db: &mut Db, manifest: &Manifest, config_overrides: &BTreeMap, cfg: &mut Config, - receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { for (k, vs) in self.0.iter() { match cfg.get_mut(k) { None => { let mut v = Value::Null; - vs.update(ctx, db, manifest, config_overrides, &mut v, receipts) - .await?; + vs.update(ctx, manifest, config_overrides, &mut v).await?; cfg.insert(k.clone(), v); } - Some(v) => match vs - .update(ctx, db, manifest, config_overrides, v, receipts) - .await - { + Some(v) => match vs.update(ctx, manifest, config_overrides, v).await { Err(ConfigurationError::NoMatch(e)) => { Err(ConfigurationError::NoMatch(e.prepend(k.clone()))) } @@ -1286,14 +1212,12 @@ impl ValueSpec for ValueSpecString { fn validate(&self, _manifest: &Manifest) -> Result<(), NoMatchWithPath> { Ok(()) } - async fn update( + async fn update( &self, _ctx: &RpcContext, - _db: &mut Db, _manifest: &Manifest, _config_overrides: &BTreeMap, _value: &mut Value, - _receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { Ok(()) } @@ -1497,14 +1421,12 @@ impl ValueSpec for ValueSpecUnion { } Ok(()) } - async fn update( + async fn update( &self, ctx: &RpcContext, - db: &mut Db, manifest: &Manifest, config_overrides: &BTreeMap, value: &mut Value, - receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { if let Value::Object(o) = value { match o.get(&self.tag.id) { @@ -1515,10 +1437,7 @@ impl ValueSpec for ValueSpecUnion { None => Err(ConfigurationError::NoMatch(NoMatchWithPath::new( MatchError::Union(tag.clone(), self.variants.keys().cloned().collect()), ))), - Some(spec) => { - spec.update(ctx, db, manifest, config_overrides, o, receipts) - .await - } + Some(spec) => spec.update(ctx, manifest, config_overrides, o).await, }, Some(other) => Err(ConfigurationError::NoMatch( NoMatchWithPath::new(MatchError::InvalidType("string", other.type_of())) @@ -1643,24 +1562,16 @@ impl ValueSpec for ValueSpecPointer { ValueSpecPointer::System(a) => a.validate(manifest), } } - async fn update( + async fn update( &self, ctx: &RpcContext, - db: &mut Db, manifest: &Manifest, config_overrides: &BTreeMap, value: &mut Value, - receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { match self { - ValueSpecPointer::Package(a) => { - a.update(ctx, db, manifest, config_overrides, value, receipts) - .await - } - ValueSpecPointer::System(a) => { - a.update(ctx, db, manifest, config_overrides, value, receipts) - .await - } + ValueSpecPointer::Package(a) => a.update(ctx, manifest, config_overrides, value).await, + ValueSpecPointer::System(a) => a.update(ctx, manifest, config_overrides, value).await, } } fn pointers(&self, _value: &Value) -> Result, NoMatchWithPath> { @@ -1697,23 +1608,17 @@ impl PackagePointerSpec { PackagePointerSpec::Config(ConfigPointer { package_id, .. }) => package_id, } } - async fn deref( + async fn deref( &self, ctx: &RpcContext, - db: &mut Db, manifest: &Manifest, config_overrides: &BTreeMap, - receipts: &ConfigPointerReceipts, ) -> Result { match &self { - PackagePointerSpec::TorKey(key) => key.deref(&manifest.id, &ctx.secret_store).await, - PackagePointerSpec::TorAddress(tor) => { - tor.deref(db, &receipts.interface_addresses_receipt).await - } - PackagePointerSpec::LanAddress(lan) => { - lan.deref(db, &receipts.interface_addresses_receipt).await - } - PackagePointerSpec::Config(cfg) => cfg.deref(ctx, db, config_overrides, receipts).await, + PackagePointerSpec::TorKey(key) => key.deref(ctx, &manifest.id).await, + PackagePointerSpec::TorAddress(tor) => tor.deref(ctx).await, + PackagePointerSpec::LanAddress(lan) => lan.deref(ctx).await, + PackagePointerSpec::Config(cfg) => cfg.deref(ctx, config_overrides).await, } } } @@ -1754,18 +1659,14 @@ impl ValueSpec for PackagePointerSpec { _ => Ok(()), } } - async fn update( + async fn update( &self, ctx: &RpcContext, - db: &mut Db, manifest: &Manifest, config_overrides: &BTreeMap, value: &mut Value, - receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { - *value = self - .deref(ctx, db, manifest, config_overrides, receipts) - .await?; + *value = self.deref(ctx, manifest, config_overrides).await?; Ok(()) } fn pointers(&self, _value: &Value) -> Result, NoMatchWithPath> { @@ -1788,18 +1689,18 @@ pub struct TorAddressPointer { interface: InterfaceId, } impl TorAddressPointer { - async fn deref( - &self, - db: &mut Db, - receipt: &InterfaceAddressesReceipt, - ) -> Result { - let addr = receipt - .interface_addresses - .get(db, (&self.package_id, &self.interface)) - .await - .map_err(|e| ConfigurationError::SystemError(Error::from(e)))? - .and_then(|addresses| addresses.tor_address); - Ok(addr.to_owned().map(Value::String).unwrap_or(Value::Null)) + async fn deref(&self, ctx: &RpcContext) -> Result { + let addr = ctx + .db + .peek() + .await? + .as_package_data() + .as_idx(&self.package_id) + .and_then(|pde| pde.as_installed()) + .and_then(|i| i.as_interface_addresses().as_idx(&self.interface)) + .and_then(|a| a.as_tor_address().de().transpose()) + .transpose()?; + Ok(addr.map(Value::String).unwrap_or(Value::Null)) } } impl fmt::Display for TorAddressPointer { @@ -1813,39 +1714,6 @@ impl fmt::Display for TorAddressPointer { } } -pub struct InterfaceAddressesReceipt { - interface_addresses: LockReceipt, -} - -impl InterfaceAddressesReceipt { - pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result { - let mut locks = Vec::new(); - - let setup = Self::setup(&mut locks); - Ok(setup(&db.lock_all(locks).await?)?) - } - - pub fn setup( - locks: &mut Vec, - ) -> impl FnOnce(&patch_db::Verifier) -> Result { - // let cleanup_receipts = CleanupFailedReceipts::setup(locks); - - let interface_addresses = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .map(|x| x.interface_addresses().star()) - .make_locker(LockType::Read) - .add_to_keys(locks); - move |skeleton_key| { - Ok(Self { - // cleanup_receipts: cleanup_receipts(skeleton_key)?, - interface_addresses: interface_addresses.verify(skeleton_key)?, - }) - } - } -} - #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct LanAddressPointer { @@ -1862,73 +1730,21 @@ impl fmt::Display for LanAddressPointer { } } impl LanAddressPointer { - async fn deref( - &self, - db: &mut Db, - receipts: &InterfaceAddressesReceipt, - ) -> Result { - let addr = receipts - .interface_addresses - .get(db, (&self.package_id, &self.interface)) - .await - .ok() - .flatten() - .and_then(|x| x.lan_address); + async fn deref(&self, ctx: &RpcContext) -> Result { + let addr = ctx + .db + .peek() + .await? + .as_package_data() + .as_idx(&self.package_id) + .and_then(|pde| pde.as_installed()) + .and_then(|i| i.as_interface_addresses().as_idx(&self.interface)) + .and_then(|a| a.as_lan_address().de().transpose()) + .transpose()?; Ok(addr.to_owned().map(Value::String).unwrap_or(Value::Null)) } } -pub struct ConfigPointerReceipts { - interface_addresses_receipt: InterfaceAddressesReceipt, - manifest_volumes: LockReceipt, - manifest_version: LockReceipt, - config_actions: LockReceipt, -} - -impl ConfigPointerReceipts { - pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result { - let mut locks = Vec::new(); - - let setup = Self::setup(&mut locks); - Ok(setup(&db.lock_all(locks).await?)?) - } - - pub fn setup( - locks: &mut Vec, - ) -> impl FnOnce(&patch_db::Verifier) -> Result { - let interface_addresses_receipt = InterfaceAddressesReceipt::setup(locks); - - let manifest_volumes = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .map(|x| x.manifest().volumes()) - .make_locker(LockType::Read) - .add_to_keys(locks); - let manifest_version = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .map(|x| x.manifest().version()) - .make_locker(LockType::Read) - .add_to_keys(locks); - let config_actions = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .and_then(|x| x.manifest().config()) - .make_locker(LockType::Read) - .add_to_keys(locks); - move |skeleton_key| { - Ok(Self { - interface_addresses_receipt: interface_addresses_receipt(skeleton_key)?, - manifest_volumes: manifest_volumes.verify(skeleton_key)?, - config_actions: config_actions.verify(skeleton_key)?, - manifest_version: manifest_version.verify(skeleton_key)?, - }) - } - } -} #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct ConfigPointer { @@ -1940,25 +1756,32 @@ impl ConfigPointer { pub fn select(&self, val: &Value) -> Value { self.selector.select(self.multi, val) } - async fn deref( + async fn deref( &self, ctx: &RpcContext, - db: &mut Db, config_overrides: &BTreeMap, - receipts: &ConfigPointerReceipts, ) -> Result { if let Some(cfg) = config_overrides.get(&self.package_id) { Ok(self.select(&Value::Object(cfg.clone()))) } else { let id = &self.package_id; - let version = receipts.manifest_version.get(db, id).await.ok().flatten(); - let cfg_actions = receipts.config_actions.get(db, id).await.ok().flatten(); - let volumes = receipts.manifest_volumes.get(db, id).await.ok().flatten(); - if let (Some(version), Some(cfg_actions), Some(volumes)) = - (&version, &cfg_actions, &volumes) - { + let manifest = ctx + .db + .peek() + .await? + .as_package_data() + .as_idx(id) + .map(|pde| pde.as_manifest()); + let cfg_actions = manifest.and_then(|m| m.as_config().transpose_ref()); + if let (Some(manifest), Some(cfg_actions)) = (manifest, cfg_actions) { let cfg_res = cfg_actions - .get(ctx, &self.package_id, version, volumes) + .de()? + .get( + ctx, + &self.package_id, + &manifest.as_version().de()?, + &manifest.as_volumes().de()?, + ) .await .map_err(|e| ConfigurationError::SystemError(e))?; if let Some(cfg) = cfg_res.config { @@ -2092,7 +1915,7 @@ impl fmt::Display for SystemPointerSpec { } } impl SystemPointerSpec { - async fn deref(&self, _db: &mut Db) -> Result { + async fn deref(&self, ctx: &RpcContext) -> Result { #[allow(unreachable_code)] Ok(match *self {}) } @@ -2115,17 +1938,14 @@ impl ValueSpec for SystemPointerSpec { fn validate(&self, _manifest: &Manifest) -> Result<(), NoMatchWithPath> { Ok(()) } - async fn update( + async fn update( &self, - _ctx: &RpcContext, - db: &mut Db, + ctx: &RpcContext, _manifest: &Manifest, _config_overrides: &BTreeMap, value: &mut Value, - - _receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { - *value = self.deref(db).await?; + *value = self.deref(ctx).await?; Ok(()) } fn pointers(&self, _value: &Value) -> Result, NoMatchWithPath> { diff --git a/backend/src/db/model.rs b/backend/src/db/model.rs index a38607e0d..5c16d013f 100644 --- a/backend/src/db/model.rs +++ b/backend/src/db/model.rs @@ -342,6 +342,16 @@ impl Model { PackageDataEntryMatchModel::Error(_) => Model::from(Value::Null), } } + pub fn as_manifest(&self) -> &Model { + match self.into_match() { + PackageDataEntryMatchModelRef::Installing(a) => a.as_manifest(), + PackageDataEntryMatchModelRef::Updating(a) => a.as_installed().as_manifest(), + PackageDataEntryMatchModelRef::Restoring(a) => a.as_manifest(), + PackageDataEntryMatchModelRef::Removing(a) => a.as_manifest(), + PackageDataEntryMatchModelRef::Installed(a) => a.as_manifest(), + PackageDataEntryMatchModelRef::Error(_) => (&Value::Null).into(), + } + } pub fn into_installed(self) -> Option> { match self.into_match() { PackageDataEntryMatchModel::Installing(_) => None, diff --git a/backend/src/manager/mod.rs b/backend/src/manager/mod.rs index 50039e61e..d45c2d5d4 100644 --- a/backend/src/manager/mod.rs +++ b/backend/src/manager/mod.rs @@ -9,7 +9,7 @@ use embassy_container_init::ProcessGroupId; use futures::future::BoxFuture; use futures::{Future, FutureExt, TryFutureExt}; use helpers::UnixRpcClient; -use models::{ErrorKind, PackageId}; +use models::{ErrorKind, OptionExt, PackageId}; use nix::sys::signal::Signal; use persistent_container::PersistentContainer; use rand::SeedableRng; @@ -158,7 +158,7 @@ impl Manager { pub async fn configure( &self, configure_context: ConfigureContext, - ) -> Result, Error> { + ) -> Result, Error> { if self._is_transition_configure() { return Ok(configure_context.breakages); } @@ -328,42 +328,27 @@ async fn configure( ctx: RpcContext, id: PackageId, mut configure_context: ConfigureContext, -) -> Result, Error> { - let mut db = ctx.db.handle(); - let mut tx = db.begin().await?; - let db = &mut tx; - - let receipts = ConfigReceipts::new(db).await?; +) -> Result, Error> { + let db = ctx.db.peek().await?; let id = &id; let ctx = &ctx; let overrides = &mut configure_context.overrides; // fetch data from db - let action = receipts - .config_actions - .get(db, id) - .await? - .ok_or_else(|| not_found!(id))?; - let dependencies = receipts - .dependencies - .get(db, id) - .await? - .ok_or_else(|| not_found!(id))?; - let volumes = receipts - .volumes - .get(db, id) - .await? - .ok_or_else(|| not_found!(id))?; - let version = receipts - .version - .get(db, id) - .await? - .ok_or_else(|| not_found!(id))?; + let manifest = db + .as_package_data() + .as_idx(id) + .or_not_found(id)? + .as_manifest() + .de()?; // get current config and current spec let ConfigRes { config: old_config, spec, - } = action.get(ctx, id, &version, &volumes).await?; + } = manifest + .actions + .get(ctx, id, &manifest.version, &manifest.volumes) + .await?; // determine new config to use let mut config = if let Some(config) = configure_context.config.or_else(|| old_config.clone()) { @@ -375,23 +360,10 @@ async fn configure( )? }; - let manifest = receipts - .manifest - .get(db, id) - .await? - .ok_or_else(|| not_found!(id))?; - spec.validate(&manifest)?; spec.matches(&config)?; // check that new config matches spec - spec.update( - ctx, - db, - &manifest, - overrides, - &mut config, - &receipts.config_receipts, - ) - .await?; // dereference pointers in the new config + spec.update(ctx, &db, &manifest, overrides, &mut config) + .await?; // dereference pointers in the new config // create backreferences to pointers let mut sys = receipts From ebd5ae60c8c4a3364caad5f26f437981c7a7a08f Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Thu, 31 Aug 2023 10:24:57 -0600 Subject: [PATCH 38/89] wip manager/mod --- backend/src/install/cleanup.rs | 21 ++-- backend/src/manager/mod.rs | 220 ++++++++++++++++++++++----------- 2 files changed, 152 insertions(+), 89 deletions(-) diff --git a/backend/src/install/cleanup.rs b/backend/src/install/cleanup.rs index 4bbb28127..2a58e1a5b 100644 --- a/backend/src/install/cleanup.rs +++ b/backend/src/install/cleanup.rs @@ -1,26 +1,20 @@ use std::path::PathBuf; use std::sync::Arc; -use patch_db::{DbHandle, LockReceipt, LockTargetId, LockType, PatchDbHandle, Verifier}; use sqlx::{Executor, Postgres}; use tracing::instrument; use super::PKG_ARCHIVE_DIR; use crate::config::{not_found, ConfigReceipts}; use crate::context::RpcContext; -use crate::db::model::{ - AllPackageData, CurrentDependencies, CurrentDependents, InstalledPackageDataEntry, - PackageDataEntry, -}; -use crate::dependencies::{ - reconfigure_dependents_with_live_pointers, DependencyErrors, TryHealReceipts, -}; +use crate::db::model::{AllPackageData, CurrentDependencies, CurrentDependents, PackageDataEntry}; +use crate::dependencies::reconfigure_dependents_with_live_pointers; use crate::error::ErrorCollection; +use crate::prelude::*; use crate::s9pk::manifest::{Manifest, PackageId}; use crate::util::{Apply, Version}; use crate::volume::{asset_dir, script_dir}; use crate::Error; - pub struct UpdateDependencyReceipts { try_heal: TryHealReceipts, dependency_errors: LockReceipt, @@ -237,11 +231,10 @@ pub async fn cleanup_failed( } #[instrument(skip_all)] -pub async fn remove_from_current_dependents_lists<'a>( - db: &mut Db, - id: &'a PackageId, - current_dependencies: &'a CurrentDependencies, - current_dependent_receipt: &LockReceipt, +pub fn remove_from_current_dependents_lists( + db: &mut PatchDb, + id: &PackageId, + current_dependencies: &CurrentDependencies, ) -> Result<(), Error> { for dep in current_dependencies.0.keys().chain(std::iter::once(id)) { if let Some(mut current_dependents) = current_dependent_receipt.get(db, dep).await? { diff --git a/backend/src/manager/mod.rs b/backend/src/manager/mod.rs index d45c2d5d4..da75304ce 100644 --- a/backend/src/manager/mod.rs +++ b/backend/src/manager/mod.rs @@ -27,7 +27,7 @@ use crate::backup::target::PackageBackupInfo; use crate::backup::PackageBackupReport; use crate::config::action::ConfigRes; use crate::config::spec::ValueSpecPointer; -use crate::config::{not_found, ConfigReceipts, ConfigureContext}; +use crate::config::ConfigureContext; use crate::context::RpcContext; use crate::db::model::{CurrentDependencies, CurrentDependencyInfo}; use crate::dependencies::add_dependent_to_current_dependents_lists; @@ -362,15 +362,27 @@ async fn configure( spec.validate(&manifest)?; spec.matches(&config)?; // check that new config matches spec - spec.update(ctx, &db, &manifest, overrides, &mut config) - .await?; // dereference pointers in the new config + + // TODO Commit or not? + spec.update(ctx, &manifest, overrides, &mut config).await?; // dereference pointers in the new config // create backreferences to pointers - let mut sys = receipts - .system_pointers - .get(db, id) - .await? - .ok_or_else(|| not_found!(id))?; + let mut sys = db + .as_package_data() + .as_idx(id) + .or_not_found(id)? + .as_installed() + .or_not_found(id)? + .as_system_pointers() + .de()?; + let dependencies = db + .as_package_data() + .as_idx(id) + .or_not_found(id)? + .as_installed() + .or_not_found(id)? + .as_dependencies() + .de()?; sys.truncate(0); let mut current_dependencies: CurrentDependencies = CurrentDependencies( dependencies @@ -404,8 +416,38 @@ async fn configure( ValueSpecPointer::System(s) => sys.push(s), } } - receipts.system_pointers.set(db, sys, id).await?; + let sys = sys; + let action = db + .as_package_data() + .as_idx(id) + .or_not_found(id)? + .as_installed() + .or_not_found(id)? + .as_manifest() + .as_config() + .de()? + .or_not_found(id)?; + let version = db + .as_package_data() + .as_idx(id) + .or_not_found(id)? + .as_installed() + .or_not_found(id)? + .as_manifest() + .as_version() + .de()? + .or_not_found(id)?; + let volumes = db + .as_package_data() + .as_idx(id) + .or_not_found(id)? + .as_installed() + .or_not_found(id)? + .as_manifest() + .as_volumes() + .de()?; + sys.truncate(0); if !configure_context.dry_run { // run config action let res = action @@ -437,42 +479,24 @@ async fn configure( .collect() }); } - - // update dependencies - let prev_current_dependencies = receipts - .current_dependencies - .get(db, id) - .await? - .unwrap_or_default(); - remove_from_current_dependents_lists( - db, - id, - &prev_current_dependencies, - &receipts.current_dependents, - ) .await?; // remove previous - add_dependent_to_current_dependents_lists(db, id, ¤t_dependencies).await?; // add new - current_dependencies.0.remove(id); - receipts - .current_dependencies - .set(db, current_dependencies.clone(), id) - .await?; - let errs = receipts - .dependency_errors - .get(db, id) - .await? - .ok_or_else(|| not_found!(id))?; - tracing::warn!("Dependency Errors: {:?}", errs); - let errs = DependencyErrors::init( - ctx, - db, - &manifest, - ¤t_dependencies, - &receipts.dependency_receipt.try_heal, - ) - .await?; - receipts.dependency_errors.set(db, errs, id).await?; + // TODO @dr-bonez Could we convert this + // let errs = receipts + // .dependency_errors + // .get(db, id) + // .await? + // .ok_or_else(|| not_found!(id))?; + // tracing::warn!("Dependency Errors: {:?}", errs); + // let errs = DependencyErrors::init( + // ctx, + // db, + // &manifest, + // ¤t_dependencies, + // &receipts.dependency_receipt.try_heal, + // ) + // .await?; + // receipts.dependency_errors.set(db, errs, id).await?; // cache current config for dependents configure_context @@ -480,25 +504,47 @@ async fn configure( .insert(id.clone(), config.clone()); // handle dependents - let dependents = receipts - .current_dependents - .get(db, id) - .await? - .ok_or_else(|| not_found!(id))?; + + let dependents = db + .as_package_data() + .as_idx(id) + .or_not_found(id)? + .as_installed() + .or_not_found(id)? + .as_current_dependents() + .de()?; for (dependent, _dep_info) in dependents.0.iter().filter(|(dep_id, _)| dep_id != &id) { - let dependent_container = receipts.docker_containers.get(db, dependent).await?; + let dependent_container = db + .as_package_data() + .as_idx(dependent) + .or_not_found(dependent)? + .as_installed() + .or_not_found(dependent)? + .as_manifest() + .as_containers() + .de()?; let dependent_container = &dependent_container; // check if config passes dependent check - if let Some(cfg) = receipts - .manifest_dependencies_config - .get(db, (dependent, id)) - .await? + if let Some(cfg) = db + .as_package_data() + .as_idx(dependent) + .or_not_found(dependent)? + .as_installed() + .or_not_found(dependent)? + .as_manifest() + .as_dependencies() + .as_idx(id) + .config() + .de()? { - let manifest = receipts - .manifest - .get(db, dependent) - .await? - .ok_or_else(|| not_found!(id))?; + let manifest = db + .as_package_data() + .as_idx(dependent) + .or_not_found(dependent)? + .as_installed() + .or_not_found(dependent)? + .as_manifest() + .de()?; if let Err(error) = cfg .check( ctx, @@ -511,28 +557,52 @@ async fn configure( ) .await? { - let dep_err = DependencyError::ConfigUnsatisfied { error }; - break_transitive( - db, - dependent, - id, - dep_err, - &mut configure_context.breakages, - &receipts.break_transitive_receipts, - ) - .await?; + // let dep_err = DependencyError::ConfigUnsatisfied { error }; + // break_transitive( + // db, + // dependent, + // id, + // dep_err, + // &mut configure_context.breakages, + // &receipts.break_transitive_receipts, + // ) + // .await?; } - heal_all_dependents_transitive(ctx, db, id, &receipts.dependency_receipt).await?; + // heal_all_dependents_transitive(ctx, db, id, &receipts.dependency_receipt).await?; } } - receipts.configured.set(db, true, id).await?; - - if configure_context.dry_run { - tx.abort().await?; - } else { - tx.commit().await?; + if !configure_context.dry_run { + ctx.db + .mutate(|db| { + remove_from_current_dependents_lists(db, id, &dependencies)?; + add_dependent_to_current_dependents_lists(db, id, ¤t_dependencies)?; + current_dependencies.0.remove(id); + db.as_package_data_mut() + .as_idx_mut(id) + .or_not_found(id)? + .as_installed_mut() + .or_not_found(id)? + .as_current_dependencies_mut() + .ser(¤t_dependencies)?; + db.as_package_data_mut() + .as_idx_mut(id) + .or_not_found(id)? + .as_installed_mut() + .or_not_found(id)? + .as_system_pointers_mut() + .ser(sys)?; + db.as_package_data_mut() + .as_idx_mut(id) + .or_not_found(id)? + .as_installed_mut() + .or_not_found(id)? + .as_status_mut() + .as_configured_mut() + .ser(&true) + }) + .await?; // add new } Ok(configure_context.breakages) From a2d3fd0287f4bd5d1c890e531f4d1fa61ae64937 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Fri, 1 Sep 2023 13:56:07 -0600 Subject: [PATCH 39/89] install wip Co-authored-by: J H --- backend/src/config/action.rs | 5 +- backend/src/config/mod.rs | 276 ++------------ backend/src/context/rpc.rs | 5 +- backend/src/db/model.rs | 22 ++ backend/src/db/package.rs | 10 +- backend/src/install/cleanup.rs | 356 +++++------------- backend/src/install/mod.rs | 640 +++++++++++++------------------- backend/src/install/progress.rs | 68 ++-- backend/src/install/update.rs | 89 +---- backend/src/manager/mod.rs | 124 ++++--- backend/src/status/mod.rs | 11 +- 11 files changed, 531 insertions(+), 1075 deletions(-) diff --git a/backend/src/config/action.rs b/backend/src/config/action.rs index 3d349c846..f4bca5581 100644 --- a/backend/src/config/action.rs +++ b/backend/src/config/action.rs @@ -2,7 +2,6 @@ use std::collections::{BTreeMap, BTreeSet}; use color_eyre::eyre::eyre; use models::ImageId; -use nix::sys::signal::Signal; use patch_db::HasModel; use serde::{Deserialize, Serialize}; use tracing::instrument; @@ -10,6 +9,7 @@ use tracing::instrument; use super::{Config, ConfigSpec}; use crate::context::RpcContext; use crate::dependencies::Dependencies; +use crate::prelude::*; use crate::procedure::docker::DockerContainers; use crate::procedure::{PackageProcedure, ProcedureName}; use crate::s9pk::manifest::PackageId; @@ -18,7 +18,7 @@ use crate::util::Version; use crate::volume::Volumes; use crate::{Error, ResultExt}; -#[derive(Debug, Deserialize, Serialize, HasModel)] +#[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub struct ConfigRes { pub config: Option, @@ -26,6 +26,7 @@ pub struct ConfigRes { } #[derive(Clone, Debug, Deserialize, Serialize, HasModel)] +#[model = "Model"] pub struct ConfigActions { pub get: PackageProcedure, pub set: PackageProcedure, diff --git a/backend/src/config/mod.rs b/backend/src/config/mod.rs index fd7b5e201..13d539a96 100644 --- a/backend/src/config/mod.rs +++ b/backend/src/config/mod.rs @@ -5,22 +5,15 @@ use std::time::Duration; use color_eyre::eyre::eyre; use indexmap::IndexSet; use itertools::Itertools; -use models::ErrorKind; -use patch_db::{DbHandle, LockReceipt, LockTarget, LockTargetId, LockType, Verifier}; +use models::{ErrorKind, OptionExt}; use regex::Regex; use rpc_toolkit::command; use serde_json::Value; use tracing::instrument; use crate::context::RpcContext; -use crate::db::model::{CurrentDependencies, CurrentDependents}; -use crate::dependencies::{ - BreakTransitiveReceipts, BreakageRes, Dependencies, DependencyConfig, DependencyErrors, - DependencyReceipt, TaggedDependencyError, TryHealReceipts, -}; -use crate::install::cleanup::UpdateDependencyReceipts; -use crate::procedure::docker::DockerContainers; -use crate::s9pk::manifest::{Manifest, PackageId}; +use crate::prelude::*; +use crate::s9pk::manifest::PackageId; use crate::util::display_none; use crate::util::serde::{display_serializable, parse_stdin_deserializable, IoFormat}; use crate::Error; @@ -32,8 +25,8 @@ pub mod util; pub use spec::{ConfigSpec, Defaultable}; use util::NumRange; -use self::action::{ConfigActions, ConfigRes}; -use self::spec::{ConfigPointerReceipts, ValueSpecPointer}; +use self::action::ConfigRes; +use self::spec::ValueSpecPointer; pub type Config = serde_json::Map; pub trait TypeOf { @@ -162,55 +155,6 @@ pub fn config(#[arg] id: PackageId) -> Result { Ok(id) } -pub struct ConfigGetReceipts { - manifest_volumes: LockReceipt, - manifest_version: LockReceipt, - manifest_config: LockReceipt, ()>, -} - -impl ConfigGetReceipts { - pub async fn new<'a>(db: &'a mut impl DbHandle, id: &PackageId) -> Result { - let mut locks = Vec::new(); - - let setup = Self::setup(&mut locks, id); - Ok(setup(&db.lock_all(locks).await?)?) - } - - pub fn setup( - locks: &mut Vec, - id: &PackageId, - ) -> impl FnOnce(&Verifier) -> Result { - let manifest_version = crate::db::DatabaseModel::new() - .package_data() - .idx_model(id) - .and_then(|x| x.installed()) - .map(|x| x.manifest().version()) - .make_locker(LockType::Write) - .add_to_keys(locks); - let manifest_volumes = crate::db::DatabaseModel::new() - .package_data() - .idx_model(id) - .and_then(|x| x.installed()) - .map(|x| x.manifest().volumes()) - .make_locker(LockType::Write) - .add_to_keys(locks); - let manifest_config = crate::db::DatabaseModel::new() - .package_data() - .idx_model(id) - .and_then(|x| x.installed()) - .map(|x| x.manifest().config()) - .make_locker(LockType::Write) - .add_to_keys(locks); - move |skeleton_key| { - Ok(Self { - manifest_volumes: manifest_volumes.verify(skeleton_key)?, - manifest_version: manifest_version.verify(skeleton_key)?, - manifest_config: manifest_config.verify(skeleton_key)?, - }) - } - } -} - #[command(display(display_serializable))] #[instrument(skip_all)] pub async fn get( @@ -220,16 +164,23 @@ pub async fn get( #[arg(long = "format")] format: Option, ) -> Result { - let mut db = ctx.db.handle(); - let receipts = ConfigGetReceipts::new(&mut db, &id).await?; - let action = receipts - .manifest_config - .get(&mut db) + let manifest = ctx + .db + .peek() .await? + .as_package_data() + .as_idx(&id) + .or_not_found(&id)? + .as_installed() + .or_not_found(&id)? + .as_manifest(); + let action = manifest + .as_config() + .de()? .ok_or_else(|| Error::new(eyre!("{} has no config", id), crate::ErrorKind::NotFound))?; - let volumes = receipts.manifest_volumes.get(&mut db).await?; - let version = receipts.manifest_version.get(&mut db).await?; + let volumes = manifest.as_volumes().de()?; + let version = manifest.as_version().de()?; action.get(&ctx, &id, &version, &volumes).await } @@ -250,172 +201,12 @@ pub fn set( Ok((id, config, timeout.map(|d| *d))) } -/// So, the new locking finds all the possible locks and lifts them up into a bundle of locks. -/// Then this bundle will be passed down into the functions that will need to touch the db, and -/// instead of doing the locks down in the system, we have already done the locks and can -/// do the operation on the db. -/// An UnlockedLock has two types, the type of setting and getting from the db, and the second type -/// is the keys that we need to insert on getting/setting because we have included wild cards into the paths. -pub struct ConfigReceipts { - pub dependency_receipt: DependencyReceipt, - pub config_receipts: ConfigPointerReceipts, - pub update_dependency_receipts: UpdateDependencyReceipts, - pub try_heal_receipts: TryHealReceipts, - pub break_transitive_receipts: BreakTransitiveReceipts, - pub configured: LockReceipt, - pub config_actions: LockReceipt, - pub dependencies: LockReceipt, - pub volumes: LockReceipt, - pub version: LockReceipt, - pub manifest: LockReceipt, - pub system_pointers: LockReceipt, String>, - pub current_dependents: LockReceipt, - pub current_dependencies: LockReceipt, - pub dependency_errors: LockReceipt, - pub manifest_dependencies_config: LockReceipt, - pub docker_containers: LockReceipt, -} - -impl ConfigReceipts { - pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result { - let mut locks = Vec::new(); - - let setup = Self::setup(&mut locks); - Ok(setup(&db.lock_all(locks).await?)?) - } - - pub fn setup(locks: &mut Vec) -> impl FnOnce(&Verifier) -> Result { - let dependency_receipt = DependencyReceipt::setup(locks); - let config_receipts = ConfigPointerReceipts::setup(locks); - let update_dependency_receipts = UpdateDependencyReceipts::setup(locks); - let break_transitive_receipts = BreakTransitiveReceipts::setup(locks); - let try_heal_receipts = TryHealReceipts::setup(locks); - - let configured: LockTarget = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .map(|x| x.status().configured()) - .make_locker(LockType::Write) - .add_to_keys(locks); - - let config_actions = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .and_then(|x| x.manifest().config()) - .make_locker(LockType::Read) - .add_to_keys(locks); - - let dependencies = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .map(|x| x.manifest().dependencies()) - .make_locker(LockType::Read) - .add_to_keys(locks); - - let volumes = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .map(|x| x.manifest().volumes()) - .make_locker(LockType::Read) - .add_to_keys(locks); - - let version = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .map(|x| x.manifest().version()) - .make_locker(LockType::Read) - .add_to_keys(locks); - - let manifest = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .map(|x| x.manifest()) - .make_locker(LockType::Read) - .add_to_keys(locks); - - let system_pointers = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .map(|x| x.system_pointers()) - .make_locker(LockType::Write) - .add_to_keys(locks); - - let current_dependents = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .map(|x| x.current_dependents()) - .make_locker(LockType::Write) - .add_to_keys(locks); - - let current_dependencies = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .map(|x| x.current_dependencies()) - .make_locker(LockType::Write) - .add_to_keys(locks); - - let dependency_errors = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .map(|x| x.status().dependency_errors()) - .make_locker(LockType::Write) - .add_to_keys(locks); - - let manifest_dependencies_config = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .and_then(|x| x.manifest().dependencies().star().config()) - .make_locker(LockType::Write) - .add_to_keys(locks); - let docker_containers = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .and_then(|x| x.manifest().containers()) - .make_locker(LockType::Write) - .add_to_keys(locks); - - move |skeleton_key| { - Ok(Self { - dependency_receipt: dependency_receipt(skeleton_key)?, - config_receipts: config_receipts(skeleton_key)?, - try_heal_receipts: try_heal_receipts(skeleton_key)?, - break_transitive_receipts: break_transitive_receipts(skeleton_key)?, - update_dependency_receipts: update_dependency_receipts(skeleton_key)?, - configured: configured.verify(skeleton_key)?, - config_actions: config_actions.verify(skeleton_key)?, - dependencies: dependencies.verify(skeleton_key)?, - volumes: volumes.verify(skeleton_key)?, - version: version.verify(skeleton_key)?, - manifest: manifest.verify(skeleton_key)?, - system_pointers: system_pointers.verify(skeleton_key)?, - current_dependents: current_dependents.verify(skeleton_key)?, - current_dependencies: current_dependencies.verify(skeleton_key)?, - dependency_errors: dependency_errors.verify(skeleton_key)?, - manifest_dependencies_config: manifest_dependencies_config.verify(skeleton_key)?, - docker_containers: docker_containers.verify(skeleton_key)?, - }) - } - } -} - #[command(rename = "dry", display(display_serializable))] #[instrument(skip_all)] pub async fn set_dry( #[context] ctx: RpcContext, #[parent_data] (id, config, timeout): (PackageId, Option, Option), -) -> Result { +) -> Result, Error> { let breakages = BTreeMap::new(); let overrides = Default::default(); @@ -428,7 +219,7 @@ pub async fn set_dry( }; let breakages = configure(&ctx, &id, configure_context).await?; - Ok(BreakageRes(breakages)) + Ok(breakages) } pub struct ConfigureContext { @@ -463,22 +254,17 @@ pub async fn configure( ctx: &RpcContext, id: &PackageId, configure_context: ConfigureContext, -) -> Result, Error> { - let mut db = ctx.db.handle(); - let version = crate::db::DatabaseModel::new() - .package_data() - .idx_model(id) - .expect(&mut db) - .await? - .installed() - .expect(&mut db) - .await? - .manifest() - .version() - .get(&mut ctx.db.handle()) - .await?; +) -> Result, Error> { + let db = ctx.db.peek().await?; + let package = db + .as_package_data() + .as_idx(id) + .or_not_found(&id)? + .as_installed() + .or_not_found(&id)?; + let version = package.as_manifest().as_version().de()?; ctx.managers - .get(&(id.clone(), version.clone())) + .get(&(id, version)) .await .ok_or_else(|| { Error::new( diff --git a/backend/src/context/rpc.rs b/backend/src/context/rpc.rs index 04bf0f4af..738babe4e 100644 --- a/backend/src/context/rpc.rs +++ b/backend/src/context/rpc.rs @@ -278,6 +278,7 @@ impl RpcContext { deps.ser(&CurrentDependents(current_dependents))?; } } + Ok(()) }) .await?; let peek = self.db.peek().await?; @@ -288,7 +289,9 @@ impl RpcContext { | PackageDataEntryMatchModelRef::Updating(_) => { cleanup_failed(self, &package_id).await } - PackageDataEntryMatchModelRef::Removing(_) => uninstall(self, &package_id).await, + PackageDataEntryMatchModelRef::Removing(_) => { + uninstall(self, &mut self.secret_store.acquire().await?, &package_id).await + } PackageDataEntryMatchModelRef::Installed(m) => { let version = m.as_manifest().as_version().clone().de()?; for (volume_id, volume_info) in &*m.as_manifest().as_volumes().clone().de()? { diff --git a/backend/src/db/model.rs b/backend/src/db/model.rs index 5c16d013f..9e4c738a8 100644 --- a/backend/src/db/model.rs +++ b/backend/src/db/model.rs @@ -332,6 +332,18 @@ impl Model { )) } } + pub fn expect_as_installing_mut( + &mut self, + ) -> Result<&mut Model, Error> { + if let PackageDataEntryMatchModelMut::Installing(a) = self.as_match_mut() { + Ok(a) + } else { + Err(Error::new( + eyre!("package is not in installing state"), + ErrorKind::InvalidRequest, + )) + } + } pub fn into_manifest(self) -> Model { match self.into_match() { PackageDataEntryMatchModel::Installing(a) => a.into_manifest(), @@ -382,6 +394,16 @@ impl Model { PackageDataEntryMatchModel::Error(_) => None, } } + pub fn as_install_progress(&self) -> Option<&Model> { + match self.into_match() { + PackageDataEntryMatchModel::Installing(a) => Some(a.as_install_progress()), + PackageDataEntryMatchModel::Updating(a) => Some(a.as_install_progress()), + PackageDataEntryMatchModel::Restoring(_) => None, + PackageDataEntryMatchModel::Removing(_) => None, + PackageDataEntryMatchModel::Installed(_) => None, + PackageDataEntryMatchModel::Error(_) => None, + } + } } #[derive(Debug, Deserialize, Serialize, HasModel)] diff --git a/backend/src/db/package.rs b/backend/src/db/package.rs index bcc670e19..c73a95e15 100644 --- a/backend/src/db/package.rs +++ b/backend/src/db/package.rs @@ -1,14 +1,12 @@ use crate::prelude::*; use crate::s9pk::manifest::{Manifest, PackageId}; -pub async fn get_packages(db: &PatchDb) -> Result, Error> { - Ok(db.peek().await?.as_package_data().keys()?) +pub fn get_packages(db: &Peeked) -> Result, Error> { + Ok(db.as_package_data().keys()?) } -pub async fn get_manifest(db: &PatchDb, pkg: &PackageId) -> Result, Error> { - db.peek() - .await? - .into_package_data() +pub fn get_manifest(db: &Peeked, pkg: &PackageId) -> Result, Error> { + db.into_package_data() .into_idx(pkg) .map(|pde| pde.into_manifest().de()) .transpose() diff --git a/backend/src/install/cleanup.rs b/backend/src/install/cleanup.rs index 2a58e1a5b..b2044ae4b 100644 --- a/backend/src/install/cleanup.rs +++ b/backend/src/install/cleanup.rs @@ -1,95 +1,23 @@ use std::path::PathBuf; use std::sync::Arc; +use models::OptionExt; use sqlx::{Executor, Postgres}; use tracing::instrument; use super::PKG_ARCHIVE_DIR; -use crate::config::{not_found, ConfigReceipts}; use crate::context::RpcContext; -use crate::db::model::{AllPackageData, CurrentDependencies, CurrentDependents, PackageDataEntry}; +use crate::db::model::{ + CurrentDependencies, Database, PackageDataEntry, PackageDataEntryInstalled, + PackageDataEntryMatchModelMut, PackageDataEntryMatchModelRef, +}; use crate::dependencies::reconfigure_dependents_with_live_pointers; use crate::error::ErrorCollection; use crate::prelude::*; -use crate::s9pk::manifest::{Manifest, PackageId}; +use crate::s9pk::manifest::PackageId; use crate::util::{Apply, Version}; use crate::volume::{asset_dir, script_dir}; use crate::Error; -pub struct UpdateDependencyReceipts { - try_heal: TryHealReceipts, - dependency_errors: LockReceipt, - manifest: LockReceipt, -} -impl UpdateDependencyReceipts { - pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result { - let mut locks = Vec::new(); - - let setup = Self::setup(&mut locks); - Ok(setup(&db.lock_all(locks).await?)?) - } - - pub fn setup(locks: &mut Vec) -> impl FnOnce(&Verifier) -> Result { - let dependency_errors = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .map(|x| x.status().dependency_errors()) - .make_locker(LockType::Write) - .add_to_keys(locks); - let manifest = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .map(|x| x.manifest()) - .make_locker(LockType::Write) - .add_to_keys(locks); - let try_heal = TryHealReceipts::setup(locks); - move |skeleton_key| { - Ok(Self { - dependency_errors: dependency_errors.verify(skeleton_key)?, - manifest: manifest.verify(skeleton_key)?, - try_heal: try_heal(skeleton_key)?, - }) - } - } -} - -#[instrument(skip_all)] -pub async fn update_dependency_errors_of_dependents<'a>( - ctx: &RpcContext, - db: &mut Db, - id: &PackageId, - deps: &CurrentDependents, - receipts: &UpdateDependencyReceipts, -) -> Result<(), Error> { - for dep in deps.0.keys() { - if let Some(man) = receipts.manifest.get(db, dep).await? { - if let Err(e) = if let Some(info) = man.dependencies.0.get(id) { - info.satisfied(ctx, db, id, None, dep, &receipts.try_heal) - .await? - } else { - Ok(()) - } { - let mut errs = receipts - .dependency_errors - .get(db, dep) - .await? - .ok_or_else(|| not_found!(dep))?; - errs.0.insert(id.clone(), e); - receipts.dependency_errors.set(db, errs, dep).await? - } else { - let mut errs = receipts - .dependency_errors - .get(db, dep) - .await? - .ok_or_else(|| not_found!(dep))?; - errs.0.remove(id); - receipts.dependency_errors.set(db, errs, dep).await? - } - } - } - Ok(()) -} #[instrument(skip_all)] pub async fn cleanup(ctx: &RpcContext, id: &PackageId, version: &Version) -> Result<(), Error> { @@ -130,66 +58,24 @@ pub async fn cleanup(ctx: &RpcContext, id: &PackageId, version: &Version) -> Res errors.into_result() } -pub struct CleanupFailedReceipts { - package_data_entry: LockReceipt, - package_entries: LockReceipt, -} - -impl CleanupFailedReceipts { - pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result { - let mut locks = Vec::new(); - - let setup = Self::setup(&mut locks); - Ok(setup(&db.lock_all(locks).await?)?) - } - - pub fn setup(locks: &mut Vec) -> impl FnOnce(&Verifier) -> Result { - let package_data_entry = crate::db::DatabaseModel::new() - .package_data() - .star() - .make_locker(LockType::Write) - .add_to_keys(locks); - let package_entries = crate::db::DatabaseModel::new() - .package_data() - .make_locker(LockType::Write) - .add_to_keys(locks); - move |skeleton_key| { - Ok(Self { - package_data_entry: package_data_entry.verify(skeleton_key).unwrap(), - package_entries: package_entries.verify(skeleton_key).unwrap(), - }) - } - } -} - #[instrument(skip_all)] -pub async fn cleanup_failed( - ctx: &RpcContext, - db: &mut Db, - id: &PackageId, - receipts: &CleanupFailedReceipts, -) -> Result<(), Error> { - let pde = receipts - .package_data_entry - .get(db, id) +pub async fn cleanup_failed(ctx: &RpcContext, id: &PackageId) -> Result<(), Error> { + if let Some(version) = match ctx + .db + .peek() .await? - .ok_or_else(|| not_found!(id))?; - if let Some(manifest) = match &pde { - PackageDataEntry::Installing { manifest, .. } - | PackageDataEntry::Restoring { manifest, .. } => Some(manifest), - PackageDataEntry::Updating { - manifest, - installed: - InstalledPackageDataEntry { - manifest: installed_manifest, - .. - }, - .. - } => { - if &manifest.version != &installed_manifest.version { - Some(manifest) + .as_package_data() + .as_idx(id) + .or_not_found(id)? + .as_match() + { + PackageDataEntryMatchModelRef::Installing(m) => m.as_manifest().as_version().de()?, + PackageDataEntryMatchModelRef::Restoring(m) => m.as_manifest().as_version().de()?, + PackageDataEntryMatchModelRef::Updating(m) => { + if m.as_manifest().as_version() != m.as_installed().as_manifest().as_version() { + Some(m.as_manifest().as_version().de()?) } else { - None + None // do not remove existing data } } _ => { @@ -197,168 +83,102 @@ pub async fn cleanup_failed( None } } { - cleanup(ctx, id, &manifest.version).await?; - } - - match pde { - PackageDataEntry::Installing { .. } | PackageDataEntry::Restoring { .. } => { - let mut entries = receipts.package_entries.get(db).await?; - entries.0.remove(id); - receipts.package_entries.set(db, entries).await?; - } - PackageDataEntry::Updating { - installed, - static_files, - .. - } => { - receipts - .package_data_entry - .set( - db, - PackageDataEntry::Installed { - manifest: installed.manifest.clone(), - installed, - static_files, - }, - id, - ) - .await?; - } - _ => (), + cleanup(ctx, id, &version).await?; } - Ok(()) + ctx.db + .mutate(|v| { + match v + .clone() + .into_package_data() + .into_idx(id) + .or_not_found(id)? + .into_match() + { + PackageDataEntryMatchModelMut::Installing(_) + | PackageDataEntryMatchModelMut::Restoring(_) => v.as_package_data_mut().remove(id), + PackageDataEntryMatchModelRef::Updating(pde) => v + .as_package_data_mut() + .as_idx_mut(id) + .or_not_found(id)? + .ser(&PackageDataEntry::Installed(PackageDataEntryInstalled { + manifest: pde.as_installed().as_manifest().de()?, + static_files: pde.as_static_files().de()?, + installed: pde.into_installed().de()?, + })), + } + }) + .await } #[instrument(skip_all)] pub fn remove_from_current_dependents_lists( - db: &mut PatchDb, + db: &mut Model, id: &PackageId, current_dependencies: &CurrentDependencies, ) -> Result<(), Error> { for dep in current_dependencies.0.keys().chain(std::iter::once(id)) { - if let Some(mut current_dependents) = current_dependent_receipt.get(db, dep).await? { - if current_dependents.0.remove(id).is_some() { - current_dependent_receipt - .set(db, current_dependents, dep) - .await?; - } + if let Some(mut current_dependents) = db + .as_package_data_mut() + .as_idx_mut(dep) + .and_then(|d| d.as_installed_mut()) + .map(|i| i.as_current_dependents_mut()) + { + current_dependents.remove(id)?; } } Ok(()) } -pub struct UninstallReceipts { - config: ConfigReceipts, - removing: LockReceipt, - packages: LockReceipt, - current_dependents: LockReceipt, - update_depenency_receipts: UpdateDependencyReceipts, -} -impl UninstallReceipts { - pub async fn new<'a>(db: &'a mut impl DbHandle, id: &PackageId) -> Result { - let mut locks = Vec::new(); - - let setup = Self::setup(&mut locks, id); - Ok(setup(&db.lock_all(locks).await?)?) - } - pub fn setup( - locks: &mut Vec, - id: &PackageId, - ) -> impl FnOnce(&Verifier) -> Result { - let config = ConfigReceipts::setup(locks); - let removing = crate::db::DatabaseModel::new() - .package_data() - .idx_model(id) - .and_then(|pde| pde.removing()) - .make_locker(LockType::Write) - .add_to_keys(locks); - - let current_dependents = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .map(|x| x.current_dependents()) - .make_locker(LockType::Write) - .add_to_keys(locks); - let packages = crate::db::DatabaseModel::new() - .package_data() - .make_locker(LockType::Write) - .add_to_keys(locks); - let update_depenency_receipts = UpdateDependencyReceipts::setup(locks); - move |skeleton_key| { - Ok(Self { - config: config(skeleton_key)?, - removing: removing.verify(skeleton_key)?, - current_dependents: current_dependents.verify(skeleton_key)?, - update_depenency_receipts: update_depenency_receipts(skeleton_key)?, - packages: packages.verify(skeleton_key)?, - }) - } - } -} #[instrument(skip_all)] -pub async fn uninstall( - ctx: &RpcContext, - db: &mut PatchDbHandle, - secrets: &mut Ex, - id: &PackageId, -) -> Result<(), Error> +pub async fn uninstall(ctx: &RpcContext, secrets: &mut Ex, id: &PackageId) -> Result<(), Error> where for<'a> &'a mut Ex: Executor<'a, Database = Postgres>, { - let mut tx = db.begin().await?; - crate::db::DatabaseModel::new() - .package_data() - .idx_model(&id) - .lock(&mut tx, LockType::Write) - .await?; - let receipts = UninstallReceipts::new(&mut tx, id).await?; - let entry = receipts.removing.get(&mut tx).await?; - cleanup(ctx, &entry.manifest.id, &entry.manifest.version).await?; + let db = ctx.db.peek().await?; + let entry = db + .as_package_data() + .as_idx(id) + .or_not_found(id)? + .expect_as_removing()?; - let packages = { - let mut packages = receipts.packages.get(&mut tx).await?; - packages.0.remove(id); - packages - }; let dependents_paths: Vec = entry - .current_dependents + .as_removing() + .as_current_dependents() .0 .keys() - .flat_map(|x| packages.0.get(x)) - .flat_map(|x| x.manifest_borrow().volumes.values()) + .filter(|x| x != id) + .flat_map(|x| db.as_package_data().as_idx(x)) + .flat_map(|x| x.as_installed()) + .flat_map(|x| x.as_manifest().as_volumes().values()) .flat_map(|x| x.pointer_path(&ctx.datadir)) .collect(); - receipts.packages.set(&mut tx, packages).await?; - // once we have removed the package entry, we can change all the dependent pointers to null - reconfigure_dependents_with_live_pointers(ctx, &mut tx, &receipts.config, &entry).await?; - remove_from_current_dependents_lists( - &mut tx, - &entry.manifest.id, - &entry.current_dependencies, - &receipts.current_dependents, - ) - .await?; - update_dependency_errors_of_dependents( - ctx, - &mut tx, - &entry.manifest.id, - &entry.current_dependents, - &receipts.update_depenency_receipts, - ) - .await?; - let volumes = ctx + let volume_dir = ctx .datadir .join(crate::volume::PKG_VOLUME_DIR) .join(&entry.manifest.id); - - tracing::debug!("Cleaning up {:?} at {:?}", volumes, dependents_paths); - cleanup_folder(volumes, Arc::new(dependents_paths)).await; - remove_tor_keys(secrets, &entry.manifest.id).await?; - tx.commit().await?; - Ok(()) + let version = entry.as_removing().as_manifest().as_version().de()?; + tracing::debug!( + "Cleaning up {:?} except for {:?}", + volume_dir, + dependents_paths + ); + cleanup(ctx, id, &version).await?; + cleanup_folder(volume_dir, Arc::new(dependents_paths)).await; + remove_tor_keys(secrets, id).await?; + reconfigure_dependents_with_live_pointers(ctx, &entry.as_removing().de()?).await?; + + ctx.db + .mutate(|d| { + d.as_package_data_mut().remove(id)?; + remove_from_current_dependents_lists( + d, + id, + &entry.as_removing().as_current_dependencies().de()?, + ) + }) + .await } #[instrument(skip_all)] diff --git a/backend/src/install/mod.rs b/backend/src/install/mod.rs index c29a1d1f3..4720e9e83 100644 --- a/backend/src/install/mod.rs +++ b/backend/src/install/mod.rs @@ -14,7 +14,6 @@ use futures::{FutureExt, StreamExt, TryStreamExt}; use http::header::CONTENT_LENGTH; use http::{Request, Response, StatusCode}; use hyper::Body; -use patch_db::{DbHandle, LockType}; use reqwest::Url; use rpc_toolkit::command; use rpc_toolkit::yajrc::RpcError; @@ -27,25 +26,25 @@ use tokio_stream::wrappers::ReadDirStream; use tracing::instrument; use self::cleanup::{cleanup_failed, remove_from_current_dependents_lists}; -use crate::config::{ConfigReceipts, ConfigureContext}; +use crate::config::ConfigureContext; use crate::context::{CliContext, RpcContext}; use crate::core::rpc_continuations::{RequestGuid, RpcContinuation}; use crate::db::model::{ - CurrentDependencies, CurrentDependencyInfo, CurrentDependents, InstalledPackageDataEntry, - PackageDataEntry, StaticDependencyInfo, StaticFiles, + CurrentDependencies, CurrentDependencyInfo, CurrentDependents, InstalledPackageInfo, + PackageDataEntry, PackageDataEntryInstalled, PackageDataEntryInstalling, + PackageDataEntryRemoving, PackageDataEntryRestoring, PackageDataEntryUpdating, + StaticDependencyInfo, StaticFiles, }; use crate::dependencies::{ - add_dependent_to_current_dependents_lists, break_all_dependents_transitive, - reconfigure_dependents_with_live_pointers, BreakTransitiveReceipts, BreakageRes, - DependencyError, DependencyErrors, + add_dependent_to_current_dependents_lists, reconfigure_dependents_with_live_pointers, }; -use crate::install::cleanup::{cleanup, update_dependency_errors_of_dependents}; +use crate::install::cleanup::cleanup; use crate::install::progress::{InstallProgress, InstallProgressTracker}; use crate::marketplace::with_query_params; use crate::notifications::NotificationLevel; use crate::s9pk::manifest::{Manifest, PackageId}; use crate::s9pk::reader::S9pkReader; -use crate::status::{MainStatus, Status}; +use crate::status::{DependencyConfigErrors, MainStatus, Status}; use crate::util::docker::CONTAINER_TOOL; use crate::util::io::{copy_and_shutdown, response_to_reader}; use crate::util::serde::{display_serializable, Port}; @@ -55,7 +54,6 @@ use crate::{Error, ErrorKind, ResultExt}; pub mod cleanup; pub mod progress; -pub mod update; pub const PKG_ARCHIVE_DIR: &str = "package-data/archive"; pub const PKG_PUBLIC_DIR: &str = "package-data/public"; @@ -63,30 +61,23 @@ pub const PKG_WASM_DIR: &str = "package-data/wasm"; #[command(display(display_serializable))] pub async fn list(#[context] ctx: RpcContext) -> Result { - let mut hdl = ctx.db.handle(); - let package_data = crate::db::DatabaseModel::new() - .package_data() - .get(&mut hdl) - .await?; - - Ok(package_data - .0 + Ok(ctx.db.peek().await?.as_package_data().as_entries() .iter() .filter_map(|(id, pde)| { serde_json::to_value(match pde { - PackageDataEntry::Installed { installed, .. } => { + PackageDataEntry::Installed(PackageDataEntryInstalled { installed, .. }) => { json!({ "status":"installed","id": id.clone(), "version": installed.manifest.version.clone()}) } - PackageDataEntry::Installing { manifest, install_progress, .. } => { + PackageDataEntry::Installing(PackageDataEntryInstalling { manifest, install_progress, .. }) => { json!({ "status":"installing","id": id.clone(), "version": manifest.version.clone(), "progress": install_progress.clone()}) } - PackageDataEntry::Updating { manifest, installed, install_progress, .. } => { + PackageDataEntry::Updating(PackageDataEntryUpdating { manifest, installed, install_progress, .. }) => { json!({ "status":"updating","id": id.clone(), "version": installed.manifest.version.clone(), "progress": install_progress.clone()}) } - PackageDataEntry::Restoring { manifest, install_progress, .. } => { + PackageDataEntry::Restoring(PackageDataEntryRestoring { manifest, install_progress, .. }) => { json!({ "status":"restoring","id": id.clone(), "version": manifest.version.clone(), "progress": install_progress.clone()}) } - PackageDataEntry::Removing { manifest, .. } => { + PackageDataEntry::Removing(PackageDataEntryRemoving { manifest, .. }) => { json!({ "status":"removing", "id": id.clone(), "version": manifest.version.clone()}) } }) @@ -128,12 +119,12 @@ impl std::fmt::Display for MinMax { } } -#[command( - custom_cli(cli_install(async, context(CliContext))), - display(display_none), - metadata(sync_db = true) -)] -#[instrument(skip_all)] +// #[command( +// custom_cli(cli_install(async, context(CliContext))), +// display(display_none), +// metadata(sync_db = true) +// )] +// #[instrument(skip_all)] pub async fn install( #[context] ctx: RpcContext, #[arg] id: String, @@ -278,43 +269,45 @@ pub async fn install( let progress = InstallProgress::new(s9pk.content_length()); let static_files = StaticFiles::local(&man.id, &man.version, icon_type); - let mut db_handle = ctx.db.handle(); - let mut tx = db_handle.begin().await?; - let mut pde = crate::db::DatabaseModel::new() - .package_data() - .idx_model(&man.id) - .get_mut(&mut tx) + let peek = ctx.db.peek().await?; + ctx.db + .mutate(|db| { + let mut pde = db + .as_package_data() + .as_idx(&man.id) + .or_not_found(&man.id)? + .de()?; + + match pde.take() { + Some(PackageDataEntry::Installed(PackageDataEntryInstalled { + installed, + static_files, + .. + })) => { + *pde = Some(PackageDataEntry::Updating(PackageDataEntryUpdating { + install_progress: progress.clone(), + static_files, + installed, + manifest: man.clone(), + })) + } + None => { + *pde = Some(PackageDataEntry::Installing(PackageDataEntryInstalling { + install_progress: progress.clone(), + static_files, + manifest: man.clone(), + })) + } + _ => { + return Err(Error::new( + eyre!("Cannot install over a package in a transient state"), + crate::ErrorKind::InvalidRequest, + )) + } + } + db.as_package_data_mut().insert(&man.id, &pde) + }) .await?; - match pde.take() { - Some(PackageDataEntry::Installed { - installed, - static_files, - .. - }) => { - *pde = Some(PackageDataEntry::Updating { - install_progress: progress.clone(), - static_files, - installed, - manifest: man.clone(), - }) - } - None => { - *pde = Some(PackageDataEntry::Installing { - install_progress: progress.clone(), - static_files, - manifest: man.clone(), - }) - } - _ => { - return Err(Error::new( - eyre!("Cannot install over a package in a transient state"), - crate::ErrorKind::InvalidRequest, - )) - } - } - pde.save(&mut tx).await?; - tx.commit().await?; - drop(db_handle); tokio::spawn(async move { let mut db_handle = ctx.db.handle(); @@ -411,47 +404,43 @@ pub async fn sideload( }; let progress = InstallProgress::new(content_length); - let mut hdl = new_ctx.db.handle(); - let mut tx = hdl.begin().await?; - - let mut pde = crate::db::DatabaseModel::new() - .package_data() - .idx_model(&manifest.id) - .get_mut(&mut tx) + ctx.db + .mutate(|db| { + let mut pde = db.as_package_data().as_idx(&manifest.id).de()?; + match pde.take() { + Some(PackageDataEntry::Installed(PackageDataEntryInstalled { + installed, + static_files, + .. + })) => { + *pde = Some(PackageDataEntry::Updating(PackageDataEntryUpdating { + install_progress: progress.clone(), + installed, + manifest: manifest.clone(), + static_files, + })) + } + None => { + *pde = Some(PackageDataEntry::Installing(PackageDataEntryInstalling { + install_progress: progress.clone(), + static_files: StaticFiles::local( + &manifest.id, + &manifest.version, + &manifest.assets.icon_type(), + ), + manifest: manifest.clone(), + })) + } + _ => { + return Err(Error::new( + eyre!("Cannot install over a package in a transient state"), + crate::ErrorKind::InvalidRequest, + )) + } + } + db.as_package_data_mut().insert(&manifest.id, pde) + }) .await?; - match pde.take() { - Some(PackageDataEntry::Installed { - installed, - static_files, - .. - }) => { - *pde = Some(PackageDataEntry::Updating { - install_progress: progress.clone(), - installed, - manifest: manifest.clone(), - static_files, - }) - } - None => { - *pde = Some(PackageDataEntry::Installing { - install_progress: progress.clone(), - static_files: StaticFiles::local( - &manifest.id, - &manifest.version, - &manifest.assets.icon_type(), - ), - manifest: manifest.clone(), - }) - } - _ => { - return Err(Error::new( - eyre!("Cannot install over a package in a transient state"), - crate::ErrorKind::InvalidRequest, - )) - } - } - pde.save(&mut tx).await?; - tx.commit().await?; let (send, recv) = oneshot::channel(); @@ -484,7 +473,7 @@ pub async fn sideload( if let Err(e) = new_ctx .notification_manager .notify( - &mut hdl, + ctx.db.clone(), Some(manifest.id), NotificationLevel::Error, String::from("Install Failed"), @@ -604,87 +593,41 @@ async fn cli_install( Ok(()) } -#[command( - subcommands(self(uninstall_impl(async)), uninstall_dry), - display(display_none), - metadata(sync_db = true) -)] -pub async fn uninstall(#[arg] id: PackageId) -> Result { - Ok(id) -} - -#[command(rename = "dry", display(display_serializable))] -#[instrument(skip_all)] -pub async fn uninstall_dry( +#[command(display(display_none), metadata(sync_db = true))] +pub async fn uninstall( #[context] ctx: RpcContext, - #[parent_data] id: PackageId, -) -> Result { - let mut db = ctx.db.handle(); - let mut tx = db.begin().await?; - let mut breakages = BTreeMap::new(); - let receipts = BreakTransitiveReceipts::new(&mut tx).await?; - break_all_dependents_transitive( - &mut tx, - &id, - DependencyError::NotInstalled, - &mut breakages, - &receipts, - ) - .await?; - - tx.abort().await?; - - Ok(BreakageRes(breakages)) -} - -#[instrument(skip_all)] -pub async fn uninstall_impl(ctx: RpcContext, id: PackageId) -> Result<(), Error> { - let mut handle = ctx.db.handle(); - let mut tx = handle.begin().await?; - crate::db::DatabaseModel::new() - .package_data() - .idx_model(&id) - .lock(&mut tx, LockType::Write) - .await?; - - let mut pde = crate::db::DatabaseModel::new() - .package_data() - .idx_model(&id) - .get_mut(&mut tx) + #[arg] id: PackageId, +) -> Result { + ctx.db + .mutate(|db| { + let mut pde = db.as_package_data().as_idx(&id).or_not_found(&id)?.de()?; + + let (manifest, static_files, installed) = match pde.take() { + Some(PackageDataEntry::Installed(PackageDataEntryInstalled { + manifest, + static_files, + installed, + })) => (manifest, static_files, installed), + _ => { + return Err(Error::new( + eyre!("Package is not installed."), + crate::ErrorKind::NotFound, + )); + } + }; + *pde = Some(PackageDataEntry::Removing(PackageDataEntryRemoving { + manifest, + static_files, + removing: installed, + })); + db.as_package_data_mut().insert(&id, &pde) + }) .await?; - let (manifest, static_files, installed) = match pde.take() { - Some(PackageDataEntry::Installed { - manifest, - static_files, - installed, - }) => (manifest, static_files, installed), - _ => { - return Err(Error::new( - eyre!("Package is not installed."), - crate::ErrorKind::NotFound, - )); - } - }; - *pde = Some(PackageDataEntry::Removing { - manifest, - static_files, - removing: installed, - }); - pde.save(&mut tx).await?; - tx.commit().await?; - drop(handle); tokio::spawn(async move { - if let Err(e) = async { - cleanup::uninstall( - &ctx, - &mut ctx.db.handle(), - &mut ctx.secret_store.acquire().await?, - &id, - ) - .await - } - .await + if let Err(e) = + async { cleanup::uninstall(&ctx, &mut ctx.secret_store.acquire().await?, &id).await } + .await { let err_str = format!("Uninstall of {} Failed: {}", id, e); tracing::error!("{}", err_str); @@ -711,36 +654,7 @@ pub async fn uninstall_impl(ctx: RpcContext, id: PackageId) -> Result<(), Error> Ok(()) } -pub struct DownloadInstallReceipts { - package_receipts: crate::db::package::PackageReceipts, - manifest_receipts: crate::db::package::ManifestReceipts, -} - -impl DownloadInstallReceipts { - pub async fn new<'a>(db: &'a mut impl DbHandle, id: &PackageId) -> Result { - let mut locks = Vec::new(); - - let setup = Self::setup(&mut locks, id); - Ok(setup(&db.lock_all(locks).await?)?) - } - - pub fn setup( - locks: &mut Vec, - id: &PackageId, - ) -> impl FnOnce(&patch_db::Verifier) -> Result { - let package_receipts = crate::db::package::PackageReceipts::setup(locks); - let manifest_receipts = crate::db::package::ManifestReceipts::setup(locks, id); - - move |skeleton_key| { - Ok(Self { - package_receipts: package_receipts(skeleton_key)?, - manifest_receipts: manifest_receipts(skeleton_key)?, - }) - } - } -} - -#[instrument(skip_all)] +// #[instrument(skip_all)] pub async fn download_install_s9pk( ctx: &RpcContext, temp_manifest: &Manifest, @@ -752,29 +666,22 @@ pub async fn download_install_s9pk( let pkg_id = &temp_manifest.id; let version = &temp_manifest.version; let mut previous_state: Option = None; + let peeked = ctx.db.peek().await?; if let Err(e) = async { - if crate::db::DatabaseModel::new() - .package_data() - .idx_model(&pkg_id) - .and_then(|x| x.installed()) - .exists(&mut ctx.db.handle()) - .await - .unwrap_or(false) + if peeked + .as_package_data() + .as_idx(&pkg_id) + .or_not_found(&pkg_id)? + .as_installed() + .is_some() { - previous_state = crate::control::stop_impl(ctx.clone(), pkg_id.clone()) - .await - .ok(); + previous_state = crate::control::stop(ctx.clone(), pkg_id.clone()).await.ok(); } - let mut db_handle = ctx.db.handle(); - let mut tx = db_handle.begin().await?; - let receipts = DownloadInstallReceipts::new(&mut tx, &pkg_id).await?; // Build set of existing manifests let mut manifests = Vec::new(); - for pkg in crate::db::package::get_packages(&mut tx, &receipts.package_receipts).await? { - if let Some(m) = - crate::db::package::get_manifest(&mut tx, &pkg, &receipts.manifest_receipts).await? - { + for pkg in crate::db::package::get_packages(&peeked)? { + if let Some(m) = crate::db::package::get_manifest(&peeked, &pkg) { manifests.push(m); } } @@ -808,9 +715,6 @@ pub async fn download_install_s9pk( } } } - drop(receipts); - tx.save().await?; - drop(db_handle); let pkg_archive_dir = ctx .datadir @@ -821,11 +725,12 @@ pub async fn download_install_s9pk( let pkg_archive = pkg_archive_dir.join(AsRef::::as_ref(pkg_id).with_extension("s9pk")); - let pkg_data_entry = crate::db::DatabaseModel::new() - .package_data() - .idx_model(pkg_id); + let pkg_data_entry = peeked + .as_package_data() + .as_idx(pkg_id) + .or_not_found(pkg_id)?; - let progress_model = pkg_data_entry.and_then(|pde| pde.install_progress()); + let progress_model = pkg_data_entry.and_then(|pde| pde.as_install_progress()); File::delete(&pkg_archive).await?; let mut dst = OpenOptions::new() @@ -874,15 +779,9 @@ pub async fn download_install_s9pk( crate::control::start(ctx.clone(), pkg_id.clone()).await?; } - let mut handle = ctx.db.handle(); - let mut tx = handle.begin().await?; - let receipts = cleanup::CleanupFailedReceipts::new(&mut tx).await?; - - if let Err(e) = cleanup_failed(&ctx, &mut tx, pkg_id, &receipts).await { + if let Err(e) = cleanup_failed(&ctx, pkg_id).await { tracing::error!("Failed to clean up {}@{}: {}", pkg_id, version, e); tracing::debug!("{:?}", e); - } else { - tx.commit().await?; } Err(e) } else { @@ -892,32 +791,7 @@ pub async fn download_install_s9pk( Ok(()) } } - -pub struct InstallS9Receipts { - config: ConfigReceipts, -} - -impl InstallS9Receipts { - pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result { - let mut locks = Vec::new(); - - let setup = Self::setup(&mut locks); - Ok(setup(&db.lock_all(locks).await?)?) - } - - pub fn setup( - locks: &mut Vec, - ) -> impl FnOnce(&patch_db::Verifier) -> Result { - let config = ConfigReceipts::setup(locks); - move |skeleton_key| { - Ok(Self { - config: config(skeleton_key)?, - }) - } - } -} - -#[instrument(skip_all)] +// #[instrument(skip_all)] pub async fn install_s9pk( ctx: &RpcContext, pkg_id: &PackageId, @@ -930,10 +804,9 @@ pub async fn install_s9pk( rdr.validated(); let developer_key = rdr.developer_key().clone(); rdr.reset().await?; - let model = crate::db::DatabaseModel::new() - .package_data() - .idx_model(pkg_id); - let progress_model = model.clone().and_then(|m| m.install_progress()); + let db = ctx.peek().await?; + let model = db.as_package_data().as_idx(pkg_id); + let progress_model = model.as_install_progress(); tracing::info!("Install {}@{}: Unpacking Manifest", pkg_id, version); let manifest = progress @@ -944,9 +817,9 @@ pub async fn install_s9pk( tracing::info!("Install {}@{}: Fetching Dependency Info", pkg_id, version); let mut dependency_info = BTreeMap::new(); for (dep, info) in &manifest.dependencies.0 { - let manifest: Option = if let Some(local_man) = crate::db::DatabaseModel::new() - .package_data() - .idx_model(dep) + let manifest: Option = if let Some(local_man) = db + .as_package_data() + .as_idx(dep) .map::<_, Manifest>(|pde| pde.manifest()) .get(&mut ctx.db.handle()) .await? @@ -1015,6 +888,10 @@ pub async fn install_s9pk( dependency_info.insert( dep.clone(), StaticDependencyInfo { + title: manifest + .as_ref() + .map(|x| x.title.clone()) + .unwrap_or_else(|| dep.to_string()), icon: if let Some(manifest) = &manifest { format!( "/public/package-data/{}/{}/icon.{}", @@ -1025,7 +902,6 @@ pub async fn install_s9pk( } else { "/assets/img/package-icon.png".to_owned() }, - manifest, }, ); } @@ -1149,13 +1025,7 @@ pub async fn install_s9pk( progress_model.put(&mut ctx.db.handle(), &progress).await?; - let mut handle = ctx.db.handle(); - let mut tx = handle.begin().await?; let mut sql_tx = ctx.secret_store.begin().await?; - crate::db::DatabaseModel::new() - .package_data() - .lock(&mut tx, LockType::Write) - .await?; tracing::info!("Install {}@{}: Creating volumes", pkg_id, version); manifest.volumes.install(ctx, pkg_id, version).await?; @@ -1186,117 +1056,117 @@ pub async fn install_s9pk( ); let current_dependents = { let mut deps = BTreeMap::new(); - for package in crate::db::DatabaseModel::new() - .package_data() - .keys(&mut tx) - .await? - { - // update dependency_info on dependents - if let Some(dep_info_model) = crate::db::DatabaseModel::new() - .package_data() - .idx_model(&package) - .expect(&mut tx) - .await? - .installed() - .and_then(|i| i.dependency_info().idx_model(pkg_id)) - .check(&mut tx) - .await? - { - let mut dep_info = dep_info_model.get_mut(&mut tx).await?; - *dep_info = StaticDependencyInfo { - icon: format!( - "/public/package-data/{}/{}/icon.{}", - manifest.id, - manifest.version, - manifest.assets.icon_type() - ), - manifest: Some(manifest.clone()), - }; - } + ctx.db + .mutate(|db| { + for package in db.as_package_data().keys()? { + // update dependency_info on dependents + if let Some(dep_info_model) = db + .as_package_data() + .as_idx(&package) + .or_not_found(&package)? + .as_installed() + .or_not_found(&package)? + .as_dependency_info() + .as_idx(&pkg_id) + { + dep_info_model.ser(&StaticDependencyInfo { + icon: format!( + "/public/package-data/{}/{}/icon.{}", + manifest.id, + manifest.version, + manifest.assets.icon_type() + ), + title: manifest + .as_ref() + .map(|x| x.title.clone()) + .unwrap_or_else(|_| package.to_string()), + })?; + } + + // search required dependencies + if let Some(dep) = db + .as_package_data() + .as_idx(&package) + .or_not_found(&package)? + .installed() + .and_then(|i| i.current_dependencies().as_idx(pkg_id)) + { + deps.insert(package, dep)?; + } + } + Ok(()) + }) + .await?; - // search required dependencies - if let Some(dep) = crate::db::DatabaseModel::new() - .package_data() - .idx_model(&package) - .expect(&mut tx) - .await? - .installed() - .and_then(|i| i.current_dependencies().idx_model(pkg_id)) - .get(&mut tx) - .await? - .to_owned() - { - deps.insert(package, dep); - } - } CurrentDependents(deps) }; - let mut pde = model - .clone() - .expect(&mut tx) - .await? - .get_mut(&mut tx) - .await?; - let installed = InstalledPackageDataEntry { - status: Status { - configured: manifest.config.is_none(), - main: MainStatus::Stopped, - dependency_errors: DependencyErrors::default(), - }, - marketplace_url, - developer_key, - manifest: manifest.clone(), - last_backup: match &*pde { - PackageDataEntry::Updating { - installed: - InstalledPackageDataEntry { - last_backup: Some(time), - .. - }, - .. - } => Some(*time), - _ => None, - }, - system_pointers: Vec::new(), - dependency_info, - current_dependents: current_dependents.clone(), - current_dependencies: current_dependencies.clone(), - interface_addresses, - }; - let prev = std::mem::replace( - &mut *pde, - PackageDataEntry::Installed { - installed, - manifest: manifest.clone(), - static_files, - }, - ); - pde.save(&mut tx).await?; - let receipts = InstallS9Receipts::new(&mut tx).await?; - // UpdateDependencyReceipts - let mut dep_errs = model - .expect(&mut tx) - .await? - .installed() - .expect(&mut tx) - .await? - .status() - .dependency_errors() - .get_mut(&mut tx) - .await?; - *dep_errs = DependencyErrors::init( + + let new_dependency_errors = DependencyConfigErrors::init( ctx, - &mut tx, &manifest, ¤t_dependencies, &receipts.config.try_heal_receipts, ) .await?; - dep_errs.save(&mut tx).await?; + ctx.db + .mutate(|db| { + let mut pde = db.as_package_data().as_idx(pkg_id).or_not_found(pkg_id)?.de()?; + let installed = InstalledPackageInfo { + status: Status { + configured: manifest.config.is_none(), + main: MainStatus::Stopped, + dependency_config_errors: DependencyConfigErrors::default(), + }, + marketplace_url, + developer_key, + manifest: manifest.clone(), + last_backup: match &*pde { + PackageDataEntry::Updating(PackageDataEntryUpdating { + installed: + InstalledPackageInfo { + last_backup: Some(time), + .. + }, + .. + }) => Some(*time), + _ => None, + }, + dependency_info, + current_dependents: current_dependents.clone(), + current_dependencies: current_dependencies.clone(), + interface_addresses, + }; + let prev = std::mem::replace( + &mut *pde, + PackageDataEntry::Installed(PackageDataEntryInstalled { + installed, + manifest: manifest.clone(), + static_files, + }), + ); + db.as_package_data_mut().insert(pkg_id, &pde) + // UpdateDependencyReceipts + let mut dep_errs = db.as_package_data().as_idx(pkg_id).or_not_found(pkg_id)? + .as_installed().or_not_found(pkg_id)? + .as_status() + .as_dependency_errors() + .ser()?; + *dep_errs = DependencyErrors::init( + ctx, + &mut tx, + &manifest, + ¤t_dependencies, + &receipts.config.try_heal_receipts, + ) + .await?; + dep_errs.save(&mut tx).await?; + }) + .await?; + - if let PackageDataEntry::Updating { + if let PackageDataEntry::Updating(PackageDataEntryUpdating { installed: prev, .. - } = prev + }) = prev { let prev_is_configured = prev.status.configured; let prev_migration = prev @@ -1393,7 +1263,7 @@ pub async fn install_s9pk( if &prev.manifest.version != version { cleanup(ctx, &prev.manifest.id, &prev.manifest.version).await?; } - } else if let PackageDataEntry::Restoring { .. } = prev { + } else if let PackageDataEntry::Restoring(PackageDataEntryRestoring { .. }) = prev { manifest .backup .restore( diff --git a/backend/src/install/progress.rs b/backend/src/install/progress.rs index 80bee0675..a2e803200 100644 --- a/backend/src/install/progress.rs +++ b/backend/src/install/progress.rs @@ -6,14 +6,16 @@ use std::sync::Arc; use std::task::{Context, Poll}; use std::time::Duration; -use patch_db::{DbHandle, HasModel, OptionModel, PatchDb}; +use models::{OptionExt, PackageId}; use serde::{Deserialize, Serialize}; use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite}; -use crate::Error; +use crate::db::model::Database; +use crate::prelude::*; #[derive(Debug, Deserialize, Serialize, HasModel, Default)] #[serde(rename_all = "kebab-case")] +#[model = "Model"] pub struct InstallProgress { pub size: Option, pub downloaded: AtomicU64, @@ -38,21 +40,20 @@ impl InstallProgress { pub fn download_complete(&self) { self.download_complete.store(true, Ordering::SeqCst) } - pub async fn track_download( - self: Arc, - model: OptionModel, - mut db: Db, - ) -> Result<(), Error> { + pub async fn track_download(self: Arc, db: PatchDb, id: PackageId) -> Result<(), Error> { + let update = |d: &mut Model| { + d.as_package_data_mut() + .as_idx_mut(&id) + .or_not_found(&id)? + .expect_as_installing_mut()? + .as_install_progress_mut() + .ser(&*self) + }; while !self.download_complete.load(Ordering::SeqCst) { - let mut tx = db.begin().await?; - model.put(&mut tx, &self).await?; - tx.save().await?; - tokio::time::sleep(Duration::from_secs(1)).await; + db.mutate(&update).await?; + tokio::time::sleep(Duration::from_millis(300)).await; } - let mut tx = db.begin().await?; - model.put(&mut tx, &self).await?; - tx.save().await?; - Ok(()) + db.mutate(&update).await } pub async fn track_download_during< F: FnOnce() -> Fut, @@ -60,33 +61,35 @@ impl InstallProgress { T, >( self: &Arc, - model: OptionModel, db: &PatchDb, + id: &PackageId, f: F, ) -> Result { - let local_db = db.handle(); - let tracker = tokio::spawn(self.clone().track_download(model.clone(), local_db)); + let tracker = tokio::spawn(self.clone().track_download(db.clone(), id.clone())); let res = f().await; self.download_complete.store(true, Ordering::SeqCst); tracker.await.unwrap()?; res } - pub async fn track_read( + pub async fn track_read( self: Arc, - model: OptionModel, - mut db: Db, + db: PatchDb, + id: PackageId, complete: Arc, ) -> Result<(), Error> { + let update = |d: &mut Model| { + d.as_package_data_mut() + .as_idx_mut(&id) + .or_not_found(&id)? + .expect_as_installing_mut()? + .as_install_progress_mut() + .ser(&*self) + }; while !complete.load(Ordering::SeqCst) { - let mut tx = db.begin().await?; - model.put(&mut tx, &self).await?; - tx.save().await?; - tokio::time::sleep(Duration::from_secs(1)).await; + db.mutate(&update).await?; + tokio::time::sleep(Duration::from_millis(300)).await; } - let mut tx = db.begin().await?; - model.put(&mut tx, &self).await?; - tx.save().await?; - Ok(()) + db.mutate(&update).await } pub async fn track_read_during< F: FnOnce() -> Fut, @@ -94,15 +97,14 @@ impl InstallProgress { T, >( self: &Arc, - model: OptionModel, db: &PatchDb, + id: &PackageId, f: F, ) -> Result { - let local_db = db.handle(); let complete = Arc::new(AtomicBool::new(false)); let tracker = tokio::spawn(self.clone().track_read( - model.clone(), - local_db, + db.clone(), + id.clone(), complete.clone(), )); let res = f().await; diff --git a/backend/src/install/update.rs b/backend/src/install/update.rs index bafbf04ec..694051213 100644 --- a/backend/src/install/update.rs +++ b/backend/src/install/update.rs @@ -1,105 +1,18 @@ use std::collections::BTreeMap; -use patch_db::{DbHandle, LockReceipt, LockTargetId, LockType, Verifier}; use rpc_toolkit::command; use tracing::instrument; use crate::config::not_found; use crate::context::RpcContext; use crate::db::model::CurrentDependents; -use crate::dependencies::{ - break_transitive, BreakTransitiveReceipts, BreakageRes, DependencyError, -}; +use crate::prelude::*; use crate::s9pk::manifest::PackageId; use crate::util::serde::display_serializable; use crate::util::Version; use crate::Error; -pub struct UpdateReceipts { - break_receipts: BreakTransitiveReceipts, - current_dependents: LockReceipt, - dependency: LockReceipt, -} - -impl UpdateReceipts { - pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result { - let mut locks = Vec::new(); - - let setup = Self::setup(&mut locks); - Ok(setup(&db.lock_all(locks).await?)?) - } - - pub fn setup(locks: &mut Vec) -> impl FnOnce(&Verifier) -> Result { - let break_receipts = BreakTransitiveReceipts::setup(locks); - let current_dependents = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .map(|x| x.current_dependents()) - .make_locker(LockType::Write) - .add_to_keys(locks); - let dependency = crate::db::DatabaseModel::new() - .package_data() - .star() - .installed() - .map(|x| x.manifest().dependencies().star()) - .make_locker(LockType::Write) - .add_to_keys(locks); - move |skeleton_key| { - Ok(Self { - break_receipts: break_receipts(skeleton_key)?, - current_dependents: current_dependents.verify(skeleton_key)?, - dependency: dependency.verify(skeleton_key)?, - }) - } - } -} - #[command(subcommands(dry))] pub async fn update() -> Result<(), Error> { Ok(()) } - -#[instrument(skip_all)] -#[command(display(display_serializable))] -pub async fn dry( - #[context] ctx: RpcContext, - #[arg] id: PackageId, - #[arg] version: Version, -) -> Result { - let mut db = ctx.db.handle(); - let mut tx = db.begin().await?; - let mut breakages = BTreeMap::new(); - let receipts = UpdateReceipts::new(&mut tx).await?; - - for dependent in receipts - .current_dependents - .get(&mut tx, &id) - .await? - .ok_or_else(|| not_found!(id))? - .0 - .keys() - .into_iter() - .filter(|dependent| &&id != dependent) - { - if let Some(dep_info) = receipts.dependency.get(&mut tx, (&dependent, &id)).await? { - let version_req = dep_info.version; - if !version.satisfies(&version_req) { - break_transitive( - &mut tx, - &dependent, - &id, - DependencyError::IncorrectVersion { - expected: version_req, - received: version.clone(), - }, - &mut breakages, - &receipts.break_receipts, - ) - .await?; - } - } - } - tx.abort().await?; - Ok(BreakageRes(breakages)) -} diff --git a/backend/src/manager/mod.rs b/backend/src/manager/mod.rs index da75304ce..5a5cdb9b4 100644 --- a/backend/src/manager/mod.rs +++ b/backend/src/manager/mod.rs @@ -40,7 +40,7 @@ use crate::prelude::*; use crate::procedure::docker::{DockerContainer, DockerProcedure, LongRunning}; use crate::procedure::{NoOutput, ProcedureName}; use crate::s9pk::manifest::Manifest; -use crate::status::MainStatus; +use crate::status::{DependencyConfigErrors, MainStatus}; use crate::util::docker::{get_container_ip, kill_container}; use crate::util::NonDetachingJoinHandle; use crate::volume::Volume; @@ -481,22 +481,59 @@ async fn configure( } .await?; // remove previous - // TODO @dr-bonez Could we convert this - // let errs = receipts - // .dependency_errors - // .get(db, id) - // .await? - // .ok_or_else(|| not_found!(id))?; - // tracing::warn!("Dependency Errors: {:?}", errs); - // let errs = DependencyErrors::init( - // ctx, - // db, - // &manifest, - // ¤t_dependencies, - // &receipts.dependency_receipt.try_heal, - // ) - // .await?; - // receipts.dependency_errors.set(db, errs, id).await?; + let mut dependency_config_errs = BTreeMap::new(); + for (dependency, _dep_info) in current_dependencies + .0 + .iter() + .filter(|(dep_id, _)| dep_id != &id) + { + let dependency_container = db + .as_package_data() + .as_idx(dependency) + .or_not_found(dependency)? + .as_installed() + .or_not_found(dependency)? + .as_manifest() + .as_containers() + .de()?; + let dependency_container = &dependency_container; + // check if config passes dependency check + if let Some(cfg) = db + .as_package_data() + .as_idx(dependency) + .or_not_found(dependency)? + .as_installed() + .or_not_found(dependency)? + .as_manifest() + .as_dependencies() + .as_idx(id) + .config() + .de()? + { + let manifest = db + .as_package_data() + .as_idx(dependency) + .or_not_found(dependency)? + .as_installed() + .or_not_found(dependency)? + .as_manifest() + .de()?; + if let Err(error) = cfg + .check( + ctx, + dependency_container, + dependency, + &manifest.version, + &manifest.volumes, + id, + &config, + ) + .await? + { + dependency_config_errs.insert(dependency.clone(), error); + } + } + } // cache current config for dependents configure_context @@ -557,19 +594,8 @@ async fn configure( ) .await? { - // let dep_err = DependencyError::ConfigUnsatisfied { error }; - // break_transitive( - // db, - // dependent, - // id, - // dep_err, - // &mut configure_context.breakages, - // &receipts.break_transitive_receipts, - // ) - // .await?; + configure_context.breakages.insert(dependent.clone(), error); } - - // heal_all_dependents_transitive(ctx, db, id, &receipts.dependency_receipt).await?; } } @@ -579,28 +605,34 @@ async fn configure( remove_from_current_dependents_lists(db, id, &dependencies)?; add_dependent_to_current_dependents_lists(db, id, ¤t_dependencies)?; current_dependencies.0.remove(id); - db.as_package_data_mut() + for (dep, errs) in + db.as_package_data_mut() + .as_entries_mut() + .and_then(|(id, pde)| { + pde.as_installed_mut() + .map(|i| (id, i.as_status_mut().as_dependency_config_errors_mut())) + }) + { + errs.remove(id)?; + if let Some(err) = configure_context.breakages.get(dep) { + errs.insert(id, &err)?; + } + } + let mut installed = db + .as_package_data_mut() .as_idx_mut(id) .or_not_found(id)? .as_installed_mut() - .or_not_found(id)? + .or_not_found(id)?; + installed .as_current_dependencies_mut() .ser(¤t_dependencies)?; - db.as_package_data_mut() - .as_idx_mut(id) - .or_not_found(id)? - .as_installed_mut() - .or_not_found(id)? - .as_system_pointers_mut() - .ser(sys)?; - db.as_package_data_mut() - .as_idx_mut(id) - .or_not_found(id)? - .as_installed_mut() - .or_not_found(id)? - .as_status_mut() - .as_configured_mut() - .ser(&true) + installed.as_system_pointers_mut().ser(sys)?; + let status = installed.as_status_mut(); + status.as_configured_mut().ser(&true)?; + status + .as_dependency_config_errors_mut() + .ser(&DependencyConfigErrors(dependency_config_errs)) }) .await?; // add new } diff --git a/backend/src/status/mod.rs b/backend/src/status/mod.rs index 6294325a7..ea4b5f0d3 100644 --- a/backend/src/status/mod.rs +++ b/backend/src/status/mod.rs @@ -15,7 +15,16 @@ pub mod health_check; pub struct Status { pub configured: bool, pub main: MainStatus, - pub dependency_config_errors: BTreeMap, + pub dependency_config_errors: DependencyConfigErrors, +} + +#[derive(Clone, Debug, Deserialize, Serialize, HasModel)] +#[serde(rename_all = "kebab-case")] +#[model = "Model"] +pub struct DependencyConfigErrors(pub BTreeMap); +impl Map for DependencyConfigErrors { + type Key = PackageId; + type Value = String; } #[derive(Debug, Clone, Deserialize, Serialize)] From d53030d93f0dc606a13640d4f30189256e5a4338 Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Fri, 1 Sep 2023 14:50:05 -0600 Subject: [PATCH 40/89] chore: Update the last of the errors --- backend/src/install/mod.rs | 213 ++++++++++++++++++------------------- backend/src/lib.rs | 1 - 2 files changed, 103 insertions(+), 111 deletions(-) diff --git a/backend/src/install/mod.rs b/backend/src/install/mod.rs index 4720e9e83..df399e1c8 100644 --- a/backend/src/install/mod.rs +++ b/backend/src/install/mod.rs @@ -119,12 +119,12 @@ impl std::fmt::Display for MinMax { } } -// #[command( -// custom_cli(cli_install(async, context(CliContext))), -// display(display_none), -// metadata(sync_db = true) -// )] -// #[instrument(skip_all)] +#[command( + custom_cli(cli_install(async, context(CliContext))), + display(display_none), + metadata(sync_db = true) +)] +#[instrument(skip_all)] pub async fn install( #[context] ctx: RpcContext, #[arg] id: String, @@ -654,7 +654,7 @@ pub async fn uninstall( Ok(()) } -// #[instrument(skip_all)] +#[instrument(skip_all)] pub async fn download_install_s9pk( ctx: &RpcContext, temp_manifest: &Manifest, @@ -791,7 +791,9 @@ pub async fn download_install_s9pk( Ok(()) } } -// #[instrument(skip_all)] + +/// TODO @Blu-J @dr-bonez Need to make sure that we end load the db models +#[instrument(skip_all)] pub async fn install_s9pk( ctx: &RpcContext, pkg_id: &PackageId, @@ -1101,16 +1103,16 @@ pub async fn install_s9pk( CurrentDependents(deps) }; - let new_dependency_errors = DependencyConfigErrors::init( - ctx, - &manifest, - ¤t_dependencies, - &receipts.config.try_heal_receipts, - ) - .await?; - ctx.db + let new_dependency_errors = + DependencyConfigErrors::init(ctx, &manifest, ¤t_dependencies).await?; + let prev = ctx + .db .mutate(|db| { - let mut pde = db.as_package_data().as_idx(pkg_id).or_not_found(pkg_id)?.de()?; + let mut pde = db + .as_package_data() + .as_idx(pkg_id) + .or_not_found(pkg_id)? + .de()?; let installed = InstalledPackageInfo { status: Status { configured: manifest.config.is_none(), @@ -1144,25 +1146,30 @@ pub async fn install_s9pk( static_files, }), ); - db.as_package_data_mut().insert(pkg_id, &pde) + db.as_package_data_mut().insert(pkg_id, &pde)?; + // TODO @dr-bonez // UpdateDependencyReceipts - let mut dep_errs = db.as_package_data().as_idx(pkg_id).or_not_found(pkg_id)? - .as_installed().or_not_found(pkg_id)? - .as_status() - .as_dependency_errors() - .ser()?; - *dep_errs = DependencyErrors::init( - ctx, - &mut tx, - &manifest, - ¤t_dependencies, - &receipts.config.try_heal_receipts, - ) - .await?; - dep_errs.save(&mut tx).await?; + // let mut dep_errs = db + // .as_package_data() + // .as_idx(pkg_id) + // .or_not_found(pkg_id)? + // .as_installed() + // .or_not_found(pkg_id)? + // .as_status() + // .as_dependency_errors() + // .ser()?; + // *dep_errs = DependencyConfigErrors::init( + // ctx, + // &mut tx, + // &manifest, + // ¤t_dependencies, + // &receipts.config.try_heal_receipts, + // ) + // .await?; + // dep_errs.save(&mut tx).await?; + Ok(prev) }) .await?; - if let PackageDataEntry::Updating(PackageDataEntryUpdating { installed: prev, .. @@ -1197,14 +1204,11 @@ pub async fn install_s9pk( } else { migration.or(prev_migration) }; - - remove_from_current_dependents_lists( - &mut tx, - pkg_id, - &prev.current_dependencies, - &receipts.config.current_dependents, - ) - .await?; // remove previous + ctx.db + .mutate(|db| { + remove_from_current_dependents_lists(db, pkg_id, &prev.current_dependencies) + }) + .await?; let configured = if let Some(f) = viable_migration { f.await?.configured && prev_is_configured @@ -1224,42 +1228,39 @@ pub async fn install_s9pk( }; crate::config::configure(&ctx, pkg_id, configure_context).await?; } else { - add_dependent_to_current_dependents_lists( - &mut tx, - pkg_id, - ¤t_dependencies, - &receipts.config.current_dependents, - ) - .await?; // add new + ctx.db + .mutate(|db| { + add_dependent_to_current_dependents_lists(db, pkg_id, ¤t_dependencies) + }) + .await?; } if configured || manifest.config.is_none() { - let mut main_status = crate::db::DatabaseModel::new() - .package_data() - .idx_model(pkg_id) - .expect(&mut tx) - .await? - .installed() - .expect(&mut tx) - .await? - .status() - .main() - .get_mut(&mut tx) + ctx.db + .mutate(|db| { + db.as_package_data() + .as_idx(pkg_id) + .or_not_found(pkg_id)? + .as_installed() + .or_not_found(pkg_id)? + .as_status() + .as_main() + .ser(&prev.status.main) + }) .await?; - *main_status = prev.status.main; - main_status.save(&mut tx).await?; } - update_dependency_errors_of_dependents( - ctx, - &mut tx, - pkg_id, - &CurrentDependents({ - let mut current_dependents = current_dependents.0.clone(); - current_dependents.append(&mut prev.current_dependents.0.clone()); - current_dependents - }), - &receipts.config.update_dependency_receipts, - ) - .await?; + // TODO @dr-bonez DELETE right? + // update_dependency_errors_of_dependents( + // ctx, + // &mut tx, + // pkg_id, + // &CurrentDependents({ + // let mut current_dependents = current_dependents.0.clone(); + // current_dependents.append(&mut prev.current_dependents.0.clone()); + // current_dependents + // }), + // &receipts.config.update_dependency_receipts, + // ) + // .await?; if &prev.manifest.version != version { cleanup(ctx, &prev.manifest.id, &prev.manifest.version).await?; } @@ -1268,53 +1269,45 @@ pub async fn install_s9pk( .backup .restore( ctx, - &mut tx, pkg_id, version, &manifest.interfaces, &manifest.volumes, ) .await?; - add_dependent_to_current_dependents_lists( - &mut tx, - pkg_id, - ¤t_dependencies, - &receipts.config.current_dependents, - ) - .await?; - update_dependency_errors_of_dependents( - ctx, - &mut tx, - pkg_id, - ¤t_dependents, - &receipts.config.update_dependency_receipts, - ) - .await?; + ctx.db + .mutate(|db| { + add_dependent_to_current_dependents_lists(db, pkg_id, ¤t_dependencies) + }) + .await?; + // TODO @dr-bonez DELETE right? + // update_dependency_errors_of_dependents(ctx, pkg_id, ¤t_dependents).await?; } else { - add_dependent_to_current_dependents_lists( - &mut tx, - pkg_id, - ¤t_dependencies, - &receipts.config.current_dependents, - ) - .await?; - update_dependency_errors_of_dependents( - ctx, - &mut tx, - pkg_id, - ¤t_dependents, - &receipts.config.update_dependency_receipts, - ) - .await?; - } - - if let Some(installed) = pde.installed() { - reconfigure_dependents_with_live_pointers(ctx, &mut tx, &receipts.config, installed) + ctx.db + .mutate(|db| { + add_dependent_to_current_dependents_lists(db, pkg_id, ¤t_dependencies) + }) .await?; + // TODO @dr-bonez DELETE right? + // update_dependency_errors_of_dependents( + // ctx, + // pkg_id, + // ¤t_dependents, + // &receipts.config.update_dependency_receipts, + // ) + // .await?; } - sql_tx.commit().await?; - tx.commit().await?; + if let Some(installed) = db + .as_package_data() + .as_idx(pkg_id) + .or_not_found(pkg_id)? + .de()? + .as_installed() + .or_not_found(pkg_id)? + { + reconfigure_dependents_with_live_pointers(ctx, installed).await?; + } tracing::info!("Install {}@{}: Complete", pkg_id, version); diff --git a/backend/src/lib.rs b/backend/src/lib.rs index f20de5d73..dda755dba 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -106,7 +106,6 @@ pub fn server() -> Result<(), RpcError> { install::sideload, install::uninstall, install::list, - install::update::update, config::config, control::start, control::stop, From 0544355e5c87298372952cfab792be8d43170eb2 Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Fri, 1 Sep 2023 16:07:40 -0600 Subject: [PATCH 41/89] feat: Change the prelude de to borrow --- backend/src/db/prelude.rs | 4 ++-- backend/src/setup.rs | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/backend/src/db/prelude.rs b/backend/src/db/prelude.rs index f51863f4f..a0b04d6bc 100644 --- a/backend/src/db/prelude.rs +++ b/backend/src/db/prelude.rs @@ -75,8 +75,8 @@ pub struct Model { phantom: PhantomData, } impl Model { - pub fn de(self) -> Result { - from_value(self.value) + pub fn de(&self) -> Result { + from_value(self.value.clone()) } } impl Model { diff --git a/backend/src/setup.rs b/backend/src/setup.rs index 88cfaa817..4e172a057 100644 --- a/backend/src/setup.rs +++ b/backend/src/setup.rs @@ -64,8 +64,12 @@ async fn setup_init( if let Some(password) = password { account.set_password(&password)?; account.save(&mut secrets_tx).await?; - db.mutate(|m| m.server_info().password_hash().ser(&account.password)) - .await?; + db.mutate(|m| { + m.as_server_info_mut() + .as_password_hash_mut() + .ser(&account.password) + }) + .await?; } secrets_tx.commit().await?; From a4de0163f33a064b2a487d0e056c071af4d8b80e Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Fri, 1 Sep 2023 16:28:46 -0600 Subject: [PATCH 42/89] feat: Adding in some more things --- backend/src/dependencies.rs | 14 ++++++++------ backend/src/init.rs | 2 +- backend/src/install/mod.rs | 15 ++++++++------- backend/src/notifications.rs | 25 ++++++++++--------------- 4 files changed, 27 insertions(+), 29 deletions(-) diff --git a/backend/src/dependencies.rs b/backend/src/dependencies.rs index ec8fa16aa..0e7b6125d 100644 --- a/backend/src/dependencies.rs +++ b/backend/src/dependencies.rs @@ -10,12 +10,11 @@ use rpc_toolkit::command; use serde::{Deserialize, Serialize}; use tracing::instrument; -use crate::config::action::ConfigRes; +use crate::config::{action::ConfigRes, not_found}; use crate::config::{Config, ConfigSpec, ConfigureContext}; use crate::context::RpcContext; use crate::db::model::{CurrentDependencies, Database, InstalledPackageInfo}; use crate::prelude::*; -use crate::procedure::docker::DockerContainers; use crate::procedure::{NoOutput, PackageProcedure, ProcedureName}; use crate::s9pk::manifest::PackageId; use crate::util::serde::display_serializable; @@ -72,7 +71,6 @@ impl DependencyConfig { pub async fn check( &self, ctx: &RpcContext, - container: &Option, dependent_id: &PackageId, dependent_version: &Version, dependent_volumes: &Volumes, @@ -96,7 +94,6 @@ impl DependencyConfig { pub async fn auto_configure( &self, ctx: &RpcContext, - container: &Option, dependent_id: &PackageId, dependent_version: &Version, dependent_volumes: &Volumes, @@ -187,7 +184,11 @@ pub async fn configure_logic( .or_not_found(&pkg_id)? .as_installed() .or_not_found(&pkg_id)?; - let dependency_config_action = dependency.as_manifest().as_config().de()?; + let dependency_config_action = dependency + .as_manifest() + .as_config() + .de()? + .ok_or_else(|| not_found!("Manifest Config"))?; let dependency_version = dependency.as_manifest().as_version().de()?; let dependency_volumes = dependency.as_manifest().as_volumes().de()?; let dependency = pkg @@ -195,7 +196,6 @@ pub async fn configure_logic( .as_dependencies() .as_idx(&dependency_id) .or_not_found(&dependency_id)?; - let pkg_docker_container = pkg.as_manifest().as_containers().de()?; let ConfigRes { config: maybe_config, @@ -221,6 +221,7 @@ pub async fn configure_logic( let new_config = dependency .as_config() .de()? + .ok_or_else(|| not_found!("Config"))? .auto_configure .sandboxed( &ctx, @@ -265,6 +266,7 @@ pub async fn reconfigure_dependents_with_live_pointers( i: &InstalledPackageInfo, ) -> Result<(), Error> { todo!(); + // TODO @dr-bonez // let dependents = &pde.current_dependents; // let me = &pde.manifest.id; // for (dependent_id, dependency_info) in &dependents.0 { diff --git a/backend/src/init.rs b/backend/src/init.rs index e09ccfad1..e2842772d 100644 --- a/backend/src/init.rs +++ b/backend/src/init.rs @@ -361,7 +361,7 @@ pub async fn init(cfg: &RpcContextConfig) -> Result { }) .await?; - crate::version::init(&mut db, &secret_store).await?; + crate::version::init(&db, &secret_store).await?; if should_rebuild { match tokio::fs::remove_file(SYSTEM_REBUILD_PATH).await { diff --git a/backend/src/install/mod.rs b/backend/src/install/mod.rs index df399e1c8..c5de9a86e 100644 --- a/backend/src/install/mod.rs +++ b/backend/src/install/mod.rs @@ -42,6 +42,7 @@ use crate::install::cleanup::cleanup; use crate::install::progress::{InstallProgress, InstallProgressTracker}; use crate::marketplace::with_query_params; use crate::notifications::NotificationLevel; +use crate::prelude::*; use crate::s9pk::manifest::{Manifest, PackageId}; use crate::s9pk::reader::S9pkReader; use crate::status::{DependencyConfigErrors, MainStatus, Status}; @@ -61,23 +62,23 @@ pub const PKG_WASM_DIR: &str = "package-data/wasm"; #[command(display(display_serializable))] pub async fn list(#[context] ctx: RpcContext) -> Result { - Ok(ctx.db.peek().await?.as_package_data().as_entries() + Ok(ctx.db.peek().await?.as_package_data().as_entries()? .iter() .filter_map(|(id, pde)| { - serde_json::to_value(match pde { - PackageDataEntry::Installed(PackageDataEntryInstalled { installed, .. }) => { + serde_json::to_value(match pde.as_match() { + PackageDataEntryMatchModelRef::Installed(PackageDataEntryInstalled { installed, .. }) => { json!({ "status":"installed","id": id.clone(), "version": installed.manifest.version.clone()}) } - PackageDataEntry::Installing(PackageDataEntryInstalling { manifest, install_progress, .. }) => { + PackageDataEntryMatchModelRef::Installing(PackageDataEntryInstalling { manifest, install_progress, .. }) => { json!({ "status":"installing","id": id.clone(), "version": manifest.version.clone(), "progress": install_progress.clone()}) } - PackageDataEntry::Updating(PackageDataEntryUpdating { manifest, installed, install_progress, .. }) => { + PackageDataEntryMatchModelRef::Updating(PackageDataEntryUpdating { manifest, installed, install_progress, .. }) => { json!({ "status":"updating","id": id.clone(), "version": installed.manifest.version.clone(), "progress": install_progress.clone()}) } - PackageDataEntry::Restoring(PackageDataEntryRestoring { manifest, install_progress, .. }) => { + PackageDataEntryMatchModelRef::Restoring(PackageDataEntryRestoring { manifest, install_progress, .. }) => { json!({ "status":"restoring","id": id.clone(), "version": manifest.version.clone(), "progress": install_progress.clone()}) } - PackageDataEntry::Removing(PackageDataEntryRemoving { manifest, .. }) => { + PackageDataEntryMatchModelRef::Removing(PackageDataEntryRemoving { manifest, .. }) => { json!({ "status":"removing", "id": id.clone(), "version": manifest.version.clone()}) } }) diff --git a/backend/src/notifications.rs b/backend/src/notifications.rs index 40699e0f1..91df0ec43 100644 --- a/backend/src/notifications.rs +++ b/backend/src/notifications.rs @@ -30,7 +30,6 @@ pub async fn list( #[arg] limit: Option, ) -> Result, Error> { let limit = limit.unwrap_or(40); - let peek = ctx.db.peek().await?; match before { None => { let records = sqlx::query!( @@ -70,8 +69,8 @@ pub async fn list( ctx.db .mutate(|d| { d.as_server_info_mut() - .as_unread_notification_count() - .ser(&0)? + .as_unread_notification_count_mut() + .ser(&0) }) .await?; Ok(notifs) @@ -141,15 +140,7 @@ pub async fn create( #[arg] message: String, ) -> Result<(), Error> { ctx.notification_manager - .notify( - &mut ctx.db.handle(), - package, - level, - title, - message, - (), - None, - ) + .notify(ctx.db.clone(), package, level, title, message, (), None) .await } @@ -267,9 +258,13 @@ impl NotificationManager { message, sql_data ).execute(&self.sqlite).await?; - *count += 1; - count.save(db).await?; - Ok(()) + count += 1; + db.mutate(|db| { + db.as_server_info_mut() + .as_unread_notification_count_mut() + .ser(&count) + }) + .await } async fn should_notify( &self, From da261390446ccdafdf937a3c0911cb56a0d8f612 Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Fri, 1 Sep 2023 16:39:08 -0600 Subject: [PATCH 43/89] chore: add to the prelude --- backend/src/prelude.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/prelude.rs b/backend/src/prelude.rs index 83d821c31..ab5de1d38 100644 --- a/backend/src/prelude.rs +++ b/backend/src/prelude.rs @@ -1,4 +1,5 @@ pub use color_eyre::eyre::eyre; +pub use models::OptionExt; pub use crate::db::prelude::*; pub use crate::ensure_code; From 5248743b53d088339a9a972cd7bb38c28dd89ec1 Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Fri, 1 Sep 2023 17:02:35 -0600 Subject: [PATCH 44/89] chore: Small fixes --- backend/Cargo.lock | 1 + backend/Cargo.toml | 1 + backend/src/auth.rs | 7 +++--- backend/src/db/model.rs | 3 +-- backend/src/net/dhcp.rs | 3 +-- backend/src/net/wifi.rs | 41 +++++++++------------------------ backend/src/update/mod.rs | 23 +++++++++--------- backend/src/version/mod.rs | 39 ++++++++----------------------- backend/src/version/v0_3_4.rs | 11 +++++---- backend/src/version/v0_3_4_4.rs | 4 ++-- 10 files changed, 47 insertions(+), 86 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index c466b9e5d..54efbe954 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -4562,6 +4562,7 @@ dependencies = [ "hyper", "hyper-ws-listener", "imbl", + "imbl-value", "include_dir", "indexmap 1.9.3", "ipnet", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index caf0a7094..b7b9c4b8e 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -78,6 +78,7 @@ http = "0.2.8" hyper = { version = "0.14.20", features = ["full"] } hyper-ws-listener = "0.2.0" imbl = "2.0.0" +imbl-value = { git = "https://github.com/Start9Labs/imbl-value.git" } include_dir = "0.7.3" indexmap = { version = "1.9.1", features = ["serde"] } ipnet = { version = "2.7.1", features = ["serde"] } diff --git a/backend/src/auth.rs b/backend/src/auth.rs index 2819fe77e..132c80ef8 100644 --- a/backend/src/auth.rs +++ b/backend/src/auth.rs @@ -368,15 +368,14 @@ pub async fn reset_password( } account.set_password(&new_password)?; account.save(&ctx.secret_store).await?; + let account_password = &account.password; ctx.db .mutate(|d| { d.as_server_info_mut() .as_password_hash_mut() - .ser(&account.password) + .ser(account_password) }) - .await?; - - Ok(()) + .await } #[command( diff --git a/backend/src/db/model.rs b/backend/src/db/model.rs index 9e4c738a8..f3e9ecbe5 100644 --- a/backend/src/db/model.rs +++ b/backend/src/db/model.rs @@ -7,7 +7,7 @@ use emver::VersionRange; use ipnet::{Ipv4Net, Ipv6Net}; use isocountry::CountryCode; use itertools::Itertools; -use models::{AddressId, DataUrl, HealthCheckId, InterfaceId}; +use models::{DataUrl, HealthCheckId, InterfaceId}; use openssl::hash::MessageDigest; use patch_db::{HasModel, Value}; use reqwest::Url; @@ -15,7 +15,6 @@ use serde::{Deserialize, Serialize}; use ssh_key::public::Ed25519PublicKey; use crate::account::AccountInfo; -use crate::action::Actions; use crate::install::progress::InstallProgress; use crate::net::utils::{get_iface_ipv4_addr, get_iface_ipv6_addr}; use crate::prelude::*; diff --git a/backend/src/net/dhcp.rs b/backend/src/net/dhcp.rs index c58b898bb..cbe7ff19d 100644 --- a/backend/src/net/dhcp.rs +++ b/backend/src/net/dhcp.rs @@ -63,8 +63,7 @@ pub async fn update(#[context] ctx: RpcContext, #[arg] interface: String) -> Res .mutate(|db| { db.as_server_info_mut() .as_ip_info_mut() - .as_idx_model_mut(&interface) - .ser(&ip_info) + .insert(&interface, &ip_info) }) .await?; diff --git a/backend/src/net/wifi.rs b/backend/src/net/wifi.rs index 1fdbc7315..d0d0dc6c0 100644 --- a/backend/src/net/wifi.rs +++ b/backend/src/net/wifi.rs @@ -52,8 +52,6 @@ pub async fn add( #[context] ctx: RpcContext, #[arg] ssid: String, #[arg] password: String, - #[arg] priority: isize, - #[arg] connect: bool, ) -> Result<(), Error> { let wifi_manager = wifi_manager(&ctx)?; if !ssid.is_ascii() { @@ -73,22 +71,18 @@ pub async fn add( wifi_manager: WifiManager, ssid: &Ssid, password: &Psk, - priority: isize, ) -> Result<(), Error> { tracing::info!("Adding new WiFi network: '{}'", ssid.0); let mut wpa_supplicant = wifi_manager.write().await; - wpa_supplicant - .add_network(db, ssid, password, priority) - .await?; + wpa_supplicant.add_network(db, ssid, password).await?; drop(wpa_supplicant); Ok(()) } if let Err(err) = add_procedure( - ctx.db, + ctx.db.clone(), wifi_manager.clone(), &Ssid(ssid.clone()), &Psk(password.clone()), - priority, ) .await { @@ -113,7 +107,7 @@ pub async fn connect(#[context] ctx: RpcContext, #[arg] ssid: String) -> Result< )); } async fn connect_procedure( - mut db: PatchDb, + db: PatchDb, wifi_manager: WifiManager, ssid: &Ssid, ) -> Result<(), Error> { @@ -121,7 +115,7 @@ pub async fn connect(#[context] ctx: RpcContext, #[arg] ssid: String) -> Result< let current = wpa_supplicant.get_current_network().await?; drop(wpa_supplicant); let mut wpa_supplicant = wifi_manager.write().await; - let connected = wpa_supplicant.select_network(&mut db, ssid).await?; + let connected = wpa_supplicant.select_network(db.clone(), ssid).await?; if connected { tracing::info!("Successfully connected to WiFi: '{}'", ssid.0); } else { @@ -131,19 +125,15 @@ pub async fn connect(#[context] ctx: RpcContext, #[arg] ssid: String) -> Result< tracing::info!("No WiFi to revert to!"); } Some(current) => { - wpa_supplicant.select_network(&mut db, ¤t).await?; + wpa_supplicant.select_network(db, ¤t).await?; } } } Ok(()) } - if let Err(err) = connect_procedure( - &mut ctx.db.handle(), - wifi_manager.clone(), - &Ssid(ssid.clone()), - ) - .await + if let Err(err) = + connect_procedure(ctx.db.clone(), wifi_manager.clone(), &Ssid(ssid.clone())).await { tracing::error!("Failed to connect to WiFi network '{}': {}", &ssid, err); return Err(Error::new( @@ -176,9 +166,7 @@ pub async fn delete(#[context] ctx: RpcContext, #[arg] ssid: String) -> Result<( return Err(Error::new(color_eyre::eyre::eyre!("Forbidden: Deleting this network would make your server unreachable. Either connect to ethernet or connect to a different WiFi network to remedy this."), ErrorKind::Wifi)); } - wpa_supplicant - .remove_network(&mut ctx.db.handle(), &ssid) - .await?; + wpa_supplicant.remove_network(ctx.db.clone(), &ssid).await?; Ok(()) } #[derive(serde::Serialize, serde::Deserialize)] @@ -397,7 +385,7 @@ pub async fn set_country( } wpa_supplicant.remove_all_connections().await?; - wpa_supplicant.save_config(&mut ctx.db.handle()).await?; + wpa_supplicant.save_config(ctx.db.clone()).await?; Ok(()) } @@ -645,7 +633,7 @@ impl WpaCli { Ok(()) } - pub async fn save_config(&mut self, mut db: PatchDb) -> Result<(), Error> { + pub async fn save_config(&mut self, db: PatchDb) -> Result<(), Error> { let new_country = Some(self.get_country_low().await?); db.mutate(|d| { d.as_server_info_mut() @@ -752,20 +740,13 @@ impl WpaCli { db: PatchDb, ssid: &Ssid, psk: &Psk, - priority: isize, ) -> Result<(), Error> { self.set_add_network_low(ssid, psk).await?; self.save_config(db).await?; Ok(()) } #[instrument(skip_all)] - pub async fn add_network( - &mut self, - db: PatchDb, - ssid: &Ssid, - psk: &Psk, - priority: isize, - ) -> Result<(), Error> { + pub async fn add_network(&mut self, db: PatchDb, ssid: &Ssid, psk: &Psk) -> Result<(), Error> { self.add_network_low(ssid, psk).await?; self.save_config(db).await?; Ok(()) diff --git a/backend/src/update/mod.rs b/backend/src/update/mod.rs index 15c5fd051..1bb102f03 100644 --- a/backend/src/update/mod.rs +++ b/backend/src/update/mod.rs @@ -1,13 +1,11 @@ use std::path::PathBuf; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; use clap::ArgMatches; use color_eyre::eyre::{eyre, Result}; use emver::Version; use helpers::{Rsync, RsyncOptions}; use lazy_static::lazy_static; -use patch_db::Revision; use reqwest::Url; use rpc_toolkit::command; use tokio::process::Command; @@ -77,7 +75,7 @@ fn display_update_result(status: UpdateResult, _: &ArgMatches) { } #[instrument(skip_all)] -async fn maybe_do_update(ctx: RpcContext, marketplace_url: Url) -> Result<(), Error> { +async fn maybe_do_update(ctx: RpcContext, marketplace_url: Url) -> Result, Error> { let peeked = ctx.db.peek().await?; let latest_version: Version = ctx .client @@ -93,10 +91,9 @@ async fn maybe_do_update(ctx: RpcContext, marketplace_url: Url) -> Result<(), Er .with_kind(ErrorKind::Network)? .version; let current_version = peeked.as_server_info().as_version().de()?; - if latest_version < current_version { + if latest_version < *current_version { return Ok(None); } - let status = peeked.as_status_info().as_server_status().de()?; let eos_url = EosUrl { base: marketplace_url, @@ -105,7 +102,7 @@ async fn maybe_do_update(ctx: RpcContext, marketplace_url: Url) -> Result<(), Er let status = ctx .db .mutate(|db| { - let mut status = peeked.as_status_info().as_server_status().de()?; + let mut status = peeked.as_server_info().as_status_info().de()?; if status.update_progress.is_some() { return Err(Error::new( eyre!("Server is already updating!"), @@ -117,7 +114,8 @@ async fn maybe_do_update(ctx: RpcContext, marketplace_url: Url) -> Result<(), Er size: None, downloaded: 0, }); - db.as_status_info_mut().as_server_status_mut().ser(&status) + db.as_server_info_mut().as_status_info_mut().ser(&status)?; + Ok(status) }) .await?; @@ -153,7 +151,7 @@ async fn maybe_do_update(ctx: RpcContext, marketplace_url: Url) -> Result<(), Er Err(e) => { ctx.notification_manager .notify( - ctx.db, + ctx.db.clone(), None, NotificationLevel::Error, "embassyOS Update Failed".to_owned(), @@ -182,8 +180,9 @@ async fn maybe_do_update(ctx: RpcContext, marketplace_url: Url) -> Result<(), Er .expect("could not play song: update failed 4"); } } + Ok::<(), Error>(()) }); - Ok(()) + Ok(Some(())) } #[instrument(skip_all)] @@ -197,13 +196,13 @@ async fn do_update(ctx: RpcContext, eos_url: EosUrl) -> Result<(), Error> { while let Some(progress) = rsync.progress.next().await { ctx.db .mutate(|db| { - db.as_server_status_mut() + db.as_server_info_mut() .as_status_info_mut() .as_update_progress_mut() - .ser(&UpdateProgress { + .ser(&Some(UpdateProgress { size: Some(100), downloaded: (100.0 * progress) as u64, - }) + })) }) .await?; } diff --git a/backend/src/version/mod.rs b/backend/src/version/mod.rs index b3e7e28f5..4cc68a4a0 100644 --- a/backend/src/version/mod.rs +++ b/backend/src/version/mod.rs @@ -39,16 +39,6 @@ impl Version { #[cfg(test)] fn as_sem_ver(&self) -> emver::Version { match self { - Version::V0_3_0(Wrapper(x)) => x.semver(), - Version::V0_3_0_1(Wrapper(x)) => x.semver(), - Version::V0_3_0_2(Wrapper(x)) => x.semver(), - Version::V0_3_0_3(Wrapper(x)) => x.semver(), - Version::V0_3_1(Wrapper(x)) => x.semver(), - Version::V0_3_1_1(Wrapper(x)) => x.semver(), - Version::V0_3_1_2(Wrapper(x)) => x.semver(), - Version::V0_3_2(Wrapper(x)) => x.semver(), - Version::V0_3_2_1(Wrapper(x)) => x.semver(), - Version::V0_3_3(Wrapper(x)) => x.semver(), Version::V0_3_4(Wrapper(x)) => x.semver(), Version::V0_3_4_1(Wrapper(x)) => x.semver(), Version::V0_3_4_2(Wrapper(x)) => x.semver(), @@ -72,10 +62,10 @@ where async fn down(&self, db: PatchDb, secrets: &PgPool) -> Result<(), Error>; async fn commit(&self, db: PatchDb) -> Result<(), Error> { db.mutate(|d| { - db.as_server_info_mut() - .as_server_version_mut() + d.as_server_info_mut() + .as_version_mut() .ser(&self.semver().into())?; - db.as_server_info_mut() + d.as_server_info_mut() .as_eos_version_compat() .ser(&self.compat().clone())?; Ok(()) @@ -170,23 +160,14 @@ where } pub async fn init(db: &PatchDb, secrets: &PgPool) -> Result<(), Error> { - let version = Version::from_util_version(db.peek().await?.as_server_version().de()); + let version = Version::from_util_version(db.peek().await?.as_server_info().as_version().de()?); + match version { - Version::V0_3_0(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, - Version::V0_3_0_1(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, - Version::V0_3_0_2(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, - Version::V0_3_0_3(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, - Version::V0_3_1(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, - Version::V0_3_1_1(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, - Version::V0_3_1_2(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, - Version::V0_3_2(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, - Version::V0_3_2_1(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, - Version::V0_3_3(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, - Version::V0_3_4(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, - Version::V0_3_4_1(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, - Version::V0_3_4_2(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, - Version::V0_3_4_3(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, - Version::V0_3_4_4(v) => v.0.migrate_to(&Current::new(), db, secrets).await?, + Version::V0_3_4(v) => v.0.migrate_to(&Current::new(), db.clone(), secrets).await?, + Version::V0_3_4_1(v) => v.0.migrate_to(&Current::new(), db.clone(), secrets).await?, + Version::V0_3_4_2(v) => v.0.migrate_to(&Current::new(), db.clone(), secrets).await?, + Version::V0_3_4_3(v) => v.0.migrate_to(&Current::new(), db.clone(), secrets).await?, + Version::V0_3_4_4(v) => v.0.migrate_to(&Current::new(), db.clone(), secrets).await?, Version::Other(_) => { return Err(Error::new( eyre!("Cannot downgrade"), diff --git a/backend/src/version/v0_3_4.rs b/backend/src/version/v0_3_4.rs index bb1d14cb0..c1554e33a 100644 --- a/backend/src/version/v0_3_4.rs +++ b/backend/src/version/v0_3_4.rs @@ -56,7 +56,7 @@ impl VersionT for Version { let mut account = AccountInfo::load(secrets).await?; let account = db .mutate(|d| { - d.as_server_info_mut().as_pub_key_mut().ser( + d.as_server_info_mut().as_pubkey_mut().ser( &ssh_key::PublicKey::from(Ed25519PublicKey::from(&account.key.ssh_key())) .to_openssh()?, ); @@ -83,6 +83,7 @@ impl VersionT for Version { let parsed_url = Some(COMMUNITY_URL.parse().unwrap()); db.mutate(|d| { let mut ui = d.as_ui().de()?; + use imbl_value::json; ui["marketplace"]["known-hosts"][COMMUNITY_URL] = json!({}); ui["marketplace"]["known-hosts"][MAIN_REGISTRY] = json!({}); for package_id in d.as_package_data().keys()? { @@ -90,7 +91,7 @@ impl VersionT for Version { continue; } d.as_package_data_mut() - .as_idx_model_mut(&package_id) + .as_idx_mut(&package_id) .or_not_found(&package_id)? .as_installed_mut() .or_not_found(&package_id)? @@ -108,12 +109,12 @@ impl VersionT for Version { db.mutate(|d| { let mut ui = d.as_ui().de()?; let parsed_url = Some(MAIN_REGISTRY.parse().unwrap()); - for package_id in db.as_package_data().keys()? { + for package_id in d.as_package_data().keys()? { if !COMMUNITY_SERVICES.contains(&&*package_id.to_string()) { continue; } d.as_package_data_mut() - .as_idx_model_mut(&package_id) + .as_idx_mut(&package_id) .or_not_found(&package_id)? .as_installed_mut() .or_not_found(&package_id)? @@ -121,7 +122,7 @@ impl VersionT for Version { .ser(&parsed_url)?; } - if let Value::Object(ref mut obj) = *ui { + if let imbl_value::Value::Object(ref mut obj) = ui { obj.remove("theme"); obj.remove("widgets"); } diff --git a/backend/src/version/v0_3_4_4.rs b/backend/src/version/v0_3_4_4.rs index dce555f44..95b9a8360 100644 --- a/backend/src/version/v0_3_4_4.rs +++ b/backend/src/version/v0_3_4_4.rs @@ -28,12 +28,12 @@ impl VersionT for Version { let mut tor_addr = db .mutate(|v| { let mut tor_address_lens = v.as_server_info_mut().as_tor_address_mut(); - let mut tor_addr = tor_address_lens.de(); + let mut tor_addr = tor_address_lens.de()?; tor_addr .set_scheme("https") .map_err(|_| eyre!("unable to update url scheme to https")) .with_kind(crate::ErrorKind::ParseUrl)?; - tor_address_lens.ser(tor_addr); + tor_address_lens.ser(&tor_addr) }) .await?; Ok(()) From 148c9032555d8b0519b8d9f9446cb51a8774372c Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Fri, 1 Sep 2023 17:57:52 -0600 Subject: [PATCH 45/89] chore: Fixing the small errors --- backend/src/manager/health.rs | 20 +++----- backend/src/manager/manager_container.rs | 32 ++++++------ backend/src/manager/mod.rs | 63 ++++++++---------------- 3 files changed, 42 insertions(+), 73 deletions(-) diff --git a/backend/src/manager/health.rs b/backend/src/manager/health.rs index 59a1eac86..17e968f21 100644 --- a/backend/src/manager/health.rs +++ b/backend/src/manager/health.rs @@ -11,10 +11,8 @@ use crate::Error; #[instrument(skip_all)] pub async fn check(ctx: &RpcContext, id: &PackageId) -> Result<(), Error> { let (manifest, started) = { - let pde = ctx - .db - .peek() - .await? + let peeked = ctx.db.peek().await?; + let pde = peeked .as_package_data() .as_idx(id) .or_not_found(id)? @@ -37,15 +35,14 @@ pub async fn check(ctx: &RpcContext, id: &PackageId) -> Result<(), Error> { return Ok(()); }; - let current_dependents = ctx - .db + ctx.db .mutate(|v| { - let mut pde = v + let pde = v .as_package_data_mut() .as_idx_mut(id) .or_not_found(id)? .expect_as_installed_mut()?; - let mut status = pde.as_installed_mut().as_status_mut().as_main_mut(); + let status = pde.as_installed_mut().as_status_mut().as_main_mut(); if let MainStatus::Running { health: _, started } = status.de()? { status.ser(&MainStatus::Running { @@ -53,10 +50,7 @@ pub async fn check(ctx: &RpcContext, id: &PackageId) -> Result<(), Error> { started, })?; } - - pde.as_installed().as_current_dependents().de() + Ok(()) }) - .await?; - - Ok(()) + .await } diff --git a/backend/src/manager/manager_container.rs b/backend/src/manager/manager_container.rs index ca66076c3..e6b216890 100644 --- a/backend/src/manager/manager_container.rs +++ b/backend/src/manager/manager_container.rs @@ -76,11 +76,14 @@ impl ManageContainer { } /// Set the override, but don't have a guard to revert it. Used only on the mananger to do a shutdown. - pub(super) fn lock_state_forever(&self, seed: &manager_seed::ManagerSeed) { - let mut db = seed.ctx.db.handle(); - let current_state = get_status(&mut db, &seed.manifest); + pub(super) async fn lock_state_forever( + &self, + seed: &manager_seed::ManagerSeed, + ) -> Result<(), Error> { + let current_state = get_status(seed.ctx.db.peek().await?, &seed.manifest); self.override_main_status .send_modify(|x| *x = Some(current_state)); + Ok(()) } /// We want to set the state of the service, like to start or stop @@ -172,12 +175,11 @@ async fn save_state( let current: StartStop = *current_state_receiver.borrow(); let desired: StartStop = *desired_state_receiver.borrow(); let override_status = override_main_status_receiver.borrow().clone(); - let mut db = seed.ctx.db.handle(); let res = match (override_status, current, desired) { - (Some(status), _, _) => set_status(&mut db, &seed.manifest, &status).await, + (Some(status), _, _) => set_status(seed.ctx.db.clone(), &seed.manifest, &status).await, (None, StartStop::Start, StartStop::Start) => { set_status( - &mut db, + seed.ctx.db.clone(), &seed.manifest, &MainStatus::Running { started: chrono::Utc::now(), @@ -187,13 +189,13 @@ async fn save_state( .await } (None, StartStop::Start, StartStop::Stop) => { - set_status(&mut db, &seed.manifest, &MainStatus::Stopping).await + set_status(seed.ctx.db.clone(), &seed.manifest, &MainStatus::Stopping).await } (None, StartStop::Stop, StartStop::Start) => { - set_status(&mut db, &seed.manifest, &MainStatus::Starting).await + set_status(seed.ctx.db.clone(), &seed.manifest, &MainStatus::Starting).await } (None, StartStop::Stop, StartStop::Stop) => { - set_status(&mut db, &seed.manifest, &MainStatus::Stopped).await + set_status(seed.ctx.db.clone(), &seed.manifest, &MainStatus::Stopped).await } }; if let Err(err) = res { @@ -241,10 +243,10 @@ async fn run_main_log_result(result: RunMainResult, seed: Arc (), // restart Ok(Err(e)) => { + // TODO @dr-bonez Do we do unstable anymore #[cfg(feature = "unstable")] { use crate::notifications::NotificationLevel; - let mut db = seed.ctx.db.handle(); let started = crate::db::DatabaseModel::new() .package_data() .idx_model(&seed.manifest.id) @@ -295,12 +297,8 @@ async fn run_main_log_result(result: RunMainResult, seed: Arc MainStatus { db.as_package_data() .as_idx(&manifest.id) - .or_not_found(&manifest.id)? - .as_installed() - .or_not_found(&manifest.id)? - .as_status() - .as_main() - .de() + .and_then(|x| x.as_installed()) + .and_then(|x| x.as_status().as_main().de().ok()) .unwrap_or_else(|_| MainStatus::Stopped) } @@ -319,7 +317,7 @@ async fn set_status( else { return Ok(()); }; - installed.as_status_mut().as_main_main().ser(main_status) + installed.as_status_mut().as_main_mut().ser(main_status) }) .await } diff --git a/backend/src/manager/mod.rs b/backend/src/manager/mod.rs index 5a5cdb9b4..3b4e107a9 100644 --- a/backend/src/manager/mod.rs +++ b/backend/src/manager/mod.rs @@ -30,17 +30,15 @@ use crate::config::spec::ValueSpecPointer; use crate::config::ConfigureContext; use crate::context::RpcContext; use crate::db::model::{CurrentDependencies, CurrentDependencyInfo}; -use crate::dependencies::add_dependent_to_current_dependents_lists; use crate::disk::mount::backup::BackupMountGuard; use crate::disk::mount::guard::TmpMountGuard; -use crate::install::cleanup::remove_from_current_dependents_lists; use crate::net::net_controller::NetService; use crate::net::vhost::AlpnInfo; use crate::prelude::*; use crate::procedure::docker::{DockerContainer, DockerProcedure, LongRunning}; use crate::procedure::{NoOutput, ProcedureName}; use crate::s9pk::manifest::Manifest; -use crate::status::{DependencyConfigErrors, MainStatus}; +use crate::status::MainStatus; use crate::util::docker::{get_container_ip, kill_container}; use crate::util::NonDetachingJoinHandle; use crate::volume::Volume; @@ -262,10 +260,10 @@ impl Manager { let manage_container = self.manage_container.clone(); let seed = self.seed.clone(); async move { + let peek = seed.ctx.db.peek().await?; let state_reverter = DesiredStateReverter::new(manage_container.clone()); - let mut tx = seed.ctx.db.handle(); - let override_guard = manage_container - .set_override(Some(get_status(&mut tx, &seed.manifest).backing_up())); + let override_guard = + manage_container.set_override(Some(get_status(peek, &seed.manifest).backing_up())); manage_container.wait_for_desired(StartStop::Stop).await; let backup_guard = backup_guard.lock().await; let guard = backup_guard.mount_package_backup(&seed.manifest.id).await?; @@ -381,7 +379,7 @@ async fn configure( .or_not_found(id)? .as_installed() .or_not_found(id)? - .as_dependencies() + .as_current_dependencies() .de()?; sys.truncate(0); let mut current_dependencies: CurrentDependencies = CurrentDependencies( @@ -436,8 +434,7 @@ async fn configure( .or_not_found(id)? .as_manifest() .as_version() - .de()? - .or_not_found(id)?; + .de()?; let volumes = db .as_package_data() .as_idx(id) @@ -479,7 +476,6 @@ async fn configure( .collect() }); } - .await?; // remove previous let mut dependency_config_errs = BTreeMap::new(); for (dependency, _dep_info) in current_dependencies @@ -487,16 +483,6 @@ async fn configure( .iter() .filter(|(dep_id, _)| dep_id != &id) { - let dependency_container = db - .as_package_data() - .as_idx(dependency) - .or_not_found(dependency)? - .as_installed() - .or_not_found(dependency)? - .as_manifest() - .as_containers() - .de()?; - let dependency_container = &dependency_container; // check if config passes dependency check if let Some(cfg) = db .as_package_data() @@ -507,7 +493,8 @@ async fn configure( .as_manifest() .as_dependencies() .as_idx(id) - .config() + .or_not_found(id)? + .as_config() .de()? { let manifest = db @@ -521,7 +508,6 @@ async fn configure( if let Err(error) = cfg .check( ctx, - dependency_container, dependency, &manifest.version, &manifest.volumes, @@ -551,16 +537,6 @@ async fn configure( .as_current_dependents() .de()?; for (dependent, _dep_info) in dependents.0.iter().filter(|(dep_id, _)| dep_id != &id) { - let dependent_container = db - .as_package_data() - .as_idx(dependent) - .or_not_found(dependent)? - .as_installed() - .or_not_found(dependent)? - .as_manifest() - .as_containers() - .de()?; - let dependent_container = &dependent_container; // check if config passes dependent check if let Some(cfg) = db .as_package_data() @@ -571,7 +547,8 @@ async fn configure( .as_manifest() .as_dependencies() .as_idx(id) - .config() + .or_not_found(id)? + .as_config() .de()? { let manifest = db @@ -585,7 +562,6 @@ async fn configure( if let Err(error) = cfg .check( ctx, - dependent_container, dependent, &manifest.version, &manifest.volumes, @@ -605,17 +581,18 @@ async fn configure( remove_from_current_dependents_lists(db, id, &dependencies)?; add_dependent_to_current_dependents_lists(db, id, ¤t_dependencies)?; current_dependencies.0.remove(id); - for (dep, errs) in - db.as_package_data_mut() - .as_entries_mut() - .and_then(|(id, pde)| { - pde.as_installed_mut() - .map(|i| (id, i.as_status_mut().as_dependency_config_errors_mut())) - }) + for (dep, errs) in db + .as_package_data_mut() + .as_entries_mut()? + .into_iter() + .filter_map(|(id, pde)| { + pde.as_installed_mut() + .map(|i| (id, i.as_status_mut().as_dependency_config_errors_mut())) + }) { errs.remove(id)?; - if let Some(err) = configure_context.breakages.get(dep) { - errs.insert(id, &err)?; + if let Some(err) = configure_context.breakages.get(&dep) { + errs.insert(id, err)?; } } let mut installed = db From e1875c62a742cb9b877a8b65b0b69c9c3894973b Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Wed, 6 Sep 2023 17:02:44 -0600 Subject: [PATCH 46/89] wip: Cleaning up check errors --- backend/src/db/prelude.rs | 1 + backend/src/install/cleanup.rs | 55 +++++++++++++----------- backend/src/install/progress.rs | 2 +- backend/src/setup.rs | 76 ++++++++++++++++++--------------- backend/src/volume.rs | 1 - 5 files changed, 75 insertions(+), 60 deletions(-) diff --git a/backend/src/db/prelude.rs b/backend/src/db/prelude.rs index a0b04d6bc..f6c9340d8 100644 --- a/backend/src/db/prelude.rs +++ b/backend/src/db/prelude.rs @@ -247,6 +247,7 @@ where .into()), } } + pub fn into_entries(self) -> Result)>, Error> { use patch_db::ModelExt; use serde::de::Error; diff --git a/backend/src/install/cleanup.rs b/backend/src/install/cleanup.rs index b2044ae4b..3987734a5 100644 --- a/backend/src/install/cleanup.rs +++ b/backend/src/install/cleanup.rs @@ -9,7 +9,7 @@ use super::PKG_ARCHIVE_DIR; use crate::context::RpcContext; use crate::db::model::{ CurrentDependencies, Database, PackageDataEntry, PackageDataEntryInstalled, - PackageDataEntryMatchModelMut, PackageDataEntryMatchModelRef, + PackageDataEntryMatchModelRef, }; use crate::dependencies::reconfigure_dependents_with_live_pointers; use crate::error::ErrorCollection; @@ -69,11 +69,13 @@ pub async fn cleanup_failed(ctx: &RpcContext, id: &PackageId) -> Result<(), Erro .or_not_found(id)? .as_match() { - PackageDataEntryMatchModelRef::Installing(m) => m.as_manifest().as_version().de()?, - PackageDataEntryMatchModelRef::Restoring(m) => m.as_manifest().as_version().de()?, + PackageDataEntryMatchModelRef::Installing(m) => Some(m.as_manifest().as_version().de()?), + PackageDataEntryMatchModelRef::Restoring(m) => Some(m.as_manifest().as_version().de()?), PackageDataEntryMatchModelRef::Updating(m) => { - if m.as_manifest().as_version() != m.as_installed().as_manifest().as_version() { - Some(m.as_manifest().as_version().de()?) + let manifest_version = m.as_manifest().as_version().de()?; + let installed = m.as_installed().as_manifest().as_version().de()?; + if manifest_version != installed { + Some(manifest_version) } else { None // do not remove existing data } @@ -93,20 +95,25 @@ pub async fn cleanup_failed(ctx: &RpcContext, id: &PackageId) -> Result<(), Erro .into_package_data() .into_idx(id) .or_not_found(id)? - .into_match() + .as_match() { - PackageDataEntryMatchModelMut::Installing(_) - | PackageDataEntryMatchModelMut::Restoring(_) => v.as_package_data_mut().remove(id), - PackageDataEntryMatchModelRef::Updating(pde) => v - .as_package_data_mut() - .as_idx_mut(id) - .or_not_found(id)? - .ser(&PackageDataEntry::Installed(PackageDataEntryInstalled { - manifest: pde.as_installed().as_manifest().de()?, - static_files: pde.as_static_files().de()?, - installed: pde.into_installed().de()?, - })), + PackageDataEntryMatchModelRef::Installing(_) + | PackageDataEntryMatchModelRef::Restoring(_) => { + v.as_package_data_mut().remove(id)?; + } + PackageDataEntryMatchModelRef::Updating(pde) => { + v.as_package_data_mut() + .as_idx_mut(id) + .or_not_found(id)? + .ser(&PackageDataEntry::Installed(PackageDataEntryInstalled { + manifest: pde.as_installed().as_manifest().de()?, + static_files: pde.as_static_files().de()?, + installed: pde.as_installed().de()?, + }))?; + } + _ => (), } + Ok(()) }) .await } @@ -145,19 +152,20 @@ where let dependents_paths: Vec = entry .as_removing() .as_current_dependents() - .0 - .keys() + .keys()? + .into_iter() .filter(|x| x != id) - .flat_map(|x| db.as_package_data().as_idx(x)) + .flat_map(|x| db.as_package_data().as_idx(&x)) .flat_map(|x| x.as_installed()) - .flat_map(|x| x.as_manifest().as_volumes().values()) + .flat_map(|x| x.as_manifest().as_volumes().de()) + .flat_map(|x| x.values().cloned().collect::>()) .flat_map(|x| x.pointer_path(&ctx.datadir)) .collect(); let volume_dir = ctx .datadir .join(crate::volume::PKG_VOLUME_DIR) - .join(&entry.manifest.id); + .join(&*entry.as_manifest().as_id().de()?); let version = entry.as_removing().as_manifest().as_version().de()?; tracing::debug!( "Cleaning up {:?} except for {:?}", @@ -186,8 +194,7 @@ pub async fn remove_tor_keys(secrets: &mut Ex, id: &PackageId) -> Result<(), where for<'a> &'a mut Ex: Executor<'a, Database = Postgres>, { - let id_str = id.as_str(); - sqlx::query!("DELETE FROM tor WHERE package = $1", id_str) + sqlx::query!("DELETE FROM tor WHERE package = $1", &*id) .execute(secrets) .await?; Ok(()) diff --git a/backend/src/install/progress.rs b/backend/src/install/progress.rs index a2e803200..69f04d21c 100644 --- a/backend/src/install/progress.rs +++ b/backend/src/install/progress.rs @@ -97,7 +97,7 @@ impl InstallProgress { T, >( self: &Arc, - db: &PatchDb, + db: PatchDb, id: &PackageId, f: F, ) -> Result { diff --git a/backend/src/setup.rs b/backend/src/setup.rs index 4e172a057..e789aba26 100644 --- a/backend/src/setup.rs +++ b/backend/src/setup.rs @@ -263,39 +263,47 @@ pub async fn execute( complete: false, })); drop(status); - tokio::task::spawn(async move { - match execute_inner( - ctx.clone(), - embassy_logicalname, - embassy_password, - recovery_source, - recovery_password, - ) - .await - { - Ok((guid, hostname, tor_addr, root_ca)) => { - tracing::info!("Setup Complete!"); - *ctx.setup_result.write().await = Some(( - guid, - SetupResult { - tor_address: format!("https://{}", tor_addr), - lan_address: hostname.lan_address(), - root_ca: String::from_utf8( - root_ca.to_pem().expect("failed to serialize root ca"), - ) - .expect("invalid pem string"), - }, - )); - *ctx.setup_status.write().await = Some(Ok(SetupStatus { - bytes_transferred: 0, - total_bytes: None, - complete: true, - })); - } - Err(e) => { - tracing::error!("Error Setting Up Server: {}", e); - tracing::debug!("{:?}", e); - *ctx.setup_status.write().await = Some(Err(e.into())); + tokio::task::spawn({ + async move { + let ctx = ctx.clone(); + let recovery_source = recovery_source; + + let embassy_password = embassy_password; + let recovery_source = recovery_source; + let recovery_password = recovery_password; + match execute_inner( + ctx.clone(), + embassy_logicalname, + embassy_password, + recovery_source, + recovery_password, + ) + .await + { + Ok((guid, hostname, tor_addr, root_ca)) => { + tracing::info!("Setup Complete!"); + *ctx.setup_result.write().await = Some(( + guid, + SetupResult { + tor_address: format!("https://{}", tor_addr), + lan_address: hostname.lan_address(), + root_ca: String::from_utf8( + root_ca.to_pem().expect("failed to serialize root ca"), + ) + .expect("invalid pem string"), + }, + )); + *ctx.setup_status.write().await = Some(Ok(SetupStatus { + bytes_transferred: 0, + total_bytes: None, + complete: true, + })); + } + Err(e) => { + tracing::error!("Error Setting Up Server: {}", e); + tracing::debug!("{:?}", e); + *ctx.setup_status.write().await = Some(Err(e.into())); + } } } }); @@ -394,7 +402,7 @@ async fn recover( ) -> Result<(Arc, Hostname, OnionAddressV3, X509), Error> { let recovery_source = TmpMountGuard::mount(&recovery_source, ReadWrite).await?; recover_full_embassy( - ctx.clone(), + ctx, guid.clone(), embassy_password, recovery_source, diff --git a/backend/src/volume.rs b/backend/src/volume.rs index 030c7ed22..875e82096 100644 --- a/backend/src/volume.rs +++ b/backend/src/volume.rs @@ -4,7 +4,6 @@ use std::path::{Path, PathBuf}; pub use helpers::script_dir; pub use models::VolumeId; -use patch_db::HasModel; use serde::{Deserialize, Serialize}; use tracing::instrument; From 506878834aacf086366a5ca63d4e7ce7d30ddf02 Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Fri, 8 Sep 2023 11:02:05 -0600 Subject: [PATCH 47/89] wip: Fix some of the issues --- backend/src/backup/mod.rs | 67 ++-- backend/src/backup/restore.rs | 30 +- backend/src/config/mod.rs | 117 +++--- backend/src/context/rpc.rs | 30 +- backend/src/db/model.rs | 33 +- backend/src/dependencies.rs | 3 +- backend/src/install/mod.rs | 619 +++++++++++++++-------------- backend/src/install/progress.rs | 12 +- backend/src/manager/manager_map.rs | 8 +- backend/src/manager/mod.rs | 69 ++-- backend/src/marketplace.rs | 4 +- backend/src/net/interface.rs | 10 +- backend/src/net/keys.rs | 10 +- backend/src/status/mod.rs | 2 +- libs/models/src/data_url.rs | 2 +- 15 files changed, 517 insertions(+), 499 deletions(-) diff --git a/backend/src/backup/mod.rs b/backend/src/backup/mod.rs index 1585ff584..f63dbdb64 100644 --- a/backend/src/backup/mod.rs +++ b/backend/src/backup/mod.rs @@ -1,5 +1,8 @@ -use std::collections::{BTreeMap, BTreeSet}; use std::path::{Path, PathBuf}; +use std::{ + collections::{BTreeMap, BTreeSet}, + sync::Arc, +}; use chrono::{DateTime, Utc}; use color_eyre::eyre::eyre; @@ -14,9 +17,8 @@ use tracing::instrument; use self::target::PackageBackupInfo; use crate::context::RpcContext; -use crate::dependencies::reconfigure_dependents_with_live_pointers; use crate::install::PKG_ARCHIVE_DIR; -use crate::net::interface::{InterfaceId, Interfaces}; +use crate::net::interface::InterfaceId; use crate::net::keys::Key; use crate::prelude::*; use crate::procedure::docker::DockerContainers; @@ -26,6 +28,9 @@ use crate::util::serde::{Base32, Base64, IoFormat}; use crate::util::Version; use crate::version::{Current, VersionT}; use crate::volume::{backup_dir, Volume, VolumeId, Volumes, BACKUP_DIR}; +use crate::{ + dependencies::reconfigure_dependents_with_live_pointers, manager::manager_seed::ManagerSeed, +}; use crate::{Error, ErrorKind, ResultExt}; pub mod backup_bulk; @@ -94,18 +99,14 @@ impl BackupActions { } #[instrument(skip_all)] - pub async fn create( - &self, - ctx: &RpcContext, - pkg_id: &PackageId, - pkg_title: &str, - pkg_version: &Version, - interfaces: &Interfaces, - volumes: &Volumes, - ) -> Result { - let mut volumes = volumes.to_readonly(); + pub async fn create(&self, seed: Arc) -> Result { + let manifest = &seed.manifest; + let mut volumes = seed.manifest.volumes.to_readonly(); + let ctx = &seed.ctx; + let pkg_id = &manifest.id; + let pkg_version = &manifest.version; volumes.insert(VolumeId::Backup, Volume::Backup { readonly: false }); - let backup_dir = backup_dir(pkg_id); + let backup_dir = backup_dir(&manifest.id); if tokio::fs::metadata(&backup_dir).await.is_err() { tokio::fs::create_dir_all(&backup_dir).await? } @@ -122,26 +123,27 @@ impl BackupActions { .await? .map_err(|e| eyre!("{}", e.1)) .with_kind(crate::ErrorKind::Backup)?; - let (network_keys, tor_keys) = Key::for_package(&ctx.secret_store, pkg_id) - .await? - .into_iter() - .filter_map(|k| { - let interface = k.interface().map(|(_, i)| i)?; - Some(( - (interface.clone(), Base64(k.as_bytes())), - (interface, Base32(k.tor_key().as_bytes())), - )) - }) - .unzip(); + let (network_keys, tor_keys): (Vec<_>, Vec<_>) = + Key::for_package(&ctx.secret_store, pkg_id) + .await? + .into_iter() + .filter_map(|k| { + let interface = k.interface().map(|(_, i)| i)?; + Some(( + (interface.clone(), Base64(k.as_bytes())), + (interface, Base32(k.tor_key().as_bytes())), + )) + }) + .unzip(); let marketplace_url = ctx .db .peek() .await? .as_package_data() - .as_idx(pkg_id) + .as_idx(&pkg_id) .or_not_found(pkg_id)? .expect_as_installed()? - .as_installed_mut() + .as_installed() .as_marketplace_url() .de()?; let tmp_path = Path::new(BACKUP_DIR) @@ -171,6 +173,8 @@ impl BackupActions { let mut outfile = AtomicFile::new(&metadata_path, None::) .await .with_kind(ErrorKind::Filesystem)?; + let network_keys = network_keys.into_iter().collect(); + let tor_keys = tor_keys.into_iter().collect(); outfile .write_all(&IoFormat::Cbor.to_vec(&BackupMetadata { timestamp, @@ -182,7 +186,7 @@ impl BackupActions { outfile.save().await.with_kind(ErrorKind::Filesystem)?; Ok(PackageBackupInfo { os_version: Current::new().semver().into(), - title: pkg_title.to_owned(), + title: manifest.title.clone(), version: pkg_version.clone(), timestamp, }) @@ -194,7 +198,6 @@ impl BackupActions { ctx: &RpcContext, pkg_id: &PackageId, pkg_version: &Version, - interfaces: &Interfaces, volumes: &Volumes, ) -> Result<(), Error> { let mut volumes = volumes.clone(); @@ -227,9 +230,11 @@ impl BackupActions { v.as_package_data_mut() .as_idx_mut(pkg_id) .or_not_found(pkg_id)? - .expect_as_restoring_mut()? // TODO: Restoring? + .as_installed_mut() + .or_not_found(pkg_id)? .as_marketplace_url_mut() - .ser(metadata.marketplace_url)?; + .ser(&metadata.marketplace_url)?; + v.as_package_data() .as_idx(pkg_id) .or_not_found(pkg_id)? diff --git a/backend/src/backup/restore.rs b/backend/src/backup/restore.rs index dd3c4dd5e..9fd7ed116 100644 --- a/backend/src/backup/restore.rs +++ b/backend/src/backup/restore.rs @@ -11,7 +11,6 @@ use openssl::x509::X509; use rpc_toolkit::command; use sqlx::Connection; use tokio::fs::File; -use tokio::sync::watch; use torut::onion::OnionAddressV3; use tracing::instrument; @@ -68,7 +67,7 @@ pub async fn restore_packages_rpc( if let Err(err) = ctx .notification_manager .notify( - &ctx.db, + ctx.db.clone(), Some(package_id.clone()), NotificationLevel::Error, "Restoration Failure".to_string(), @@ -105,7 +104,7 @@ async fn approximate_progress( if tokio::fs::metadata(&dir).await.is_err() { *size = 0; } else { - *size = dir_size(&dir).await?; + *size = dir_size(&dir, None).await?; } } Ok(()) @@ -129,7 +128,7 @@ async fn approximate_progress_loop( #[derive(Debug, Default)] struct ProgressInfo { - package_installs: BTreeMap>, + package_installs: BTreeMap>, src_volume_size: BTreeMap, target_volume_size: BTreeMap, } @@ -205,7 +204,7 @@ pub async fn recover_full_embassy( let rpc_ctx = RpcContext::init(ctx.config_path.clone(), disk_guid.clone()).await?; - let ids = backup_guard + let ids: Vec<_> = backup_guard .metadata .package_backups .keys() @@ -222,7 +221,7 @@ pub async fn recover_full_embassy( (Ok(_), _) => (), (Err(err), package_id) => { if let Err(err) = ctx.notification_manager.notify( - &ctx.db, + ctx.db.clone(), Some(package_id.clone()), NotificationLevel::Error, "Restoration Failure".to_string(), format!("Error restoring package {}: {}", package_id,err), (), None).await{ @@ -272,10 +271,12 @@ async fn restore_packages( for (manifest, guard) in guards { let id = manifest.id.clone(); let (progress, task) = restore_package(ctx.clone(), manifest, guard).await?; - progress_info.package_installs.insert(id.clone(), progress); + progress_info + .package_installs + .insert(id.clone(), progress.clone()); progress_info .src_volume_size - .insert(id.clone(), dir_size(backup_dir(&id)).await?); + .insert(id.clone(), dir_size(backup_dir(&id), None).await?); progress_info.target_volume_size.insert(id.clone(), 0); let package_id = id.clone(); tasks.push( @@ -311,7 +312,7 @@ async fn assure_restoring( let manifest = rdr.manifest().await?; let version = manifest.version.clone(); - let progress = InstallProgress::new(Some(tokio::fs::metadata(&s9pk_path).await?.len())); + // let progress = InstallProgress::new(Some(tokio::fs::metadata(&s9pk_path).await?.len())); // ctx.db.apply_fn(|mut v| v.package_data().idx()) * model = // Some(PackageDataEntry::Restoring { @@ -373,8 +374,8 @@ async fn restore_package<'a>( let k = key.0.as_slice(); sqlx::query!( "INSERT INTO network_keys (package, interface, key) VALUES ($1, $2, $3) ON CONFLICT (package, interface) DO NOTHING", - id, - iface, + id.to_string(), + iface.to_string(), k, ) .execute(&mut secrets_tx).await?; @@ -384,8 +385,8 @@ async fn restore_package<'a>( let k = key.0.as_slice(); sqlx::query!( "INSERT INTO tor (package, interface, key) VALUES ($1, $2, $3) ON CONFLICT (package, interface) DO NOTHING", - id, - iface, + id.to_string(), + iface.to_string(), k, ) .execute(&mut secrets_tx).await?; @@ -404,10 +405,11 @@ async fn restore_package<'a>( let progress = InstallProgress::new(Some(len)); let marketplace_url = metadata.marketplace_url; + let progress = Arc::new(progress); Ok(( progress.clone(), async move { - download_install_s9pk(&ctx, &manifest, marketplace_url, progress, file).await?; + download_install_s9pk(ctx, manifest, marketplace_url, progress, file, None).await?; guard.unmount().await?; diff --git a/backend/src/config/mod.rs b/backend/src/config/mod.rs index 13d539a96..65837fdee 100644 --- a/backend/src/config/mod.rs +++ b/backend/src/config/mod.rs @@ -164,10 +164,8 @@ pub async fn get( #[arg(long = "format")] format: Option, ) -> Result { - let manifest = ctx - .db - .peek() - .await? + let db = ctx.db.peek().await?; + let manifest = db .as_package_data() .as_idx(&id) .or_not_found(&id)? @@ -264,7 +262,7 @@ pub async fn configure( .or_not_found(&id)?; let version = package.as_manifest().as_version().de()?; ctx.managers - .get(&(id, version)) + .get(&(id.clone(), version.clone())) .await .ok_or_else(|| { Error::new( @@ -290,58 +288,59 @@ pub(crate) use not_found; /// Found that earlier the paths where not what we expected them to be. #[tokio::test] async fn ensure_creation_of_config_paths_makes_sense() { - let mut fake = patch_db::test_utils::NoOpDb(); - let config_locks = ConfigReceipts::new(&mut fake).await.unwrap(); - assert_eq!( - &format!("{}", config_locks.configured.lock.glob), - "/package-data/*/installed/status/configured" - ); - assert_eq!( - &format!("{}", config_locks.config_actions.lock.glob), - "/package-data/*/installed/manifest/config" - ); - assert_eq!( - &format!("{}", config_locks.dependencies.lock.glob), - "/package-data/*/installed/manifest/dependencies" - ); - assert_eq!( - &format!("{}", config_locks.volumes.lock.glob), - "/package-data/*/installed/manifest/volumes" - ); - assert_eq!( - &format!("{}", config_locks.version.lock.glob), - "/package-data/*/installed/manifest/version" - ); - assert_eq!( - &format!("{}", config_locks.volumes.lock.glob), - "/package-data/*/installed/manifest/volumes" - ); - assert_eq!( - &format!("{}", config_locks.manifest.lock.glob), - "/package-data/*/installed/manifest" - ); - assert_eq!( - &format!("{}", config_locks.manifest.lock.glob), - "/package-data/*/installed/manifest" - ); - assert_eq!( - &format!("{}", config_locks.system_pointers.lock.glob), - "/package-data/*/installed/system-pointers" - ); - assert_eq!( - &format!("{}", config_locks.current_dependents.lock.glob), - "/package-data/*/installed/current-dependents" - ); - assert_eq!( - &format!("{}", config_locks.dependency_errors.lock.glob), - "/package-data/*/installed/status/dependency-errors" - ); - assert_eq!( - &format!("{}", config_locks.manifest_dependencies_config.lock.glob), - "/package-data/*/installed/manifest/dependencies/*/config" - ); - assert_eq!( - &format!("{}", config_locks.system_pointers.lock.glob), - "/package-data/*/installed/system-pointers" - ); + // TODO @dr-bonez + // let mut fake = patch_db::test_utils::NoOpDb(); + // let config_locks = ConfigReceipts::new(&mut fake).await.unwrap(); + // assert_eq!( + // &format!("{}", config_locks.configured.lock.glob), + // "/package-data/*/installed/status/configured" + // ); + // assert_eq!( + // &format!("{}", config_locks.config_actions.lock.glob), + // "/package-data/*/installed/manifest/config" + // ); + // assert_eq!( + // &format!("{}", config_locks.dependencies.lock.glob), + // "/package-data/*/installed/manifest/dependencies" + // ); + // assert_eq!( + // &format!("{}", config_locks.volumes.lock.glob), + // "/package-data/*/installed/manifest/volumes" + // ); + // assert_eq!( + // &format!("{}", config_locks.version.lock.glob), + // "/package-data/*/installed/manifest/version" + // ); + // assert_eq!( + // &format!("{}", config_locks.volumes.lock.glob), + // "/package-data/*/installed/manifest/volumes" + // ); + // assert_eq!( + // &format!("{}", config_locks.manifest.lock.glob), + // "/package-data/*/installed/manifest" + // ); + // assert_eq!( + // &format!("{}", config_locks.manifest.lock.glob), + // "/package-data/*/installed/manifest" + // ); + // assert_eq!( + // &format!("{}", config_locks.system_pointers.lock.glob), + // "/package-data/*/installed/system-pointers" + // ); + // assert_eq!( + // &format!("{}", config_locks.current_dependents.lock.glob), + // "/package-data/*/installed/current-dependents" + // ); + // assert_eq!( + // &format!("{}", config_locks.dependency_errors.lock.glob), + // "/package-data/*/installed/status/dependency-errors" + // ); + // assert_eq!( + // &format!("{}", config_locks.manifest_dependencies_config.lock.glob), + // "/package-data/*/installed/manifest/dependencies/*/config" + // ); + // assert_eq!( + // &format!("{}", config_locks.system_pointers.lock.glob), + // "/package-data/*/installed/system-pointers" + // ); } diff --git a/backend/src/context/rpc.rs b/backend/src/context/rpc.rs index 738babe4e..f99b52b4a 100644 --- a/backend/src/context/rpc.rs +++ b/backend/src/context/rpc.rs @@ -133,7 +133,7 @@ pub struct Hardware { pub struct RpcContext(Arc); impl RpcContext { #[instrument(skip_all)] - pub async fn init + Send + 'static>( + pub async fn init + Send + Sync + 'static>( cfg_path: Option

, disk_guid: Arc, ) -> Result { @@ -166,7 +166,7 @@ impl RpcContext { ); tracing::info!("Initialized Net Controller"); let managers = ManagerMap::default(); - let metrics_cache = RwLock::new(None); + let metrics_cache = RwLock::>::new(None); let notification_manager = NotificationManager::new(secret_store.clone()); tracing::info!("Initialized Notification Manager"); let tor_proxy_url = format!("socks5h://{tor_proxy}"); @@ -216,16 +216,10 @@ impl RpcContext { hardware: Hardware { devices, ram }, }); - let res = Self(seed); + let res = Self(seed.clone()); res.cleanup().await?; tracing::info!("Cleaned up transient states"); - res.managers - .init( - &res, - &mut res.db.handle(), - &mut res.secret_store.acquire().await?, - ) - .await?; + res.managers.init(res, &res.db.peek().await?).await?; tracing::info!("Initialized Package Managers"); Ok(res) } @@ -246,18 +240,20 @@ impl RpcContext { .mutate(|f| { let mut current_dependents = f .as_package_data() - .keys() + .keys()? + .into_iter() .map(|k| (k.clone(), BTreeMap::new())) .collect::>(); - for (package_id, package) in f.as_package_data() { + for (package_id, package) in f.as_package_data_mut().as_entries_mut()? { for (k, v) in package - .into_installed() + .as_installed_mut() .into_iter() - .flat_map(|i| i.current_dependencies.0) + .flat_map(|i| i.clone().into_current_dependencies().into_entries()) + .flatten() { let mut entry: BTreeMap<_, _> = current_dependents.remove(&k).unwrap_or_default(); - entry.insert(package_id.clone(), v); + entry.insert(package_id.clone(), v.de()?); current_dependents.insert(k, entry); } } @@ -283,6 +279,7 @@ impl RpcContext { .await?; let peek = self.db.peek().await?; for (package_id, package) in peek.as_package_data().as_entries()?.into_iter() { + let package = package.clone(); let action = match package.as_match() { PackageDataEntryMatchModelRef::Installing(_) | PackageDataEntryMatchModelRef::Restoring(_) @@ -294,7 +291,8 @@ impl RpcContext { } PackageDataEntryMatchModelRef::Installed(m) => { let version = m.as_manifest().as_version().clone().de()?; - for (volume_id, volume_info) in &*m.as_manifest().as_volumes().clone().de()? { + let volumes = m.as_manifest().as_volumes().de()?; + for (volume_id, volume_info) in &*volumes { let tmp_path = to_tmp_path(volume_info.path_for( &self.datadir, &package_id, diff --git a/backend/src/db/model.rs b/backend/src/db/model.rs index f3e9ecbe5..28695de68 100644 --- a/backend/src/db/model.rs +++ b/backend/src/db/model.rs @@ -214,7 +214,7 @@ impl StaticFiles { pub struct PackageDataEntryInstalling { pub static_files: StaticFiles, pub manifest: Manifest, - pub install_progress: InstallProgress, + pub install_progress: Arc, } #[derive(Debug, Deserialize, Serialize, HasModel)] @@ -224,7 +224,7 @@ pub struct PackageDataEntryUpdating { pub static_files: StaticFiles, pub manifest: Manifest, pub installed: InstalledPackageInfo, - pub install_progress: InstallProgress, + pub install_progress: Arc, } #[derive(Debug, Deserialize, Serialize, HasModel)] @@ -355,12 +355,12 @@ impl Model { } pub fn as_manifest(&self) -> &Model { match self.into_match() { - PackageDataEntryMatchModelRef::Installing(a) => a.as_manifest(), - PackageDataEntryMatchModelRef::Updating(a) => a.as_installed().as_manifest(), - PackageDataEntryMatchModelRef::Restoring(a) => a.as_manifest(), - PackageDataEntryMatchModelRef::Removing(a) => a.as_manifest(), - PackageDataEntryMatchModelRef::Installed(a) => a.as_manifest(), - PackageDataEntryMatchModelRef::Error(_) => (&Value::Null).into(), + PackageDataEntryMatchModel::Installing(a) => a.as_manifest(), + PackageDataEntryMatchModel::Updating(a) => a.as_installed().as_manifest(), + PackageDataEntryMatchModel::Restoring(a) => a.as_manifest(), + PackageDataEntryMatchModel::Removing(a) => a.as_manifest(), + PackageDataEntryMatchModel::Installed(a) => a.as_manifest(), + PackageDataEntryMatchModel::Error(_) => (&Value::Null).into(), } } pub fn into_installed(self) -> Option> { @@ -393,7 +393,7 @@ impl Model { PackageDataEntryMatchModel::Error(_) => None, } } - pub fn as_install_progress(&self) -> Option<&Model> { + pub fn as_install_progress(&self) -> Option<&Model>> { match self.into_match() { PackageDataEntryMatchModel::Installing(a) => Some(a.as_install_progress()), PackageDataEntryMatchModel::Updating(a) => Some(a.as_install_progress()), @@ -403,6 +403,16 @@ impl Model { PackageDataEntryMatchModel::Error(_) => None, } } + pub fn as_install_progress_mut(&mut self) -> Option<&Model>> { + match self.into_match() { + PackageDataEntryMatchModel::Installing(mut a) => Some(a.as_install_progress_mut()), + PackageDataEntryMatchModel::Updating(mut a) => Some(a.as_install_progress_mut()), + PackageDataEntryMatchModel::Restoring(_) => None, + PackageDataEntryMatchModel::Removing(_) => None, + PackageDataEntryMatchModel::Installed(_) => None, + PackageDataEntryMatchModel::Error(_) => None, + } + } } #[derive(Debug, Deserialize, Serialize, HasModel)] @@ -422,6 +432,11 @@ pub struct InstalledPackageInfo { pub interface_addresses: InterfaceAddressMap, } +impl Map for BTreeMap { + type Key = PackageId; + type Value = StaticDependencyInfo; +} + #[derive(Debug, Clone, Default, Deserialize, Serialize)] pub struct CurrentDependents(pub BTreeMap); impl CurrentDependents { diff --git a/backend/src/dependencies.rs b/backend/src/dependencies.rs index 0e7b6125d..6d9a4f417 100644 --- a/backend/src/dependencies.rs +++ b/backend/src/dependencies.rs @@ -3,7 +3,6 @@ use std::time::Duration; use color_eyre::eyre::eyre; use emver::VersionRange; -use futures::FutureExt; use models::OptionExt; use rand::SeedableRng; use rpc_toolkit::command; @@ -265,7 +264,7 @@ pub async fn reconfigure_dependents_with_live_pointers( ctx: &RpcContext, i: &InstalledPackageInfo, ) -> Result<(), Error> { - todo!(); + todo!("DR_BONEZ"); // TODO @dr-bonez // let dependents = &pde.current_dependents; // let me = &pde.manifest.id; diff --git a/backend/src/install/mod.rs b/backend/src/install/mod.rs index c5de9a86e..fa9fd8171 100644 --- a/backend/src/install/mod.rs +++ b/backend/src/install/mod.rs @@ -14,19 +14,22 @@ use futures::{FutureExt, StreamExt, TryStreamExt}; use http::header::CONTENT_LENGTH; use http::{Request, Response, StatusCode}; use hyper::Body; +use models::DataUrl; use reqwest::Url; use rpc_toolkit::command; use rpc_toolkit::yajrc::RpcError; use serde_json::{json, Value}; -use tokio::fs::{File, OpenOptions}; use tokio::io::{AsyncRead, AsyncSeek, AsyncSeekExt}; use tokio::process::Command; use tokio::sync::oneshot; +use tokio::{ + fs::{File, OpenOptions}, + sync::Mutex, +}; use tokio_stream::wrappers::ReadDirStream; use tracing::instrument; use self::cleanup::{cleanup_failed, remove_from_current_dependents_lists}; -use crate::config::ConfigureContext; use crate::context::{CliContext, RpcContext}; use crate::core::rpc_continuations::{RequestGuid, RpcContinuation}; use crate::db::model::{ @@ -51,6 +54,7 @@ use crate::util::io::{copy_and_shutdown, response_to_reader}; use crate::util::serde::{display_serializable, Port}; use crate::util::{display_none, AsyncFileExt, Version}; use crate::volume::{asset_dir, script_dir}; +use crate::{config::ConfigureContext, db::model::PackageDataEntryMatchModelRef}; use crate::{Error, ErrorKind, ResultExt}; pub mod cleanup; @@ -65,23 +69,27 @@ pub async fn list(#[context] ctx: RpcContext) -> Result { Ok(ctx.db.peek().await?.as_package_data().as_entries()? .iter() .filter_map(|(id, pde)| { - serde_json::to_value(match pde.as_match() { - PackageDataEntryMatchModelRef::Installed(PackageDataEntryInstalled { installed, .. }) => { - json!({ "status":"installed","id": id.clone(), "version": installed.manifest.version.clone()}) + let status = match pde.as_match() { + PackageDataEntryMatchModelRef::Installed(_) => { + "installed" } - PackageDataEntryMatchModelRef::Installing(PackageDataEntryInstalling { manifest, install_progress, .. }) => { - json!({ "status":"installing","id": id.clone(), "version": manifest.version.clone(), "progress": install_progress.clone()}) + PackageDataEntryMatchModelRef::Installing(_) => { + "installing" } - PackageDataEntryMatchModelRef::Updating(PackageDataEntryUpdating { manifest, installed, install_progress, .. }) => { - json!({ "status":"updating","id": id.clone(), "version": installed.manifest.version.clone(), "progress": install_progress.clone()}) + PackageDataEntryMatchModelRef::Updating(_) => { + "updating" } - PackageDataEntryMatchModelRef::Restoring(PackageDataEntryRestoring { manifest, install_progress, .. }) => { - json!({ "status":"restoring","id": id.clone(), "version": manifest.version.clone(), "progress": install_progress.clone()}) + PackageDataEntryMatchModelRef::Restoring(_) => { + "restoring" } - PackageDataEntryMatchModelRef::Removing(PackageDataEntryRemoving { manifest, .. }) => { - json!({ "status":"removing", "id": id.clone(), "version": manifest.version.clone()}) + PackageDataEntryMatchModelRef::Removing(_) => { + "removing" } - }) + PackageDataEntryMatchModelRef::Error(_) => { + "error" + } + }; + serde_json::to_value(json!({ "status":status, "id": id.clone(), "version": pde.as_manifest().as_version().de().ok()?})) .ok() }) .collect()) @@ -147,7 +155,7 @@ pub async fn install( let man: Manifest = ctx .client .get(with_query_params( - &ctx, + ctx.clone(), format!( "{}/package/v0/manifest/{}?spec={}&version-priority={}", marketplace_url, id, version, version_priority, @@ -165,7 +173,7 @@ pub async fn install( let s9pk = ctx .client .get(with_query_params( - &ctx, + ctx.clone(), format!( "{}/package/v0/{}.s9pk?spec=={}&version-priority={}", marketplace_url, id, man.version, version_priority, @@ -175,10 +183,9 @@ pub async fn install( .send() .await .with_kind(crate::ErrorKind::Registry)? - .error_for_status() - .with_kind(crate::ErrorKind::Registry)?; + .error_for_status()?; - if man.id.as_str() != id || !man.version.satisfies(&version) { + if *man.id != *id || !man.version.satisfies(&version) { return Err(Error::new( eyre!("Fetched package does not match requested id and version"), ErrorKind::Registry, @@ -199,7 +206,7 @@ pub async fn install( &mut response_to_reader( ctx.client .get(with_query_params( - &ctx, + ctx.clone(), format!( "{}/package/v0/license/{}?spec=={}", marketplace_url, id, man.version, @@ -220,7 +227,7 @@ pub async fn install( &mut response_to_reader( ctx.client .get(with_query_params( - &ctx, + ctx.clone(), format!( "{}/package/v0/instructions/{}?spec=={}", marketplace_url, id, man.version, @@ -241,7 +248,7 @@ pub async fn install( &mut response_to_reader( ctx.client .get(with_query_params( - &ctx, + ctx.clone(), format!( "{}/package/v0/icon/{}?spec=={}", marketplace_url, id, man.version, @@ -268,67 +275,59 @@ pub async fn install( tracing::warn!("Failed to pre-download icon: {}", e); } - let progress = InstallProgress::new(s9pk.content_length()); + let progress = Arc::new(InstallProgress::new(s9pk.content_length())); let static_files = StaticFiles::local(&man.id, &man.version, icon_type); - let peek = ctx.db.peek().await?; ctx.db .mutate(|db| { - let mut pde = db + let pde = match db .as_package_data() .as_idx(&man.id) - .or_not_found(&man.id)? - .de()?; - - match pde.take() { + .map(|x| x.de()) + .transpose()? + { Some(PackageDataEntry::Installed(PackageDataEntryInstalled { installed, static_files, .. - })) => { - *pde = Some(PackageDataEntry::Updating(PackageDataEntryUpdating { - install_progress: progress.clone(), - static_files, - installed, - manifest: man.clone(), - })) - } - None => { - *pde = Some(PackageDataEntry::Installing(PackageDataEntryInstalling { - install_progress: progress.clone(), - static_files, - manifest: man.clone(), - })) - } + })) => PackageDataEntry::Updating(PackageDataEntryUpdating { + install_progress: progress.clone(), + static_files, + installed, + manifest: man.clone(), + }), + None => PackageDataEntry::Installing(PackageDataEntryInstalling { + install_progress: progress.clone(), + static_files, + manifest: man.clone(), + }), _ => { return Err(Error::new( eyre!("Cannot install over a package in a transient state"), crate::ErrorKind::InvalidRequest, )) } - } + }; db.as_package_data_mut().insert(&man.id, &pde) }) .await?; + let downloading = download_install_s9pk( + ctx.clone(), + man.clone(), + Some(marketplace_url), + Arc::new(InstallProgress::new(s9pk.content_length())), + response_to_reader(s9pk), + None, + ); tokio::spawn(async move { - let mut db_handle = ctx.db.handle(); - if let Err(e) = download_install_s9pk( - &ctx, - &man, - Some(marketplace_url), - InstallProgress::new(s9pk.content_length()), - response_to_reader(s9pk), - None, - ) - .await - { + if let Err(e) = downloading.await { let err_str = format!("Install of {}@{} Failed: {}", man.id, man.version, e); tracing::error!("{}", err_str); tracing::debug!("{:?}", e); if let Err(e) = ctx .notification_manager .notify( - &mut db_handle, + ctx.db.clone(), Some(man.id), NotificationLevel::Error, String::from("Install Failed"), @@ -342,11 +341,11 @@ pub async fn install( tracing::debug!("{:?}", e); } } + Ok::<_, String>(()) }); Ok(()) } - #[command(rpc_only, display(display_none))] #[instrument(skip_all)] pub async fn sideload( @@ -403,43 +402,45 @@ pub async fn sideload( Ok(a) => Some(a), }, }; - let progress = InstallProgress::new(content_length); + let progress = Arc::new(InstallProgress::new(content_length)); + let install_progress = progress.clone(); - ctx.db + new_ctx + .db .mutate(|db| { - let mut pde = db.as_package_data().as_idx(&manifest.id).de()?; - match pde.take() { + let pde = match db + .as_package_data() + .as_idx(&manifest.id) + .map(|x| x.de()) + .transpose()? + { Some(PackageDataEntry::Installed(PackageDataEntryInstalled { installed, static_files, .. - })) => { - *pde = Some(PackageDataEntry::Updating(PackageDataEntryUpdating { - install_progress: progress.clone(), - installed, - manifest: manifest.clone(), - static_files, - })) - } - None => { - *pde = Some(PackageDataEntry::Installing(PackageDataEntryInstalling { - install_progress: progress.clone(), - static_files: StaticFiles::local( - &manifest.id, - &manifest.version, - &manifest.assets.icon_type(), - ), - manifest: manifest.clone(), - })) - } + })) => PackageDataEntry::Updating(PackageDataEntryUpdating { + install_progress, + installed, + manifest: manifest.clone(), + static_files, + }), + None => PackageDataEntry::Installing(PackageDataEntryInstalling { + install_progress, + static_files: StaticFiles::local( + &manifest.id, + &manifest.version, + &manifest.assets.icon_type(), + ), + manifest: manifest.clone(), + }), _ => { return Err(Error::new( eyre!("Cannot install over a package in a transient state"), crate::ErrorKind::InvalidRequest, )) } - } - db.as_package_data_mut().insert(&manifest.id, pde) + }; + db.as_package_data_mut().insert(&manifest.id, &pde) }) .await?; @@ -447,8 +448,8 @@ pub async fn sideload( tokio::spawn(async move { if let Err(e) = download_install_s9pk( - &new_ctx, - &manifest, + new_ctx.clone(), + manifest.clone(), None, progress, tokio_util::io::StreamReader::new(req.into_body().map_err(|e| { @@ -474,8 +475,8 @@ pub async fn sideload( if let Err(e) = new_ctx .notification_manager .notify( - ctx.db.clone(), - Some(manifest.id), + new_ctx.db.clone(), + Some(manifest.id.clone()), NotificationLevel::Error, String::from("Install Failed"), err_str, @@ -601,30 +602,31 @@ pub async fn uninstall( ) -> Result { ctx.db .mutate(|db| { - let mut pde = db.as_package_data().as_idx(&id).or_not_found(&id)?.de()?; - - let (manifest, static_files, installed) = match pde.take() { - Some(PackageDataEntry::Installed(PackageDataEntryInstalled { - manifest, - static_files, - installed, - })) => (manifest, static_files, installed), - _ => { - return Err(Error::new( - eyre!("Package is not installed."), - crate::ErrorKind::NotFound, - )); - } - }; - *pde = Some(PackageDataEntry::Removing(PackageDataEntryRemoving { + let (manifest, static_files, installed) = + match db.as_package_data().as_idx(&id).or_not_found(&id)?.de()? { + PackageDataEntry::Installed(PackageDataEntryInstalled { + manifest, + static_files, + installed, + }) => (manifest, static_files, installed), + _ => { + return Err(Error::new( + eyre!("Package is not installed."), + crate::ErrorKind::NotFound, + )); + } + }; + let pde = PackageDataEntry::Removing(PackageDataEntryRemoving { manifest, static_files, removing: installed, - })); + }); db.as_package_data_mut().insert(&id, &pde) }) .await?; + let return_id = id.clone(); + tokio::spawn(async move { if let Err(e) = async { cleanup::uninstall(&ctx, &mut ctx.secret_store.acquire().await?, &id).await } @@ -636,7 +638,7 @@ pub async fn uninstall( if let Err(e) = ctx .notification_manager .notify( - &mut ctx.db.handle(), // allocating separate handle here because the lifetime of the previous one is the expression + ctx.db.clone(), // allocating separate handle here because the lifetime of the previous one is the expression Some(id), NotificationLevel::Error, String::from("Uninstall Failed"), @@ -652,13 +654,13 @@ pub async fn uninstall( } }); - Ok(()) + Ok(return_id) } #[instrument(skip_all)] pub async fn download_install_s9pk( - ctx: &RpcContext, - temp_manifest: &Manifest, + ctx: RpcContext, + temp_manifest: Manifest, marketplace_url: Option, progress: Arc, mut s9pk: impl AsyncRead + Unpin, @@ -666,117 +668,120 @@ pub async fn download_install_s9pk( ) -> Result<(), Error> { let pkg_id = &temp_manifest.id; let version = &temp_manifest.version; - let mut previous_state: Option = None; - let peeked = ctx.db.peek().await?; + let previous_state: Arc>> = Default::default(); + let db = ctx.db.peek().await?; + let after_previous_state = previous_state.clone(); - if let Err(e) = async { - if peeked - .as_package_data() - .as_idx(&pkg_id) - .or_not_found(&pkg_id)? - .as_installed() - .is_some() - { - previous_state = crate::control::stop(ctx.clone(), pkg_id.clone()).await.ok(); - } - // Build set of existing manifests - let mut manifests = Vec::new(); - for pkg in crate::db::package::get_packages(&peeked)? { - if let Some(m) = crate::db::package::get_manifest(&peeked, &pkg) { + if let Result::<(), Error>::Err(e) = { + let ctx = ctx.clone(); + async move { + if db + .as_package_data() + .as_idx(&pkg_id) + .or_not_found(&pkg_id)? + .as_installed() + .is_some() + { + *previous_state.lock().await = + crate::control::stop(ctx.clone(), pkg_id.clone()).await.ok(); + } + // // Build set of existing manifests + let mut manifests = Vec::new(); + for (_id, pkg) in db.as_package_data().as_entries()? { + let m = pkg.as_manifest().de()?; manifests.push(m); } - } - // Build map of current port -> ssl mappings - let port_map = ssl_port_status(&manifests); - tracing::info!("SSL Port Map: {:?}", &port_map); - - // if any of the requested interface lan configs conflict with current state, fail the install - for (_id, iface) in &temp_manifest.interfaces.0 { - if let Some(cfg) = &iface.lan_config { - for (p, lan) in cfg { - if p.0 == 80 && lan.ssl || p.0 == 443 && !lan.ssl { - return Err(Error::new( - eyre!("SSL Conflict with embassyOS"), - ErrorKind::LanPortConflict, - )); - } - match port_map.get(&p) { - Some((ssl, pkg)) => { - if *ssl != lan.ssl { - return Err(Error::new( - eyre!("SSL Conflict with package: {}", pkg), - ErrorKind::LanPortConflict, - )); - } + // Build map of current port -> ssl mappings + let port_map = ssl_port_status(&manifests); + tracing::info!("SSL Port Map: {:?}", &port_map); + + // if any of the requested interface lan configs conflict with current state, fail the install + for (_id, iface) in &temp_manifest.interfaces.0 { + if let Some(cfg) = &iface.lan_config { + for (p, lan) in cfg { + if p.0 == 80 && lan.ssl || p.0 == 443 && !lan.ssl { + return Err(Error::new( + eyre!("SSL Conflict with embassyOS"), + ErrorKind::LanPortConflict, + )); } - None => { - continue; + match port_map.get(&p) { + Some((ssl, pkg)) => { + if *ssl != lan.ssl { + return Err(Error::new( + eyre!("SSL Conflict with package: {}", pkg), + ErrorKind::LanPortConflict, + )); + } + } + None => { + continue; + } } } } } - } - let pkg_archive_dir = ctx - .datadir - .join(PKG_ARCHIVE_DIR) - .join(pkg_id) - .join(version.as_str()); - tokio::fs::create_dir_all(&pkg_archive_dir).await?; - let pkg_archive = - pkg_archive_dir.join(AsRef::::as_ref(pkg_id).with_extension("s9pk")); - - let pkg_data_entry = peeked - .as_package_data() - .as_idx(pkg_id) - .or_not_found(pkg_id)?; - - let progress_model = pkg_data_entry.and_then(|pde| pde.as_install_progress()); + let pkg_archive_dir = ctx + .datadir + .join(PKG_ARCHIVE_DIR) + .join(pkg_id) + .join(version.as_str()); + tokio::fs::create_dir_all(&pkg_archive_dir).await?; + let pkg_archive = + pkg_archive_dir.join(AsRef::::as_ref(pkg_id).with_extension("s9pk")); + + File::delete(&pkg_archive).await?; + let mut dst = OpenOptions::new() + .create(true) + .write(true) + .read(true) + .open(&pkg_archive) + .await?; - File::delete(&pkg_archive).await?; - let mut dst = OpenOptions::new() - .create(true) - .write(true) - .read(true) - .open(&pkg_archive) - .await?; + progress + .track_download_during(ctx.db.clone(), pkg_id, || async { + let mut progress_writer = + InstallProgressTracker::new(&mut dst, progress.clone()); + tokio::io::copy(&mut s9pk, &mut progress_writer).await?; + progress.download_complete(); + if let Some(complete) = download_complete { + complete.send(()).unwrap_or_default(); + } + Ok(()) + }) + .await?; - progress - .track_download_during(progress_model.clone(), &ctx.db, || async { - let mut progress_writer = InstallProgressTracker::new(&mut dst, progress.clone()); - tokio::io::copy(&mut s9pk, &mut progress_writer).await?; - progress.download_complete(); - if let Some(complete) = download_complete { - complete.send(()).unwrap_or_default(); - } - Ok(()) - }) - .await?; + dst.seek(SeekFrom::Start(0)).await?; - dst.seek(SeekFrom::Start(0)).await?; + let progress_reader = InstallProgressTracker::new(dst, progress.clone()); + let mut s9pk_reader = progress + .track_read_during(ctx.db.clone(), pkg_id, || { + S9pkReader::from_reader(progress_reader, true) + }) + .await?; - let progress_reader = InstallProgressTracker::new(dst, progress.clone()); - let mut s9pk_reader = progress - .track_read_during(progress_model.clone(), &ctx.db, || { - S9pkReader::from_reader(progress_reader, true) - }) + install_s9pk( + ctx.clone(), + pkg_id, + version, + marketplace_url, + &mut s9pk_reader, + progress, + ) .await?; - install_s9pk( - &ctx, - pkg_id, - version, - marketplace_url, - &mut s9pk_reader, - progress, - ) - .await?; - - Ok(()) + Ok(()) + } } .await { - if previous_state.map(|x| x.running()).unwrap_or(false) { + let previous_state = after_previous_state.lock().await; + if previous_state + .as_ref() + .map(|x| x.running()) + .unwrap_or(false) + { crate::control::start(ctx.clone(), pkg_id.clone()).await?; } @@ -786,17 +791,22 @@ pub async fn download_install_s9pk( } Err(e) } else { - if previous_state.map(|x| x.running()).unwrap_or(false) { + let previous_state = after_previous_state.lock().await; + if previous_state + .as_ref() + .map(|x| x.running()) + .unwrap_or(false) + { crate::control::start(ctx.clone(), pkg_id.clone()).await?; } - Ok(()) + Ok::<_, Error>(()) } } /// TODO @Blu-J @dr-bonez Need to make sure that we end load the db models #[instrument(skip_all)] pub async fn install_s9pk( - ctx: &RpcContext, + ctx: RpcContext, pkg_id: &PackageId, version: &Version, marketplace_url: Option, @@ -807,13 +817,11 @@ pub async fn install_s9pk( rdr.validated(); let developer_key = rdr.developer_key().clone(); rdr.reset().await?; - let db = ctx.peek().await?; - let model = db.as_package_data().as_idx(pkg_id); - let progress_model = model.as_install_progress(); + let db = ctx.db.peek().await?; tracing::info!("Install {}@{}: Unpacking Manifest", pkg_id, version); let manifest = progress - .track_read_during(progress_model.clone(), &ctx.db, || rdr.manifest()) + .track_read_during(ctx.db.clone(), pkg_id, || rdr.manifest()) .await?; tracing::info!("Install {}@{}: Unpacked Manifest", pkg_id, version); @@ -823,17 +831,14 @@ pub async fn install_s9pk( let manifest: Option = if let Some(local_man) = db .as_package_data() .as_idx(dep) - .map::<_, Manifest>(|pde| pde.manifest()) - .get(&mut ctx.db.handle()) - .await? - .into_owned() + .map(|pde| pde.as_manifest().de()) { - Some(local_man) + Some(local_man?) } else if let Some(marketplace_url) = &marketplace_url { match ctx .client .get(with_query_params( - ctx, + ctx.clone(), format!( "{}/package/v0/manifest/{}?spec={}", marketplace_url, dep, info.version, @@ -871,7 +876,7 @@ pub async fn install_s9pk( let icon = ctx .client .get(with_query_params( - ctx, + ctx.clone(), format!( "{}/package/v0/icon/{}?spec={}", marketplace_url, dep, info.version, @@ -895,16 +900,21 @@ pub async fn install_s9pk( .as_ref() .map(|x| x.title.clone()) .unwrap_or_else(|| dep.to_string()), - icon: if let Some(manifest) = &manifest { - format!( - "/public/package-data/{}/{}/icon.{}", - manifest.id, - manifest.version, - manifest.assets.icon_type() - ) - } else { - "/assets/img/package-icon.png".to_owned() - }, + icon: DataUrl::from_path( + if let Some(manifest) = &manifest { + format!( + "/public/package-data/{}/{}/icon.{}", + manifest.id, + manifest.version, + manifest.assets.icon_type() + ) + } else { + "/assets/img/package-icon.png".to_owned() + } + .parse::() + .map_err(|e| Error::new(e, ErrorKind::Filesystem))?, + ) + .await?, }, ); } @@ -919,7 +929,7 @@ pub async fn install_s9pk( tracing::info!("Install {}@{}: Unpacking LICENSE.md", pkg_id, version); progress - .track_read_during(progress_model.clone(), &ctx.db, || async { + .track_read_during(ctx.db.clone(), pkg_id, || async { let license_path = public_dir_path.join("LICENSE.md"); let mut dst = File::create(&license_path).await?; tokio::io::copy(&mut rdr.license().await?, &mut dst).await?; @@ -931,7 +941,7 @@ pub async fn install_s9pk( tracing::info!("Install {}@{}: Unpacking INSTRUCTIONS.md", pkg_id, version); progress - .track_read_during(progress_model.clone(), &ctx.db, || async { + .track_read_during(ctx.db.clone(), pkg_id, || async { let instructions_path = public_dir_path.join("INSTRUCTIONS.md"); let mut dst = File::create(&instructions_path).await?; tokio::io::copy(&mut rdr.instructions().await?, &mut dst).await?; @@ -949,7 +959,7 @@ pub async fn install_s9pk( icon_path.display() ); progress - .track_read_during(progress_model.clone(), &ctx.db, || async { + .track_read_during(ctx.db.clone(), pkg_id, || async { let icon_path = public_dir_path.join(&icon_path); let mut dst = File::create(&icon_path).await?; tokio::io::copy(&mut rdr.icon().await?, &mut dst).await?; @@ -966,7 +976,7 @@ pub async fn install_s9pk( tracing::info!("Install {}@{}: Unpacking Docker Images", pkg_id, version); progress - .track_read_during(progress_model.clone(), &ctx.db, || async { + .track_read_during(ctx.db.clone(), pkg_id, || async { let mut load = Command::new(CONTAINER_TOOL) .arg("load") .stdin(Stdio::piped()) @@ -999,7 +1009,7 @@ pub async fn install_s9pk( tracing::info!("Install {}@{}: Unpacking Assets", pkg_id, version); progress - .track_read_during(progress_model.clone(), &ctx.db, || async { + .track_read_during(ctx.db.clone(), pkg_id, || async { let asset_dir = asset_dir(&ctx.datadir, pkg_id, version); if tokio::fs::metadata(&asset_dir).await.is_err() { tokio::fs::create_dir_all(&asset_dir).await?; @@ -1026,17 +1036,28 @@ pub async fn install_s9pk( progress.unpack_complete.store(true, Ordering::SeqCst); - progress_model.put(&mut ctx.db.handle(), &progress).await?; + progress + .track_read( + ctx.db.clone(), + pkg_id.clone(), + Arc::new(::std::sync::atomic::AtomicBool::new(true)), + ) + .await?; let mut sql_tx = ctx.secret_store.begin().await?; tracing::info!("Install {}@{}: Creating volumes", pkg_id, version); - manifest.volumes.install(ctx, pkg_id, version).await?; + manifest.volumes.install(&ctx, pkg_id, version).await?; tracing::info!("Install {}@{}: Created volumes", pkg_id, version); tracing::info!("Install {}@{}: Installing interfaces", pkg_id, version); let interface_addresses = manifest.interfaces.install(&mut sql_tx, pkg_id).await?; - tracing::info!("Install {}@{}: Installed interfaces", pkg_id, version); + tracing::info!( + "Install {}@{}: Installed interfaces {:?}", + pkg_id, + version, + interface_addresses + ); tracing::info!("Install {}@{}: Creating manager", pkg_id, version); ctx.managers.add(ctx.clone(), manifest.clone()).await?; @@ -1059,53 +1080,60 @@ pub async fn install_s9pk( ); let current_dependents = { let mut deps = BTreeMap::new(); - ctx.db - .mutate(|db| { - for package in db.as_package_data().keys()? { - // update dependency_info on dependents - if let Some(dep_info_model) = db - .as_package_data() - .as_idx(&package) - .or_not_found(&package)? - .as_installed() - .or_not_found(&package)? - .as_dependency_info() - .as_idx(&pkg_id) - { - dep_info_model.ser(&StaticDependencyInfo { - icon: format!( - "/public/package-data/{}/{}/icon.{}", - manifest.id, - manifest.version, - manifest.assets.icon_type() - ), - title: manifest - .as_ref() - .map(|x| x.title.clone()) - .unwrap_or_else(|_| package.to_string()), - })?; - } - - // search required dependencies - if let Some(dep) = db - .as_package_data() - .as_idx(&package) - .or_not_found(&package)? - .installed() - .and_then(|i| i.current_dependencies().as_idx(pkg_id)) - { - deps.insert(package, dep)?; - } - } - Ok(()) - }) - .await?; + for package in db.as_package_data().keys()? { + if db + .as_package_data() + .as_idx(&package) + .or_not_found(&package)? + .as_installed() + .or_not_found(&package)? + .as_dependency_info() + .as_idx(&pkg_id) + .is_some() + { + let icon = DataUrl::from_path( + format!( + "/public/package-data/{}/{}/icon.{}", + manifest.id, + manifest.version, + manifest.assets.icon_type() + ) + .parse::() + .map_err(|e| Error::new(e, ErrorKind::Filesystem))?, + ) + .await?; + ctx.db + .mutate(|db| { + db.as_package_data_mut() + .as_idx_mut(&package) + .or_not_found(&package)? + .as_installed_mut() + .or_not_found(&package)? + .as_dependency_info_mut() + .insert( + &pkg_id, + &StaticDependencyInfo { + icon, + title: manifest.title.clone(), + }, + ) + }) + .await?; + } + if let Some(dep) = db + .as_package_data() + .as_idx(&package) + .or_not_found(&package)? + .as_installed() + .and_then(|i| i.as_current_dependencies().as_idx(pkg_id)) + { + deps.insert(package, dep.de()?); + } + } CurrentDependents(deps) }; - let new_dependency_errors = - DependencyConfigErrors::init(ctx, &manifest, ¤t_dependencies).await?; let prev = ctx .db .mutate(|db| { @@ -1123,7 +1151,7 @@ pub async fn install_s9pk( marketplace_url, developer_key, manifest: manifest.clone(), - last_backup: match &*pde { + last_backup: match pde { PackageDataEntry::Updating(PackageDataEntryUpdating { installed: InstalledPackageInfo { @@ -1131,7 +1159,7 @@ pub async fn install_s9pk( .. }, .. - }) => Some(*time), + }) => Some(time), _ => None, }, dependency_info, @@ -1140,7 +1168,7 @@ pub async fn install_s9pk( interface_addresses, }; let prev = std::mem::replace( - &mut *pde, + &mut pde, PackageDataEntry::Installed(PackageDataEntryInstalled { installed, manifest: manifest.clone(), @@ -1181,7 +1209,7 @@ pub async fn install_s9pk( .manifest .migrations .to( - ctx, + &ctx, version, pkg_id, &prev.manifest.version, @@ -1192,7 +1220,7 @@ pub async fn install_s9pk( .migrations .from( &manifest.containers, - ctx, + &ctx, &prev.manifest.version, pkg_id, version, @@ -1238,13 +1266,13 @@ pub async fn install_s9pk( if configured || manifest.config.is_none() { ctx.db .mutate(|db| { - db.as_package_data() - .as_idx(pkg_id) + db.as_package_data_mut() + .as_idx_mut(pkg_id) .or_not_found(pkg_id)? - .as_installed() + .as_installed_mut() .or_not_found(pkg_id)? - .as_status() - .as_main() + .as_status_mut() + .as_main_mut() .ser(&prev.status.main) }) .await?; @@ -1263,18 +1291,12 @@ pub async fn install_s9pk( // ) // .await?; if &prev.manifest.version != version { - cleanup(ctx, &prev.manifest.id, &prev.manifest.version).await?; + cleanup(&ctx, &prev.manifest.id, &prev.manifest.version).await?; } } else if let PackageDataEntry::Restoring(PackageDataEntryRestoring { .. }) = prev { manifest .backup - .restore( - ctx, - pkg_id, - version, - &manifest.interfaces, - &manifest.volumes, - ) + .restore(&ctx, pkg_id, version, &manifest.volumes) .await?; ctx.db .mutate(|db| { @@ -1302,12 +1324,11 @@ pub async fn install_s9pk( if let Some(installed) = db .as_package_data() .as_idx(pkg_id) - .or_not_found(pkg_id)? - .de()? - .as_installed() - .or_not_found(pkg_id)? + .and_then(|x| x.as_installed()) + .map(|x| x.de()) { - reconfigure_dependents_with_live_pointers(ctx, installed).await?; + let installed = installed?; + reconfigure_dependents_with_live_pointers(&ctx, &installed).await?; } tracing::info!("Install {}@{}: Complete", pkg_id, version); diff --git a/backend/src/install/progress.rs b/backend/src/install/progress.rs index 69f04d21c..6c8f21d98 100644 --- a/backend/src/install/progress.rs +++ b/backend/src/install/progress.rs @@ -26,8 +26,8 @@ pub struct InstallProgress { pub unpack_complete: AtomicBool, } impl InstallProgress { - pub fn new(size: Option) -> Arc { - Arc::new(InstallProgress { + pub fn new(size: Option) -> Self { + InstallProgress { size, downloaded: AtomicU64::new(0), download_complete: AtomicBool::new(false), @@ -35,7 +35,7 @@ impl InstallProgress { validation_complete: AtomicBool::new(false), unpacked: AtomicU64::new(0), unpack_complete: AtomicBool::new(false), - }) + } } pub fn download_complete(&self) { self.download_complete.store(true, Ordering::SeqCst) @@ -47,7 +47,7 @@ impl InstallProgress { .or_not_found(&id)? .expect_as_installing_mut()? .as_install_progress_mut() - .ser(&*self) + .ser(&self) }; while !self.download_complete.load(Ordering::SeqCst) { db.mutate(&update).await?; @@ -61,7 +61,7 @@ impl InstallProgress { T, >( self: &Arc, - db: &PatchDb, + db: PatchDb, id: &PackageId, f: F, ) -> Result { @@ -83,7 +83,7 @@ impl InstallProgress { .or_not_found(&id)? .expect_as_installing_mut()? .as_install_progress_mut() - .ser(&*self) + .ser(&self) }; while !complete.load(Ordering::SeqCst) { db.mutate(&update).await?; diff --git a/backend/src/manager/manager_map.rs b/backend/src/manager/manager_map.rs index a4ef10f38..1c7100dcc 100644 --- a/backend/src/manager/manager_map.rs +++ b/backend/src/manager/manager_map.rs @@ -18,12 +18,7 @@ use crate::Error; pub struct ManagerMap(RwLock>>); impl ManagerMap { #[instrument(skip_all)] - pub async fn init( - &self, - ctx: &RpcContext, - peeked: &Peeked, - secrets: &mut Ex, - ) -> Result<(), Error> + pub async fn init(&self, ctx: RpcContext, peeked: &Peeked) -> Result<(), Error> where for<'a> &'a mut Ex: Executor<'a, Database = Postgres>, { @@ -57,7 +52,6 @@ impl ManagerMap { if let Some(man) = lock.remove(&id) { man.exit().await; } - lock.insert(id, Arc::new(Manager::new(ctx, manifest).await?)); Ok(()) } diff --git a/backend/src/manager/mod.rs b/backend/src/manager/mod.rs index 3b4e107a9..061d7d0d8 100644 --- a/backend/src/manager/mod.rs +++ b/backend/src/manager/mod.rs @@ -23,8 +23,6 @@ use tokio::sync::{ use tracing::instrument; use transition_state::TransitionState; -use crate::backup::target::PackageBackupInfo; -use crate::backup::PackageBackupReport; use crate::config::action::ConfigRes; use crate::config::spec::ValueSpecPointer; use crate::config::ConfigureContext; @@ -43,6 +41,11 @@ use crate::util::docker::{get_container_ip, kill_container}; use crate::util::NonDetachingJoinHandle; use crate::volume::Volume; use crate::Error; +use crate::{ + backup::target::PackageBackupInfo, dependencies::add_dependent_to_current_dependents_lists, + install::cleanup::remove_from_current_dependents_lists, +}; +use crate::{backup::PackageBackupReport, status::DependencyConfigErrors}; pub mod health; mod manager_container; @@ -256,7 +259,7 @@ impl Manager { fn perform_backup( &self, backup_guard: BackupGuard, - ) -> impl Future, Error>> + 'static { + ) -> BoxFuture, Error>> { let manage_container = self.manage_container.clone(); let seed = self.seed.clone(); async move { @@ -268,18 +271,7 @@ impl Manager { let backup_guard = backup_guard.lock().await; let guard = backup_guard.mount_package_backup(&seed.manifest.id).await?; - let res = seed - .manifest - .backup - .create( - &seed.ctx, - &seed.manifest.id, - &seed.manifest.title, - &seed.manifest.version, - &seed.manifest.interfaces, - &seed.manifest.volumes, - ) - .await; + let res = seed.manifest.backup.create(seed.clone()).await; guard.unmount().await?; drop(backup_guard); @@ -288,6 +280,7 @@ impl Manager { drop(override_guard); Ok::<_, Error>(return_value) } + .boxed() } fn _transition_backup( &self, @@ -297,7 +290,7 @@ impl Manager { ( TransitionState::BackingUp( tokio::spawn( - self.perform_backup(backup_guard) + self.perform_backup(backup_guard.clone()) .then(finish_up_backup_task(self.transition.clone(), send)), ) .into(), @@ -344,7 +337,9 @@ async fn configure( config: old_config, spec, } = manifest - .actions + .config + .as_ref() + .or_not_found("Manifest config")? .get(ctx, id, &manifest.version, &manifest.volumes) .await?; @@ -364,24 +359,15 @@ async fn configure( // TODO Commit or not? spec.update(ctx, &manifest, overrides, &mut config).await?; // dereference pointers in the new config - // create backreferences to pointers - let mut sys = db - .as_package_data() - .as_idx(id) - .or_not_found(id)? - .as_installed() - .or_not_found(id)? - .as_system_pointers() - .de()?; let dependencies = db .as_package_data() .as_idx(id) .or_not_found(id)? .as_installed() .or_not_found(id)? - .as_current_dependencies() + .as_manifest() + .as_dependencies() .de()?; - sys.truncate(0); let mut current_dependencies: CurrentDependencies = CurrentDependencies( dependencies .0 @@ -398,11 +384,11 @@ async fn configure( for ptr in spec.pointers(&config)? { match ptr { ValueSpecPointer::Package(pkg_ptr) => { - if let Some(current_dependency) = - current_dependencies.0.get_mut(pkg_ptr.package_id()) + if current_dependencies + .0 + .get_mut(pkg_ptr.package_id()) + .is_none() { - current_dependency.pointers.push(pkg_ptr); - } else { current_dependencies.0.insert( pkg_ptr.package_id().to_owned(), CurrentDependencyInfo { @@ -411,10 +397,9 @@ async fn configure( ); } } - ValueSpecPointer::System(s) => sys.push(s), + ValueSpecPointer::System(_) => (), } } - let sys = sys; let action = db .as_package_data() @@ -444,7 +429,6 @@ async fn configure( .as_manifest() .as_volumes() .de()?; - sys.truncate(0); if !configure_context.dry_run { // run config action let res = action @@ -576,9 +560,10 @@ async fn configure( } if !configure_context.dry_run { - ctx.db - .mutate(|db| { - remove_from_current_dependents_lists(db, id, &dependencies)?; + return ctx + .db + .mutate(move |db| { + remove_from_current_dependents_lists(db, id, ¤t_dependencies)?; add_dependent_to_current_dependents_lists(db, id, ¤t_dependencies)?; current_dependencies.0.remove(id); for (dep, errs) in db @@ -595,7 +580,7 @@ async fn configure( errs.insert(id, err)?; } } - let mut installed = db + let installed = db .as_package_data_mut() .as_idx_mut(id) .or_not_found(id)? @@ -604,14 +589,14 @@ async fn configure( installed .as_current_dependencies_mut() .ser(¤t_dependencies)?; - installed.as_system_pointers_mut().ser(sys)?; let status = installed.as_status_mut(); status.as_configured_mut().ser(&true)?; status .as_dependency_config_errors_mut() - .ser(&DependencyConfigErrors(dependency_config_errs)) + .ser(&DependencyConfigErrors(dependency_config_errs)); + Ok(configure_context.breakages) }) - .await?; // add new + .await; // add new } Ok(configure_context.breakages) diff --git a/backend/src/marketplace.rs b/backend/src/marketplace.rs index 40b81d1cb..6c0bcb96a 100644 --- a/backend/src/marketplace.rs +++ b/backend/src/marketplace.rs @@ -12,7 +12,7 @@ pub fn marketplace() -> Result<(), Error> { Ok(()) } -pub fn with_query_params(ctx: &RpcContext, mut url: Url) -> Url { +pub fn with_query_params(ctx: RpcContext, mut url: Url) -> Url { url.query_pairs_mut() .append_pair( "os.version", @@ -38,7 +38,7 @@ pub fn with_query_params(ctx: &RpcContext, mut url: Url) -> Url { pub async fn get(#[context] ctx: RpcContext, #[arg] url: Url) -> Result { let mut response = ctx .client - .get(with_query_params(&ctx, url)) + .get(with_query_params(ctx.clone(), url)) .send() .await .with_kind(crate::ErrorKind::Network)?; diff --git a/backend/src/net/interface.rs b/backend/src/net/interface.rs index c7929ab65..1244c0129 100644 --- a/backend/src/net/interface.rs +++ b/backend/src/net/interface.rs @@ -48,16 +48,16 @@ impl Interfaces { let key_vec = key.as_bytes().to_vec(); sqlx::query!( "INSERT INTO tor (package, interface, key) VALUES ($1, $2, $3) ON CONFLICT (package, interface) DO NOTHING", - **package_id, - **id, - key_vec, + package_id, + id, + key_vec.clone(), ) .execute(&mut *secrets) .await?; let key_row = sqlx::query!( "SELECT key FROM tor WHERE package = $1 AND interface = $2", - **package_id, - **id, + package_id, + id, ) .fetch_one(&mut *secrets) .await?; diff --git a/backend/src/net/keys.rs b/backend/src/net/keys.rs index 0d9bbe702..50193e328 100644 --- a/backend/src/net/keys.rs +++ b/backend/src/net/keys.rs @@ -21,8 +21,8 @@ async fn compat( if let Some((package, interface)) = interface { if let Some(r) = sqlx::query!( "SELECT key FROM tor WHERE package = $1 AND interface = $2", - **package, - **interface + package, + interface ) .fetch_optional(secrets) .await? @@ -148,7 +148,7 @@ impl Key { WHERE network_keys.package = $1 "#, - **package + package ) .fetch_all(secrets) .await? @@ -192,8 +192,8 @@ impl Key { let k = tentative.as_slice(); let actual = sqlx::query!( "INSERT INTO network_keys (package, interface, key) VALUES ($1, $2, $3) ON CONFLICT (package, interface) DO UPDATE SET package = EXCLUDED.package RETURNING key", - **pkg, - **iface, + pkg, + iface, k, ) .fetch_one(&mut *secrets) diff --git a/backend/src/status/mod.rs b/backend/src/status/mod.rs index ea4b5f0d3..43e140726 100644 --- a/backend/src/status/mod.rs +++ b/backend/src/status/mod.rs @@ -18,7 +18,7 @@ pub struct Status { pub dependency_config_errors: DependencyConfigErrors, } -#[derive(Clone, Debug, Deserialize, Serialize, HasModel)] +#[derive(Clone, Debug, Deserialize, Serialize, HasModel, Default)] #[serde(rename_all = "kebab-case")] #[model = "Model"] pub struct DependencyConfigErrors(pub BTreeMap); diff --git a/libs/models/src/data_url.rs b/libs/models/src/data_url.rs index 5661d262b..cceefa6b4 100644 --- a/libs/models/src/data_url.rs +++ b/libs/models/src/data_url.rs @@ -1,5 +1,5 @@ -use std::borrow::Cow; use std::path::Path; +use std::{borrow::Cow, str::FromStr}; use base64::Engine; use color_eyre::eyre::eyre; From 94db263c77b9ed952895ad4ff7e4c72e02431a9f Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Fri, 8 Sep 2023 12:52:26 -0600 Subject: [PATCH 48/89] chore: Fix setup --- backend/src/context/rpc.rs | 3 ++- backend/src/manager/manager_container.rs | 2 +- backend/src/manager/manager_map.rs | 6 +----- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/backend/src/context/rpc.rs b/backend/src/context/rpc.rs index f99b52b4a..f01ec2ce7 100644 --- a/backend/src/context/rpc.rs +++ b/backend/src/context/rpc.rs @@ -219,7 +219,8 @@ impl RpcContext { let res = Self(seed.clone()); res.cleanup().await?; tracing::info!("Cleaned up transient states"); - res.managers.init(res, &res.db.peek().await?).await?; + let peeked = res.db.peek().await?; + res.managers.init(res.clone(), peeked).await?; tracing::info!("Initialized Package Managers"); Ok(res) } diff --git a/backend/src/manager/manager_container.rs b/backend/src/manager/manager_container.rs index e6b216890..6841b153c 100644 --- a/backend/src/manager/manager_container.rs +++ b/backend/src/manager/manager_container.rs @@ -299,7 +299,7 @@ pub(super) fn get_status(db: Peeked, manifest: &Manifest) -> MainStatus { .as_idx(&manifest.id) .and_then(|x| x.as_installed()) .and_then(|x| x.as_status().as_main().de().ok()) - .unwrap_or_else(|_| MainStatus::Stopped) + .unwrap_or(MainStatus::Stopped) } #[instrument(skip(db, manifest))] diff --git a/backend/src/manager/manager_map.rs b/backend/src/manager/manager_map.rs index 1c7100dcc..edf97602e 100644 --- a/backend/src/manager/manager_map.rs +++ b/backend/src/manager/manager_map.rs @@ -2,7 +2,6 @@ use std::collections::BTreeMap; use std::sync::Arc; use color_eyre::eyre::eyre; -use sqlx::{Executor, Postgres}; use tokio::sync::RwLock; use tracing::instrument; @@ -18,10 +17,7 @@ use crate::Error; pub struct ManagerMap(RwLock>>); impl ManagerMap { #[instrument(skip_all)] - pub async fn init(&self, ctx: RpcContext, peeked: &Peeked) -> Result<(), Error> - where - for<'a> &'a mut Ex: Executor<'a, Database = Postgres>, - { + pub async fn init(&self, ctx: RpcContext, peeked: Peeked) -> Result<(), Error> { let mut res = BTreeMap::new(); for package in peeked.as_package_data().keys()? { let man: Manifest = if let Some(manifest) = peeked From d6d59337b68f98ec26f82a4f5d38e4e3d6e0b5e1 Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Fri, 8 Sep 2023 12:53:50 -0600 Subject: [PATCH 49/89] chore:fix version --- backend/src/version/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/version/mod.rs b/backend/src/version/mod.rs index 4cc68a4a0..0920cf496 100644 --- a/backend/src/version/mod.rs +++ b/backend/src/version/mod.rs @@ -61,13 +61,13 @@ where async fn up(&self, db: PatchDb, secrets: &PgPool) -> Result<(), Error>; async fn down(&self, db: PatchDb, secrets: &PgPool) -> Result<(), Error>; async fn commit(&self, db: PatchDb) -> Result<(), Error> { + let semver = self.semver().into(); + let compat = self.compat(); db.mutate(|d| { - d.as_server_info_mut() - .as_version_mut() - .ser(&self.semver().into())?; + d.as_server_info_mut().as_version_mut().ser(&semver)?; d.as_server_info_mut() .as_eos_version_compat() - .ser(&self.compat().clone())?; + .ser(&compat)?; Ok(()) }) .await?; From 836d5bc8abaf9026aa68c5c4d440a94841707bb7 Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Fri, 8 Sep 2023 13:00:12 -0600 Subject: [PATCH 50/89] chore: prelude, mod, http_reader --- backend/src/db/prelude.rs | 9 ++++++++- backend/src/update/mod.rs | 2 +- backend/src/util/http_reader.rs | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/backend/src/db/prelude.rs b/backend/src/db/prelude.rs index f6c9340d8..4f45a3c6e 100644 --- a/backend/src/db/prelude.rs +++ b/backend/src/db/prelude.rs @@ -1,5 +1,5 @@ -use std::marker::PhantomData; use std::panic::UnwindSafe; +use std::{collections::BTreeMap, marker::PhantomData}; use patch_db::value::InternedString; pub use patch_db::{HasModel, PatchDb, Value}; @@ -9,6 +9,8 @@ use serde::Serialize; use crate::db::model::DatabaseModel; use crate::prelude::*; +use super::model::IpInfo; + pub type Peeked = Model; pub fn to_value(value: &T) -> Result @@ -180,6 +182,11 @@ pub trait Map: DeserializeOwned + Serialize { type Value; } +impl Map for BTreeMap { + type Key = String; + type Value = IpInfo; +} + impl Model where T::Key: AsRef, diff --git a/backend/src/update/mod.rs b/backend/src/update/mod.rs index 1bb102f03..92c73f01f 100644 --- a/backend/src/update/mod.rs +++ b/backend/src/update/mod.rs @@ -80,7 +80,7 @@ async fn maybe_do_update(ctx: RpcContext, marketplace_url: Url) -> Result

6Kp7JP2C#Jr= z0rXrkKub?`E}}cEP`~@iiBWpCt%CFGN_SPNNtUNxrbHmcp_vJ*h|*{_SjFrdl8Opt)XN+BH+k z%Bk}ZebI^(R0T%|Xg-ul=aat2&;)(|Lnx`7A2-vT^X+gvJpMqP_ae*Q=ay+`Q>ZEK zW_Z?fMMJKA;2ah1qY=^VB|98hcVPR0O{XN7zKCrYqgviV;N@NgP=}y4WUu~!Sa9@{ zAj)9anJX-WITf3m$NfjIX7MW2%UzsY4hT>% zyDC$@Bf{Vr1V1JO%$1s%RYDT^fzjRc?WFo3PI5(HvN9y~@V8*?!E)k*f4@Ph#0|EE zQ-Gy85as6QM8}3E&07)vY^C096j`W;^OXrqTvhB$Ue!bkrA7(A86NvBv=5buEh(-I8k~HjT$vAlR6B# zJW0;dZA9(2cu!resDA~6i?0;mWEhyD7^s;Ygs4gDnk0c&_U;s$bshF-L|>z(4{wX< z%GStcXX=(jN#eB;)g&825xyp`JiyC^yBr~IsX9C*Oq6Us8MH>d#+Zu@HrA7C^r@XC zS+iMDyp(UFXFE)up!Vl^XF}w{2ukRk7}tJ+FA=_;If_Gb&J>RB7U1z*_VpoA$W|^p zy~^beJKQJ)d!VPKZGDGJnZI2vrDht6$jal4hQ8J&QhDPyW{=C_AaoD%b*u1D9aXs~ zDz(xiT?|3g>*=t2Hw2FHO)~Mj(m%|B3fqlHkO(Lqu&A6HZA7I7ChKCP`e~Ja7vES=K+Q(}+}&R0+eO3roIdV5I3)=f4hZHN0hOvAFgN01w#47Z z((LIQ=BuV}`6X`)$&P^ftu!~*!kkXutu0NfS47PUcTS-YvR7KbA8{B5p@$o@k3-hm zU+zUKGrz@0)VBpNstNv7h4udpVltBkT7JfW9hx6fm#%EExSgw6*R`Q9-KO$YKRKNa zQbJU|PsX7bCuw%xLkT;`+yrpt%jW3*$S*n)v-U^A?2QOK;gW$uJ~D{unLTO~?0NnG zY)Bs6s`aQk!QWYXmHx0l%ehmW&y`d zeQ#ysf<(M6W2-lXJS$-p3Z1f=k}LY`i1I=R1X>Wm_QOjYA?OUQK(A-wFcBnaRk(|B zmXFzQUIrbl%(BBo)R0TyGqTTz(ef9`D;w$?pYyrMRT+`-; zI=sYQw^**+xv5V8G>d8_r0Pw5JgO`JmN9*ty2KH%z;pH^W*w~Db|7U2n#U-4eCc*| zQWMl`5Ks?6qD>wXpK}+jj)cc@bhXr2KH+=#RYkR$de$+N)AzpPO@hT^6kGbo@eBtM zH_OCZ@a&oV=M0Ma0kmfQFU=`z{8N&B7qQPUL}%=q`rdu+w1d&~qPe1;xl!W5PTISS zDJC)+Muc8Rk|qVS+y598h4ug>p=9+Po~U7JLqj8Mor5J>Q&j|GtK~I(a;UEt^%oGI zBDl2l*5p;yhD}2f(lYH77YcHtEdF~qRVlix{B0LVqXKyYj=gg>gBv^%~G5j$|OAE7e`ybY?D z0fZSG>1p-*@O3KOl+wO$+NV9}CKPeS+Di~P+X?d{;MKvy7k?7@5h`qsETYUn% zV7-|?Hn0QfQ^WtK`C7v!R0JzV)x9x zBJs)6KT6?F7&2MD@p2UZZ%RR7Ri*f`j(mf0a$TduXUI5bro~hz3cjv7U%}9F2DPEC z3)JAsT<`STEbctzi+;mP+jvqP0`)iQk6X;#JbI`uqR4Rk7Id4ljz)04iTVx?V zvaDJkJC;DRxP8gG{@GS>r5y0KUYydua8|%rDS`zeUY_N z;P}sG*i#TNPoemvKgzMu`u|^Ik18&|4`POTGh2)eNn9#up*-rAAC)Y61)W~LE&&J z;;;YF%QYPxJjn~KUV1vU=CCC*WT7(Gzg7jh+y(hofBeCO-zcK%-N#i#k8m;Y2TrO( z&sym#wcd%wJ`vpGc#j1N#!{tVG&UWkWZ#+x~`k{Ih^bn-{PJLo^v%@JLI|H zZW{GToCrCq66k_3Q$5B1byu$vmCA0wapZiuQt>D9yU}<19)T5c1ALI#n0}no^XTiOjyv?1(Sq&vG4pIWU!n423KcFS z9}T`P)fR=65=f9>EWtfuYw&Q!qq8D8$0k8}fI`|;1`3#GsGLXzw9A~1E#cgYn*5Y5 zTH9uyx@bKf9+vQCh&gf&cQEAG!M@~4{)7SOKIp&fB&+eqXR!&C3d8U+*0YV3fxT^D!I7b;bfV{LV_0_^#l_TLRnIq z0E7qZL687(f`K~94Tzr5@+{KWly7d-xl^I|;i)6kT1mVMsN$MlTi-s%b)5 zd&5R#MiJ-*97W+zHngk=pm+$Q&KIio#X~7re@(a=))LocOW1U^aQW~%o%HHU#wy8K z1sh52Lt8*#dAYnLLUT9ZuaS9t#btO`?8%BVotv1dIWBZ?j{bqnW@@Tf?sZ7;5bLlV z)Z1cM1%N-f(FZy82+z?G$X!;O|4TsO-K?lOzu7Fe!yN$aSJ?sWn|aQyGQER(st<5K z(w2)BI(>(|wmb+!z$@7zh{^$)8ur_zHBOceFv{p8)J-11l67PjORXhBX}Z`H;kCLW zaxJ0JKB|6$G|>zL&|6VKw_;h2V5YBu9S}nSB|cu=Jk;^|5pna{-pE#W+Zmd}TPDJc zh#x+8oBo)?HMz3Po%%TBJ-ps_wGaWd9W`JOx}X#&x!6(M`07-*ITBHk{W~i~`(<}Q z&ho_%`XHH7&8=?=`6S^`3dh0|(~%Z9KcIjoHdU^JYb++`x^T+8Mv)d7evO9U?321q zOg4!ro_#*)01IQ@oWa$LBQaizT^r~I#_y2~$(aWe|)n!W~?!)*_tM4vMHwz22~ zkq1gpI&t2E70SB^|t^7?vnnCuoZ` zzUKwdtTMhE<{1Ej>r10reHh?eI;DtWkj4h1LH=iR8nX1{AGNO<+?XcbRFaV&Qpoq zkSUw)iweB=dG#>g`34aKsHM5!FZ37TdT*g8mTXR#{QTqq02Jb0Pyhe`2@n7?Xy7^Z4odE<3<)#!d5ztdGl4H;I!UE^Djk}tUDKd=6q$Av%lQx?ruwK3E z8ZPx;bk;uk=ev zrTztU7<5Ai%}i*)D#FhgZE zP}-l_h;r}~Rg5~w!jJ+P&dHW2N;j`OVAEtYF;|09Mgg@8g703?>uPTAg&1N3nkcwN zwR%CE#%gXUmtt86umw-&?WhEh8}r(_Wz3B?ozYw6O@K0t2XY&IS&fBoTYNeszUIE9 zL@wvOqhLowOij=lrhI-G07EFod}o$W0xOPM!Q}azU;%L8kYA4Swg&N3&{C)SX^)S{j%3T;_Ay_9GM3y`)sh*IF9@00Aedjer0E z0EGYn{H0A3>JW}UuSe^7H-^>-h2wY*5g?(%Pu?sCo(Vj2t4+S6M@^;SXT6{y+=k>K z@^=v#dKocIY7t=_9ah!^M9q@Zuav%kf4GTA=0w+pz!!r zyX6zYJ?Ux!A`XhJRnOA;FU_F+qn^&vPy8Awcu!9LX8Y1vO7C@MeYq_nRRIp9 zx`OZrckJw+AFlTqFH?E_aUHLkxq?ULjC`!F3%wFs?;>$Gmj5@=y)z_piGnYdD$p&rQIcF={{pXwWAoj>wJc+D} z$bU?lmOrtG5_L+a&&}j*Ob8u7vaH=?s$~e7f?lgOzBa3DX=(Q4c`tvDWrpP?lu&`f zQR}Vxf1AO!>K_Z{^FCw1tac_&>|r4{dgK{9ZnW(DR5xH+KUt3n)tcfs=IF;gigav8 z*45Z0W2c4&W{|)Ml@Y1`JZVT&b+wB~g^!Sv61NXI(w+!HB_l=P@Q~V250J@(DE}D# z+XF9CRMTM$!{c&fNbs|c^yk_YeY z*lElEa@vF^G_wwu6LX3fAo5N15dg)q*E*lz4gMX3t z>Y@X|;OSPLP`eg|mCBBd3--OT*@9~1 zMh(3$xGrj)Rm@n>(tzm;=2&5b$TPclL8;-Q7ecp^ryLAtSE?6k41nbI*i=#OHZt0y zqZV;#Qq34?G4J%UI4lx#%b_8I!=vytuX~yz%pbxwCh$EeBA3bYcWY+Pqz3X%`>oSz z$I723AaElS#*2@#K5mbpHhEl=ux^*KqSPn%LqwK1H_p1txq-`nCugfHqpgL$L>^LINBt%w%J1h&{H^=y9DJ z0XIR5+5Up**MyE87?FYl$XF42dg+XXd`WvJw=y~+NoXIA6=rxs4R{O zZooKuZI06c=v?6r#2y>#?L`YatdkiUi7VioD#?2>bYMI*0sI+JULNyC!ngXCvj)0J zQlL`^Sm(nxNu(7$cU7>MzAzY4(}YccM$*s;O#uXE@`5jr3hsAK_dc=4qgaulqOlu- zK|fZwNhzYN{-D0C`)BadbvGC4sw7JMhQY!)jPo%v6p^C^Ci-*g;5Mtb`@mUZtI5jU zo6$R$bFDCJUBcKX`mCLThYJbF-Oj!u4joPb;ZnfeF`IK%q=#8zBdV7>%>WWq0NMwt z7ZFTafXrGV!5Gw{mkLmW9$t%}h@+lvdC%Rntr^}{x)*rKIciH+1-R%t zXu!zWHM{fLsxYMwnU#wAgIewqlPi}@Sm){}v^Lnj96NnXFI?}4A`!R%>?4Rfc=T7x zW0=;i0~zUo1^Rr}FNlhSDef{2vbjehc`iwH$=f7?2qKh64>uvcKc3uQ4Lq)6=1#{x<0C>GHt}(HiJ=CrB2DvqPk=U z-&q0S_q(gq!Ff9HKcW1<%o54sp##&W{i_8%tmY&UQ0d$=w9kC}HYwqj;wR=2l*K85 zBZ9}0>j@%-;d-nZ`LH0jHtpAJa^CxS6^%VMlevgnt`H1_>*E>LADLM*N{=`39}|ZT z&=u{=6y}`$SF)H47gtUPfsZ8U>RGN4mV3EnPqvCH35%0d=je;vh5QZ6wep^Y=m}CkPl9%RdPh?l2#H|0Z=H=1VmH#jwQMy+85Jx1=`R{=le!k9+^yAC(W2e;^UZ@0bR;NDRu z6wZZlf2-%z=91|N+7jX#$VhGG1sHZ0Z@5R}8pP0LAl|_k`B?J+00O?ViA}j3ks64HWTMQ0(MOBQK7eCID-RT)WF35&?WGIDis>|kvmnlQ{)O* zjwflNflIge-|Fs`k}x0FIqZGn-zP_L*i?(3M9;Xb#jf~#=*u+e)Vi?SxHA5|*l@DxAO|f=N2waRzVMR|LIX z=&R<}24#Hb$+RFaRRMP!p^&xW9CYUDL`PEMB&COaz-#jZDvT{6#Akz*W6?O3>%RWc z4iL~I339OB=XiC<-$Ab~QL}pP*5s{UY@wX$8MIv|`nJo4$T;_r8?y;lZJP&_JtbYd z$DvI!7rJ1RxiD_b`!+Gf$}Q()2mfhul1(A3KkZ|%5)c?T5QWQI?9_`CtkWuoFc-+d z3Jz(dbuqni#~SMH;E;*b@Wxdt zTcb;)S=+d~z*$AKB+W2E7JASC z0015U05mh8I-nsUCR9o44~amS0005J00P5L`64&W+fZQHK+i<;^x-lVMWr+x!O0Ia z3dm6=ySCy^p>2u(D@zyE4*S`Yz`$1`*0tM)ZZCKtDOt{GROMDn9zi1X*5K|#*%uEB zff#3j_474yEqk`j^x4YoAaqK0BO9vhZ@^pYS1FPl=(cayx zMc-V1C$bqb32_UsP?BiX!wM7ee)KSIDk|X=jXS-$1iPpS)E=0n-DK&YdUqRb5AB*Bv946~pGiWUvA8emH&eR!qxE{Pv`0Inojsb5_xgw4*E6{AqnH z@+Q@vMSXvOp~#cU?hSR z#7yb#;6VEZAgjI6{Vt@9VK9bUeT#SkS-$a50UGT&#on$wh5!Hn00ig&2jK@7u&8KL z7sAdXq8LzC_e4b&qUNc)^K8N}v9JjyPeA|<+i=SuPCrbAY$Ft`D4iH=AO>)#L>uSyHmDE`b`9y%xywR!ZxGsRua4=zotJ))};owD^m(d@ZWNq1e42#B0#eIdiws1`ImXFk0tG; z>slw#8(V-s44VSdk2Hr>5iz$qH@_0&mPd|OHjCGfWJ|y33Fn!|ZKjBt=_OD~!A^bD z05}|8{^j$nvZ-cBp>S3dRs;eH&B*a4uZ)3Q+yDTG6@pBI30{UhBxB+cJ?`Y-e{8Nv zaZmbeV-&Kg!umJd-a_E5o%QU~?xuB#ggu3yrY%eWH}>@wcHlRS-|Dl!;mKKSW6PrgIb<5NYhcZtZrA^i7FCrv<)@WgjuNt`Qb}ULuGQ zVCHf`Do^Pm?=|6J^QsK`i-Ye%>a4f8=E&O@>Tdx;O~%=t5E*+xk18HQKn7>Btnc@u zAyA=n%4+X}iRxMSj203*&$setFN_DGJ0(zcC<%!Q!#3PTU5$=28{#1A2RB9Yju}L( zQvd)kB(tU-{^+(BQM`wXD0Ojqda%H%sX*K=)4$m87toiVI$1dPw4rGtNA|u^5CK|J zD5;X{>A9_mWfp=?JdMnyWH}j?iNC1QWEb%A$*kMobqg-&z|=ZnzHjH_HM)Yi(KkE- zyZ0RVTd)%0q;vY{JwXA$Z#G6Eo1>h}d&anNxEQLGlX1SL*+PEiPyhe`P{06x0ZQPK zph%Gs3@Au9X@J$y6Lh;yWXr`LeyLkwVK1r@*J8lD(>7Aa{8;Q`(Fm-M1`9!VUG>fn zAhT+gOOD!}+f2uYle6hu3bGS;8X>zw)zkd%>Bw(?bTse~ze9Ei_OqUYo?g4_{G`FH z+Tp1DG@0Zm)Rbhacai^++pdW|RE}nxmuHUd(1u~%pvNsFdS1_#V(dK-B2`GxIUb1u z$zw1F8+t~!za?8z-Pu5Us>&2rnYewrYt(x(M3t27~`+;QEeUJrcj)T<9 z8@nbnQUk4`l)C<2pDUu*4967e;l$_K8!yoCpOOFo0VDtc=mjf+N`WFoL@=SO(SR$r z+7P+Llz`lkOeFPI4g1$zQGCOm@oo51<9E=*W{)FC1Lp1P#v>@t;sf%e3L|5Q@W9Pk zI{58ZDTSTG13^u&=DhOIirO$};fIQXZtP`engEJ;?E}&-IW>bl5w4CT%)pp|!+LxWi3wrg+U6=R9i{YGe4kJ5e_nKZzqD1z2Xa?-346Tj34xA@c|4%3b^aYxb;y)09?Avve-j$Zx8udssxzu>nmhtjr9(}vj;@%xA*`7HIy8Z zJ<3zy`Znof`)Y*TCkJJ(yqt$8`*bS6wngE%FFdo0upCNE;Y4oNDJ=lx$gThhr$1$8F21oOYSN#;@iVJ3t%_$mggMQ$%CY)?Jj5N9!%tSGC z`G$JyPeOLU9jS5B*x^JiWC0fVPG<}X0L@&00(nY^M!R3CvHs9kP>_Hs6!&UbG$e-7 z#d}&k(W?7NS%c!PW_E`q2F`7j=+)kex09VYZv-I2VZ~xTUR)II#1Gr0 zSD1q?H2!net^NQWuz)@*dqamtkKye6>QNiJhlqLy5-Z{g;)f`l4Q!kWm=V3so7CWU zO@X+QWmTvD&^!S=s9x`KlDUMG+x)2rNt(&=c;e4HKLVh0x69xF000Qk01h5PTS83J z1QBPgFse<;fEA;_oBv&608Ci$TvRa0v2q$*xaUhZtG09*lB(CTwl>RV6-7PA5c1l! zh#Rq&c}JN_I=}2v<5^;@Y88t_&bInLG%k1q?vuZp5lPrlhkM_hhn}%#sR-1bCn`; zD{*rnEJ#kAaX_;2^Q3kAE%0DF5snB|_+6Y$PS9wucb($qQE3tSjW2T%o0>RjlW^mV z%mngd+89zx-Ip0(a8}P*t@5${KxDeThNvu^^vzv2Xz;QgnGEO900Y`$X%#=F=IGYW zXC`|C6kR9_-aNcU0W#yvek*B9EFH&k*#p+tth>){MW3vka$K?mKXc}V#`7cOd-jMs z(A{Po1~$io2@nTGSwdiiKBf$v?`xw-7a=;JDweW-5{Rp=k)GxQ_$a>Y7Of6F{Nnf; zQPdef5{o$?hSaMoR8mk72$iB=VFneQ3_OB7GF+pB-`hTKegF0vT$vVZ>xCO7pEgq^ zgXyV_2UDWdeu_`g-LsE|Zt(};4qlOif*0`E*+LtzfT?U7y6S*Ea42uw!74A7b6yGR z!_JDEAV_Z6v;Nd&8?ko3Mf_X;I#q4krVPyRxBp)x&rQLd1RjwhTit+j!9s8KOyT?m z(6JEba28G50_41ISZy2insMMiQI%=FnO;a78DAm{$0^QbLo&rpg){ta6iFPTnq95r z)1p8rmHOEwORr|h>l_XOFrAi*W$;8YFfmS`2>hFfLQ>;0>!~%+wdPk1qej~Ea?mC} zGb_7585eh0{01P$KAFo^qlCBtfB*mh08qdHcoft6(|_2Z%ROpwfFp$RMdt)xIy`5^ z?DX?(qL3J_fcWJB++S(o9YK+@R!1?F>jq%1A>|x zh-sfo!yltmHIobgnl^@+Bn5K#Ok}53<#E;-f%Wazs`Qe!6rs8Xo6?NwgBBy(3z(

2XBPVsO7I#?*4Mm*LUdsdet#?fIWJJ%=6sf=w4}d*RMZV8~Ci}d)1{b>ZOE1l}vSlI_ zRGvdOQ_cr90(pO6D6_N+6lz!CB`3FLiy&us^YF+qP}nwry);Z=8*7+uYc;ZEUd7&3?c8 zd>`)9`FC~ooSDM3s;3ALZlIvlw`bO%#uh=BpR)TfBUvoz1VuVdT_})ujQz6IGb=Js zEw`#V-ni!9%j4k8%s@t_VVDDeKQa3L#Ja4Qnt(_sXr?s^l$lmljg1^(o~bK`!I}BI zPKUukBiiVUn6dntYibr#)T9e!s4mvfEOkyU8TF=)l%GlW>ulArN%<@MD^<{|&yy;Q zqe}zF>X-Ws{`^B=dG_{An{T=nLWVgFz!|p6){u?3-9$72>be8VZ~SHA%A2Tdjwp{n z7No3KFD&VndgDy?uOhI{)2prHKP!cJ0l@JDN^WzHvfLp5a2au<_PE^g(5Ed3CjzQj zeOA;@lcpvEu-QCuc6C4R=)^HV>%06EI|zAYW8Da0Rg&>kegbOHOn>T@jM|tx6d?6~ zvK)+FIemfMBw*Ys7d{kr5MOPD{@bH4TAR{}1OLE~pT@S7RNCvE$8Iq?-8+&sAUU-t z2r1N?HUHf3#(=U}TcXvxKN=*E<1yr5Gm+mw+;r4JvSR_q!8`;{a;qmSy0%%sF<#eZ ze6h7<6LOHktP`zMv9*6d8Gv`jHnLv-5ex( zW;wZd!u`|m*VCTxOaZ*f8R|=e7vJZB2SprOU`pzcW6td^;H+9vSow0UIcfzOag$r+-oW8OtTA6c7XSIFttK=*Bf`1qTXRaq&ytZ}jT~jx6D-8sAg) zFJ-GaJFvN&h1NY@Ug8-lId#UP<5G;Ugzz7bn_i}czqRnEx|zlH7zd*( zg1LB;uUM<;W674)Arz{P3{#O1$Ydj_dEU!RhDpZxCtB2{j(+>&j$rFu9vGt+@J=(!+Eoe{;T1sD+O(#`p@{TMaVVy|Nl>rq}h>JkB z;6V2#BojAe;7ba;R)1NNY?NnnRN>y z@-#S9=|))>H7Tlr%CTk#WA*%N%SJAp>a=m-(xq8j7|!cfwI4jT+PW3A7zgH~491+c z6IQMjEfuegFZi4UFZWD@U_j(CekL^X5fg4x=Ntifj;J?-c9n^DUi?+UD66sj;9N>% zn_{)Ar8GcGL>@Lr1(_Y^i1sRUP=You=wq<8&dU)+^JrmKBdwf8*7N3DM&VoPNyovs z_9-zB4AtV%R?IXux?@`>+_)4iAO##ytE|O7Fvk-krkz_1o8@ElpvhjfXzJSTL$Sz$tfV!- zgw!{Zw}kvkU`LlR#L?KgWGY5WG|f251J3|X3Mn|#}2gU!*1T}S~2qJs~6 zhV%y3X8(CeHndHV5B=_^N6m(gPbUD-Y)>c9^FZFp_lo-}x51Z9AQDH86&ejd#zwh? zR|@7X2#v{tN-(aqKPi*wX#D*XYd-w(neyZ<&a$!M#D_ITRQ2wXx*Jpk4VEAt|5)X& z0Hrei;ibBJs0F9H9UHGUx9nBB#>{&*Db9`52VNAVr66mil@-v9W?aA=9>svQ*o{i| zn%`4*4CZf^)z@OO;i_YiYaigzT7|StG#DjzUlYT`UUz+KWdAGDWEcIiZ+-V*w)kLv zgDRg6aDT{8slj^>oVgFB1Z2+ZP(L9agCm_-4H1j#;QXIfpeX@}u)a={Rs3t<1d1zUM(lCcR4rVnav=zQ@09L)@aC+dX@rX(5>Vi=HC816ns;Dbb z*fjHa_wa-b7-+oN_CYCB0c11ZG#t(=+$)7lOKjtq5tl~H=_loBU3B0Z@-+r#W0S(N z!{+>UfWo$~jm|T-xAr=Epsom}^?`HCCswRAv^tuRtA5}ys-;ToAyj#e+8q;ZX@dFf zIm&g$((lD7y@&fuoWx};7jul{obu02FcfhQIq5T!bE77w+$%qAPy$L6O_&##1lT&^ zW}!=b`m0snn#XozHMesj8MnNG~MhcIc%R-)13ehj>%}K5K(zG{V@00Z{&zfUn^97+!rt3lF}U+jQ)wqtdkQv^Fu z*OxwyT|-U64pj{qqP~CuT(FXTNuZqCY(5km>cf9nk8Ck*ki~$`sB_6qJ&*qj2M~ zlwqd?p@&Z$pyGLOaGUiAzqZ&|TbcQ%tFaP9nA%WiUy1NK*}HPua@!y9se80l>ON9K ze3O1R(MM8v%JZoR*##BV4(iR;o?{+F5tuyt`mZyo(#yF>5m>U1%(`+!*@c};A~>Y= z{ilap0F?H{+V)}%)@jV^*anPIjHpcAF_C6%5)J>nYo{$vyTE31eENesv%8g%C-C?U zLS_(y^12KL-!AK_$utToKbqmHyJgzziWTaIga>~uPr+Tt;d8zD%6jWW4XO_wY|HY?clhpx~ z+&Y_PG9Vks7}>%t_9)L>+|nj+PkU)r(89snrQ9vd>vjRNqrR!$cOihe# zDw~xb7-O->E<-K57(uDcJ@8tUP9sSn5*q^@4QLo8$VS-0?fNs_*G(?^EkUdvh7uUR zMUE?&nl)fw%)CHfm=7)EM^4O>T zGwP2gR1w2R4C}ZTuOIWROKq%9H`9OPSpXy>0A6^UE}z^gSc;WO+w_^wQ9~Un`cm%} z77@;pxk%%LJyFc;iEMVTeUUY>`AYHj)Fy`u17FrEQ`|IkMx|J=lDB6JdW(~TVk)m* zh1HI)85|?$wl~;bN77_mo zMDS2)iTZ5q8NcT|pn*l7v337VDu%vp;P2MF&O@MixR2qFuh(crpb6E4mfn1*OE%0O z(jYiw*jmaYr-)5Q;BN&nl6!68xkFRW@4y&TKJkOOt(9E2169{KkK|lQWqrS1nGE9% zirjdsuG64Xm?BmPGO4Y@?NqKZD)Q-8fql{Oj={5)rqow;Xy8HSG%^)P1ZcydmNz7^ z7#WM3;;#GQoQV9t*4DBxHbjlZHRO?19BtkBTWVT9m23GA6P;VDx83rcZAHy}oNq&c zPL#QT+k#vwf~1Yrpmgm;G66mCY-wOL;!7inGcT2F?E<&2L+lmcg8v)`Jr6)ACusX+ z`UUbx!dHL7m&D~)uQ&(Wd&=oKJKPhu=>tpDS_7*(U@dzL!?%EG|)irO&x@FhZGA)UBA%eu4QF!DKMhm@Q5`$$13X@kx z+rIfM$f>~~mNF2*uM8v*>A{Ht5px#&W|MXsB$KdK{{*9SHTVXHZi;)j3(Z|yO1d#( zQxZ{_)DK_asUF^}?0%PtE6DoDY|c)az~NJj_xpGJKEfmmTXsj>IO`x>G1$szP8fj# zLIJQkUty>+xF5(i2XHi576nPNjS0)4BXwc%4J$E<6RAc<@KxIUZ9V_W*YW4oV zSP{p))C2>Xl(;t-f^dU+)+WNgO4hGZZqXE@33F>Xv4R?Ei)0f7J%q7G5SjLOe$@}3 z`VjG(Bn^_XZzUbEsvs(_gns#wzNQ!(xR}BD743})WR+bzVCQl~M(xmsQ`;-oDdgr` z^OX|qRQy_GX4@&74A*?pI0jzvFu#^o++zZVl|MD0iFu|BwD%YdZKRsv#AQe!L8Gnk z2v^!G8`fU$h);Ij;sg&Xh4l)nU&5r!Y1`z0BKi_^{yy`1HPv3*-xA7B(t{A;Zvf+m z(8P~kSZA-otTxruxRKIExw=po)SqZIM)=P-0JysV;7PNt10`)>?8t~X)>7}v;dHs9 zm#o96+kpP;0FBaQ4fcq7OtYj#l+BXcMyM~+;SSQCrSNyh6(_afT2-|G9V{^XzMlD= zI1b8kgaDtAd%0?W%kFjIW?u?Rc>|&*qxi{JX3l8t586pj3D!a{_>{AX$4eC@Qsdft za%fvAUP|P^^~_7)U>urk27OfZ5@eafD*rh{cq5yFR)lApKI|pM*s8%QPzTV2s+bhn z;~uMu!Hl?!RmnUrW^67wNJy0j$=>ZDbJqK`=9%64PO__^yefV&f{jalVT^aH8Y-?0 z2S*9PTAYH4-1X#)Q`+BeTRAe~5^2BR5u|2x#fvC2DGn z#hQJoT;121_A7cLSdl5(Hq)i?93k_1abC&wl^ckFFLWG8;kRpfJDR{xV1CUaErIPI z0+|Mh>%{N_D6amPV38ZFmxM4v)4+96B^Nes1p128Ex}!v;h~{K7B2aW!&HH4Md%9r z-|cU#8uGTQf-nq6!6MRW*Qgovn!_zs-zTDf6}^<~+%Q4uV_iuHA@l-Y>vN~n{G z@{SR4oBA64v#;}%i3jg{0rxU_@xe=(eH$IVBkkANpBR>L{MB_nn`UnTEy=yJUZpMY zJ2_Us;1n-C83I+PUvtS>*NV1T*1pv+-J@Q|w^iS|*&+hO#SVS%hc_O&Aw4!`fyP=} z?j7J9QE6re+5TsBKmq^&&MCslGlU~2l>&$Z$}=MP z=3-y0Pmi7K>SH9LR>5tXRE4P`+`dVG$FM;a^B!9G5KE|iUkhF;aR@A3^S)|>yAfE3 z8aLw9lsR-Tp_K4#rUY@77-`Kmxh{~0DQHAy&ou`uPw8%cS?&G>o~*x}oMMG0vx2Sl zp+ZsGQ!|S5jKm{ur=$GE^iM(2d1VS>p12pVKj+$1ILpu-yL_zh;_9dYD6(A8Hh-!- zXXCc~C76)R%~`=O%0q@qU{x>L*F{sO2;q!v!~Vce^s~AoUp39>Wj|d917DEe!yT{Q zL@dcl!ge0N^5-DtGAmxSvO!G(-DJQ#^Jh^i=gEjYJO=P5{81JMb_E%gwNFHj(etN3 zcZOHQ^abjl9ThY04?lHh@FBbn&UDExcsxv-JzCA#tcYjDf*ri$9PC5s+)1LcVu!@I z-6H%|>fsC%;ay=A1lksT6tJsWmrs~rg_kKt`eDn`t+U*8J!HsDV_2yf|JU6E^F_6A z8|EHPuhKM5o0^hTCIB0fUsGm6fE*UNMop*zh_*>k z4r5Gqk^k;D9YhlVHnElMD$)vUCvusvWk~iQ;MAlJogd8fPcT4*0HDU-+YL>WsNZpB z006uq01S@s8-TSW(-zBvJa zkX<9q-o9@PJ_~^Ebw&I}KurMP+AYv;1h4}Ds-}Q=|C7_V9I%P47JDusx_1K+IM1F) zXYl`%%{LRnr4-hGOml&Nr@cJr)WGk?5SjoKVt=d6e;2X=02Xul{vn9(6Sg{5f&XA1 z0LZ<3?;nKA1EBi!fr$uyV~{EURNQ9#KL}0;fKPI<_(lN8bO5@uNkHH?{AMQVbmsd< z#oUBiG*C${<`fO+^H44y^(H}rxMWJYy=u~bfT!(se(=|m&zuB2qf@5Dt(#KUyOuEpF-#j*jPw$J7_-B7YFsuk=d(7e^0H5dCN?9v zLXhWd0;UR=qgA?uqXF*)L$@DON_6OfmSsKu@D{^(-4uZ9-ymB3PQY}vOfW_Y75JxX zZn_9n1Aw<+J$AG~L{~$i4Zqx84pLb{kRoW93Fp|ogesP*>?1XFb=rbwbOK=Kc8p=> z@AV$oP1w|~O{-!T4sXr&>%hQVg(gaZKss2{=NT__NAuDd^f+)a=N&?(3}ncV!9^P5 zgglpgO6w#ogQtoKAcDQh{q}MtX;#)g=NG(i^Dn=W+eB=>)n@Y;H?^`x z=In7>RfvL>ei|_57iv2xzN$xFTl5f9+jI}68mqKk|J`w(GY^&s!nPnOKMI|uA|*ey z$_oHT{jH23m}vUW?zikge8rLlp6mo>KHy8NWl@Zg6;Q&eIt3hzQa}BEBIJ%Zd=}nA zKz!WTg(h5Yp7Igv>aoyHR+TO}p0XGi=H)(gagANnH2n3g*~nVrXk(u+LSj!n5w1UW z_-hD~e7ML#)uD_Gak%ab)i1;&$-!IWvxd{y%9;P}UOXn#jFA}pyu^I7=}`ld_gO$w z3Nx9_#+)wi7vKS9uEi^U{7TP=kSj)t*aKD?n@!|j8niDb8y8EpT3CDpOyuMOB5ZwG z%b=z75+5#l#VWAmNnOzmHWc#1v@~UD8|#IeVDVQ?u6X%Sm>dlR?=mZLLtE96J~G9A z=$#<#%PfO25%RE0zBx6tK8r{__b*C_ul8`rtjb7en|bDQ)LUmFs_4btHt3%so7U<( z4%s9JOpMg(F#&hfT0!0eYqzY`f&&tlHEohxA`58r+2+I9GBFwpzq0&g(zdgZf%AFB ziAtpFE-CkChYeb2;2k0KAp=}%+v~B1`m-i&c<}pH;#;xM1oEsL0_B??HEuuO4!5+G zTSb)UB06xfbQFT1OvG+7I9aM7uz-X23#pNWDH^)$H>HeZ}i6juXVpj1n+M0bHKJ6i*fj4lfLjMj|ofcXD zO5BJp5CysO0lDcsuvCQxp?DOe$1j?0>%XZ12h(`#F7tf##f#{fi`|>G{A4kSU8w-4 zRo?l4wGr|HLFnvs7J4k;%T9V-W3+GP!SQwZa;{yCAauK&S_Ke-A5Z`DYNV_c1+I4A zqubnKX4G`Jo?m6u{;2#Nf(?}eU&RBzz53B#(EOl2+v)A+@b(E4gzkAgz^I@(5n_p` z`MQkdgj^-GIRmy8>PtcrFpWQ$u(#L6`ZHzM!a$32a^4Dc3heWH4>2aKV4F481S@{B z_i(BaND9pBp$kT6r9S@_MIbSVPqSbv7S41?kQ`#L@&XT{-iO5a@!Nj8Y;eRHI=f)} zIMP_pf)l@DdD(DKj5VO+E3`Ia%Y=}GM_hm}SE0QZYuq?2h&m_%0EmW8z0R7g&z340 zX)#sJiDX^DYi*KDu*Z8^78m?eg^174RrYELF$hTmY3POe$k_~Dk#(Efo!R zA?JaJ=V*+7=@sc76kTo$39)&^ zY?UaQ+WJ>f<7Ag}4r68e$e(>U)>@ADCjG2y2?a~Pzb8Q8m>c`RQ>kFeTg=-yiORw~ zehq(=Pn?lW6l6VvzeuJT>!-bP^H1a9^lwSa#32FKLCKDB%F8HRXro$ zy8fo)PTEOxrAQTqT*6x2#B+`DUQK>r6-2;q5TwR_I*J-7Od2^w{bICchSU+~hsj@SzhII>U=u}S*x^CcHp$3}_EfeJ)4 zQ$dbQ!=2@FU5g*CT(Sf%#cs24<7K=x$R}ylbe-Do4bpXPa;l>_hCK`M=hJ2tKMvFS zuf(U;1^PxtwTM9e^htTnwxegEMn~T0} zyJFE|ZS>VF&;cCqBGaG?BRPEj?9A8^%bKa5S&Y7q&Mm2w&K z)l+_picuK7>PbSiTlw@@p`G0T%WA5TBPjTx%q(^s_9INh0;gtti1vG-RJdy_plDo> zD;&JZYzUj%iqp#*H!&=OV=-$2M8Y5$bS(6%$DerHF?DI8Cuqv~V=PswUZc9^LnPAV zm)W9gGjN~{ox!fu_c2A{S`(bsH~MZrtM&CC>&SNj3cO8Nggx;KAP-0=I~u1aA)+t9 zWyA~(0KI2e;%f|In`gQUYCHRg4F_(ax=sEO2H z)PKX)H@P6ZAXK4WL_(7mXC=h-dZ)e{0+fMM8i2wQHLS$@`u$ag0@I3$p4GI-{o(n+ zoVLz~va`n)cbN$UZHw5vRV@dZnXG`7l=+>zGYoncCbZ91#v4oV!ozz>#H5Dk(Rbu# zDA5m5PqlZ_HBAi~=}BvhdPcorHi;)*K9~I;6Zg^`omswBR%J_9Fs|VAWD(eB)o`uy zGSqXyk2&EEv-#lydb82~fDrP%^f6r6`{=EC({t@~ehP-|hK%A5F2Bv7(2Sh{-?GC% zwfHzbRM$krqarVSCq-Ew|z4ytUH^p|BCs0L{J#PDQ; z9;uFkqRhp|kJtp%u1uxDWDXN2r~=V|=SnO6DFiBA+Wp zVxBVc>5&$_o-i>bu({38RxELHBqC^F9QhA%Xy*l0VQm+h)>~^A($g=ERH2UlCJ!P- z`WQyR1IZlD?4Nm1s=o((p2z=4{0W@9vgE*3IQUVa?tAUr)RQ2*LKH7}`!H=CYm^03(IhB9^L(@62&J1rd<`sC#XqF6En9YzM%x9*gZytcxY7)1-K2#$kDux)vAkuPiIJD+&1R9}^ zsmc%s>)jWjC4oi4@F2--i0^Bl(mADVUV(1MMnyyoc-=MCY&HnP`oq(>#9$DZ4H-5T_JikOF71dJY+W4A?|7XfDT7rch zgZEjSY{oJAmZ0|}>jjZF%HIktPf2Ilp=2%37(PsE$7{m9^e!dFA?!`L6HL)UQj%OS zjYXa30Q(X7^ddL|%+|0`!%y7WC_OgYk22C=d5l-ycT>vRka(9m+=Hzi3**1X03cS2 zkxrOW@Hs-&Pf?4G=Uo{z<#QzWX!O?K3Bq|e5U$(G04JWeaSj3lMdW&k+d)uJ`7abP z@&M@@8n3+rorUhkf9cNi1FL^6Y_W$mXjs2bK`&hLIAoMU8^uiXi6DX`X>W_Jok%c`zkBQ2*i|EKF@ehVwF+puBza^1a6IX024u3 z|J(PKkA01rt@sh4+qb0@mrmKov4}R%%wR%yQ7UT%I}~pVP;zi>(Jp>DSCbk{9xw*% zjQNar5R;RF9|{^QY!|wDTVqF)fs_!I?r=RxR{2P9y7y_*fTM5BT`|Z^d9y0`c|r{U z)mB`n+&`PgN?+4@=<1RtsOb($N_EluLO;y9LOYMkL$_|!nabO7SVO1&l%#Yn=ALr? z%H|}wDy`P1cjJa#6MCcwq_-q15ntG^u^dOi$dyhY*}mIJMWVF$9;8=(4CQ0;-BT807Jxxgi#KtyXlN&XvO0=#pG9)^q7nH z3|zYhX^Ok8$G6X``#yv>7)Fg`jpb-k4VPa-4yZ<_D&VQNamt9$tKPRtPCclY-NkcB zbVqZUX=z*3H!TiRPub=*3hW{!U;%rRd6<3qQ(;k0e7tY_0f_5q?|-l;SWjJ?Sd@_@ z#OvCyCA$?0U73!Y>%Y!ei5mt;4=G5Cqwl%Lg3{28wRWh?A$#f_|ay^+(Ff3$3`bL?)b7b+pJl2hEu&W zkJ3}p%+@ast`mC_Hq=IsKJka;g;`d@=h|~tx-MVO)Dyj|vLndz-8qdoH%Uhl-ye-S z0lE-R)qLp9Y{4spI}V|Hz?DF?r8@f+{kOX+S=Ssv6VhD`DyZBi!>BpATS#{#ZvBs= z!DoQ`+VWjF%;^OJ-N&%)fbkHx4lOzHmU5ZI=KB_8O`{7n!Fj5T;k&RcG4Q&}_0M-e z)jiQmejudH>cl`V_)?MRN-i1-6d@^uKHKFo1>l*$8m3Wp0Y9Lan=9oOSP1?Ue(QM0 zLcr`8W$ud2eU#N)f2>IHm_jpNKAMj+ZH!@1s7uFo%MWuaDb^e1P};_Xs4|gB#aI#M z=Djp_liV&wo+TQ}=Z~y?C67mCJRcJ0xpg!#juE~7Vq)ZzMEU}7D?0;@iHjTwGD!dObo${d<3Z zcx|#u-;HEmH$68ZjDCTW6T0l%(-&3({AUEM63O7MR3=t7EOJ4xA#rbh)+;`S>xuR8 zhnYiNG>JQKKJ)wF3Y)b2d;m$oPLkn^~a9q6LH-(PasjP9C z_*ZmTWt)H1l=dn%B4K|FWm^o zndQ73qP+e~wp4n+9%l|!M>&_wDdk;7qj$mPs~_5rK7$L7|EyfnKQbuD84zRT6$Ml@ix?bevbz z_Ch?%rB;h}auyG9<(=W~0e_IIjG7+1SL+OQI~#9|v?eEUP6vKc671{m!HzWodBAcs z(n8uRh$SQe9(!ScmY~0=iIlBl3YhHQW5~HzCTrX8OK*_;fHm#tJo5eBcphK_`Ykk7 z56UiH48LCiVvdqhBr}^s?~!t3Z-8B1Z1#wvXebuIx)E?SHQhWs(&;=+Qdshw(*Z@@zA{_{Q#I|V7$y-@M^=`!uNQzWzfx&5Z zev{}XdO7*4+rdGcXZzQga13<2*7&Lhty>}EhaVY{9Wh$kU$MDKM?oktmXa?>|5-5C zM(ldT41#)FwuvJVUx^)>x!$A_J*`}B43Z68wVKM$y=T=-#gJ0R4s{$GJ(FWg zKSL0YaZqc*yyiPt4cLGbB~BSeTxWH}%M}&Rdw(A{Bhp_t&zo6bbencN3Jucg3=Ftl z_MHhF#}jo%FD``Q{Ar#!WpIFU1A1e@5Zye4IJvK%)XpFWVZm_gj8d>Gr@jb^1u(55 z7F4e_mDz8W^a@5Q@m;~RqbN`wQEd3?3oc40o3DUw<`9-6+PAbvbYs@=xlCx8a>6e- zE=Sv&L+nJC&qk&FryN`)vH__C6cCuxg;mT(LHLaX&x&3E;Mcp}MYuv^OTQ8REV2EI zIGE8GKZA3kBzi9KzOp_oRUM}5RzZf6W#Z=pDy}oDyq@x_eNjBQbhl-rq^CMk`E5f~ zl=(F9-osLvW`M<|Z}4y|MJw`_pTl}2pY2Ngw!H%XP%C@v-)JB&bu}THXBfWYWcXC- zr(__XpO|Kl=YsRcg-ZCHD(*_gISr!3@@Kv>$(|HKu>v&g0|B8OJ3B~F#N;ER;Ubb) z3vaM+K1K?UJEDeHqy{*W1wCf(=@YV?8VUQVx3hF!RCO>EgK2hG%sT`(lrBy!7AQ%V zCjj1Vi*~FA_+n{SbumiEAqa9Y)-kB)vl+??J5(gnij5*0JBtt~q(J|)=wEy9e~gIAjusE$#>h=DB zh0fA;1c!7%C@1_1MPI?uH3bioVJe3K?VMOAWqos^Xp-n-8_d}^7-l3!4cbocf-nA% zd9hi<=H^;Se+;Oud?HlBzG0}Ylt$5S#|2QF=d?zedGwm?63*+6)M7g_fm#}xXAC(( z$pPF6e7N0=6zwUDPml66fD_nhEQV^H(+)}-vd6g`49HMtSlyWt>Bq|YtC2ncFZ2y=283^DWK_w zA(eKFtuDgJ_!F725w4yWY-aUf$(a#K75&QuPf)XuFpxXm_2(K7W%zHDujh3)D}!!! zJ!i-rVCm(Yk~hdwTB4GzLSghoMmTk8irE>b2&NGuwRPs6aRVC4J!h2q#c5@W+i6hbH-wkD4H!hgkup{XM8 z{5#;GVdRw&slzifCbP?(tG3V?VS*>%Q(Cpm-3(4vnFKy|y?&ANSIW_{ck3_SMMWhj zMz66G^zZpFg|sD`B#ew#Md~%)ZmF@l4%&`qpZZH`KvSHR?iT^^1x`=AVQ z^X&sp5TQ&LuLr$@oquNrshs=)o*wtcKXNcdRV2b%m#heWZ|Up?Q=3O`)j6qo(Is*? zLT`Gt2iNbQrzuv4ROLMTYvByEz+hhBxg;y5gz@W(xsh;@R+V+`rvBjGVSAo}q3V)G z8_Lm+p&HYqh*yIk<#}`^7mU)IDngtG4LH8ys9;GzMn76Z7k%7QNU9ElBdMhYMzFhU zxPa4pvVad|90uvp))3UiKmRIkT1M+bVb%6S8sEiAUC~;q2+SgTpH3;9!qySA^lmu` z8Z1&x3!kBSQO7YZ(h-K)8@;run%G!T@|)53b0KnNoKNkLiheNH`e-5xzRl6Mn)=Kw zCbm~Fgz8{daq?7WM{%auJ;5{PvW;q%w5`xt{gSp#Zo>RLCkkZ5)(-WnROoaN(Om9V z;=Cm7=ecq?Zh2MnQhUFo(Tym-@-R@Z*!X1Ses74;earobuW3pEuVRGjf=m?FlJ3#S zo(=ro9ph7K_H_*GYiyiI${#ul219jmww~=xhjiM1XdhT zN#5WAmwfs>W4)@e^uLa;6c`*6%1Ir9rXSBOFnS&Vf0drzpeXsYLMz6vCHPM<-HPg> zF}U`7)~QaI1?TMy^svIlpY37f!Ox9d9Sa`6MD{&wuftZh?&~lmkxdcopy*1RGok)0 zCa&uNlJ?jQqa*)_jjjm8{Pgr3aRUDv!9UWArCTaklX@FKh7~1Vy@z*lAo$~}7Dc~d z&hV7*%*&{&^ow1}Q;UA_1p`VTVP^+pY6=Z%xxsG27|2@N8zScY{tW+d2hqj;QfUxMS5iseXN-N+CkV$pXCCTU$LZ+cy|wW^EmPRcj})nc^tb?KLpbIxL(hh83?;IJJlI3l8a z4Lh;iYZMkt6*SYrN!N|XU90Hfh}N{{Ny^Jn^D6#A0PVUBe#U_gCr+^dI-dJt<>R)) z_gi&H0o7Pv1BJR%fBBCsa2zNYOmzJEI`#8p((ZJ{r+}6KsvE=$6=h6{7kCI~ezSV# zL?<*b(p-n}wwpdEBCGRIvCu~oLUe3Q0}b1&jG@+~HN0|wu*+F*BsRBsVwi2OKdCrd zc1|hQ1QI?#WZvU@ZO=*;6HgXUjs4v(Qcc8@{z8+=fr^p|O)on48|b_KS^3GbSb(8~ z=v$F|d`%_DFOq^1inSb!uVWh`j+sc6z@!|cPxVK$=fat*9p0koh$9(jt1P6Shny>K zTmHU4U=XqA_EuFs&z7W%XUC@{9r|~Pg9g;S7>z!v+^yi11nFF9ADo5L4>mrGl7sq4 z>LoE#ly%v(XwJQlE(xI(jwxG#5XE`V&F}3yL!L7xTxn=wb`QMJzAOPBg zD__j_vpdnL6;W*6Xp04xF3#p)DoK3d#e!0c_6Wl-s55DeaMLaH%OVeKEMK!ULjGWhXr6ZU& zOiQ3fvxyr*{fP6qL#~`|*hd~(ZN9*DO%>mVA9eZfwtQ`hqJEb@nfHE01rsxBH>FE0 z=9c{Jk8Hu8zQ#J>h|2t*wii|R*|fr}o6d(k@IDJkP^6BdU;rQqD@7$CBjiYEVLr#j zpvqNt^R%koUi#vW45&WuPp&W6ADuP`?WD+vn=--y;nQ+fB^ta@!?NO^8!uiYQC}Z@ zz70RLamhxRl1He*B)tbIRmxe}dN}SK(MqOH?NuEHi4)@xGosg@?oktD-;>FJUBsKN zC|mLdRMe+^02;{AU8x?7@kUrpsEgeSq{LJKHCi??GY5p|5b0tnWH6PuPFfbBubz+b zm9e+1M=&ekBqdlS0ko;EnDelST{mjk#JPN#OLDtbxCJ5wvpp}?iKYF@kKNlpk^8j6 z$Uf`J#!mGS&Q9fd3&rx>opM~@Tsm;e@M-B=Wd*FUy^b(EVHLP`U942#;Pv!Avw6>8 zDhY(9;pk&bFK4l9_E>Y8kz-U2#aDptEvBH;VU%3*eZ!*v2LIjp(?uI9W02Jo6AbJ0^AXUK! zvG6&3Pur0P7K7ySO?43V>c&W(_+B)>lh+V>_aPbXUZ-g0-Xb!Q<&n~TJ0bx-N6MdJ z7&3K7`@bKHJc==1(Xb?GC#Iv^n&G!+fg&!E`88BS1itB#_p5$b?60N}o=cbwQ}xoD z#~-dWO&J=lo1XBL=xR}LErR(@ZyL#G|i3|qSlj+@I}mFu(mQ&sRCqO za7UJjZ#F0e?4NnPa$y8#?%-%*zmt${1^q(L10fwCmW*&Sc3?2O{Qf6?M+}6_$trXq zb}?CKlyHz8Y`4yln%ulYvB}_$&vxw9H~MX%mb>l4Gt*iY{b={-pNY0$KS#zfWXd$z zP5j8?i5h#_1hSN5Q6P&g&qhAXFSoajsV*a&Dv^&(%+W3AjkbIv&|Y+Z7>k%!0>~7& zwGhl7ehzjIBYjb0VQ=t&7LL7#g-s5un-!c%9eUv z(k1Ia9wZR7`=u4^D6`LHcZmFkKs&I+zqX!SfRne-s%A0nPD{6)c5VwDBAX=gVz}<` zr1km%ck&$z)R(bHBh=O!#e@Dk+6X>2IP((|ipBruVnBhibb-OuN5p%T&LVMGJ}1tf zJj}5QYg5H%f%WQRa{Qq=AZiGHzm~h%h&10g@zL?M7qBT|@X;1`GI4x_Hmwht-eBAD z`fOCwh}O&S%~ss*=&R$Fj>q2AH{hy0wFQrB$3|2AG5dA(sqRcu;46(PndSP*!Hx71 z{N0Y3b%-dk_GJ;mQDPM%CeqHP*-4Vj#dtn>cw1aSozn%26%>SC}kiVC)DrCls9&_L37?UN4elt~Hll z-(6PKpI4Qk+)Bi$ky6VXQ%O>y{ExdR)~BGQ7JUSD)txkSHr&y;&dN**^_26>WaD}J zRHjEyjUi+avE-Zf^t3r*&M|fBOrE+VI!eN=59~CunUb2`J2}B84r$ohaiu>^fnI?n zeIWc`#;IaglZ%cyOsnVgeKfZoV*o3^zn8a&qyRk5Z$HCuv5ru`a2)E3L5|6dAk`%h zn5iA{B*n!6Xq;m0#{6KESP87no91ay9xtlavczCS3z5Jz|7bL2;SS-yTrIY`wM2E7Ca#l$tL%lryxW zVVri`w>iI|n4yQ_Blp_);tK}1>PJ(bwEDwjZG5SnjE%I;3DNob;71N|E{%J2_a3t( zCK_?2BDA;wO~4)WE?Wc=;g~tWXFc0X92siD&BAR~R~bo|6FdJlWugu}?7u(xa);@i za9@cEL$;IJmvbB0S23`;7UgS{l7>}L>fULXULWP{B&JN)0|29k5Yy7R z)gFodiAiwN5zNH3hFuyCRJ$51T*z1JjcV9wO8Mp7ae0#FQ9cg&26X6KOha5nhgW>U zkco53$PhM}SX5xz^eBsL#wxOqPZ?6rnGY@ zmYDL*^@1~2Nk*{(#zefwYbr00tUYfmyAR=_#Gk6dKu7Tf*rs7nzz{$A?x~R4^pWKO z7VmCa^=s{&@oKHTHXX9X+mMX(8HeDU9r0*;-r@U9l!dw*?L7^P(!mLPpjf0*0_-N0 zG{N5N9>R};3!HMsQ?Mfvyq%Jv1O7LMMZB|22Auk8bP|+EZ;5#VclxMF9sd!F)XE0# zoM!oU^Is_<-yb0P9{_wngTL=beUzoc6Io^rPp==RY+om^a_&5ro_&gCAJ!MMLInT> zc$6-F*1AHMNIh!{)p&?@^Y6w&}95=JO2f zH49$0~JOF8b=Y-P!4r6ojtAsTt&m9(%t1 z-h|;vY0wL9&UUT*8dDNd$G2}k;H?b!N15-JqO#2OE8DSs`?|#qptv@Ujn~~Oi6$os zxL{1i4k=Y0VfeKm6X4lwgQNvwDb*cRVMGGMAuA1GruHwn`OE38O5fGONm`Gk>ccwB zdkk{e=p2~W+hF)WI#3*S|0)ewrrqBTcY9eY{68^m(~_7KH8jV2SIq+8B3feuolOap z1&?nmAz>fl=N4RtK{^G zZbIrFAN5+e*3pZ|;JhCZebo5AEFhp9gd>vtfu@kaZ@UF?NwMs(#w$PzbiTK2L=6Jw z2bs_@ORIOWV7`@YJ$MQj`?lE{a)Qz_#>^5L(81=JxA9a5b?yXOz+q%UgM0Ep=*G|e z^wnn-J6VwB`az-+!khIK$uE_Ev}#|M#PY^s?gY3eReVwb2Zzg^F^tio(^ONi6XAQD z4T7K7%7Ow37gKy|^&gh%HF2Z5xxNND``|ogjI%9V5d>!veMh&xg3h=?007-%?pi@= z`}NqA#$ctao!Zpeb@#g9f!ex;pp}fYh|sGjIN6p4~h%WE?TJQ z1}KR!WFl=MhF^s8@jDID|9WJS)`w?1E$t4q5Gns6HE=P?8g*m@^T&!p-FMk7;ok!L z37)ggO#mq+{tMGrM#7=DVJOl;aeAkqit8NcCX~lMudBUO9?@oJ*aT`K$23ebH$RYH zP=Ho&p{(TrZ=}fQq-YtspwjaOxlpC6ZJekO8yXv0_`KL-e-PwROP-g2zU? z;*elsyulHA^J9~e34p~)zuZ3kua;u-T6Iw*LBqmz+=Mg6FvzjJRv!>;^N*{{tQ7!a z^hN7dTHy(ey0OY5;%xAN+B5Wu9|#`&HiT^P_%t)HnuFP7I3Uw>M|W$&c!;;5-kGb< z`Yg8jm}#id=)AqfG#8S)*DZ3P>z$hZO$|-2mgeIq`Z^(<$6Hn|JyHg5P4NJ3!-4lW z9}%!*D?*-}&EZn~Z-%jcv;WYlkj4=cg07wjo5G1p;tgrpmDlIo*m}yD0>jboP;9CW zK_3;%vybV%W8aR8XKx^?tCvr@Zw{1HbwHA8GQ<*k6zUGXcO;*`V3gO_c5;XSAdy%N z1-MXKkIUl3DLUALKmY&|K)=#em#OKb@$MoAdPk|XfU7MI5@ zFDA46p%E^atyYiKXP)-*bn%_DDt704cXumuwxVG7MiCTjpZIA8d>DyogFyAMAye-K z3VlnlW)Y*198tv4*s6eG?U&DCT!YnCl6XC`GmneVTpdX#Wi=9O4~UNBugdSd_ULgJ zokW7=hdec=Ibf0|OD92hP4-3--z!lwbdOXTW)6dLL>T>=zw#8934P7UEn&VHO*5mG zg3m;Mp$}wLl*rGn#2uW^o4Rvwi;8UGrA`&F6f|hx3_-~6!c{d$9b)WV{Y3wn3T&x{ zIw&U;1A6?2JrY6)&Q~K>wr5XMqkKi$19X-bcI&BS%s5o^2e>Py{16SK4+cK$d_-%% zK;LcMx4y}^9eQ7j@7DF!9D_QxTvl$F&w@Jf`U5f;x|304tlO&$e)jJmLTEM`N0t@< zDDcm>o7o2AzKW3xb5P@jm0LVU=Hq{71d~z!k=m3n=6A9~4n^j_E^=L_)<@ts4i%ARj(T8rOj(?0b2B6XG& zzr)9-^J@=2j%;u3RgAqpSuE#$i-|Lo8v-Wu} zIRc@TBIrH1JP6@ z=ghT`*hILt2XLGV{;7cEGt~$VcEpPg_?$>OX&YS!?Ow1()?^9I8M%!5`6SmZ{}5vf z2%~p7S2!yXqS#<-4gh>0?`uC7(eTSDlLb5-p>qY2WefeYH3gzwq<&lnx9C`FHnc!IOagvAnlZp+IwU zHH1ujz&vtR@%m~E1;;=K6s>2wsTS<+)|1?cM9pgS(Er{apdzixR+&SOr4qZ;Fc$OKmd+8~_008vkBR;#F zZXaGyuS*_ONun1W@-|VM{8@AO%v9Xz&_nXWQe zd(B-tBwWL04(%xxTY2Ch9v$a{J4XQ^4G!oDZ6oJFzjp(^u$_@o9Hi0TermQTw^e zUNz;954&_@2NhgMl$Ud^wH zX@3aL>sHuQ0aDTMYGp(d;?_Gg8cAih6zMl-Ae*r56VCp+8-_qdTe2D%rqIr<9-d-7 zsd;u!1TNC&MFRTeX;p5H1a=}vY13b?v25*{1nes7Bmsq;J6t3|RZrjIAVf;aN$JYnQj@`9?FViqbZQTptc3u4dN@9b+ zxjW*ATR-K~dG^c7+ev2f%1D0{0*dfCh;X7lL_Yu%BE_a{2!HC-ZOD%T0QM5_fc0bt zxJD1vcX{ZHK^+6P#gN220Rl^Ay?YG{fL zpA5sGr*IwyfH^Yr#6}A{sh9Km0@7Xy@pILS$k(426s^PvP18qrVK|ZJzeWcIwBp+= zCEiuaoisHS&s6D<+w!-oI=%<_7r?EeWGCmPy@_1JzIN13XYiH1 z<6rqq$SIw`@uv7;j;itN*U4dwW1G_l$1#vCv!gtES)h;U;HTzelfh}xR@XUs21GQ5mjycAA>iO(1K9; zdd1iNX%`!Y{G17o)%JG81%yLapa1{@+-Lv*0!RP_$OiVLM66z0^4TkMG}h>F?os1Y zQB8Z6Pgf45R7rOiXBaAt1GlmdLJdUTpFee?HkUCg9AqMNY8sz))rrG`b8ot1*EAFr zK{T298prM_`oK7|fJDk6ey^d+p5@MJzcE-1uP?(VHOZ~EXnB&_KIO?`W@CQ+%G`hE z^7q;+TF#w}*hWaeDkIsn5nUP7VMnO>*?t|D6g;1ZNZQ)+4+wO`PSvJVc|EKT{K$*X z^mR4Z@=_-Mh7u{bz8z*Ulp)8&&U;+6WnB zQ~PIpi_?XVE(uBpc(@=U$IY034vM;Vl$^ANuNgQts&qah4C_0gy)?RKgw&MGIvTFe zE^JTzN{o)+i6F83b1n;jAWY*luE@+dnIZU$ug;z|vYA}&*iQ~aVek*v zdi5GQ4bDa56CQSRa6?JBz~ByYU092Sc(sY>pa30s0000NZ~!8NEBqn8ARykd37mcP z#<`VoR^~&oQvzwU0eL+!Y1J2>aZfV%#isg|?^>hxOTx zceAwZgJ|6XG}!O2J|Tgu=^zE7Di&3|ek$H4m{IHZ@b)=KJ>{cyodd7<*vz5Vj6#;ejeRu!FV}FVUy*+r;05S%G%gvFCBFuPvQg#NGe-!6T z(D8!3QuC;l)`#69=}6dOEPU8to;*wg2d%sjlnH!8>clho$9)j{_Gl9Nv z{n}p^0066r$b-vB=tqS)C`}zef|jLK4KA!{&w-z%5Cm8>4f2Z~;rM~cR2MS1wh2p@ zuZ3fxti5gbs3b*l;yF1GZAu2%nJ1}=ZqiFU2coSW+@=fWj-4myAYaB6 zVe>IyN6~MxS+dxhVDhT?s5+dFOu%eIL2=21T}`kJK0%A4&w`VfEx7rgfpl76uW&9m_xXd(L<00vxz+sv$HPn zNiC@vQ|#hvU3Iqosm0&+=I6UKfasqdKg8?H*V+CFs=$R%&bpPv&;S4w`2e$62Jn`} zC;%BWq>xmd-URC))t+>G>#)nKZP;6ww4g3w^Gbk5R391&#k$ruFrrZw{F(!+?vdUG zlnol$;flb(hO~KaDbb93y?`T!|MH@|O8)XiO6^%lwIsL~tb8x= z=gE2Z*H95hJUzz9( z^UQu@F42n=1`PuvSw{ko2`?c+Y1hnPLEG}Fop!wqb1#1Kh;#gIWTS(|(+LW;0=Vo{ zoAa%#;+{jmm#*Ss7y6(>@y5p%tR(9B1fQ6<#WkJ9*Z(Qp0Eqoi!bFz=BxXW>Z6Of! zVgq`Zb_7Mckm8xgshcC}W)C{4(q%FXT4ZSbj71W>iMw3P0Hzr3%}oxbH4?j6>>1D4 z%dv$Z(0Kj$b&BG{d3B1`7}&2K2_=WCpa2BI4g9aH2A)84G76^^hZu;!{7l+DS*txf zsA3kW0r~Yr-k)q}UTAxqb zB)cO$c2C_ozplQHv> z6nG($`5^ri)uUH?9p#6m`vAc;w|0j)*b*cBsB-P zBWiBT(iqgofe$idvMwi*EaM5V(JwEdeUraTFldW*?m(qxk$Y9ksU?9k^+6~HpaY*< zuk)~(m-K@_IW0C(?ipA&GphiOJUyS#eaj~HNvS{;ZE~OjQdC`vu35FpXaE2aW`SV3 z6jBBh(x75pTd8r&U)qPo{`k}LgD1HUFi#2W%nzXi*bs5OGu_F7b%y{cyU@_pr>_(f z=F9zh$=EdmTmTtJ<}Mbf2UrP^lzZk=Fy>h^i{$y7sDr_O@gGi4hp0xNkvHs1OL{pu zRqvwG>PrvU7!hS!Gtf|3njQrT`UFEQ*+jysTWJB)Hy6`v^}8l#?YdZqg}-DwUg8$h z)~|5xB(i5w2a(_e{W{HTz~9pPx}%9Al6Yk8Z*9_c*KVB-+o=eXu%-G0kdtBl*}(F2 zyqk@dsSb*r%TK8#6wZ6;WkXT)WcL;AQlbpeWEmP>u(p@`iWPZWGFKlzpUE`^VHZ{d zSp@XZ5fU3*7QZbe0XbtiVEBxBfBEiDt}FzVsAv&lUZ2C#j$}gz2(|pii8;N$(+Cd0S1GE?l1sLMg%uX-_TL@eWB0{ zvvao?G>|zUF_FoPimrrRk4v>0fwTgw=CYCp2`b{S;a#FlK+bNlf#}R#=Mkdk9%iYE z=Ji2&S2=SX=NsL-Vh3;0IoJT3N$Zdt1WSB($JAD5^@^7g&}JsmJA2SO@b?(-_P{#m zbC6X&d;2SBKhB8?K?RGHm#dK&3pmm0@kPA);SLnX{?a@AA1V@qqGoG{SUT0Vhz$mB z!Cvxqp*P2x*?XJ}ZCU<9NQN^`#&$3>408AIxprL5XJSYl^JS!xsP_4YAr&YOD#aGJ zrlNF(2|aB8ZtBs&1bSglS%3g7D;`0Q7drA(w90_ikCD|(u%3WIZhiqsCvngKjzWoe>M)Gv zZZa561|;cRFKG@5xGMm6I4HW5k{oufHt)&j^!m_L2y*NRZvcs6A{3}+MLihtiX!TW zdPo~9Qt&zRSM>=4EqU-)+Co|e($c6SxcBpLtG+Ge)VHSu!}I8YXS1TA28i*W?QjEu z03@MvC&2zJTrQ> zpGzTt005)N2!#{FF(Mu@JoE4&jw)b-P?tY4U07^shY8!YqfF^3f|C3PpIcM+fCNMf zg<=BVFjCwLc;&)}sGh4grmNh#9)5=u$lkjRUOtLI5@m5_4U?|hyj~}97&9f=bB>_^ zvqNOJ<a$`1QkGKS+EtnEBDyqvhG9MU-Q=Y}OAovQkIpY?9#>SY*(? zF$o^EfgyBK$t^1kAVg#(o7hCNPr#$GWA$hf03!w34=ETi& zHk?M!Ka9kkvDPjnVjO0*7@iV^Zx@R>!t3>cOn6#VqqodV0JX|NfVPrNc*V={5&^3V)ugm~b(Hi(dV7wscQ=aPup8R&T%G5Pt_=9lJP-v*HYLmA@(1Jze;bW&bNc@q5%1Rah%4m3tLkQyn0uw|SL782c(pg9W?X=ZY zZjVFUx|{brxZoc&!U6%!f#Z=e=AbgQb1fhilq<%~Nl*B?t7}IBZ9E)6 z?|Um2g9p08EO^`f%Kj?F2HAITzklmS_0c;A;Ac_{2f}-f;m$(rL`6bbfWb)9i-Zf1 z2)n(DCQ81|Z^|hj*#5G*Xz#YQHCeH|M*aik{%n0fj*ct8KvXQ^?pca6qZi{R5WN~34D>ZtV% z=y;9OK{RHJ$8lyny!^9PK#}OrxGIFze*7IE)D$GC1KH&L;6~m+aPV zk?)ZVeR?=h49^auR_9%?S=R;$Q$6DC1?hs7>ZIFaS%Y!eOjT-Hr zHy|H4ka$}S0I$xeIkDwLvOS#gXe98OdZS>zAiPV8h7{0|gp5_WrBjkDP2qugH5Cb4lp+ z^kLqSRnjpUD~$UUg47)C)YqOyKo|LDUIC1Q`S`pw2WykX$0PeG(ZIa0o>pG)HuH3W z`hScNr)fu0U0VD+D3-L|N&JWfhxq63)c^nh12?c>rzb)o5rJXaf|7Rgkk3o3;a_lc zr~Kqnkr1K8zPJ0SuA8#x4ol*RZWXH|@SL6#X#d49Ea;~P;c7+WD$gjX1Q5y~h1zq~Fbjo<+oPBfiVIH5S3=CfL zm)d`iHQRzzm@!Ei_c0XQz@oBV1^CAj)P9xe3Vf{v>>JpQT`Q_A5Rz6 zpAiaH+>2DXF6c2Wdr05My!!YIMlrv4 zl7mDH zLbT78Lo`Pl5$Vn5ZGYrR_*TD^sB!rZ6oBW|Bd;pXdY;6OAW0hNFo%JLw3M7ZhTAz^ z0LE!cGf{m#ExXGZB-63zIH8GjGqc%6lvojsOx7-ZZ%}PHWV^PVX4>qMpF!TGp=eql z0CUQTBQyJ3=SnYYw-cJfFEjGkq>bX)Hm7llkX6XPRp(ZK3dl%`7 zWC^+#^-4;}=;CNa>tAuAwLsEJres0V$e}j;k7+BIa&9R;Jf=Zt$j|WW7RO~K+Crj2 zkUz5soZv|T*G!lBhA-L1;QaH9&p^7nNSAgJ z^u6@kEHA6fe{4q$6J}BK?QnB8HZc(ettlE}(ak;v7l@5HPel@0!ZxY?Lp>buC~l4} z%qSg{A@&A~3P_ygsN~a*5#(QU2vLy-XS6x|G#eryv@ zdh^3CKx<7P9hs0&zaBv7=IE;j=sL~wefU{eq6EOj{7uo(+W;-w1I)LZNbPdUwlZ3J zsgh^uBt5a8eL3l)5jyyepApJ}_ik9b#ml&4NBa9%I!G zwqav$pzaJHg0zui*&Q~QDVq48O5Z{$muxnfH~%A3TeUR=W0NbyxOadpHJw-nW8@8L z$;nau4{Q=QuFDG4TpizLGc(|`zy|5oFw?IYIMitvRqs3~_@z95&6Qx#qKGw#a8QEd?SQ2K zf8-|p2>y3+C2>Wj>{Rtxp|?b^a(c7NCP8>rYt({e_ixo5eTxYLT+A}dnj`Pg%AvRe z%~AkWH?;Zs!Pi^8(c%$U) z=pgNEe8qq60;CmMc_}`j3+P=(f@7f91G`*NP6uN2FU1D%Gpq(?G;)$mNQ{ue?NC~i zIVpc7IVg+rHpk1n?W&KsOC&}lob#Iyhgc&%I@i^qsdDo5^Cq?r{o6$1>MpH zS$kII#{4eXIxM~;F;hA5QrXdMxa$Fw9GgtNirZHWZgm`ox5QWyE)IoQsUP}%(PtHj z3rGUQp^(iKZnB`XZIuc&|~;z;mboYA3#UPyhz)4gp(y!$szGD}ief zpTX+`Tk`;F%27Kv*+?|@^EVM>9Ws5iy#1m7d&m=At)*O|#loKDVw5eSW2If3mR1@H%TIch8QOaL}wu&~wf!4+cxsWln7jz;W$ zI+T^cvj=35o>?}GuY#syK{RQ9{Zkkfc8)b;hM>|?xiF`!TUi*I6Y%@us4kp^FX9A> zmnTmyOKSHsyA64aUXnZHodmw7bO|_;i*50l9dXARDlWfue0J7peVGoOCTR4*;>8&o ze_|wtuN4P2vFO*;vjNiJJ-7_VHcZW|wX_~DZ8Ai5p`tbTB5m;lPlQr7TKj@;VreHU zCS{tq5?R@@cfwdyK8vIHtW)sh5FUG8OuirO$LATv<*0bYBzWU%j`7!~-9gZm&bVF}IaM$FwG<>PWx}(iopMC|h~zf~Mi4m4CuIHG_xp%Y0V8-Uh5^9)V?N6N3hF4|R|^ z!gBMjKw?PKUrs~)sd&riT@LMxc>aVc=6hqR|d^LX3s+p`lMMjPi%yu-`f+s{T!&YGmn?Ijs8E0*}NLKVN)FTF;_40d&&$!1weUENg zOwxb=0%1@p29FVb0o2|jlaRItc8iy)f@fng)mk<7iAf3>nGlSm25}wcwKw!z;q-U+50vfgBk9*zU{h)Rlh$Zfkma)Pz z7GCCGOnLlMX5Nv3<|3-oLzk1^-St^ktK~;$dI|+WP6VWyC^`6Yv0yu4nn81-YQ3V2 zEF+*d-dMwju|bkvSCH}Uf>5>9zDlI(wft@6S!yu!WsX>~GFMIuabHIHZ~}W{zU<;W z#{}UK%g}}D8V4`b7Z9_S7F{g<1)P)1eEu_}LZ zrrdRT1#={Ux)bbZHxwb48tOYtMCb(q;8h&}%QULlx4AIqHWc(@pgSN&N!^vt9v#Lr zhF4;@dlCOo$@BPwzbBxhL2hNrZL+9t@~dlv?6Q*JAH0Ho#iA8L$CEYoVJ%oq0+wdA z{b_3Vx3O0TL012}^$z^~g^*DzCx2Xq5O8#0|DsgeW4FRb;8AXsipX+atQ&?1`PiOz zXt4h1(IJ80;eZ5wqi6U{mSAYGQMTvCy4mC6$L-#ksr0jaqO|}1Rp=oY$= z;Wlo57Fwk@6TnjtVw(io_L@G4knR(LCB-WM&)UsOW!LY=#{kaQ4hRTc2^)p4d+}d` zNia#CtodkUxAsKC4F)#FuHNNQG`>kg(mC>sY5PURr%Ak2*U>p!MeIZ3aYql>oF|3G z{*RKh{?~p^!lsqOR3;8brVKdQT({G3yG;&lO}+pCBR>G$;O6^nL0gA1CK(AT;iW2k zwL22;n`QyCoFU6g2!wsZ+vX546$HubVUU3Wy`u)0u6F!~y!9MbXR{0em#cJwc&YOR zK?lX1G)EGD0|$ozslox^<9cg96efLflGP}~H6am^F|{xc#{{TdZ|N1LN!z;npq`V6 zXsnfBsKFW=DC`W+U3d4CTXLo`!jG{?9Fxr6MojYLoy=zU@Npx>KyAp6fvU+XxZUHk zDzG4!f#}tY8EO_vd)EIzgM-E6R;o&#l#s__*+5^ z%P|=0XDU{0hyX2&jNC~?=uMvg003dl80qTTl0tn2`POpO$>@UgGAv4}9(s%lM)m#t#?R?;;MAd zQ0Adl+;(0GQnod;C!l|}kduRlMBVpT{jFb0C?gU8@NvYF(+W#(VB${Ba~UjuJ(>Gv z(XE1;G3+|fbM!mN6Df~J0?CKmS`|%^wj?rur*!-TG8DGtfbHH9ajei;P>dd?`{oUx zcqZ9J$M5Y-Qosj;v?7ruKV)mgvuU94OmXryVoF-%T+a?>z=pQi$lGwpBWNKzO7tH! zVI}t`=}bd>^&oL^@g+g@_qn$_HH7M@mCT=(L!sdUp!fD*S6Y`GNUd##WEAjlM`slh zrM6pPY~NcZT!2yj%nx)HjuP8 z)`Pi%DHKAOPOQlxLzZ1akDVs`zL(Z>1uV`OOD;(Q=#v85B9em0Qe;^qzIL?qy!q zLx)+v+{^gQK#(gTx3k^P-gNp>+neLJ!D%P#v9u0d=d}$)IHBde-tiqG?dx(?m9sOk zkVD2=r4oQryZT2P(B2}}@J(*NogA>w2P8>C01L&Qv=a(FY(QSpdo?Sew8Gnf0006n zF`kE#NOuYHu+>Jb4bT1NRzGf3(Utd$jmE}nkgyUE?QTm9-! zxN^#!b}eQ}e^!yYjS=R-eVYiP4piYY@-3<4+^;+fHt+jJ#7wG?rFull7xaco&-f;+ z+K>lW;a2dkwXbL+&D}y+f1vsO4zm+2m{P&rLx)kwt$o3$aTLPA!>|yO=kud1!(5aR zPpieEJ9(wHZH%0C)3*r$HV^30u$E7TCAraXs#C)#AAx93Uu$N64F2UQyz1XRBqzf5yfkexW#3|(KjgH%k}i9DAbYRso

n7Wv$Y#bY+GT=uA%DwHUhYAJ$TK{gGsy8rtoyf@uPi7t>?I?Ao#RV^U8vv zR7RF+%@)#P5Y)u#kMe#mv6gpU{2wi|XrKLB-8J{`sl#DC6^Xr`Io!AB@#ku0O5#WQD&fAc>!2eKzmk5j^6=9c-e}$00E#;f_ z;Q^pJybQiy761Sh87%raCap|B1l&btTNWZM@^oESxzP8n95J||NbfWawnj;MF zD2gAQr`5#KTFDiN=4~?vp4`s&bOR)X0epu&lcQNpo&)~sLPJwwH2n7b_#+Vc!@%0r zdD2e{I{3*F4#v7}%|GQ>QWq*(E$|8`>s*jZU@Gru%dtoL!XlEgaWv&`SdBLJd;rueLpH>*OR;)-2$D7C~vTEN6gROHjSX*&C^MI;0 zD1Bm7gLCkG)Ho!s-}A2c>ycP`i4S&E!wi2kiDQtdR!kWYTd{K>9~ij!JLRu94n6M{ zzfVUB+bG`XD&RROH@kB{&8K%x=c;n}1IUHw?#DdFRl+Eq**Md{Q-!w=l}oysb?%s%y4Tjxd#JOLI)90c3kLJZcjgY_-3=4v0#*heq4( z7*@+wC>P01_%|dm#Qo^cU~_(RIs+fHQ0lEh%Kw3*AlbYP%qk#KqVrV%>z6alj0;Ya z&$DB`T|hPFuP4Lm35~=zyTE5!2SLfYx&>Vbqm}W1fk&=eVou`W*fP^G$3Lt@Z*V5g z4QvOP)`d4-ry6w?A-LxtKcZ2^4k5XH^nL+jX|2JBKdL9$?s?LMU|W1przO{M>OnXT zr34tIDwHnh_;){<#*qg2EG0HnyjH&Z0W2Vu<xA+Dhb8;6v$CZ-)d` z#!b5vL4)~o@u0O?^t|`c=ps|)ftKTGu&n6Bp|=0cQMN=IXd)w9eoNv?nXZ(mlP?!p zD5f7|Wh4&n_x&fDkb*k}R?q_2^pUh8{5D~kNyJf)U?~$mV3kJazZ2+WfDfDCDEpQD zieKsoJwd`@_NaisfPde&2=xSpMCL-9x8(O*neU$t!Eu|WtM{XnOF6VG_dSuK3C3>U z(uSYsB2Wl@LKbH6b7hqu`?)|~gkIBeaUS-o0GX|N#h%Y#msYLqvcyJw9l}^7bK;+Hc;$iPBQVxOZ z8$yRP;WWiW&bInK&mkO#m$wUqB^UXqR@<)3#sC=RZG^pO#SSMWeWIh>s~TIWfiF-D z-?u4B-WGmSuj zCDN{_X|V>`l3BoL7wy{zb%tggn-x=x{rz0J0()SgsELL;q005e@@ZpjX z)`n}jRMTp$lYLBqskRLX^{9S7xk|`w}fL;B2^XWfe9M0i!OkBhf{WlPFbBeR&HD=Sg`5>#n4x}WHRy;xh)6kR56wkBtUh!e zP;Bt;{6po>z^G+-?J@M$hyL_EX^bBOIOg5UvcdmV^RH`N>F+7Jp)iV%8M6>%jaNYQ zzo*fM2|deEMBjmLK#t$F8OH_PRgtB=tT9Xkr4faO9=pzel95tN_j|QN4>NI?UMa_oJI(^vvvIc z5}pZ^M&4Ck{bKAm-I@l}L6U@i2EyXAHVje!HK04lX**KQh#%B+*}lZ;Hw#Uwrat-3DXUHRk`3U0)PZ#;+Q^i>%sXfV%g z&QiK!ElAw;jh6A%9Y4s;2BqK^QpfZpp_(2b09e`&piRIKq~)bNG!y!xw1(-%Q?T=_ z32-~?E6;IaNEDx(_9W%+C6Nm)Rj6+zkyOX;otxO0001cNTl!p z6X5rJ;Vl*lkv@Rb88GsMO*S(?LJTl+^_3m zgk}e2%kab0U zgQX%2)#?;HMtL(*$}Az)h`CeL`hqmq*lS~+Ggcm=DTx4xuzIh%<}Dj7o!1zS0mC?U+ooPqOFbX!yKjl^C| zYXI7Coj{{`>8RnfVNbkR)k{dnZJv$f;(wT_JFn z-n!FtTA{9k%FvK(BS*#C)$aqnTh6lCqdHS-YX|PtQmS9ZaQ`BT1UNQR~{7Te&m5cjagEcw)0i233AXQQJFu~5C7p|{Y zpYU5Ys*$6T?@qAhBJyRmh+)y4lmEkZd&AU@+{BSC{tI>K$k^JKGMnanhNixEpN=q9 zHr1dkx0<5`h3q}-60%`Teo9j^DdBQP1pJ!&1A4ePY{9UG*p}PrM@J98txAnx2)rfo z4X zDR694lm17>m~qe0T{qGfmFjrpN8p423pCNj`?nL=Z|cdAx|-H#sndTVij==o1)?Bp zP+b2zxZn=CsqVcsSR*cu1guH8$a_s}&*FRYyk1k9ADyVDBlwkLOYMmM2e>h3LTnOJ}=P=4D_(0NqHhV$L&73YHYr6n84XAK~@)k@` zJ4to`o7y5gX>C|LBQ}*6HZQ2ylK-LJ&!;f%mgbr`7%PLex>XDS02jQmzig!xO?P#g zS|{Ukby86qw$HK&4@yqfp|xKQ+9J6ztxMinx4C4!bd;KkYmyT0AkJY$ro%^|oeuchz|W%3t0*rW7_b-385J~VUPWU@b}$!O_- zso%oNhdG~>Vu9aJGbC+;%!4m@Y& z=3>F+<^v$WIU=XonlgBo+R4p#lYvvrXYupae+CeHyo?|^_ajvW-JpgVWk-2D-3j`)j2z8Lh5pPX8 z(}=XVYR%MWIbNXg)k-aLmb8U2IQ4daWTDC%0qp|?D<<37r4MH64{9@HeZicwOHrt= zixE9B(0vj1nF8}fkNradREh@1&Ian5uETsFP;xA_CvvuF= zCY@pmy#S@t{!I74q@1IvFF=CX%Qkudgibr=53K# zUGGrzFc3vEmKJFrN5;^HO*qzOz-c!0os`Bw@hZ{`#6FL}sdI|ofA|YEc@TPKM;exe zTgqo*+BFVP&Ib1Nh=g)3W%d2M000000n19zvsAr7_u)_Gq3)ZG-k;MrYMsxWq(==WEd3P{+ zAcPa8F=txp5)H@v1zih$l{9F;4KrpC7>?>NLwyMp%UQx#0TI~fmY4mAgd0`A_1ttbL+ zL!Ns|=-y2o#If;H-`>Y$9YAf>jtiH0VRdrhEQKoFxJJrGEjJW*yx09%cBc>An%YJO z0hj?(8m{ab2~-Mpt*&3CfCfo;i&20Bw9+aE8Ja&4xg{V58Mwt_R}!zu+6`pm02>(! zvagM{`wFCKL-k&@lj=q&#QeB(6K}QF>?HE3X^9c9#JwT@o3w*IzuR-n2fu7%`xsG1 z|H`=9#&VkA?Q@LA<~oWC0l+ry1HvBR^zPVD#7tK>GE7EpM+3};I=x!HuzFJy8il*wli#YcZz{Gd z#BJidRoWhczM{{ko*gb$h*d}FP9m-DDC!j?C0SYE0n=Kbuh(4VzyJ~_g%hp;e^j*P z8jcwnq}|b!8mAsm3Sv;})!g)=i#z!<)5(k+0}ft|2BV;qZyS+KD#RMz2e*lcPqx5x zuFI4A=k=_af9nQ(EOjl6sBB}HbFH4VWjSQhIGH4o0jmQ2V>vYA>!8m-mm_}CNhQ_p zV2gdN-OvMtMF7OC6~6HHsRZQd^$_k4Qys7XUMKb+?_O4;@#(mnliDZn0R*?}(Z#!|1M@1H5n z7@huy`PQuvSuKS^BLr5f-H_Y;q6=hI0ADYQ05nPz+`Ma&fT(^uRFo`_PT8HF+;a>5 zSCnf^kX}E{d$w>rX~Mb< z!X}I{4Lh6sl9RXx!H)Uxgq@2myfjV-PTPr!3EQdfBDW7C({Pf(-`%5vKa1Zd?NKG> zpS~L{=n!r(T+Ap%|W z=Ret~Tuk3XpvC3)jKz%4JQ2K9N!exvh4IFKSmqfGyB9~AlEJ}x7@Tw;ltWlVcLv=t zBBttR!m9Uc+X~Jq+?`lpGISVNpkYN&;k1X$#AL<4={#w-sMHlt@!3ylGi*?*8Pc(! zKU|;#py-QzpQ*PfeccE8Eo~aQ#L(;gR~f9lohw3iL8b}3I7;P78C!NqzE!hu(uvbW zJfzK|o%&}N3V-V#at3$*K>)bae(x&BYm$gCP)2)LGOizQSK_9sbrV&!khML*^qr#wYRNUooMU_J!9SR zKO7+u!^0WW{$Uc#kF7b2YxSaYz#pZS4cx69LKmY&$09%wt>rUO&I6cxZ-+zpkPMs@%2*>xl zWpV%q-Pe)#EbF*7j#746pL8^?#|#PehW}ZXEm!-xrJn~%TTk!|PoYEb9Xg&Ck=&9& zPe%@}&1O!+kv56Bh~8T})tF^fg!?Wb?i&-fmJ$cwZ5H!)BdNN$%1zett!e~Wn7|g1 z_D1~*c%%Wg)>X$@IdM1OGy6|r!zl7Rs=yK=!@1HF}2ASx(hEM4B6`i z@;nu>B*2bLK1#y8RXBWK-9vZWt!cBbsVD{F9|v%z$g6y8=Yq&kwME;dcFb?k3WYYf zF;!GWfp`jdtctMgiFHaaw4t!*ZP@8f8L5~6dFIKb{BBw@-tSuEq`Cd*$qBbn8caz^ zfjDsGE#Sv}L4ayK(r6yxcZuK5C^ZNVslYVUhz@F5mvDA5hzM z(~6-g&pR6YWn$aB^`Ow8Ehw5SZG|Lm>ND=JP=*)&A1icKveyK#E7E-XgQ~UK>%Yp1kIS3wG1yu@#-o3wA z!TJ;cO%dpb1C>PkDGbKWlx12ocR_)Y@fK9YGK#`0R3wPy`7tPnqOn??oLNI+{4+V3 zz}!TqcHJg?@<*{*{IaFBr11IEqTNg==XzA$0G}PpyXol)wqPlD8xQz(HvkO8xt!mJ zi3pOFalOncm=Nprsh`zz)c1}aj!;~nw3rd`N$0TdO-i7gZNKH~g&41^c>$;ZFyzrd z3Wzhaf_02wku{skn3Uf8UQBIMCik9#B?$!eKQuT$q)(HF9=c|NLr=s5;RFZ%>@&>c z;!fNV?!X;N%~T0lqUVUDeMim>3ovk=oM_P^7+t1rl^cZrjFGt{7H^}t{fMLI{{5ybmh!%PzD%7QIi zRo4yVls)CF+E2%LrXT>sh2h6}G6WovtA_Obfj^)Z;9y(yO`~8yr~&&I+w+Bw;%KDU zdNrTjC_b>wkwp-@&wUbn)_*JV5aG;p<+p#?JBHX4!HAzVuWP$y4?b&siP)uOjOe6( ziP;`HubtK5K@!Su%gEgu;Sc};;u_~+f&w5&H?Q?}j*VGAs$(Biz7FU&8&3!qGGZPk zS#DZ-!bh6D9ddsaI&9l9^BhA>?9dCAo|n$Rt&vkRfSmSLl!BB1i*W}V2t`#bCwb}* zCkMr|yNJhfJuNdl@pA=}=vwa8kI}PE`LeBbPHrA4aL0|0Z!P zOVHd+Wh;6dkDx!>!NR65n3mUMZ(goV=!35(^)#Kl7yPZH_x2rhDimZsByY852xErdtw6?h;#&XRrJN_49^QaJ2PZLaNqg^xSGQ4OgoDn&GG%NH;`Nnb}NH5g#E8Cc8 z;VK?i$l>CN6Q1giRC#`%mV^UP`_GXVu_g_Soz=7XO@XHEYTW-q!OQuT5g*i%r|d^N zOfjtP4{Q^~k>J4SZQq1^)cQdD^P#T+xgKv;hz1!N?!gs9UfAryH3pkJt5)|I*)Wqk zT&|25kzms2Xj(<0vdZKFwe@R7D10aZNrCO>^dIE@zo3y4r-z9j9%J@&g_SDfo4M3Q z-~a*nB3&JYv|UTK)nw?nlRhG$2v?4w{Dz-I6*Fu|*~z}}Ef_OG=H?OUtRZ=#HD*gM zYwpWX6(|Ku{^?)C-((q8?LsetrWEUWHll7r-VpPvR)GW0Nm|h;at&d1nG}&?IS&LD zHupX-zOno$$9bUKJ#ISJ!FFR-s=gf>%=;qQtWp&{Y|xt#(wbrr09rQ+Pxpxk=jM+- zP>J|Op8o!mxAchb)33Z|>RU6NPyn@)?wJw5W?L`C@r|C&nY#n>FZ0+dmz?aBh9JA2 zTk?CpD&^eZ9<}x`=rh;uZdM;8A<0`mhHXZK)1dMWX87)r0RAXq{+zcm-T}JC^7ZYA z?JKodZ8p8oe$I}8w(87(!SU`(=K1#cj&wjr68gk%RG(yoJ9Olci&SCPbvANb4?op= zTvGwE)svUcP5OqsK~*0qds3du`#I`pdv-$>Ca6w$S@2e#ei%O5rkq7VDiAcZ57~l{ z*cu|3pEp>Uf++cu_VKDc$8jHw1S-=00)VYS$7L{j#`FU zj4b+K2+lDn8r&}z9F9fC5SlSl@68~kZf-=>oTGSRR=q~&zh&iW^*>R1&9l8b)ou20 zc}t7An)I-e%^ug9-rMl#rvFlP>}TJ5ki>;>YKIsE?SQTV?Cel!hr^pv$G+{m+rR(- z000A7&XKSN69TqVY9W7oAnjHjk^E%6XY!bR`t4BRtwz0>564RBs|Rw0RhC^Xv*y*61d43Y+-~?0Fi)x zzaQmPU&OnG>&+~2Kl7s5Y7FOfG-zLzgim&LMvcFZZH<2TYiE>)cz^R0?vc%$jn&@y zy6T_ga=SO-iFM+Ud*YY;$#sRWGptBEyD64Tbuy>~_D7m^@{nv@v+zygM#0$Q=Ai(N zl(nXqaRu{Z&>qx&X)rYz7d?N;KzLYIScljg6+%g|9bIFLV?Ta zif9bMBpU6*2c9aOd;oI;19q9(qRIp&>@d@;|tBEV^YgfZUAKT|7i86H|JejDxGobqf55b zwNmQlk$mUBU1qxstzrnO`#s*_X%C(uEEQPe9P!Bak>y@dX2F=Gy68M?1XZ+Hmj4$m zWc?brmBn>XqveBW{ZXF%gHm8%_+{PTyM%|pLlUs+N0-su2K|ou%mpz z-5xcZW49Y;Q%{CHb+mLuixmbLoG*Yb6H+-$7FIqs2sHko5Lp)2_kIrb#eL9kVWW0`&}sG%vF_yZL3uL(*<>pI~IOkLjK zUING&Y52BO)`?CjA*3G7 zl;D)@kxK{ln_{Z!h=+8v|92ZekJ@6Vt2h9mIVyTP$3q;P4DSm~1)X>^AdRC9Gv{-j zL@l%L#O+2k0J`hH*_oE6sGE2xfx|sWaJ@ff!$N8SGN6bGPHZ3~kqG+HVI4|;o24G~ z0=-w%l^#a+Qw%`pZjF?RH%5-;9;-?MvcN%$oi+EIm-#g4jig;E{17 zftAXN4{?v-GV3cu|&&;NerJO z`=m%t2Q{p%-2l%Q^NTP%@eT01`+cLoG?4Cqu8U0F~EP+>wbd3yDe9za$Bg7J@alxcfpymtX?EHM zHufDM`ZU%saA|on3Xdm(f#MN80kPr1)N$G=aIu2qu)9ai?OefN)v^n0;L1%WD7;yR zDut|^>d$qD0ae{lP1pX)Y>j-V^F%^tIC$iu0}W&YbyEbDv<4v)>FH~AY3v~9_rV76 z3PZtdyhr#lOul$9cx207aJzsdPmiJJ3U7n$u14Iy3)=W&vj55H{QsmqE^Li+o8kpm zwnG2{j)%gFr2Hyly=LLD02_nzM^jR;1%5*tGg8Hb%;JI~y(k6I#Q@pc>>bP16=gGI ztoN135T8D{Vof|K&`wCvpJd2^e$+5gP&(O?crLYDxqftyza<>jWHwO&ucoS}6Stha zrLiZ=w{kUfe;oc0(Gye!>Jn3&AS5xP5rP+6GM2G&e!lFjiyUU9C2;Vz5>QIoo5$iA zZ*9s-{>7T1Uuxfb$JP}$UF)R;A(*iyIH$oapnRgk-;~yrPi4IBgV>X%J;quBryo5V zt=U)+x{F)4SZpGkfTYR8zoqH(zOkDUTY9>sNFS0*fyr9VnULU?wJP>(dskC|KmY&$ z09{^W?>HB3+F8wvfkyrKqf@etBHY0wOq>i@f1VQiIBt3so)%N^R~jB0x}fi3aaD39 z=$uUusz12sI%g-c`UlDY2q1N|EAiWFDfnFsJl*;!s*(zeBNohs%Oc&Syxc>hiTdIz%erAEjycMfdyR zg3X7bFb6Qsaj@dae6+ua0kIJYfwBaHybD{ru7Ja1bY{$oci43NggZF|UJx`DqfLYp z__a^VwH!V(=~v)&QMH&6qDlM@sQeO#foYL_duYSZyj@tMhPLiL?&ta}r+9{S?)Hxq zP{MKwwT{1iRDEiETvIV&T29||FiVA?$I@F3`<}X3rUiLrk}at6M#tY`Wlt56PUpF_ z?9-~7xNPy$2;Pc?09}sU=E$z>WJ$>ta40q=_7|H*DDf6SuYCh1^7`p_chEypahpe# z31kd`WqUP}at7agL=i-+`;$jIgp}TF=1>(D25|Mj#(BqV)EyW`PK#KpXsl1FkS`Cd za81vDJk_|ZxF}M*h_OHw{8ljPfS(mgd}C5FFc4tG{|Sjb$_CVrSOwPdzO-eS1{t18 zr;Wc}upSaAkY|e-!MR>^QA4#K2fi%a?j`=)si0%26#JI=EFb3f{wm4y0f=lK<(>4y z^Sp!+t5=E_6I8CE;t@Kr-t9R>!8Cwbsvb%42ZR~LI*rHSsG3(a0QE+f3@tzgFO6fJ z5;r?A&yJZ!F8)+!wMr}&5FP$~r-1qaJ`E0927`DL6fI+SvwI#X-@qi}E{>!_wSW`1 zv5mVO)z9c6^{XGi?LmgUzZDc%Tnx*jb`%g3@j9O{q-&8r0Bz@Y8!Tif}u`SEHGwMQ^ zqb9SVt>>4miDC*qGQj~J!*}gO%rWDtVs6i2Lg$c7YFs$+-kE#tv1YI%8LZY^Pg}NI z=@%HrJYl5<4klc->vzfEx&%X@+E)pAqP@TX9q!m52Hk6f`j*n;9Ua+5J}pYVqjCUN zs2vyNqGQ2ba<9s7=HPCzGy`zOgiw`(6f_k@U65y9WATc(QTCgTLT95$04JRAup$AL zU`O|`>{ZWzr0bBBwr9;>P*?&2-zkvhHJ{*VjH$&GI({gTMHf-}BiUHzm zpqIhdTff#-WA2*jL2C55UKe-J7Ul%F#l}B`Ov^Fy12i9KT+T)cB5Pl*{!Y-zNK_?5 zth48YKUO$X00jLZD}w^CLd)u)QLAhU4_w5IgbzRIO)0)?|H$+WXShMGs~PzM{(>y~36nHew`(Wen~xo@Z)`t?*I2eT$B$ z@-?GKA==qx^kEorpAGKStwl*J4V>O|XPrOwKE?iUZ`4>O9l;XxSj)#d{IflrT7nya9C$y;Yx=Sp4?Svu(y$W zG&Q|Ii->TpFS$-oiImeW8_5-mxqfq<$f&WQ01`A7a=O4{>IarI`{aU_06RQ+6PHwE z000000HSN9UqeRs2hETR?b zbk~PlU|Yb_Py`4}6RL>N(PFZ2W=cN(sdL!X)MpDto=(pk`S>9DjF%ar_^&Ed34$ImwVxoiRN!%vKjeLhoEm$hLqT-hR^KEMkgD6|$ zWJKeV5*$4MS@pi{Y*ag8Z;)1(2(up36vzg@nPwF>;BlO1Sm1W)JVd~_o?ja@L>;m7 znU~>AMxbqNKYHK^R1$E~u`VJ@W>?0+=hVUqiK$PbRq);N;0F5u%t4szMcnV=Gwe=8%GS8FRHS3!(eH?aGrLS|n#iYOHem0>LS9x2C7OXAz`#1LGvR1f%O1LvdwEmv{HxU;p=by}k#p`^6!oJ1!o&=Mmhv^~N(a z?sK#zV~R!ld0Oq3ULESXr=}+C_7pC0+C^T%zo~kA>ZZIc*M^~_$&Yifr$?!E!EW@K z!}blX@c`xeBS)hq$s`J#khB9bHWQvwv3uX(00l(`rR%}#*a?R*Zx|s{gZ-V8d(bIo zgUa-N(K5nIuUeTJa07cAJiJM}5*0?cm*2?8L`=^K)~*3>_wHluC+DBRhF+Tlu=opm z{<2;5kQ3omO#vE=U63^lq|+1@Im^p_*je zbeOu??0)+bpi3|ve^ygfn}b1>K_Ge*nyma{i(EV~#swM4nzOI5TB6CnC+37#HZ#29 z3a0iK4Y#bP*)TY{PUBx;Go`Y8Q$cfC>v1&ZE#dvRLFm!GNtQ2 zg1{{!i$w8^Q0lAvNGoXd80h4Qb-5qnsLJjiuAOR>*1jtbuh04=NTnKKXQG+M6OfcE zoNy4s;u!q5tZta--Z@8CFM^{y?jI4-^fUj4ts!B9M46XhXKr+Ncx!ur zEw`%g*zvg1BTvTh6+NWZ`)u+oPxjT!kSg_^Z4wwMtdOqFJ-_B_3_|8UjkLGpk6yPh z=-dpP6aneFq|cM#$_}VKlRne#)XlGpwIO06$Sf_X0|G?~3r759Ac%vj`HzZ~vV9=~ zttO0=*W|!!CxA+>U3t?;(16+I`LnuE8F37WLb)nI{k=k2NR1@Iz7S3LlgpwW#9g7e z!o)AZb42H96)cJFT#cwmsYDsZltn-&7n>&Z2@!s$iDnXA;Y7ulyiI&&j|=b-TbIE@ zxNb%DbSMrvQNoF-nM(v|enTrFjOd;E&j_EaIJ{RxOV^0dC<73^J>;O+*0uByOxT{c#4?e0?mc0g zn`w$56A5Ba!wP_>AxJ38I!2`uNKkSoK7sc2LHMh4aH&#he7u)LC_jgp;JOD&Ri}$g zRRO!?L=%x6f+si2Q13cOu%GMd9N+%8Ni!9POrX+Cqe`?B$LA6isGHqQ(aP2b( ztkfveXhVUBBvD8yzBLKoO+r8+u@b}MW)0RtAZEeBkO2aroafuqfB*mh001?F3>Lzn zNkLZ_oO@q7QY5Ql0;qAMVl?suvDbUF(ysTi$r3@pjld@@u+9o-x%4kFx*Hel>2b8R2I zytpE{O*@(?`y;h8jPYtj$os(iLj)GICREC~y~v#bcsCj)engE*UvW}H;ZverXz1Ou6A+P{)%>pfFvMSs#RHdUnoZLpMj!+n zWm+^+G9BSzjQb2@Jlk=<-1~&MYZPLx_9V8;F00SzX`={RO%h_&_4i#YG8RV9PpM~+ zw7{c?Lo3ZUUo{_hjwzCc#F4PLhU$DNqquR(mKn!*8zg-Fw^&TjYBKi3U|q^(nM>Rs za`iy9iwWvY1|3p@Vj2j<=YT98#$A&A^{CQbW*_4E??6lJrS6<%xYC6}6k)SjyLDq7 z^oEh9xfF|vtZ#h~NM3+)6q6w!odf^8C#^2IWp>o!QQ`sO1(GLfEM&m9w3}n&35+oJ zf1-~mrz5zrb~um<)m2<8!GvLle5G(@^O016Rq_1N4pK0O|ClpaW4dAm7l?B(U-0-W zvBvmaWV=Qbq-_?U-mfO&VtLAcfDK`HCG7rW&2~!8tmV5Hx0761vZsFDtzZ%t&|oD} znu!Xq2ec?KgNOwOu*u0ZoS2Xu!NFFKQ@3RqQ9kfHwWz(Ky?qFCec$!|=r@LZB*}Vm7EHLo z*wPbUdsn)}eD4W;G4~NQ?j$%eX25VHNMGIM^$E4BHYDPNZ0I?Xqhf$1e_+PT$-cK- z$*xF=iGmHS(8M~jHjDyyOxOSbDAb*qS{tl>frqtUN?6NJ%hDnAVxGTLoH8K?O>>fq z$}zC@TsJ9yH#=K;$to0m`}1=TnX{h2`J zpGEOV>Asvj|5^S|xhPjJ6RZaeF;mu=1w)A15TY64T zDWp-#Ft4u$G@LX zM*kaJun&fQ)TU!?Ldsxn9);7Ta^LW2awXy_ot6;&bn*((cS5qsPdC``k5(;71CZ9( z)e|5$k{|jo#R%9SHeF&q-scDafKky9r5U&)#vY_m_76%>wHYH88WzwJ{4KH|1h?(- z*x&#F>JxgV_l}yJyx#K)SXz)bM$WcnO~z>VMMEK6IFf3(Fb5Xh?NzYlJOsIwc@-vL zhl|Ipb3C!W&bX93k>=+J z9)b5DsdSGZipF-gT)w}sZ*>^OTkhJba_4EK(A#M^mwv4-BL5ijGXA8}>t?mjKR-Lf z1R**gucfPyYK(>Vp>&%)aDXtW@GNmz^n9Rkx@zN@kq}XmEbyi+*0UnFE3}Hoz(R!- zf))7*F<+|1XWn>8@^5Aot}DC}K5vs~E$+GV*~P$F+p=o)UnVIm(Lu-a8lR^aroBgS z-Kngm+V7y*lm=)!$*js-x!KAgp7g7aeuK3YFK(BY#=Cud;Z35kr&i0EYQ={f*$Nz1 zCV!K(CJT$K8bZzKnL1QRTk^An^iR3gmu12m)S^q z&)uoLiFwqd`HVBb`uUk1|J_I*7i2PhzEhYwb7iT53}_B!d5UY=2F>>1N59jlE=2tl z+LNn-f$$;Z>o(9ZuxRMx7YIP<;*tb@OWg27WKM1=Flw9DWAF9cOzb`zQ;j=^BD)tA zj|B)u?ML`8%CNVZAW@5-vcAjGa${Fw%NMmyM@%KMn3o9evdP5OiZ z8pYd8`t4uCms$Zn;Rrn+jQRjoYk=`ZAJwt7U9E~ zUd&&q-gk?fS#D+DCm!4q`R1sa)W;3H_oe!SM(eeQ?05#6TJ&P zYwyC1Y*v?66h3*^_p^DV_^JLVZn5B{Hi160DY!} z4oPh}H!DOi>@^LnQUD&apszB@N#>*O0%dIkE!B-NrW&^=4Ru$U%|F#3o(&S8HMB#b zG5^zxgW_e_o;u(DoMJ5EU=|5UY*nSa_?Qr>funtusy=nr1aw26un*7gVoXWOQ6jAe zn1HmHA{`Rx1mGO%2-Gt5qrb!ua)rX%TC)YDE%0Mv=Qo7p8rQX8;n7^V@>eC^S%=aH ziE_O6k`UQ3ku|fvAr*d6!}mE$6l(NhJ*#R~H{e?0I;nDd5}CXR7N4_hAN>zh#^9^$ zk@#9S1?F8bqTBL(dLknBqA z3I^QeV=E@qlf?E1W`o7DT7YH86WU2<{weFh`-AW z)!+(IUq{}7v|NNCSj}-peGf$k>;Gw=ib-m(mnfAHzHLIbTBe#lB4}xGB2e@UFE=-D z+Ts!V3th+rhqyKes`MpQQ6Smjbe3-XAzYrSNZ#DnkFU0ga@qch${MjKVEz1vo2$mG z%Ay5R=w6U#l-0aEnp-dgR{FNEp+(7@PS~j~=T=M`D8JoLEgM3g03wUhJ6*SOSy_uH zg%^q;@|hDO{a5gJ>#Q+t&dW`BnFVCWD9=dL6;%q}Z{aeU9C`*nzhv{=YR;Jci8$F9 zYn&dkc z0x_sg2m%*vf8_^FF-wg)&_#Mh_!g51y7dIIcZAr#w|*-mvkvyYr6JCTI{2y`O=|ZO+RVtUZCg>a>lC@NeuWbrcL`MY6wg^J zO1DG=4>r6s|8R6!>*`q^B@(BjPam}1GttW!Gvw|1u5g2MpExZocmVzc%M0H$Si-Qc z$itWf1lipZ;D-1m*y<>OfYzNFH9@%yE)NdfcFSB^X+o(X+tAbAy8Z=Q*)r3?LzQ4a z>N|4Y)u)_m#-p?nHi>BTmhU*F8+u3#mCU#Sg0Yu#6G_6fPz2bkjY)F*p$$2PKgmz1 zuWX_8sVO!vn~%{d6K7V6-67#7^^B8A= z_46`8XVW+mu5|%9DcEo;$RUW+y9VU#N#M;~ zz6wD%e7t!f4Y9WJ^=R7hJ!~6W63OW2ygud(I)KYBl$w`ooyxF@^%6C=#>eC)gKCUw zebSL*OoxP&Ym6WQB_pIz<$+_;PsZE!Ws`!Pb<+}#UY-x`P&GfsTk^lX;EK14f&xbm z1JZYZ0000000GKcI8q~C$22R`AmMxRkU*uyTh;{E%FzX@L!j zSH>vvHahTl_AQ+F9e;W2jW>#aTFa7koYSSjYjy4-x1{F^dr->FU5#+FU3%Pz(q8^MQ%6{-L{ za(g#VKjYF8 znLE^FwK=ObBJp)zD!AgW)W+8x5t-yuLA90+#V+_|UL#0~_=TJ4 zk@7EO>b+)SI*x9|;NKU>3`s2ZW}01tvS=FH-XrrCiu4#w3zU0_X@CxS3{ESxA7UKq zC7*^CoT8?-T-^g;T>3141?LadisG{s>gGhPA?$Ere{gzpC=5Ngh%+(chJEiDvCZMR z#;Au+?8{3%jzu&==(oVNi$HqtZdWD~s z1ATk`M)DjMdR#KqZy~aQwqHYmc)tr9J5Hh9y9KD^v~vwLbi07wtM>gBqM@Wz=SFy& ziiI?UTBA&<{{k6O?U0rE8;^~D%>LjE=ZK&TmKusL7yI%pF7{H)ec%(2uJTUNkN^Pr z6f|&{re!((BH)v-E7vmtP6t&;9VS$Fp>ji^)@NS*k_BX4Icg`K&(Rt_( z!8BL)(LOD>%EZ3Vz0LP1FXocIVRtPh=tONs=kpK*5z#~KZ2u_0uQ?vpzK5*OaKMqK z-2zdnwBTC8LGUQ@7#2INj`$l?F; z(*;4C^Q22PSL&IEbd&x#xvafaKf? zwHdoZ)aV}%pnpU?d}Y)mqE&zO+fYkmIl_c9AqfjnG4{m;TKO8b`hoRPb<4wN5;UXW6k;`Y@B1g;#@lXqSzO~OqIIB44+B|0WLef|TG-!XA@kkre7CR9fH1V9?rDfr;6%koj1v&ytKBkf(T2wlEAe=g*#4M&Jo6>ws*RB8HQ zALwDMcr1C@Dh&3aqA6I3nPn7;paaxjSILRTdqn$7O#7`)oK0%=?HdnFL{P*XB13Q)dwu1EmO;76O8cVu)t$i3KYPix;wQr&coK zexh3NQJ;3g2}iEikCKA{fdr6no@bLNBGp#6{fX%<5`pm0@1_yP1mXcMia+Z$hvt01 zM(f08!I<=q5@+`qJD=vA20RP#lBo)DF3uYXt&Iw~^HcScAc}y-nxnmm0Fgj$zX!4d z^=t87y2y{5NRJZBV%5gA-|nco*gs@@PtGhzhhV7vmb*LY8xDxdG}^F!KXHVn`bTpuy@oliHU`|{&>nrwW}xEsX;9g5r5&Q?{-{6#I#s!>m^-v6^Q5Vo z7yK>T_QapD5eo39TUn*C<-jn?wtzS`nH?c565lLpr>I=UnzvyZw>DG zkM$LgoV{|XsU!0zz`&2`ZuH-x5y^byKYb)@p|xXBJBq((ZpB*kc{LlDTN*Iz+HNwZ z@h4PBvK;Z*Q)Y4F8e6H#6|;15LBxr89VkvKtc6jk<2!VRy`p0s0E>I|+C{tefiEd? zr$pA&!NYOZZi;)UPAgvTrcUR;Txq}FuIK5;lqtv)c4Jv2^uxTt&cE{<39GnWlbpY} z18n%bQn6?(eO$#%2-{`h&sq0kx^7Nk_9z7nr0{h2tpcS8jzP1jshNEHfGv$;VPTTF zw-=H)=5^uMvF*lqj^W1!o}iev;-6k;Zl!pq8c`RFi1vTc-~a%BAq!{d5zT{ZdIi2u z7n%)YHlhXhr07<5laUezbMHU0R{R5`&6J@vwJfi%$2!JwB0<;^j>Ip|C(9u!h61+Z z^5&*h&MDG(xTfLCKVo3#SUUA5fBxT%RYKuiom;$Mr=6PnAC_2#nWgM<$l*)TuO;A` zDPoDSudviq*C*E@ZDPNy-Bw%A#GcgmS`Bnfx%G}QJz_Ei&-zOoU$4#HkK-6duQ~)s zXNEi#$x?+S!EEF_54s1u{y9aWpmV5RZ$@yXK<@l;Ts!5C>hp;_0?UH;c5Nc0r0mJ| zdkzKQ{g2|yUQr!J2Q)NI7re|cW){ofes23D1IPmLQ8mu9Nu!B3H76GVc-u`vo5In8 zFNlg?nLdTOZ4BgsEELx1qr~SJN+}U}g_iF)Z%PmUPDFjC|1aKFSq3wTQ-0)RfDlEU zNa7}3QxhiC$n2)ze0Hr#2SX4fG&Nxkt=~@O|2**zDbW!OfC|69+al?02DfoU)rEq$|B;lu)~nAr$rpB+W6{kc zUm~pGop2jdk)0rin4-u0CL5!1!AV5(BrT6+qW21`$qh{}q(t|LPZ|SI_YHbwao{qC z_bR*@Pt-RhCsAPk^oJd;k9SVmehQ!Se>~f{?4opwb@)`y?@TacPRjiZ7ggwC00000 z0000iBYQ&g%_~DY2Lpd9uQK<+|BIr}Oz@7@zbjIfsp)43fndqEo#yN~_bh*lArq}x zAriivup=CNthk~>6F8f>8l{)cT7DNt8;W>zrlGJjC2VAZ!2na@!WUq9P#jM3QXKUO zT5G&&F%3&l`GY5(Q>}+TTQ2rJn5dR}YzXFEC&{tqu5KN$$ifATM;Nb~pvlRP7%)K| z6vGtc0m-KFeVYNLK+rOOfk9mNP@bYIgRR+>*ctx~KeBdzBCL?wIII5B-$fF5+$V@! zhS}zwBB>IYvUOzF*F*@{4OR|98Bx*)bh7Kdk-;cIEL1&ly^#`GQ2ptZ4=Zd}0BO)v zd)<%`1+^5<@sU5dq-)I1wlh-bpeavTz6E*VKb-#)SW0PJPU13 z=*Ww89ICI~-2dYXkGn1&=L%O*%+1hCO+B}!z3`-KS@#QNWEmEBsk`YhlV*Zw!T;4Rs+d%6>fsxI-SCCtuMv=EqQvuh`v>yRLKd^}UZ!_>!?8NI0Scaw{yThdHlj&*%))r~rdY zx6H|e*a!0Qu!f-cbe7!)o;$EPfZ7()7-*gaYy}sVr!rlT$wJTS+TP-BZpGAbRy45K zb7&&;k;rt~zk^M9Ju-t}PbnHs?Xz*#WlHl?l1lbF7&f+#WB$S*?5R~ptP=YCeXLZ* zo`6mgy^C>n$bHq$$`|_cStdx)W*AGJ-Th#oJFkSkLu#^!eEUumtk5?_(dXRSa12@& zQctAlm4AQ4m58%AdP0h}&5!!rFkY3Lg-D)48zc4NK_%+%nOoi>^ZuQ3_t@D1YTe9A ze;U;6K;{5&Gy)a}wE^z(St)bP@X<*)uZ6EPLSBv!bmi^j&rzwPU>^Kh!2k8_X)=b= zNY%%hO5&Z?4@0%oKMFT=AVFd7-kR2XG`&hV!9%I{Xz&$z2Xv%MX5%>(rVdIRWlJ+n zk2uE#zJ63)y?>N-g3HSnZS3~|N2AmhRC{nllFQd%MB*^_2(zOaKBJ0zmnemL8VD1N zLfxe8l#aJG(r2<>Qk<>x&I`z9%Xa5}%)EE?^e(Uf00V7F7ckf_IaboacG)2Y%hpmH zHBTyd7Pf`8-+tD%lq|zQW|{csBHGU|4si*(e|;gwA8AM;1g1p`Akn2O|S$7ImX{ zYBQS94_CwV4!>Xr@Y+9rJj=E9rKn?8Tel7q>Jcat%f_<9T#(MOa^D!9zruENkT3w7 zdEV$O7xaMif)j3l!(ABakNO*10fd#-GsGe&T`INJ*gcB6D2vnmy6@#=9Yl~cRNair zpC;t>!@!$rB7s&nl=`~A1oooE3Gs;V*uW#xcg}z!T%kd(_m}zt#WZ?(6_m-Ef2p3Q z|77LSGz|dB-Q`JbDN9G0$;@JMXFJ=oUq2fISqj9i5Z-wfH7#k$YkxVh_1Po6b+oH00000 z003Kv0*D3wFS~*!8c*WjC_V;z!89))<5zM! z#c)<<@Hf1qPUu?y^Oqd>=Lx8X4L_Eor%=~Qu&D8R5i(MD_kdwt?;iS)p)!-1wj4h- znosYahAr4LCx(W~w;GY>HG1WNxgJF|JgC)_8p(OnBj$`CTa!~apm0IR8am^M^9kXd z_ct|kAa26+v5I4TLv(6*IS{XNQ=jh){zhsAHyNihwI)`fBy-4=VI?mW38zK5F0ThK zePH#){8Y)_dU&9n4{PNu`cYG5936ZU;eo)katkeOc0>gpcd z<#KhVGFEhH?K428SxG&nb*IQYO%JfuxFMl zFbp=8POPbP1%TL`p-;!Y1m_cssr8c44#vvt~x8%OKS8^5K&jm9csz*?;C4-u8FHYT*dR$4}?JvPQ;Bk*p z>OJ2WMIUZ+xFvk$uD^e?cb23fveAd zuzo8JTljTlo04mBhKsQUJYkuqpOE2sOH}|Nl={!U{kgqg`kf_&_Z6FCgwV0y3L=uT z%a0ngNd#)Gp>%um7No3P5!q!UZN;PQwuB&_BOTS40G+$`E3Jijph(1LT;z`~(*OVg zQ&U5R+>I3B1ztlmQ9!{Emg-Y}vaefK$W=oyvSF$$W_3uN4!w`3;z0fN+SSd@rWpCD zsUyjSP%>k+RtTE5H(`~Ztzenlb&+=&j77IwK3<}m8mcxGDXQ^+o@kf<=XHd3K2;*p zict>4Ya(h1skoK$FKE<|jTYXQ4SkdEM)WWLR1dLRTv5wa#b_N7DQ!?VUmu#^AWtP| z;AmK}{90%~$$!8N%b3Ii*aefeD)>6C@uYBJB#x@U|E^qH8>Gf(kto|;X7r&r>q0<9K>bZ;_v3y^8ivwKLlfIB^3WE)6{tCD z!-1%MsLVEF`@k3MnLCAl3w{6fW`LVM9$T-V!pJXiol8G~QIk_IBozymwGzZ9MEo zYjsJ*7z{r@fQHj&dB6P}tf!gMLz|-Ca`Ggg>WK*vo_}$w$b^flR?e4KpZ$%H zMz5wJm=Q)gy-D8&dUU-y+Xui}-+{Frjf)&8NL{14rJ7NShR3A7H9QBXq;{EED@I`Z zy#GDL?uMb-gGnW+g)t6UYQEITaahl&@oE4jCAf!^b5 zR`}Ogl6>~KFt+H61hT8DAT+lb>}z zNo8a8wN@lOR)fC7{{N~ZoDdAv;Avv0Wk&H93KqLC-zEwB>-pd#Qtuy=fA0&N-kAL6PCTS(4ZK8U) zV^nfs%}d}BiGw6NF>{juL`E>EJPZVioSlPY>*kLT(Xcl@UqBtfDd%-q5~Rs+T~c zW=upauZ_k(m~j2>`{Fvg&Eq)`XZZPHWB4kN45x{E9B@_M(!|U5Mj*N+h5iAE;r#Qb z!HQ3vS#cavfjo7~q^clN64%FTtl(&7GwH@u)PmWA!-~4tX<8AYaK&bkIEPE!S>%;O zHE!5zYmt$))3k_G@&Mf+KX~>xgEEf|z%kS?$Ebm9xPEVj|NcdmmS-UI86`TUjM`s` zd8U0empA|SE!y%zXqt|H00q~a2e7L511+}`+Ac&E^_Ezfd)TH}mU+vp^UuUc@j1va zC?bivien*;x)_#JaT2g+kj5K-J~G?7e13A;npoSDCc3lP4HikQ6+CJ2=l-seoN~)w zL_grnD#)B6t_ctLWqLjG;K$y@#FvSXvZGOWP`vZ$*w804HKtgX&#u!ZjXn4sc; z8&CiRFvpcxj`zsesAlM#N1qXzku*|PysGZ{)vD0YGg8o}&~8c#PRvlTbCR3yE+9N| zf?>=&T_if;;&ydebDD4@19vXA{b5+yV3Fh)xR#$X)OOe5mv&Ww!dKv&~Wez!EK_0l37DM`j1VR7$WO~2!BBCMWt9J zH-}+slyRMW)U1s_HOE*dA~q2aYFloF(1#QAS}y|iL^VeirgHH;9etEO zDm_~uxYaoJ(~`jGO`+01qJo(0!Et%A=|CLH8)oolM@V`y=nnZV1)~D_PL~j)4 zr~7Sgmb1{&X5o+?aLYNxC?Pst8>i^t?`DI58^$YMlA<{2Vp-wiu>BikErm*y+sKMU zc&bh;y>e0oBQ?sX&(3ziv57H;_38IRH0D0@Ca9#gz4fD%e+5z_yQXTky&8w-V-DJc z(-4%@y@&|#M7WUo);VIvb>mJ?>OJs|#b_mVmh1T*k zwnK60M@8(rACNT&-^TE;oOt`_sRR^Uitp4hLy0kIxB$`uDxEndYV)CvC=hMbo+M>_ z7phj#Tn0fLe~e#$`$5dZC+SbU5+&>?ftPaGeu<z}2OU^U(cj7uQ4f34$M0cEdqbu5W$L9J8(F+-YEkQ5}H8mRgPc81NR- zvv)jTBGd7I%FBrU$@{x`-z53$e7>j|_Ts)gaWem1*8aD3v2Q#QZYj9ZWl^VeeBloX z*G34Bo4KVM;{}>Io~~LhuQL}6H~377NP^=~tk7#dUH0!9y#e6glc+94BKbW+@2B?^ zthHqB|Hg7XW=_P&Konf^Ldlc7RyyS|giXb)m8=ZFX3?#=!nu)l?nmV6B>^ClruFUJ zS$f0d!Oyh$X@z!_C!K!_3^&_q9c2Ps?8dPblTb?Todhxh>6^Cd6j_z!{OpnR!O|!{ zPs}@`Yf{La{&+5i?{=Wra!R07xycj_59diZ>v9ZdJIDjtw}VvfZ%Hp6t!8F-rdd$C zf5%%tXi1t2^kd&RO=yU)bEAx$xeiNqFtpU|WL%%a$-tj!tkMr`)+7i z+hHEgD_|}&4FY)QiW29xkZxy3O=^*5#IkKjO``V7AFaB|x`R}aIWFHzqO8^e71MUx4vsPhso|c_OONWzPECz(sU%(h>2d)cLmyn*d2aLb5!$9Nvm;lZJ9= zg2wo~GP4LNHS(}=Xd3I$q->mcsr)>PaZ_SCKthw{ek>Q|Rh zep%nbuPTIFmgkLsN*bjl*}8x*phzcCd@h595nvgUuB9xa6>lNKcB45Dj9v%&AdyKj zovCmx)XAq;zH;MqKc_8Ez22W*%ONCu6T4UNdka2L z<}z5=9gL6xcJ;w~^rJf3+Lh1&_bDo(n@LW;YstVeIkRw_oBdWyuy3Y68?vH_Ww7HF z$^T|TW7{@;@qL2cYnX_}Ov#@qCQSUu#8Yjfg|6TB1>ffJ<$O3z1_wADPS|4rwq%Cs zrf_}NFxTrS&#EF*h9=JS?9lnr^Z>$}8JvGJtELZwwNrJ8TVFv6^!!f;kf}p8Pib$x z!bLWCW<;L^R&yuiSKQ8C29W8bqYjz{;*6n@c$5{O5#dAk!lDqa*3;;CMyB>4iEuXkqIXu zm-}^D=$r&??i+TG!{&Vx;#ZwoMVGh--&U9ze1?JZ@FuDrnGEOMUqEgntWXw&3iQIn z8-RCa?Z>iNVSgfi`}^O~*OR4a`bu$xjR2f!cK$tS_Ed7_3o={5xMlGKFd%3?waMcH z*B-~ROJ5(c0f$8AnV~LJVDpNA04|GQD&Xv>8rz!Rg43OUmNPzyTBV6~GKZZPk0%}k^x30vf!#fKYp|O+RV1%Vq=ZYQF0oO&4jVSux zrA~uF(mN_}5UX?!8Rdwp0amEz3LZ8)*6&aP*yfLS%lB=Y zL@V%Y=!F6&NjQMxYD#3N&U(^e%Pb|2d027j;(YJzsag}t?L?~%>i8goG%Z_M=Dsi> z&a&PB0000000J&?o}h#jYrE#|{R9-flN~(n;BFi40dgSx{hX5a8U6Ksemm_+g13eM zNhbe;!Gqpr#903GlaV$cwzs=#R2yC2K*W+n5l7gd(n-Za9~&K#>%e=7tliYD9&D{$ z9D>pW04}ezQ^8aJK=^RZ2Lr7{Q9efa^7FuaKe=R0> zMV{@H2hED3`)r=^hiKu^NW&TT{Z!J3&j;Zko^%N}I_XW)MrkLMl)q+`V*(cHU8X7xlqV-_OVNPz9wfpfabwt><# z_*kFCCYcccV;Yit5D&!XU*nz*u`*jjhf)y{hdVaKLv4z$(q5J!ZPf3cA z{hJVsJe8kc(~qD_K@dRSXt&OS%C8EFc_zIFBoW8jw(a z9XAN^Tx&wHF0VKZR&5PM4=Mp{Ocbm`eO~1K9%8v<6wSR%)9WAAuF;grBMB(y1B;(m zD7T<5X_=wQ^_F*tK{q1OQ!g`yEdg+Ti0dZocE?co><6Q(AD1Czi@(#n<{J-Ge#q?g zL2z$;*j!lK$RJ@G?7kw`PGie*s*pp%QXldv7`{fe-du%^gt)TuPX>)$!=YsLEG>U) z>Y^lKY4(d7O_pv1ep$x&UNv2sKX23tUOLk=k9tr8%91ea-i40TDgGX{U7NOC&~8q* zN9G9U^jFNW5>G5No}iG1X5jVdX~}J_^6r1#fq$Kp%KjNH<-ELv8$cZwMh~1}x>x3>rk_E3OA_c4ds=i4z}nNMGSe-y zr#(^tgN#+qz(HWwU4IlwUP6l{hW7t0x3kwU?FLc@^tMlJSc_EKJuM(W0_GQCbuVvl z&UF)2mn8Mb7_&Jc3*{jHeYpQ1+=u;6SkRR|XeC8F!qaSN;YqX|74`Uf0L;D!Zld>y z2^gFEf3~~>STf@cG891Xs-(07;hf-n-8Y;zIee`jr60rN5DRiQ8muXu8G-}J^E^E6 zZs+TO_a?oi3&2UCZdu(oeu94HJah}_le_-Bwn+Fb#$7PN-@xgdg3D5A^qF&o-G-K1 z6ARnQi@Hwya{jO|Pq8JcgsNIZ5@PbB@SQ1z(6fR|QLpgtYe-A`3SOy z_TRk)(kcewXdvwQ)x1it)Y0#|R(@&P=AX_um7B&j1!CXBj0096a`15zz`72*6DWU`jyg}c7T^A;# zL4SZ8bN_}us?X=9hTKiIxS5QYuF3CV;~pqf6Ji{`Z(frS zM7TTDTQ!+m^;vCl-~Sx$at+Y?U#=7OtfWMlToqbemxX*))v!trSZ}(%J8LgdG(!9~&75}J^OIpC=R zu5&s$pVxKXAmbv^Ud;?5r`NQEnR z%lHXPu2Z)4XC&}kj37^)Bk##cML09H;P=&fJC#yqH-7apc49*fNJTW#W))YT>n`1# zQFp0Z$o4T&zPFdRe2s9#U+q1Q) zh?%%|2SL2!O#HoF0=d-E9P!X*HTWfASx88m*FGzS+@coXDi!k?h1LXMHLe*wIwBRa zpL5CJB`6kDF6x{);&RUdQcU^Zba7Q%Janydr6uqR)V zK;W`_TY%a48S0-A?*N8d7*fB(1)9dQV^b2feV4*49AWmLMDI0$%9uUlch6zv$!g+l zSTX01-d8mCl)I@`_tz1$8Q#LEVmSR+{Rv0GAjd;y3QqB>dlm76T-K(bJk#VJg*hj8(n=Z;wa!g zeLe)jNqB;I@fQ2sJV7fa##h}E>J!=6fZ($S*MSaL@#$;~egeq(?~m1daIamco)R@w zdN!GaztLqRQ_i}x$>Kq*@e}do(uj+L1}oH27Y6^3{7FIuZ`cYC<2G(v>K|*7v)^3D z_rNmMJy_2fqT_He<*`I`e2!Zj#ZUB)%xH^3iA|AF4rdv;G7}$&qboBH?aMjkT|}Wh zp$4rfu4gmo7BXT2_c}?=ls@7}!!yt*&>a^t*{8^N-t%dQpWaM6qxi0eC26}*ImC(D zg|iRO%$;bosMhB{d-2>myYbTdXifP&Iqg%k4P8efHg6g zq#U93V2N231_ceI&&t`D+rnNk?p}zW|5MT(EN6`99UxW?5sFS(^LRwT(d#FX!M&?i zKd_B=PC4>3`{K0HA7s{~?CYT`M|zf8I38lWfGPu!&GItIvAp@jw)+yKJMy~BCNNM& z0|lg5_0l&#)3{I4a9JMb3?Sw^dit#O2`XQ9x3d(1ixiV*Ds|&ThKX>I^>r*ppvAsm zv|)z@{2DEQ$(NNr9UAYJ%F&`EmX~*WNfBi+9 zubZyHrRbIzjS$Z1N_yA!2FKcaB#bJp4ukn=D@umk zjZ2v&U@3{ZE)M-#?(i(%vK-aw&HO4BZf;)nV;$#0s``6bY9%i~>k;i4HO5dIhCrk9 zI$L}w#pQPV#I_C;f!OFoZE;I&D=KUwcnB@rVrHYMFjBAV?^6tI^k9)=Z+S~+uyzSF_ zZwUA95MY)O2tf+J)dfFb;AxFTKK@R&R}hgdVhmvDZ?R)J-RBp(35ZM1k@w`H46{Y> z>ba8eGuzmFH(yvO|8~dQFwbej?QQ%8J2?Q2S;#@EHId9w0iQtH-`x$JXb~gpM{EdU z*YwQv=Mjl9fbu{&zF)sDn45S%QhQteJ(e7Rs_VZ3H9H^640G_!BagVJXcyd>lj4ph zN3@eo<$cG$@Z*V`&vFlmWISe5vbFV+5x0H}MW2LzG3-3GjLD5%Io z9rNwycnbzI?ICu*c|dSJW=l+vIo2WAI?A#G|HD4lyY60q4w)kg9uhC;79J2pxm^WQ zPvfyR!FLEO$c}vGyc75o>>l~S4M#JbnDQj#+v$L9ej<6{&Z!ke@a6{0$a|osD*LC* zw=q*sKc%LQFvMmSu55q2HE~^TJKIK2sD&H440He@YT8w^&=bb^7!kMS>>i&u#Auz4 z@zv@d?=V+A`>x^r@hl+HOM>o7*hrM(b8&w8{*u|Qn~?VY&-fJ!tYWx*gqwi*|8b`z zg2z6rlE}Gx)PyV`U1x7W4e%zNv?C@h5VJc$w`9W_&2NYuk!=iV`k;$#b=aLFiP!l? z;80#Y@~Tn+B&i`MCjcsFcGnvgO^zlpb3ffqRzpn`8+f(e!KY@L+mVJ9{uArKjRZQ{ zn7Yo=Sc%3Pmev=8cIkS{QAaMGI3e#vWM}?Gjo+em?~1koCtw|+-~Icd;6RHdYmvle zU;q*V@rbahn_v~L7zLGL#ymvY?;XVknHq7yYjaW9_)trZnV@3;?bE9k4R{P&qj=w@ zbF$uqGn;sUsPb(g;U1CS)z$=9jRo$JYqaYVz{AjK&y6Huu)n6|EBIpOq)!(T3#{Ic zte8t#B~k+@NYs;Mp0r&`&r(lH@zfcb14Q&)XW9ZoroGD#EfF9q=HbRc#tgw<2z-wz zuJap$p-{%b@a&6^4Q5z+xVq}ZTwAH5ZyzlXF~Qex1)3N^8%hUr)i{)6>fEO;Ht9+ZMxP@mTjyKvcK5JHQrnsyU04kV>`gs#Z zPuH~T`kDj7T^s&gH6;I0g}v$u_g?VHlCVE{ zt3)7kU}(AvJ=x;saLfCefer+C4I$#7Z_-t0uIhp?mtjh6UJi_$*w3??hbgE*%C~oL z@H*%PR#*se;P%@2H%-Q+s9X~x0TVMv~t zBL1f;BWfGrIO3n*;yFI0!_4gSH^OkL)<|ud4cfIlcQ`{K!G$JWU=qKAZJg9F&S3D0 z9chuWD!_Ci-jH@hqT7UhHEqQTexp}xuzZ3X{L1@X_>{yBFDPas*|Ao{#KUI!+N;8N z+n@lAspVbbW>WneIt8^gS)w;A(gCu~)woty_fLl5bql=#xbV&5zjlC6y6j4Qh3IS{ z;KYCOw9qlUBpX5gdJ1)i(JKh{9IeJ5JJYx|I#l}_Y=kHl1Lla;Zi&HIz$_oJnP28RC>IiVjaxLmM`^MqQv-O))D(|h zgHi>kIoegn!}{@pB`(9VBRt;JR9O@uMRLvYu2QF)ftn$G4sJ`5kDT8g!XMpbU??9v zj?v;%pxRTi%L^@*V55~g;J-)TJ5@KUe%KyzgCzWG0qBeG@S2-6n;sdjH+)*_x{i&7KEx(#f%WIUJZ#Iuka!IQw2##-#uo$+)TwN@slFMb+A ziy3AIDD^2l-nN!L(ksG^*QqB-?}41W_FfrjM%phfh={yfYaXLOd90uF z0$-rMD8@u4%9cP)35?UU6$f469JGSf_v`bEsLy1aq$@UNQ+JI@Cvb6UB$wcPND3-G zM*BMl25J!rnVRcZ&5vBN5ocLJ03<7z^5u(ezA$0$U+;xtN@+9pG1h2ldW zl_3JodttPX^|WNe>3f@;+8K(ZMUWw#8&1iRSuIge0s}k)RUQ9#QCA)DJz>*+7kH*BvGNc|3douB;*b3KVW(uR=7~3*sj{QOD z@c%|3R21|bebY{l6Tc5r1HPya*D{`;eGb<~1`oej!w?M?cE}PcQ3i3P5l{fUz$!&t zYD?Nl!@K-F%A*u{u{f<&2ZJKmGZvtU=H;FvP zCOaas{Q_hi`od`LLoj!WhV~`rQj_K|&jah`WE3>lFWk+B#J|2nh`m6`V0qPt^NaiHDT)Z>&l$r!(~xu}cV+po~_h~PAJHbptN8);R!o(t-n z#6TOW9>QHj~mn$HjbAD2#I$>*PWO4zo+qgUmoey0VPLwDzd+UWXpUls_M z&sszg>;F8gmF({VXxxNN2q<(tA-qPMIsv0g!>Y~P!)S8`$M?ATw-`cH>2lHO0Ix4S zy4E|I_*P4|2_@Py66fp8{b0ktbg37e$MAdI3<-SKG_03aS4rQy68$Nav+gChu-v=S z?}v`*#I7SJ&jddPXj*FGl}e9M(Bp(J`#ww>YXGO(5po@bL8-vIq=c~;-xbz`L!Nop z@tt{Yzd}6x9W!Rrqe8V#Cckyx;x<;uBlvC^#$h!iSkiZx6@Y>^pK9M+Lz z4l7F;pi(hhKN{G$=G8)x1hdo~L~6!0m(x%)OQ8u%el+vm&k96*{#~Uu6WYLXzbztw zL+;Us?U{sXq@=PJzkJq549kL3Hf%5$JGgQ=%|;|=c?$IIR=IjAO?r@kH5x>R*w}j! z`3=zbHVc?a%yBB}TO= zf8?BIaWQ~PQ?omm8~wejn?zwitA*q#uJZU}r1Vu7`&W$lXdKX?U1{UfWYuh|D|>SL zHJRk!%}<4=L<&Btik!BrlgGcXAOe$TF~Fw< z%pQU>@}I$MM-(!-1XG*&D`-%f{9Sg=;})d3223=tWulbBG9ov*<{%~(~5M1(okoU zyn|jI;5%S6f*9MbK`l9r>`Ts>wX9*D2iMAcz$5x`D%z_YT@^_l-|AoQ?4LvJ-tGP} zk8qeb69{f@KH+ZSJp<`Fx5XRDYIHxes?a9_UdR}}4#5~eYILsPreNAyz5(q*_sE3A zl-I^CD|weC@7aYxv%M0S7u7hU5^GWK72WB!Twaa~&8`GXM8n~$i)Wc_Ic8Y15Q!Wy zq0(;tndjl|{gm}=MuE6{Zg&Vda#nUa+u8h)?cf}U0TIH#Xq=#;ND$~x|JS2)neylp zhv9iZ@Gx1k5oH4(och+N82|tP000000PJ#hJy=~mh}Oi5&}mi*S)`Z^O*E;%hgZ$BXZ6PN@>gJThke@wTb8vS0qZn(-Z&JHDn5o&a(@6LgT)MXJ{BO6 z5r%g?m8XXPvL(C7e1<9KgLW?~R1k88Ur&Hm*O9#3QiYv6s=B7Y<&a{FbKO!(Q0U5e zzZzw$vZ~d6RUh_-qsT9Fr@X?pX8q(FFLcd5FIJ!^dZBJ{^^T=2m)kYZcNBPBL5D4B zqGN0W?Yulx7E68bquNy}8`=~ShNi8N&1uIlGMdF_3) zYPB#sRkojeLJ9!}CCAIX3i*UDypNl+MLq$WEliS{AZc3{9ovL&0x?%Dgz>p!<@YDW6V+MBc&MLEV+MMyAsekVn{X3?Z_ z(#S~uBbNUJK7k!!2O_YvF0t{+ly@pNa;OWhd;@p^qc~*SY=oXV1n_8(`_S$(b%Va{ z?9d~!jEf*?9%-^6Ho*W$D7=7_o&Pc0|DNJHB#^iMpwETY8zTosiw$W}yINseLL^E@ zDeZ<pCdPc+cE`tt;Lb2XA zQ7azsZCW&ZltH}z^@30#SUy!DIAftlPKCPtl%gIgM}qS2xT#F?m4lweQBPGj7yHyB z%U&|3td*-zGV-VnU-l=f;;_8-G-U9TW6uVxAJLKT7@%xYdmCClR5tCsQ@3crwxm04 zdrjN6ZQHhO+qP}nwr$(CSFKaE&;AAX@#fQb9+|DT=$Spf0KQBVtvNpNi;^K#0=MOI z{fQ2tp7E*sB+j!L#z-~ErxJJWtdtt4?oR_dqgPy$?m;I&gE zJyc7|$B1n1dQ@6gOrR3U`_u?0ikJ>s#0Mb?&27qXA$Y-SS{3U^m!~eV-$bcK3 zR_{?|ny!ri#_FrYz=D{CGhcI|d`yo9rO>U!Fg7-N?(7EeK!aFWwTEbRqxvOHEx+Sc z#$cP@N?FuVGGz7D_sfopZwcvp999hn^NIKe;x02s!4u)tWXMHGrcxBhxP_~8qo-l=5;u}i)BvR#u^NNvSWqmzdmj>fxx%B9 z1Be_(X8{?{u(+LikxmzK>)5q=j^PB8`i)yJMnZDfbRR}9l7I0_A5QhNm6{XZ^_biS z?u0&5~Le;?wZxib2|*>*s`&JJAjbIu^Y>E_caQD zE~5?K&jEVPx5KOfSDw%jfy#)d1>1yHwZmGDnl8>=jC?Jh(Dgm1a|tAVo9b6^E&g*C zbSjjDZuR=**q{mXYi^-0g!#)Obhzg0EqEIBYC_x}odG&rgaW;7HQ!GP=&oS3F&F!0 z^rV2IT;0CM2ai@0p^0nbmxURiO{zm#IplK)rQSXZV>k~=FG>-pai_}p{;rbR&H{yE zDo)5N4Y(1=9M)v_e4YibZ!}nPOT3W^rIl-_VSHPN-EezAx0F~rmOrz);&2TGf7Wjs zGbUDR$V?S4hM?pWHH6Su=#u(=@?%7^FzwqQgi@m|N2kH-RYAQ~@Ms>f7v%*$rKyW* z1k%p5|FAU?nW8zW(LZ~%?A64s)?(=QxOEZwQA}Z%QKPeK?g$z5K4tGch26LZVF1sK zHg8%|q#Rb*k~_nAjUNQ^2V-F`F)6u?o>gDn?{7fa#q|@YI%<#k06Y|nKlUk5x9C1p zk<9XE7=`6ha_jDy;j4f$uo}?w&)|j1=w`9yWr%e3v5x8K|Ahb05;w9{+v4pYOU@5t z3IIT!o&FNTn606*2mFRn#nW`n8y*uvPVtyQu#Z%)d_$hz7uFFeYG<=m&Viq3zKm%j zl|l=MhjjfBnYf!&`TT}kiJY9-Qjw>8z>2eG27l`|;#pAm64Y;>ak)z@UK4VFVg`RV z8QWI6JulkIhS(5emhZIsWozXdFBAodsenkptb_AoFkfNdkh0xqUGo{lk$mUF?-<=h z;JH)$Sd4#%dtDng;WCld2DEgq5tCEBGX>trlab5$xp=ulQsDrxo zhk$1F(=O?q{VS0jp3`2?t8Nk+(IIGKs)P}aeS8BL5S49wrt9C5{Mb{n9;Xv9GFt^^ zhVTrueV@az;`gENGf8s8^u2xS=3B#eG35?EPxOL2&;1-bXY8(Eh_MbUKKmK|EuTNe z<(kRzZf$)kwqs_EClRZE9cf9|zVDZx5vZ??=m+G)#%HBlZu=Q08RYEitX0$wqpS73pig z2%tf$%X(ubugr`qK0f@VGf;*EPDgP#!F~6x4_=!$3M(x4vK;NgJEoSiT{NTwd@Y#h z8s>E@mVV*67&|vBDn{6ufy2hh{SnZS7TV;+UuklIpxamad{4|AwUwfXp6F5FNtQ8{ zN;7+Iav7KIdd_}pF*RDR>v2yXd_aS;w;vx+HVTL3-CJTaC+4@mw8uuXT1&4$=L1cK z392TjZNCi!#Z5YDPQf_jEUIV<4L;B~+7Bu*uh4VI$APK<6s&5Fs?h>O)Gj$5!7d*t zEiRlwE4p%fD}&DKU)8Rt&60YMMJRFuX4IEHF!=w`n?UH`b+;p4qpq?&V4?ui=c{#{ zZ$qzS!%}fN>x%q0COS88Sg$BDSp8+dX1v0Sp!|o3tP};2f$_qakpGI^pZzgk7yoq>Lz@fJ4I~f$Z>X${hgUrWZte$TZyPVg8l; ziTjLObyG^j_}_cEmW?)EyIu=0Hu~llAl+{hG0((G9)XJe7@=u|jnv?I98eu%mA5Ec z7@Qy3_4UB%zdD45ypBmC6MIvD_G_OaYfwD*QpWhzAcIeYSt*--LXpJprrk~=P1gQw zfL*lr1Y;mSjp%=P5sxrq!ZvV$elcSX#=Xg23$%VeYT~+<<^5vaBa{ovS)C6LnVAnA zW|9Q0P6@vrR>ekXzJk_d>(+BN18A;A64H*v@!Q`A^^Sh&nDT*#$uv zgcF99?1EDyThIuE3`pBY0pV9K{JiaQVROm}%k<)3A(ooje9~?j=Q^=XG9Cb~)@=N} zqq(W6_3#2@^yi}leF6Z-7-DT8CVdCxC4S&caK)xlZxaGWowhx; z)z?7+A}GR*4R%-a0ZiEKPI?V02gX}_>Fzatuk;)0{yhVB1TiUh8f(md_0~gU>JCnSUfy zbr0FdQ`ywKxp+zRJN@g|OJ$8i{XJs^R~Hb1nyMoepIm8}hO7@*QCIvaVqTjrhWA{fMTDzYsVOrm94f-DN@|*l*38*#Kze zwot{_8TE+X`841dti?}DpgL$8p^tFhpO=qJ&dg4!5bN6u8*!>Djn{oR`B*HAdo`9~ z7(M=rGmwMhZfn_{Dmhd_&_v$CCX#_|M-Mn2jM|F=irCm`M6Cmq0JHWM)9>08aN1{c)8j8cz(lAyLoQA39Df zA}zRa@K|n3bUj|+!A|3}~We=FFvZoN#ANxDL~_yOAD z6Ow?b8wlU3ktGc6i#~mlSYsPw3%RrlF366=Ry$i_!4KjPHTb5nT86?p61gnpk#9S%F>Z{FmvkL_eLls3KA(IY^8up629|1j ze+Tsk0KDK<1i)vZNi?M^RL61WQ0BgG=&x;$VmqcP_?6HZ?2EF}4A`eZ97&5r`m`yg zl~Spu3Z{LA|%95e%};3mrV3zr~Q1 zpDt61 zZ|qzm;eGOm9-_%#Ldar*uGsWVti;FcgXBo0 z_s~c}Jj5b?BR-Nz<$&ijNxi`h0wA15n-?9ONsT3mgvn1l2x!?7{?E6+5obNtGGgeC zt6C0zaI#`0mnq7$b(*Qt`Y`asek9C~p`dac_*rpasA6o=`IsJK3fM$#pXXy;1#0Y8 z#zfH7-DK-A*Jja_Y-L|rH()fC7soz7G}sXQs3bGQ*icrpXIiminvDuUFEna;@NR1Y zSLyY5ep%l)(o%6s6pK&iEq_-h!D>68UMltjc~Bx9=A{hUx@bObjRPnlORFpn^6!UU z>cR{|nSGEGqg1%JBX5B_q*_w1G?0R15JK~XdmfoD7T)~(VqAdO>ZBOJDC@3njvRx` z%?`p_lZGpBH#o1nGh5fUFMgfy_d)b=Qk^V}? z*AscY9^4+aJps4EFOA<{)x$h}o!S+mgy|9jfC3A5hf7_|QT9{0j64vXq|JBE=Up-? z)z)QiD%j9zWdt}e=M|kd0TS+Cl_3IV!Kr>R-^hdoV@bU;dbuVu5no>QM6Pn~wcN5< z<&15D6XhnXgYD*)0KIiL!V=ncX0C}csx6G|tx36QY>cv-;6BZz`9xi~{7t^^kn(*s zENPLR*mjLdUf+RWhFBGAw#}}8ffEd^)3QyE`NaAMB-sP@{=zBV*u%C{US^Y+uT+y6 z&b~9{DY(J2OKe) zn#>p8`yRr2CR)<#C>+6~;8`q-{R7G%hO)6(80`=rZ)o*zL>yYoavvBOqkE5nvqp#GakUxq{CtNHV+}!@sy#WaUPqrwH#@ zOSHtjU;<(q6kw-&z%={aYsf*KJg?vYhmwyiPuR;i9UchGs_?0-!q zKx`tWO9B(#nG}#jPeKr{sddt+NY9NQ3yHOxhDq$~iIHtWayQWj2xz^RqSofUjKRXB{pp-Tu2)ner z6Mgzlrz&QsclHLOa&dEk<@COmWY-ulv?{9x9h~Xi|KWF$y18|R-**4$PQnNnapctx zo|seOR^ZOsYq$$b%t_BR6yb+kiV&uA)PgH!IL!5jA5VLE=xcrskw`2mkIh7B2ZF6G zmbtwMaU3*np$G+GcdPth`j!eH`}kCGZ!3YS*9Sn3p>&hFT*euWYViDcAFtQnXvgQ1 zVD-Ls89|8l7B|@6Z@OU^f+Wn?)ZcjeF8f1eS{oBTJaH-NZcN)4$%2A@Mzm@RBz4#H zcL@zL|LsY>D`)T7nAaKqc3&{J`{m-bsOC*fP6YeblTxwQ=rP>7FCg0HTVgC)q z4Aq%CDb#;r>s)g5Z)SU}8IB5fyR!Vc?EZO41bGs1fG8aCWT3$T5KDsEY|{O`@nP7i zsz{i$KuJHVRPbUHKnzSymgwB_U2fP;eH!cZ8-qnmR*6*toZY{)Om#eNp;CHa=z=sj zZ-9QhHFJTiUUaoTU zhlC!VKLHVpqS~^TnPO{ZfnrnO?kYqbsN_#RP-dCL+!>59Eu=0M;)kb*7~>2^V-35JC|x^o)t z;T2c0Mz4KyA%f*4k1MQ@yk5ajd)Q@oKgExAD30tCBn3@*t`M2Vw)%%GiAi$fsrRwK z0lCT3V=Mnn`v#Pt*MvBsygphsYvAfk@FbnD@R-)9M@8}Q%&c5|^fQ#sA8^WxV3@^+ zVIJOxd8gwS3DvEM(i?2{5G!uFO4m0Cq`z*@RI#$u#vP+7c&brH!8Ra+3Mn#%t7e>e zAkS+!164chn=XAvzMS8OHsJ*5866)FgxOJadR;{@NsL{S9Q97xupQE35M`aUVZ@OA zS1kj(ccMkHydgi8icN2SZY2IvKWsn_GjyjhYhBEv+xaL77wE9HdeiUXRP zj7l^R;;!mUUT+e@c=W^+kMzky^)gfA?Hj8k&8je`L3qa15M8ST(rq>FV-T7?Hf0i5 zYGKi_<6YKErPLn)Hc}h-QLnmHH@0YW>+D^xyk3Gx?zVAO?u=zlFY-S_67VP$@Xk=u zA2vyMJ5i8_*8uR3G)PvD~d^SqYr364(Z zWq;G#+&!Ns;6P1Klg11x$%H(D1Wg(tken(YHUR~NA8aOd?e@>csU~^-iKca6KSSQ$jwCv<6P_Al6!%$o4{MaKihW@GlfY>+(r#f0O{qJ`BZ|d`=F&+mH z)aS9w+>nRRulK(4Kfa-Sle*{bVYUW#j4|vLu+Sj>jOEs@FOIuV4FjHMeyZjmDuf$n z{2vK!wn-8~rKiRUP|2-Gq02fTGU##@Ejgv6!_P4Q`eadXFP3LY#OCoP32y`7#TWBb zlas0m?ErHL(OlyXGg1Yc+?uZg>& z4Og+clp0(Q-WVM=b#T!7gu>K3<(%SRBVs0B^DB{H^A!~#^QRDUU;?C zZPdN_F?E)a&?~Lasfb+4qUy;^sXOuVhfYqC~ug*x=GG@y@l<&pDqvtw_Ccgc-H6`>K^sU^t%Y)_d2{Ls^H74OvQ zJLuN{>#~K7)@#D+-PZk;ZYnyz{x8WHH@+xGuDT>|ZJqj#V@-4OB_}U{jfp}95uat+ zx@-Q7Z;(Q>Qk)J9x<(k!(>38F$5o$Amm(bGAu}ww9LED@*or-D=)D=hPD)(LRB^DshVtf{js_`lUyI0bC;7T+Q_{HU8zb2Z72p%}4NU0AGERjK@(0);b>Z~B z9Ts2SMi^t?kK4fHW4`vef?Pe`)3F-^6ziwhxY>$uJ1D&|1F6*K0xR4vvq^#^?+11hol!AZ6j7lQ!G#6*!iz}98=7q%A|6U8q zugYRW+bVSqR-q=5HO+LJ`XJ{+!!NcoV_m7G;HVQ|&K?%x=}ID0kIao5T@%@wZfmeG z&HJ@v;{Oe2%zvu*O~U+&Gi)$*xkEac#Fgv$mW$ZooAJI3m)EDWCfiCDW`1hh9AReq z5h`vh2y&!pyTlEpKknooz&lpU3ca7zIT*{$7Fzg8#^{-YIjyhv8dQU`Rm_5ZS+mh` z7=RKisD#;~Fpt_hiZS$W-E+!1NYkd0C+OV$PfY}X1J#0LCJe!qGVN@eB)Du~#)3+D zZgLRycmf{2EOeBSy6YEX`Wp*x2vj!FS~TbB!k?9twFbb#IJa)gHwI_z1 zgno+OUZL+T-Gw}+`8VeH*BYdN02oHI@0JjB#BzPgdVS3NV@(}aJo*WKV>Q2QVOOr- z21g!J-vRwZ`6G=onmoBJQxrpW8qxKg@v`V%44Vp4Qi-D8q>{*26>k`n0C1Gh?2A~| z0*uTp%BV%e&WwFQqCf0yn8^V`?&kRK`o}CqH`D?LCo~FH)yQ+x4T5OkMr3870vOUO zJQPLLsHH&{ApBILe~Z*j#pPV&o{HvmpS6IC5rjffCRor&!;!!n@I2&YWLUW@IKa@u zu8s8Oa0QwY90QadW^h*>!X}_?8!)&3^~)z=fXi(EHLVk(*Ci*unv*C+U({E%3CK-x z+aq=+R+qBA`fcpgtXq1a;)rX_B`VjL(j_63TAR~3-ea$lT43)Y7y> zjGJn`tJbKvx=dv3HOK-tj<{lJ6-3_+7|9uFlCIJ`C~-x`5?^_rhLNsE0FPeyUr6K+ zPEc&xDmyW_TT?m=bxnXL?X`T|Dk2ci3c-MQO$3f{CY|1cfHG4njNI%2C@i#zG{!iI z;z!s!_rfaM5tz$Zo->fm+yh652$TIMQ8nzif!XeCC9R}pO%pfx4^l^hmIsiz&C95p z;(qRbIt0(c>w`qIoJ(WX|6s?%@2u2=&0xpTT)BmR8oZW(BzEk1lGB)9CG|HIMlCIJ zf9NrI3-`>0ye;V6ahOYKM9tW;9fpCLUTH|vKZi;RZ{Ye05iQ>B`+?mycyU$VJFPBD!fikfETXmQH$%^V=5-m@=N%f;CY8keUwB& z4MUZ!d9j~slU-yUqPPOm0;PrCyLh7{ue%~5(o%yHu*vq}NmMG}2+g*(18MwyC57y-H#o?wPrm(-YO>9*YD z<)efMU{6y>3_nv(_(FuFYBDTEY1KLGUOQj0)Q8klRl_ktyZ+z_5bv5auh~JtsXb%8 zp*I4plHq1QbTGE$VF!q=WYJ}lcO;`9U~rqHA5#>8yon#=x%9IZb|6$P7DzITa}n>PW}daqgxJwm6g#Aq*4K6)NFxDxiy93c>_jeerDaaTFLA^ zNE(x`J``O)Xb|T(z^P%_rkNMR=l9y~Dp23hbYQL6cOMG%yM-q@opd{Vm1=4Xxz>vj zah?nP-RM0kIJT2S#v+ z|LcoA}69L`!R}nCVMw{Xr#h@Z>BBU~BlimkY z-f4u%Wu7LP-6wXHOX_EYNokpEZtUBu86mXsN@cVNEwftdtO67P)|?*Nb98rUT{3!F z(jhv%QL!*sPSsuh$vWTLm6SI}gEg9%!4QZ?%yMbEPS?XIL!{uQFm`z;ZE4*Qj^%`6bgy&(6p2nzGqko;R+%i{ny%ObK$EW1W=%XJQ5 zoX!A_@d=j^6jzZyo&bcu1BWbjL#xR-du6Uq@6xE8JC0d5=vG)_0n}33K2b68p z)>v}OS{nFOp8>XMcRAKA6`~qukvdfqAbFN%F>QmeW>*}<0Y`Pbz?I-2FP2=m8>D}N^!OqSaVU5MgQ zs+XG9p30s;hqcK;dSoP$YFtySl>CXvHn6bG^<~#d@r{6)%Lp_^2asw@j&y*8{0LXy=xQ0Y_OwKZ!RW`%}#yjU%`FW^sE=7d<|ai z-Ik_JCeJceTWAKFNRxwyTkIgclVW8iwwaGH-vIYi801BpQEuCkegnxXT?Eo|{REll zO=BW&Fw97y&5XjfmbA!9*OdCk1j^5|8zb`Q3#EZ{Q(h>0&7hk~^#T$}A4QTa&+t`H zzeDcqYt1Tf06EE*)l#I}j69vJ(j~=? zIe$xHivYcdS>m2+su>~MYyHRKD8c>~Pn@Q8{Fdc0a7}^H$CZZ$(60qkv&}s#BGga) zT>pG&KNu<|u@h4Su>t(E7$k>fNl)Kr#;Nm8&Kuf31A@aGi#*B&7a|6LsC3Jmhlv7+ z0RWjHs6h_CUU(s{Xtm^;bL1GK>KnNp_=Jyy&PZQ4Tc;ePl6F318)n|y#oM8qBcCNX zXwp%)bp=&gi3>?&fP#nU%EJL7R?<#nYF(TWdYlVgv!{V~_J1waE21DS<{0`BIc=+L zx*)4DVz|#``Bkz~9DgqT{|i$WG(6v`>O+autT5EWI^1q^Wdw(Txc$G)%iI;V54j&~pD+*1?{! zI#~t~=p2=o$jbD8aRBIulAI$6_oT63R@6-5KOz)_`u*_ZO7TZi{#pEFaElCl(_bJQc3ba}&_%s7(VLVu6e5m~t4bsy{JE)C>5bpV+_!brGW!525Zu!1ki+LtUyBoAsj zj{$F$VdORe!bzgJsGb7&S&tIOhWZt#3u}w%A`9i>G5$`}Uq0>8i-%HkkouPF$ zYM!TC1N}MkweCl?$wBmg@8BZ1w{xA)zp2hu=1!cj+6Y~OmRZbCqZ^N2my!Z0*)y8g z#UI*Iz*Pbe8-$R0yT(F3mE;V?0!}}GUJP-!nQFpu{7t1eLr^FI`|q&HCj;}{KvPtyuteP{Adxq`I7QbS*V2VelFy~giN zHdr{XTp_NxV1klqwXq-ZF+Upr5fWLFp6qP+g-&cRnsu^^EW%24~1jQsoF#ggP3y<;FV_xpW6y1-MIO!M3-I zY8AFuqbC9vykxBK^dzWbLAoafDHG?zumOq@gBO?pU550h=JzO-6UQsHjq)Y3x*A)W zITrw)H1A|RHB3NaS`1skeExrMa65@QkAz#uHbOc{BdApDE=n_4A zXeZkCu~i9g9hF_~1)%+Ghlg!m^CB)0NW%~9U-{Few^w-Jv_J_pT5O_)Z%ttI$V)|!rG@Z7VMwa zPtZf0!mvHm6C*ao3#K4ZegzO#&(bYD($jS7cs$xfOjH$dW5M`$bLzW2SoPNtPQwg# z)d1-Xs_h>bUWoDn?UzIKzR8jz3Vp*1F8(KcX@%8|7Iw}BMCE8ttFl3uh92IvLGi-Zzi?o6sbQZwVtc)(7Bt6 zxG$n(x4nhQtX+|^9)fGUyPN~pW-_1s2BDSa6HQaOZK*N!hVIVgwY+_=;1dmik9$Q&*TC3w%pZeau2b&TNCzIpSocxHLtTYYvSTcoR zY~}uuAOEo|sI=*F-&~yf)WZm}BJ;tHAK2JJn_Zma_s85rmn%yt6-7RySLh&r66g<# zW3WF$S8&%dMKHFhHRCpLXP^(B!)UW_(F*g98NTrG`nAESq83iR3gjtClEvNW^)D1+ z$L)~hh;N*}nrF6i)_dli@C$Q;|zRKK4Oyev&nNzbJyH?-m_fn5FRP&b9TQCzIr#(qIj#L5p^8ZKxzCcx^`IA`&nijUY`#g zI$co$b>XIM+ew6gy>b27eI2Vg-1c_k@7}wQg!u{dM$ls&w2HY3<+absLQyOYIzPnu zHG4pUwQ&$|O1F#UY+Q4yLVM%ttNjOFrM0y4&^TOBm+DEztgfes;F1K(obc=zS1YMX zq&9wwXR6&mHKqtlVzzj8Pa|?El?|c=A#2eB1q0Tu@}F%C_^A;<|F(` z9SY|FK`aY*Hp;tI>QWp}F1h`F^%;O+q>1Tm(E7CFL9Nhe{E}b7t z#?$ji@*{&O2GimB>{fkoh7f?ei6w3*fxHt=$}1M+0o_oA7DMw3JXPPpPRShY{@}}- z?whT4m8Oax!bfN@(F~xkc|I)&Z9!zKBZHSW!o=a6DIotuG1vdG{>t&yqUnNWU3NKV zz&@$~Mv{PR3?g+R{4n{QWG-f0?I!K{#QcpY>%8`XfZ#g=O#O7L{}kZw+wSk#oMGs3 zDxQk1+mfSB5LDVHx!o3ldt_ZksazpfxY}^Qr#{F%8O2>V=XuN6pG}D(niR6GX3Arj zxG!}WVsdwJmvJR^5#354i6g>NJ=EQI5cyh9LUK$Nui|~^0E?`&I3+i-5DAq7grYoB z)Z~cKHr8;h^Q}wuIuE&yuB`7v+MD8Awv@F#=OI%Q6T<;BU7b4BbvR-$T#U!o%jDKM zGh*B8xpPYSmIM2*|M5WzzcyI>sEYX|Vnm~da9dd7yP*Ju+61BJ&%Fm_oS|$KbPGOy z-RXPM4hW^BU2$_RF}+5Q_sD2%&7E*_HV)z^n>Ty*L_@GcqEjmD?&8ULEaR3M7%nkj3oW{N_cC2 zK5+?a%ge)R(J9Kmn)@;ALe#;Mpw@qK>kaZ7vX|aRQ3Gj(!Id0jgW2 zIrRVcV3_#~KW3I3W&P}zQ!Cy5fOz@FHNzI~?B_;Fvo_BiEu*jHIc6F+3qS?z8G>)) zL6RB_W2CWD1Ou6u30TQKZVwUImv#lu;Th6#z|0cm?Wx3i`t5vRihG zeLYY^mzO+P5`ek*SKGL!6=W`Qk$zG)2E2^b4UMZK-R^MehWIzU&Qe-Ga$zkC$sT?J z1{bsqH|XMyTh$ai$>z$9$w!Fn{3KZaPHJ6r(?Ww`M*)`K;OW;Foj!j3dwb5%JkS&_ zo!v!S10AR7YD1_yo*u=>=l)~zKc&bG^tk#ijfs2B4FbIq)C`+B6Y+^ck^hu6UXOW{ zpK+XdC!U=sgyWGpu_MzP*s?Xybo4nER79_Av%Q^imr(>CQy|2p!N52S%PmLh{|l`+2O zw3Y3rF!A6t7wzWKqYudrCy%ISwuzpwSvBSfwrTfP4ef(({D3)t4~zZh@fo;Fr&`@^ zL`W0-nKldulzXlNXcrM&Q3juF!-D@bux`BJq9@iQcb9WF4TIc*=T2lAvD(`-r2L8T zAblaOP)J6rSyDqtq zyV^1nZgZZzpr8#7S)}%0LPgCESp?K*^;pbRQ@klsFG?GC9=t)oruFf5^MaYZfoasJ zXx*nZ1By^Al#b1T~M!M@-+IY=4$vU%6aJNX^n>M9PE>-6h#5|^bQ0y6d#&cBfA^m*PIxF1xget{|M;Y4=^beZw^ z?2{0Ac8MCEo}Dr!iZ|1)j|wxp+e{?`s7rO>4Mq6h;{gDw*6A#sL`LpNC>$g}3Glm^ zSYsk#Kk`XdSu4%0JwIj^zsvPEqXQKn89!!=W(qmNLRVo!{MY=(S48CmkKkb)Y|Yc@+JiE_5aQw_Sq z<*=3873}3g*ap13KtH=95aoUC!c?6JPgMWmNMN>-OBB`f_}&X|WlMkMK^-4VJR}{6 zCc6r|XiBZGKnNO)`X;5izAkrty-CuLdoYM27G zWz&`?PY2Uaq_mBEp4R;B4G`LPTKk=b0WGNi)3Io2A^izhwmJ+h`;4|fR7AgV*&nnQ zv5kukdju!$q)q)z-8hXv)0aXGXGNCLSM3?Z>+$U02p!A7(n5i+B&Oaj75iHTHTb}+ zhN94qTb8Xy&^b)+>r->m`{^>qzOn$PFlc$Fx#6#}rgOdkKk6>ULyB>tz55jDY>{A;GM zGZN~0evOY{A0X7g>7YuqAHr)a-_YYqI;Oe-Obq}}Ylsio225}IKJrb5Naqsm=E+iu z%;$BK+q~oz&S&AsuaBh8gA6_BNLv$1)XBhixkRknFduKji{u{{r}h5MRAyGCGV_dR z$S-)LVfzG=$=AGyYm-JtXh3nUOflOl{~AdDCv|*xEs0e;4mO!NWKu?(>7aHAG9B-y zq&LzTg*KK{RX0{CL@87I%aBSY%X{WLq9Wv6bdAnB`^72i6UUTPi=G3t#m4?ie4n!zKkjKH#i9~PGu+kc`e0X zKECUbYl=Fr-8|*4wiv;gty#W`Ny{LQMdI%trZ7UJF&Zh&0H@x3jyj!0u3Z^vuld64 zjtX$ijj1C=x$aT@o4gVH7Nx`)&9dAMz=2{Yd88BPh$@h9wqz*s|86=|u!k%XM4qku zVEQ=xdv8@5iU#RCx@g7*Yc$NoqSup~$vksj90dDOp1sA3v zBW9=`%k_%##Dt0XD;xB$8hM!2G_HnBu=@do6jFxxp%)hQh*DQ@)h1-v61?p- zRzVT)*~3~ToGXN`%#o}AnNkWuuEW(usp>DcUEcmk8=P}BLSx134L-TfC7v1iRlnJ# ziBM%-0Vh2l!5CY5O*oN_q>!_|?;F0RyuzSrgHX)wFF zA#hHw)E0C1z`ie`VqVZGq9CUDz&Cjya+`#xn`jAgi+or-uKMqY5&!@~?Aa2M zW#>dz7o7QuQfzOH7q1fJQ*ypL%W2m*j)xyODU;AVnI^So!LY!PC9UzXF<=Dmn?;`P zO@$r)={zqL0js{Jd4O79W6PJ6Wv(_ge4@v!`aV8ekt}~3D7gnXPwwq$Ax>Hm7?npj zG)Z=mcWV9%0DfHj-hDbyPD>EccABjE%kYR}lVvCbHTliKK_IScZRz@}`+?k#Lcd|@ z;oQ1%Ws*iD!s_~iZy2_vt}3KPWgC?~*xVJ+LNi1tkhyuRN$FD-l07jrW2~gKfcZ)Kz&={7=iUHJO_$xlENf@OwGMYR3uKUc$arTB#OkL5QH01x-6O09 z$p)mJc1BZ)IrYCB@)HKZicIc0b-Jqw-BG5+(W(8o`KvOZN$1P2r^`2=%!LXiy5&ai zeh{0A4!WK>BODx3B(J@YiHOBs7+Xd_@qtbF?0=~qEmDLSx?Vyj2Xi|)=IGNk_&kC& zsZEH~7@UFmI{_1zRso*gNt8-t_tiSqSEW93g1%0dycW)*H|EPzKik*yYP ztCxJ0w(&`G^i5gMS0k$azWqK|%it{0jNRTQ>*SEGx#D@l#ZG!f`sT;?0C)UOMqfMS zy7oFC$)ulc6_W|?FydhkwEY4yNiT-NkxWO1j0UT^r)_@Vyg@$8rI;m3hon( z8hpJ;T`d{zkko2WjQSU4y_n{Z@uB3l(Wr};??Orpm1*9AJTY! zdXNRi8+Yi*j*14?azX}W3mh{h4x^ANYbp64Tq>ix|Zve ziQ((5W`>j$oyHs0$FJPefQ3BWuh9YP==>`xv+}N%F?C_byywRck}-4rOlEoIX?z0K zxL%8F%40UT8)nGcqF=LtHgJh;(SMx%lQJ zii$reoC_+UDSHPan=g2W%c1`q04bWxOsD>=?sY$^Na!8In7FVI1IH@2Bcv zy^Cmi=kJK^Dj=f&Z1XBJI}lV1N-d+ZI&WedQ+N>gXzj8D%ub#2LiiJwU=JpKuocuN zG+NE(2~wB%En&Je+b>_kbIvJXowIdUw+>4s!pnf=$NyU;+^OgIKxd&PAtcfxj(U+K zDJ|h5)emqy)^2y9xA#W5C70)*vWK%a*9!qd1smagIBLBDvl(@u%r0n9dn0Q7l_!=~(9!nha%24CckmTi7Mby!kgI}fVzPs8g`El`7#w0R_ zT#kwIi!cI4J@Mh`dNpdr{D3J$o$aLjDwws7^~;XDO1(x3N6c|4w;(T_yGS>K-`X+Q za@AerZm8NLhS{Hp!X{k_3(M!U5Dm|qd>fgDGC}i_NUbUfgzaT%A`+pr^S6$&buMgN pXJ$EI69Tu{lrtMhm-vr}m%Bl^yYQX2MC$#2ckuuJ`2X$?{2v%&)&&3n literal 0 HcmV?d00001 From e8286320590fbeff4724ffe9073826f18aac0ae2 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Thu, 21 Sep 2023 13:37:01 -0600 Subject: [PATCH 76/89] fix data url for icons --- backend/src/install/mod.rs | 39 ++++++++++++++++--------------------- libs/models/src/data_url.rs | 8 ++++++++ 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/backend/src/install/mod.rs b/backend/src/install/mod.rs index 7b1f6708b..d9e9ec330 100644 --- a/backend/src/install/mod.rs +++ b/backend/src/install/mod.rs @@ -14,12 +14,12 @@ use futures::{FutureExt, StreamExt, TryStreamExt}; use http::header::CONTENT_LENGTH; use http::{Request, Response, StatusCode}; use hyper::Body; -use models::DataUrl; +use models::{mime, DataUrl}; use reqwest::Url; use rpc_toolkit::command; use rpc_toolkit::yajrc::RpcError; use serde_json::{json, Value}; -use tokio::io::{AsyncRead, AsyncSeek, AsyncSeekExt}; +use tokio::io::{AsyncRead, AsyncSeek, AsyncSeekExt, AsyncWriteExt}; use tokio::process::Command; use tokio::sync::oneshot; use tokio::{ @@ -939,27 +939,31 @@ pub async fn install_s9pk( .await?; tracing::info!("Install {}@{}: Unpacked INSTRUCTIONS.md", pkg_id, version); - let icon_path = Path::new("icon").with_extension(&manifest.assets.icon_type()); + let icon_filename = Path::new("icon").with_extension(manifest.assets.icon_type()); + let icon_path = public_dir_path.join(&icon_filename); tracing::info!( "Install {}@{}: Unpacking {}", pkg_id, version, icon_path.display() ); - progress + let icon_buf = progress .track_read_during(ctx.db.clone(), pkg_id, || async { - let icon_path = public_dir_path.join(&icon_path); - let mut dst = File::create(&icon_path).await?; - tokio::io::copy(&mut rdr.icon().await?, &mut dst).await?; - dst.sync_all().await?; - Ok(()) + Ok(rdr.icon().await?.to_vec().await?) }) .await?; + let mut dst = File::create(&icon_path).await?; + dst.write_all(&icon_buf).await?; + dst.sync_all().await?; + let icon = DataUrl::from_vec( + mime(manifest.assets.icon_type()).unwrap_or("image/png"), + icon_buf, + ); tracing::info!( "Install {}@{}: Unpacked {}", pkg_id, version, - icon_path.display() + icon_filename.display() ); tracing::info!("Install {}@{}: Unpacking Docker Images", pkg_id, version); @@ -1078,18 +1082,7 @@ pub async fn install_s9pk( .and_then(|i| i.as_dependency_info().as_idx(&pkg_id)) .is_some() { - let icon = DataUrl::from_path( - format!( - "/public/package-data/{}/{}/icon.{}", - manifest.id, - manifest.version, - manifest.assets.icon_type() - ) - .parse::() - .map_err(|e| Error::new(e, ErrorKind::Filesystem))?, - ) - .await?; - dependents_static_dependency_info.insert(package.clone(), icon); + dependents_static_dependency_info.insert(package.clone(), icon.clone()); } if let Some(dep) = db .as_package_data() @@ -1221,6 +1214,8 @@ pub async fn install_s9pk( reconfigure_dependents_with_live_pointers(&ctx, &installed).await?; } + sql_tx.commit().await?; + ctx.db .mutate(|db| { for (package, icon) in dependents_static_dependency_info { diff --git a/libs/models/src/data_url.rs b/libs/models/src/data_url.rs index 5661d262b..5e92d2453 100644 --- a/libs/models/src/data_url.rs +++ b/libs/models/src/data_url.rs @@ -10,6 +10,7 @@ use yasi::InternedString; use crate::{mime, Error, ErrorKind, ResultExt}; +#[derive(Clone)] pub struct DataUrl<'a> { mime: InternedString, data: Cow<'a, [u8]>, @@ -97,6 +98,13 @@ impl DataUrl<'static> { data: Cow::Owned(data), }) } + + pub fn from_vec(mime: &str, data: Vec) -> Self { + Self { + mime: InternedString::intern(mime), + data: Cow::Owned(data), + } + } } impl<'a> std::fmt::Debug for DataUrl<'a> { From 396c136ad7da08d1733868be03ad3fd6d2418c69 Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Fri, 22 Sep 2023 10:03:34 -0600 Subject: [PATCH 77/89] fix: Bad deser --- libs/models/src/data_url.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/models/src/data_url.rs b/libs/models/src/data_url.rs index 5e92d2453..c272ee6be 100644 --- a/libs/models/src/data_url.rs +++ b/libs/models/src/data_url.rs @@ -129,7 +129,7 @@ impl<'de> Deserialize<'de> for DataUrl<'static> { E: serde::de::Error, { v.strip_prefix("data:") - .and_then(|v| v.split_once(";base64")) + .and_then(|v| v.split_once(";base64,")) .and_then(|(mime, data)| { Some(DataUrl { mime: InternedString::intern(mime), @@ -145,7 +145,7 @@ impl<'de> Deserialize<'de> for DataUrl<'static> { }) } } - deserializer.deserialize_str(Visitor) + deserializer.deserialize_any(Visitor) } } From 264efcb95de7e4d152d5a44fe2c55cea8b45cb47 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Fri, 22 Sep 2023 12:58:24 -0600 Subject: [PATCH 78/89] bugfixes --- backend/src/hostname.rs | 8 ++++++++ backend/src/install/mod.rs | 1 + backend/src/manager/mod.rs | 13 ++++--------- backend/src/status/mod.rs | 2 ++ libs/models/src/data_url.rs | 8 ++++---- 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/backend/src/hostname.rs b/backend/src/hostname.rs index 25342c4a1..f68d5c9d8 100644 --- a/backend/src/hostname.rs +++ b/backend/src/hostname.rs @@ -60,6 +60,14 @@ pub async fn set_hostname(hostname: &Hostname) -> Result<(), Error> { .arg(hostname) .invoke(ErrorKind::ParseSysInfo) .await?; + Command::new("sed") + .arg("-i") + .arg(format!( + "s/\\(\\s\\)localhost\\( {hostname}\\)\\?/\\1localhost {hostname}/g" + )) + .arg("/etc/hosts") + .invoke(ErrorKind::ParseSysInfo) + .await?; Ok(()) } diff --git a/backend/src/install/mod.rs b/backend/src/install/mod.rs index d9e9ec330..d1a1cc4c3 100644 --- a/backend/src/install/mod.rs +++ b/backend/src/install/mod.rs @@ -1110,6 +1110,7 @@ pub async fn install_s9pk( status: Status { configured: manifest.config.is_none(), main: MainStatus::Stopped, + dependency_errors: Default::default(), dependency_config_errors: DependencyConfigErrors::default(), }, marketplace_url, diff --git a/backend/src/manager/mod.rs b/backend/src/manager/mod.rs index e2328fd7c..66e58e59c 100644 --- a/backend/src/manager/mod.rs +++ b/backend/src/manager/mod.rs @@ -493,15 +493,10 @@ async fn configure( if let Some(cfg) = db .as_package_data() .as_idx(dependency) - .or_not_found(dependency)? - .as_installed() - .or_not_found(dependency)? - .as_manifest() - .as_dependencies() - .as_idx(id) - .or_not_found(id)? - .as_config() - .de()? + .and_then(|pde| pde.as_installed()) + .and_then(|i| i.as_manifest().as_dependencies().as_idx(id)) + .and_then(|d| d.as_config().de().transpose()) + .transpose()? { let manifest = db .as_package_data() diff --git a/backend/src/status/mod.rs b/backend/src/status/mod.rs index 2a5a9391f..f6290be12 100644 --- a/backend/src/status/mod.rs +++ b/backend/src/status/mod.rs @@ -16,6 +16,8 @@ pub struct Status { pub configured: bool, pub main: MainStatus, #[serde(default)] + pub dependency_errors: BTreeMap<(), ()>, // TODO: remove + #[serde(default)] pub dependency_config_errors: DependencyConfigErrors, } diff --git a/libs/models/src/data_url.rs b/libs/models/src/data_url.rs index 5e92d2453..e2141b15a 100644 --- a/libs/models/src/data_url.rs +++ b/libs/models/src/data_url.rs @@ -24,7 +24,7 @@ impl<'a> DataUrl<'a> { use std::fmt::Write; let mut res = String::with_capacity(self.data_url_len_without_mime() + self.mime.len()); let _ = write!(res, "data:{};base64,", self.mime); - base64::engine::general_purpose::URL_SAFE.encode_string(&self.data, &mut res); + base64::engine::general_purpose::STANDARD.encode_string(&self.data, &mut res); res } @@ -129,12 +129,12 @@ impl<'de> Deserialize<'de> for DataUrl<'static> { E: serde::de::Error, { v.strip_prefix("data:") - .and_then(|v| v.split_once(";base64")) + .and_then(|v| v.split_once(";base64,")) .and_then(|(mime, data)| { Some(DataUrl { mime: InternedString::intern(mime), data: Cow::Owned( - base64::engine::general_purpose::URL_SAFE + base64::engine::general_purpose::STANDARD .decode(data) .ok()?, ), @@ -145,7 +145,7 @@ impl<'de> Deserialize<'de> for DataUrl<'static> { }) } } - deserializer.deserialize_str(Visitor) + deserializer.deserialize_any(Visitor) } } From 8dc9f0fec29719f7891e108861ab76afce740595 Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Fri, 22 Sep 2023 15:39:30 -0600 Subject: [PATCH 79/89] fix: Backup --- backend/src/backup/backup_bulk.rs | 11 +++++++++-- backend/src/backup/mod.rs | 2 +- backend/src/backup/restore.rs | 1 + backend/src/notifications.rs | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/backend/src/backup/backup_bulk.rs b/backend/src/backup/backup_bulk.rs index fcb721989..3dea21f08 100644 --- a/backend/src/backup/backup_bulk.rs +++ b/backend/src/backup/backup_bulk.rs @@ -107,7 +107,10 @@ pub async fn backup_all( attempted: true, error: None, }, - packages: report, + packages: report + .into_iter() + .map(|((package_id, _), value)| (package_id, value)) + .collect(), }, None, ) @@ -126,7 +129,10 @@ pub async fn backup_all( attempted: true, error: None, }, - packages: report, + packages: report + .into_iter() + .map(|((package_id, _), value)| (package_id, value)) + .collect(), }, None, ) @@ -310,5 +316,6 @@ async fn perform_backup( ctx.db .mutate(|v| v.as_server_info_mut().as_last_backup_mut().ser(×tamp)) .await?; + Ok(backup_report) } diff --git a/backend/src/backup/mod.rs b/backend/src/backup/mod.rs index e02d4030e..131e3eb0e 100644 --- a/backend/src/backup/mod.rs +++ b/backend/src/backup/mod.rs @@ -41,7 +41,7 @@ pub mod target; #[derive(Debug, Deserialize, Serialize)] pub struct BackupReport { server: ServerBackupReport, - packages: BTreeMap<(PackageId, Version), PackageBackupReport>, + packages: BTreeMap, } #[derive(Debug, Deserialize, Serialize)] diff --git a/backend/src/backup/restore.rs b/backend/src/backup/restore.rs index 9c5d85e72..fe4efae20 100644 --- a/backend/src/backup/restore.rs +++ b/backend/src/backup/restore.rs @@ -251,6 +251,7 @@ pub async fn recover_full_embassy( )) } +#[instrument(skip(ctx, backup_guard))] async fn restore_packages( ctx: &RpcContext, backup_guard: BackupMountGuard, diff --git a/backend/src/notifications.rs b/backend/src/notifications.rs index 91df0ec43..afe92c571 100644 --- a/backend/src/notifications.rs +++ b/backend/src/notifications.rs @@ -225,7 +225,7 @@ impl NotificationManager { cache: Mutex::new(HashMap::new()), } } - #[instrument(skip_all)] + #[instrument(skip(db, subtype, self))] pub async fn notify( &self, db: PatchDb, From 28db510b5ab92067da8b883ea20318c5b08ed086 Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Fri, 22 Sep 2023 16:32:20 -0600 Subject: [PATCH 80/89] fix: Some of the restor --- backend/src/backup/restore.rs | 70 +++++++++++++++++------------------ backend/src/db/model.rs | 4 +- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/backend/src/backup/restore.rs b/backend/src/backup/restore.rs index fe4efae20..a53c71b7f 100644 --- a/backend/src/backup/restore.rs +++ b/backend/src/backup/restore.rs @@ -306,6 +306,8 @@ async fn assure_restoring( ) -> Result, Error> { let mut guards = Vec::with_capacity(ids.len()); + let mut insert_packages = BTreeMap::new(); + for id in ids { let peek = ctx.db.peek().await?; @@ -327,23 +329,6 @@ async fn assure_restoring( tokio::fs::metadata(&s9pk_path).await?.len(), ))); - ctx.db - .mutate(|db| { - db.as_package_data_mut().insert( - &id, - &PackageDataEntry::Restoring(PackageDataEntryRestoring { - install_progress: progress.clone(), - static_files: StaticFiles::local( - &id, - &version, - manifest.assets.icon_type(), - ), - manifest: manifest.clone(), - }), - ) - }) - .await?; - let public_dir_path = ctx .datadir .join(PKG_PUBLIC_DIR) @@ -366,27 +351,25 @@ async fn assure_restoring( let mut dst = File::create(&icon_path).await?; tokio::io::copy(&mut rdr.icon().await?, &mut dst).await?; dst.sync_all().await?; - - ctx.db - .mutate(|db| { - db.as_package_data_mut().insert( - &id, - &PackageDataEntry::Restoring(PackageDataEntryRestoring { - install_progress: progress.clone(), - static_files: StaticFiles::local( - &id, - &version, - manifest.assets.icon_type(), - ), - manifest: manifest.clone(), - }), - ) - }) - .await?; + insert_packages.insert( + id.clone(), + PackageDataEntry::Restoring(PackageDataEntryRestoring { + install_progress: progress.clone(), + static_files: StaticFiles::local(&id, &version, manifest.assets.icon_type()), + manifest: manifest.clone(), + }), + ); guards.push((manifest, guard)); } - + ctx.db + .mutate(|db| { + for (id, package) in insert_packages { + db.as_package_data_mut().insert(&id, &package)?; + } + Ok(()) + }) + .await?; Ok(guards) } @@ -446,6 +429,23 @@ async fn restore_package<'a>( let marketplace_url = metadata.marketplace_url; let progress = Arc::new(progress); + + ctx.db + .mutate(|db| { + db.as_package_data_mut().insert( + &id, + &PackageDataEntry::Restoring(PackageDataEntryRestoring { + install_progress: progress.clone(), + static_files: StaticFiles::local( + &id, + &manifest.version, + manifest.assets.icon_type(), + ), + manifest: manifest.clone(), + }), + ) + }) + .await?; Ok(( progress.clone(), async move { diff --git a/backend/src/db/model.rs b/backend/src/db/model.rs index 4f43ea77f..8dd6e8528 100644 --- a/backend/src/db/model.rs +++ b/backend/src/db/model.rs @@ -397,7 +397,7 @@ impl Model { match self.as_match() { PackageDataEntryMatchModelRef::Installing(a) => Some(a.as_install_progress()), PackageDataEntryMatchModelRef::Updating(a) => Some(a.as_install_progress()), - PackageDataEntryMatchModelRef::Restoring(_) => None, + PackageDataEntryMatchModelRef::Restoring(a) => Some(a.as_install_progress()), PackageDataEntryMatchModelRef::Removing(_) => None, PackageDataEntryMatchModelRef::Installed(_) => None, PackageDataEntryMatchModelRef::Error(_) => None, @@ -407,7 +407,7 @@ impl Model { match self.as_match_mut() { PackageDataEntryMatchModelMut::Installing(a) => Some(a.as_install_progress_mut()), PackageDataEntryMatchModelMut::Updating(a) => Some(a.as_install_progress_mut()), - PackageDataEntryMatchModelMut::Restoring(_) => None, + PackageDataEntryMatchModelMut::Restoring(a) => Some(a.as_install_progress_mut()), PackageDataEntryMatchModelMut::Removing(_) => None, PackageDataEntryMatchModelMut::Installed(_) => None, PackageDataEntryMatchModelMut::Error(_) => None, From f2ea01ec5e5069449f0e1b5a950a0acbe62a7f3c Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Mon, 25 Sep 2023 10:22:19 -0600 Subject: [PATCH 81/89] fix: Restoring works --- backend/src/backup/mod.rs | 25 ++----------------------- backend/src/install/mod.rs | 9 +++++---- 2 files changed, 7 insertions(+), 27 deletions(-) diff --git a/backend/src/backup/mod.rs b/backend/src/backup/mod.rs index 131e3eb0e..dd52aae8b 100644 --- a/backend/src/backup/mod.rs +++ b/backend/src/backup/mod.rs @@ -199,7 +199,7 @@ impl BackupActions { pkg_id: &PackageId, pkg_version: &Version, volumes: &Volumes, - ) -> Result<(), Error> { + ) -> Result, Error> { let mut volumes = volumes.clone(); volumes.insert(VolumeId::Backup, Volume::Backup { readonly: true }); self.restore @@ -224,28 +224,7 @@ impl BackupActions { ) })?, )?; - let entry = ctx - .db - .mutate(|v| { - v.as_package_data_mut() - .as_idx_mut(pkg_id) - .or_not_found(pkg_id)? - .as_installed_mut() - .or_not_found(pkg_id)? - .as_marketplace_url_mut() - .ser(&metadata.marketplace_url)?; - - v.as_package_data() - .as_idx(pkg_id) - .or_not_found(pkg_id)? - .as_installed() - .or_not_found(pkg_id)? - .de() - }) - .await?; - - reconfigure_dependents_with_live_pointers(ctx, &entry).await?; - Ok(()) + Ok(metadata.marketplace_url) } } diff --git a/backend/src/install/mod.rs b/backend/src/install/mod.rs index d1a1cc4c3..089daf91c 100644 --- a/backend/src/install/mod.rs +++ b/backend/src/install/mod.rs @@ -1132,11 +1132,11 @@ pub async fn install_s9pk( current_dependencies: current_dependencies.clone(), interface_addresses, }; - let next = PackageDataEntry::Installed(PackageDataEntryInstalled { + let mut next = PackageDataEntryInstalled { installed, manifest: manifest.clone(), static_files, - }); + }; let mut auto_start = false; @@ -1199,7 +1199,7 @@ pub async fn install_s9pk( cleanup(&ctx, &prev.manifest.id, &prev.manifest.version).await?; } } else if let PackageDataEntry::Restoring(PackageDataEntryRestoring { .. }) = prev { - manifest + next.installed.marketplace_url = manifest .backup .restore(&ctx, pkg_id, version, &manifest.volumes) .await?; @@ -1234,7 +1234,8 @@ pub async fn install_s9pk( }, )?; } - db.as_package_data_mut().insert(&pkg_id, &next)?; + db.as_package_data_mut() + .insert(&pkg_id, &PackageDataEntry::Installed(next))?; if let PackageDataEntry::Updating(PackageDataEntryUpdating { installed: prev, .. }) = &prev From 0815400026f5c6ccf49f48cc66f3f18d189ed985 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Mon, 25 Sep 2023 11:38:07 -0600 Subject: [PATCH 82/89] update frontend patch-db types --- frontend/projects/ui/src/app/services/patch-db/data-model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/projects/ui/src/app/services/patch-db/data-model.ts b/frontend/projects/ui/src/app/services/patch-db/data-model.ts index 43839ae43..8df7b1161 100644 --- a/frontend/projects/ui/src/app/services/patch-db/data-model.ts +++ b/frontend/projects/ui/src/app/services/patch-db/data-model.ts @@ -277,7 +277,7 @@ export interface Action { export interface Status { configured: boolean main: MainStatus - 'dependency-errors': { [id: string]: DependencyError | null } + 'dependency-config-errors': { [id: string]: string | null } } export type MainStatus = From 09f9c435d79f8585daac7e70f7c02de83323966a Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Tue, 26 Sep 2023 12:47:04 -0600 Subject: [PATCH 83/89] hack it in (#2424) * hack it in * optimize * slightly cleaner --- .../apps-routes/app-list/app-list.page.html | 2 +- .../apps-routes/app-list/package-info.pipe.ts | 17 +- .../apps-routes/app-show/app-show.module.ts | 4 - .../apps-routes/app-show/app-show.page.html | 52 ++--- .../apps-routes/app-show/app-show.page.ts | 180 ++++++++++++++- .../app-show-dependencies.component.ts | 2 +- .../app-show-status.component.ts | 6 +- .../app-show/pipes/to-dependencies.pipe.ts | 149 ------------- .../app-show/pipes/to-status.pipe.ts | 15 -- .../built-in/health/health.component.ts | 11 +- .../ui/src/app/services/api/api.fixures.ts | 11 +- .../ui/src/app/services/api/api.types.ts | 48 +++- .../services/api/embassy-mock-api.service.ts | 1 - .../ui/src/app/services/api/mock-patch.ts | 10 +- .../ui/src/app/services/dep-error.service.ts | 211 ++++++++++++++++++ .../src/app/services/patch-db/data-model.ts | 46 ---- .../services/pkg-status-rendering.service.ts | 31 +-- .../ui/src/app/util/get-package-info.ts | 8 +- 18 files changed, 507 insertions(+), 297 deletions(-) delete mode 100644 frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-dependencies.pipe.ts delete mode 100644 frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-status.pipe.ts create mode 100644 frontend/projects/ui/src/app/services/dep-error.service.ts diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list.page.html b/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list.page.html index 05e4a76cf..4dcc24234 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list.page.html +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list.page.html @@ -14,7 +14,7 @@

Welcome to StartOS

- + diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-list/package-info.pipe.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-list/package-info.pipe.ts index 76712e4a3..103845620 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-list/package-info.pipe.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-list/package-info.pipe.ts @@ -1,5 +1,5 @@ import { Pipe, PipeTransform } from '@angular/core' -import { Observable } from 'rxjs' +import { Observable, combineLatest } from 'rxjs' import { filter, map, startWith } from 'rxjs/operators' import { DataModel, @@ -7,16 +7,23 @@ import { } from 'src/app/services/patch-db/data-model' import { getPackageInfo, PkgInfo } from '../../../util/get-package-info' import { PatchDB } from 'patch-db-client' +import { DepErrorService } from 'src/app/services/dep-error.service' @Pipe({ name: 'packageInfo', }) export class PackageInfoPipe implements PipeTransform { - constructor(private readonly patch: PatchDB) {} + constructor( + private readonly patch: PatchDB, + private readonly depErrorService: DepErrorService, + ) {} transform(pkg: PackageDataEntry): Observable { - return this.patch - .watch$('package-data', pkg.manifest.id) - .pipe(filter(Boolean), startWith(pkg), map(getPackageInfo)) + return combineLatest([ + this.patch + .watch$('package-data', pkg.manifest.id) + .pipe(filter(Boolean), startWith(pkg)), + this.depErrorService.depErrors$, + ]).pipe(map(([pkg, depErrors]) => getPackageInfo(pkg, depErrors))) } } diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.module.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.module.ts index f9288494c..c69910c78 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.module.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.module.ts @@ -18,8 +18,6 @@ import { AppShowAdditionalComponent } from './components/app-show-additional/app import { HealthColorPipe } from './pipes/health-color.pipe' import { ToHealthChecksPipe } from './pipes/to-health-checks.pipe' import { ToButtonsPipe } from './pipes/to-buttons.pipe' -import { ToDependenciesPipe } from './pipes/to-dependencies.pipe' -import { ToStatusPipe } from './pipes/to-status.pipe' import { ProgressDataPipe } from './pipes/progress-data.pipe' const routes: Routes = [ @@ -36,8 +34,6 @@ const routes: Routes = [ ProgressDataPipe, ToHealthChecksPipe, ToButtonsPipe, - ToDependenciesPipe, - ToStatusPipe, AppShowHeaderComponent, AppShowProgressComponent, AppShowStatusComponent, diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.html b/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.html index 11993c913..36dc9aaa6 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.html +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.html @@ -1,9 +1,9 @@ - + - + - + - - - - + + + + + + - - - - - - - - - - - - - + > + + + + + + + + diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.ts index b7df8da9a..716fe8e1e 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.ts @@ -1,21 +1,41 @@ -import { ChangeDetectionStrategy, Component, Inject } from '@angular/core' +import { ChangeDetectionStrategy, Component } from '@angular/core' import { NavController } from '@ionic/angular' import { PatchDB } from 'patch-db-client' import { DataModel, + InstalledPackageDataEntry, + Manifest, PackageDataEntry, PackageState, } from 'src/app/services/patch-db/data-model' import { PackageStatus, PrimaryStatus, + renderPkgStatus, } from 'src/app/services/pkg-status-rendering.service' -import { tap } from 'rxjs/operators' -import { ActivatedRoute } from '@angular/router' +import { map, tap } from 'rxjs/operators' +import { ActivatedRoute, NavigationExtras } from '@angular/router' import { getPkgId } from '@start9labs/shared' -import { DOCUMENT } from '@angular/common' import { ConfigService } from 'src/app/services/config.service' import { getServerInfo } from 'src/app/util/get-server-info' +import { ModalService } from 'src/app/services/modal.service' +import { DependentInfo } from 'src/app/types/dependent-info' +import { + DepErrorService, + DependencyErrorType, + PackageDependencyErrors, +} from 'src/app/services/dep-error.service' +import { combineLatest } from 'rxjs' + +export interface DependencyInfo { + id: string + title: string + icon: string + version: string + errorText: string + actionText: string + action: () => any +} const STATES = [ PackageState.Installing, @@ -33,10 +53,21 @@ export class AppShowPage { private readonly pkgId = getPkgId(this.route) - readonly pkg$ = this.patch.watch$('package-data', this.pkgId).pipe( - tap(pkg => { + readonly pkgPlus$ = combineLatest([ + this.patch.watch$('package-data'), + this.depErrorService.depErrors$, + ]).pipe( + tap(([pkgs, _]) => { // if package disappears, navigate to list page - if (!pkg) this.navCtrl.navigateRoot('/services') + if (!pkgs[this.pkgId]) this.navCtrl.navigateRoot('/services') + }), + map(([pkgs, depErrors]) => { + const pkg = pkgs[this.pkgId] + return { + pkg, + dependencies: this.getDepInfo(pkg, depErrors), + status: renderPkgStatus(pkg, depErrors), + } }), ) @@ -45,7 +76,8 @@ export class AppShowPage { private readonly navCtrl: NavController, private readonly patch: PatchDB, private readonly config: ConfigService, - @Inject(DOCUMENT) private readonly document: Document, + private readonly modalService: ModalService, + private readonly depErrorService: DepErrorService, ) {} isInstalled({ state }: PackageDataEntry): boolean { @@ -70,4 +102,136 @@ export class AppShowPage { await getServerInfo(this.patch) onTor ? window.open(torAddress) : window.open(lanAddress) } + + private getDepInfo( + pkg: PackageDataEntry, + depErrors: PackageDependencyErrors, + ): DependencyInfo[] { + const pkgInstalled = pkg.installed + + if (!pkgInstalled) return [] + + return Object.keys(pkgInstalled['current-dependencies']) + .filter(id => !!pkgInstalled.manifest.dependencies[id]) + .map(id => this.getDepValues(pkgInstalled, id, depErrors)) + } + + private getDepValues( + pkgInstalled: InstalledPackageDataEntry, + depId: string, + depErrors: PackageDependencyErrors, + ): DependencyInfo { + const { errorText, fixText, fixAction } = this.getDepErrors( + pkgInstalled, + depId, + depErrors, + ) + + const depInfo = pkgInstalled['dependency-info'][depId] + + return { + id: depId, + version: pkgInstalled.manifest.dependencies[depId].version, // do we want this version range? + title: depInfo?.manifest?.title || depId, + icon: depInfo?.icon || '', + errorText: errorText + ? `${errorText}. ${pkgInstalled.manifest.title} will not work as expected.` + : '', + actionText: fixText || 'View', + action: + fixAction || (() => this.navCtrl.navigateForward(`/services/${depId}`)), + } + } + + private getDepErrors( + pkgInstalled: InstalledPackageDataEntry, + depId: string, + depErrors: PackageDependencyErrors, + ) { + const pkgManifest = pkgInstalled.manifest + const depError = depErrors[pkgInstalled.manifest.id][depId] + + let errorText: string | null = null + let fixText: string | null = null + let fixAction: (() => any) | null = null + + if (depError) { + if (depError.type === DependencyErrorType.NotInstalled) { + errorText = 'Not installed' + fixText = 'Install' + fixAction = () => this.fixDep(pkgManifest, 'install', depId) + } else if (depError.type === DependencyErrorType.IncorrectVersion) { + errorText = 'Incorrect version' + fixText = 'Update' + fixAction = () => this.fixDep(pkgManifest, 'update', depId) + } else if (depError.type === DependencyErrorType.ConfigUnsatisfied) { + errorText = 'Config not satisfied' + fixText = 'Auto config' + fixAction = () => this.fixDep(pkgManifest, 'configure', depId) + } else if (depError.type === DependencyErrorType.NotRunning) { + errorText = 'Not running' + fixText = 'Start' + } else if (depError.type === DependencyErrorType.HealthChecksFailed) { + errorText = 'Health check failed' + } else if (depError.type === DependencyErrorType.Transitive) { + errorText = 'Dependency has a dependency issue' + } + } + + return { + errorText, + fixText, + fixAction, + } + } + + private async fixDep( + pkgManifest: Manifest, + action: 'install' | 'update' | 'configure', + id: string, + ): Promise { + switch (action) { + case 'install': + case 'update': + return this.installDep(pkgManifest, id) + case 'configure': + return this.configureDep(pkgManifest, id) + } + } + + private async installDep( + pkgManifest: Manifest, + depId: string, + ): Promise { + const version = pkgManifest.dependencies[depId].version + + const dependentInfo: DependentInfo = { + id: pkgManifest.id, + title: pkgManifest.title, + version, + } + const navigationExtras: NavigationExtras = { + state: { dependentInfo }, + } + + await this.navCtrl.navigateForward( + `/marketplace/${depId}`, + navigationExtras, + ) + } + + private async configureDep( + pkgManifest: Manifest, + dependencyId: string, + ): Promise { + const dependentInfo: DependentInfo = { + id: pkgManifest.id, + title: pkgManifest.title, + } + + await this.modalService.presentModalConfig({ + pkgId: dependencyId, + dependentInfo, + }) + } } diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-dependencies/app-show-dependencies.component.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-dependencies/app-show-dependencies.component.ts index 26d4cd026..3a2fee53b 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-dependencies/app-show-dependencies.component.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-dependencies/app-show-dependencies.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core' -import { DependencyInfo } from '../../pipes/to-dependencies.pipe' +import { DependencyInfo } from '../../app-show.page' @Component({ selector: 'app-show-dependencies', diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.ts index 574100678..61e5e9d92 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.ts @@ -15,7 +15,6 @@ import { ErrorToastService } from '@start9labs/shared' import { AlertController, LoadingController } from '@ionic/angular' import { ApiService } from 'src/app/services/api/embassy-api.service' import { ModalService } from 'src/app/services/modal.service' -import { DependencyInfo } from '../../pipes/to-dependencies.pipe' import { hasCurrentDeps } from 'src/app/util/has-deps' import { ConnectionService } from 'src/app/services/connection.service' @@ -32,9 +31,6 @@ export class AppShowStatusComponent { @Input() status!: PackageStatus - @Input() - dependencies: DependencyInfo[] = [] - PR = PrimaryRendering readonly connected$ = this.connectionService.connected$ @@ -80,7 +76,7 @@ export class AppShowStatusComponent { } async tryStart(): Promise { - if (this.dependencies.some(d => !!d.errorText)) { + if (this.status.dependency === 'warning') { const depErrMsg = `${this.pkg.manifest.title} has unmet dependencies. It will not work as expected.` const proceed = await this.presentAlertStart(depErrMsg) diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-dependencies.pipe.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-dependencies.pipe.ts deleted file mode 100644 index 5fdc4f45e..000000000 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-dependencies.pipe.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core' -import { NavigationExtras } from '@angular/router' -import { NavController } from '@ionic/angular' -import { - DependencyError, - DependencyErrorType, - PackageDataEntry, -} from 'src/app/services/patch-db/data-model' -import { DependentInfo } from 'src/app/types/dependent-info' -import { ModalService } from 'src/app/services/modal.service' - -export interface DependencyInfo { - id: string - title: string - icon: string - version: string - errorText: string - actionText: string - action: () => any -} - -@Pipe({ - name: 'toDependencies', -}) -export class ToDependenciesPipe implements PipeTransform { - constructor( - private readonly navCtrl: NavController, - private readonly modalService: ModalService, - ) {} - - transform(pkg: PackageDataEntry): DependencyInfo[] { - if (!pkg.installed) return [] - - return Object.keys(pkg.installed?.['current-dependencies']) - .filter(id => !!pkg.manifest.dependencies[id]) - .map(id => - this.setDepValues(pkg, id, pkg.installed!.status['dependency-errors']), - ) - } - - private setDepValues( - pkg: PackageDataEntry, - id: string, - errors: { [id: string]: DependencyError | null }, - ): DependencyInfo { - let errorText = '' - let actionText = 'View' - let action: () => any = () => - this.navCtrl.navigateForward(`/services/${id}`) - - const error = errors[id] - - if (error) { - // health checks failed - if ( - [ - DependencyErrorType.InterfaceHealthChecksFailed, - DependencyErrorType.HealthChecksFailed, - ].includes(error.type) - ) { - errorText = 'Health check failed' - // not installed - } else if (error.type === DependencyErrorType.NotInstalled) { - errorText = 'Not installed' - actionText = 'Install' - action = () => this.fixDep(pkg, 'install', id) - // incorrect version - } else if (error.type === DependencyErrorType.IncorrectVersion) { - errorText = 'Incorrect version' - actionText = 'Update' - action = () => this.fixDep(pkg, 'update', id) - // not running - } else if (error.type === DependencyErrorType.NotRunning) { - errorText = 'Not running' - actionText = 'Start' - // config unsatisfied - } else if (error.type === DependencyErrorType.ConfigUnsatisfied) { - errorText = 'Config not satisfied' - actionText = 'Auto config' - action = () => this.fixDep(pkg, 'configure', id) - } else if (error.type === DependencyErrorType.Transitive) { - errorText = 'Dependency has a dependency issue' - } - errorText = `${errorText}. ${pkg.manifest.title} will not work as expected.` - } - - const depInfo = pkg.installed?.['dependency-info'][id] - - return { - id, - version: pkg.manifest.dependencies[id].version, - title: depInfo?.manifest?.title || id, - icon: depInfo?.icon || '', - errorText, - actionText, - action, - } - } - - async fixDep( - pkg: PackageDataEntry, - action: 'install' | 'update' | 'configure', - id: string, - ): Promise { - switch (action) { - case 'install': - case 'update': - return this.installDep(pkg, id) - case 'configure': - return this.configureDep(pkg, id) - } - } - - private async installDep( - pkg: PackageDataEntry, - depId: string, - ): Promise { - const version = pkg.manifest.dependencies[depId].version - - const dependentInfo: DependentInfo = { - id: pkg.manifest.id, - title: pkg.manifest.title, - version, - } - const navigationExtras: NavigationExtras = { - state: { dependentInfo }, - } - - await this.navCtrl.navigateForward( - `/marketplace/${depId}`, - navigationExtras, - ) - } - - private async configureDep( - pkg: PackageDataEntry, - dependencyId: string, - ): Promise { - const dependentInfo: DependentInfo = { - id: pkg.manifest.id, - title: pkg.manifest.title, - } - - await this.modalService.presentModalConfig({ - pkgId: dependencyId, - dependentInfo, - }) - } -} diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-status.pipe.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-status.pipe.ts deleted file mode 100644 index 1fc9e83f3..000000000 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-status.pipe.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core' -import { PackageDataEntry } from 'src/app/services/patch-db/data-model' -import { - PackageStatus, - renderPkgStatus, -} from 'src/app/services/pkg-status-rendering.service' - -@Pipe({ - name: 'toStatus', -}) -export class ToStatusPipe implements PipeTransform { - transform(pkg: PackageDataEntry): PackageStatus { - return renderPkgStatus(pkg) - } -} diff --git a/frontend/projects/ui/src/app/pages/widgets/built-in/health/health.component.ts b/frontend/projects/ui/src/app/pages/widgets/built-in/health/health.component.ts index 0da0b17af..f69a0407b 100644 --- a/frontend/projects/ui/src/app/pages/widgets/built-in/health/health.component.ts +++ b/frontend/projects/ui/src/app/pages/widgets/built-in/health/health.component.ts @@ -1,7 +1,10 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core' import { PatchDB } from 'patch-db-client' import { map } from 'rxjs/operators' -import { PackageDataEntry } from 'src/app/services/patch-db/data-model' +import { + DataModel, + PackageDataEntry, +} from 'src/app/services/patch-db/data-model' import { PrimaryStatus } from 'src/app/services/pkg-status-rendering.service' import { getPackageInfo, PkgInfo } from '../../../../util/get-package-info' @@ -20,11 +23,13 @@ export class HealthComponent { 'Transitioning', ] as const - readonly data$ = inject(PatchDB) + readonly data$ = inject(PatchDB) .watch$('package-data') .pipe( map(data => { - const pkgs = Object.values(data).map(getPackageInfo) + const pkgs = Object.values(data).map( + pkg => getPackageInfo(pkg, {}), // @TODO hack because not currently using widget + ) const result = this.labels.reduce>( (acc, label) => ({ ...acc, diff --git a/frontend/projects/ui/src/app/services/api/api.fixures.ts b/frontend/projects/ui/src/app/services/api/api.fixures.ts index 6e999e956..7d447573b 100644 --- a/frontend/projects/ui/src/app/services/api/api.fixures.ts +++ b/frontend/projects/ui/src/app/services/api/api.fixures.ts @@ -1,5 +1,4 @@ import { - DependencyErrorType, DockerIoFormat, Manifest, PackageDataEntry, @@ -1889,7 +1888,7 @@ export module Mock { started: new Date().toISOString(), health: {}, }, - 'dependency-errors': {}, + 'dependency-config-errors': {}, }, 'interface-addresses': { ui: { @@ -1935,7 +1934,7 @@ export module Mock { main: { status: PackageMainStatus.Stopped, }, - 'dependency-errors': {}, + 'dependency-config-errors': {}, }, manifest: MockManifestBitcoinProxy, 'interface-addresses': { @@ -1984,10 +1983,8 @@ export module Mock { main: { status: PackageMainStatus.Stopped, }, - 'dependency-errors': { - 'btc-rpc-proxy': { - type: DependencyErrorType.NotInstalled, - }, + 'dependency-config-errors': { + 'btc-rpc-proxy': 'Username not found', }, }, manifest: MockManifestLnd, diff --git a/frontend/projects/ui/src/app/services/api/api.types.ts b/frontend/projects/ui/src/app/services/api/api.types.ts index fbce4dc62..4ddf31190 100644 --- a/frontend/projects/ui/src/app/services/api/api.types.ts +++ b/frontend/projects/ui/src/app/services/api/api.types.ts @@ -4,7 +4,7 @@ import { PackagePropertiesVersioned } from 'src/app/util/properties.util' import { ConfigSpec } from 'src/app/pkg-config/config-types' import { DataModel, - DependencyError, + HealthCheckResult, Manifest, } from 'src/app/services/patch-db/data-model' import { StartOSDiskInfo, LogsRes, ServerLogsReq } from '@start9labs/shared' @@ -466,3 +466,49 @@ declare global { export type Encrypted = { encrypted: string } + +export type DependencyError = + | DependencyErrorNotInstalled + | DependencyErrorNotRunning + | DependencyErrorIncorrectVersion + | DependencyErrorConfigUnsatisfied + | DependencyErrorHealthChecksFailed + | DependencyErrorTransitive + +export enum DependencyErrorType { + NotInstalled = 'not-installed', + NotRunning = 'not-running', + IncorrectVersion = 'incorrect-version', + ConfigUnsatisfied = 'config-unsatisfied', + HealthChecksFailed = 'health-checks-failed', + InterfaceHealthChecksFailed = 'interface-health-checks-failed', + Transitive = 'transitive', +} + +export interface DependencyErrorNotInstalled { + type: DependencyErrorType.NotInstalled +} + +export interface DependencyErrorNotRunning { + type: DependencyErrorType.NotRunning +} + +export interface DependencyErrorIncorrectVersion { + type: DependencyErrorType.IncorrectVersion + expected: string // version range + received: string // version +} + +export interface DependencyErrorConfigUnsatisfied { + type: DependencyErrorType.ConfigUnsatisfied + error: string +} + +export interface DependencyErrorHealthChecksFailed { + type: DependencyErrorType.HealthChecksFailed + check: HealthCheckResult +} + +export interface DependencyErrorTransitive { + type: DependencyErrorType.Transitive +} diff --git a/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts b/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts index 109fc1255..ddd1d793f 100644 --- a/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts +++ b/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts @@ -10,7 +10,6 @@ import { } from 'patch-db-client' import { DataModel, - DependencyErrorType, InstallProgress, PackageDataEntry, PackageMainStatus, diff --git a/frontend/projects/ui/src/app/services/api/mock-patch.ts b/frontend/projects/ui/src/app/services/api/mock-patch.ts index 114ee9b30..6cf5fa7e1 100644 --- a/frontend/projects/ui/src/app/services/api/mock-patch.ts +++ b/frontend/projects/ui/src/app/services/api/mock-patch.ts @@ -1,6 +1,5 @@ import { DataModel, - DependencyErrorType, DockerIoFormat, HealthResult, Manifest, @@ -438,7 +437,7 @@ export const mockPatchData: DataModel = { }, }, }, - 'dependency-errors': {}, + 'dependency-config-errors': {}, }, 'interface-addresses': { ui: { @@ -637,11 +636,8 @@ export const mockPatchData: DataModel = { main: { status: PackageMainStatus.Stopped, }, - 'dependency-errors': { - 'btc-rpc-proxy': { - type: DependencyErrorType.ConfigUnsatisfied, - error: 'This is a config unsatisfied error', - }, + 'dependency-config-errors': { + 'btc-rpc-proxy': 'This is a config unsatisfied error', }, }, 'interface-addresses': { diff --git a/frontend/projects/ui/src/app/services/dep-error.service.ts b/frontend/projects/ui/src/app/services/dep-error.service.ts new file mode 100644 index 000000000..733b0a377 --- /dev/null +++ b/frontend/projects/ui/src/app/services/dep-error.service.ts @@ -0,0 +1,211 @@ +import { Injectable } from '@angular/core' +import { Emver } from '@start9labs/shared' +import { map, shareReplay } from 'rxjs/operators' +import { PatchDB } from 'patch-db-client' +import { + DataModel, + HealthCheckResult, + HealthResult, + InstalledPackageDataEntry, + PackageMainStatus, +} from './patch-db/data-model' + +export type PackageDependencyErrors = Record +export type DependencyErrors = Record + +@Injectable({ + providedIn: 'root', +}) +export class DepErrorService { + readonly depErrors$ = this.patch.watch$('package-data').pipe( + map(pkgs => + Object.keys(pkgs) + .map(id => ({ + id, + depth: dependencyDepth(pkgs, id), + })) + .sort((a, b) => (b.depth > a.depth ? -1 : 1)) + .reduce( + (errors, { id }): PackageDependencyErrors => ({ + ...errors, + [id]: this.getDepErrors(pkgs, id, errors), + }), + {} as PackageDependencyErrors, + ), + ), + shareReplay(1), + ) + + constructor( + private readonly emver: Emver, + private readonly patch: PatchDB, + ) {} + + private getDepErrors( + pkgs: DataModel['package-data'], + pkgId: string, + outerErrors: PackageDependencyErrors, + ): DependencyErrors { + const pkgInstalled = pkgs[pkgId].installed + + if (!pkgInstalled) return {} + + return currentDeps(pkgs, pkgId).reduce( + (innerErrors, depId): DependencyErrors => ({ + ...innerErrors, + [depId]: this.getDepError(pkgs, pkgInstalled, depId, outerErrors), + }), + {} as DependencyErrors, + ) + } + + private getDepError( + pkgs: DataModel['package-data'], + pkgInstalled: InstalledPackageDataEntry, + depId: string, + outerErrors: PackageDependencyErrors, + ): DependencyError | null { + const depInstalled = pkgs[depId]?.installed + + // not installed + if (!depInstalled) { + return { + type: DependencyErrorType.NotInstalled, + } + } + + const pkgManifest = pkgInstalled.manifest + const depManifest = depInstalled.manifest + + // incorrect version + if ( + !this.emver.satisfies( + depManifest.version, + pkgManifest.dependencies[depId].version, + ) + ) { + return { + type: DependencyErrorType.IncorrectVersion, + expected: pkgManifest.dependencies[depId].version, + received: depManifest.version, + } + } + + // invalid config + if ( + Object.values(pkgInstalled.status['dependency-config-errors']).some( + err => !!err, + ) + ) { + return { + type: DependencyErrorType.ConfigUnsatisfied, + } + } + + const depStatus = depInstalled.status.main.status + + // not running + if ( + depStatus !== PackageMainStatus.Running && + depStatus !== PackageMainStatus.Starting && + !( + depStatus === PackageMainStatus.BackingUp && + depInstalled.status.main.started + ) + ) { + return { + type: DependencyErrorType.NotRunning, + } + } + + // health check failure + if (depStatus === PackageMainStatus.Running) { + for (let id of pkgInstalled['current-dependencies'][depId][ + 'health-checks' + ]) { + if ( + depInstalled.status.main.health[id].result !== HealthResult.Success + ) { + return { + type: DependencyErrorType.HealthChecksFailed, + check: depInstalled.status.main.health[id], + } + } + } + } + + // transitive + const transitiveError = currentDeps(pkgs, depId).some(transitiveId => + Object.values(outerErrors[transitiveId]).some(err => !!err), + ) + + if (transitiveError) { + return { + type: DependencyErrorType.Transitive, + } + } + + return null + } +} + +function currentDeps(pkgs: DataModel['package-data'], id: string): string[] { + return Object.keys( + pkgs[id]?.installed?.['current-dependencies'] || {}, + ).filter(depId => depId !== id) +} + +function dependencyDepth( + pkgs: DataModel['package-data'], + id: string, + depth = 0, +): number { + return currentDeps(pkgs, id).reduce( + (prev, depId) => dependencyDepth(pkgs, depId, prev + 1), + depth, + ) +} + +export type DependencyError = + | DependencyErrorNotInstalled + | DependencyErrorNotRunning + | DependencyErrorIncorrectVersion + | DependencyErrorConfigUnsatisfied + | DependencyErrorHealthChecksFailed + | DependencyErrorTransitive + +export enum DependencyErrorType { + NotInstalled = 'notInstalled', + NotRunning = 'notRunning', + IncorrectVersion = 'incorrectVersion', + ConfigUnsatisfied = 'configUnsatisfied', + HealthChecksFailed = 'healthChecksFailed', + Transitive = 'transitive', +} + +export interface DependencyErrorNotInstalled { + type: DependencyErrorType.NotInstalled +} + +export interface DependencyErrorNotRunning { + type: DependencyErrorType.NotRunning +} + +export interface DependencyErrorIncorrectVersion { + type: DependencyErrorType.IncorrectVersion + expected: string // version range + received: string // version +} + +export interface DependencyErrorConfigUnsatisfied { + type: DependencyErrorType.ConfigUnsatisfied +} + +export interface DependencyErrorHealthChecksFailed { + type: DependencyErrorType.HealthChecksFailed + check: HealthCheckResult +} + +export interface DependencyErrorTransitive { + type: DependencyErrorType.Transitive +} diff --git a/frontend/projects/ui/src/app/services/patch-db/data-model.ts b/frontend/projects/ui/src/app/services/patch-db/data-model.ts index 8df7b1161..554a0dc30 100644 --- a/frontend/projects/ui/src/app/services/patch-db/data-model.ts +++ b/frontend/projects/ui/src/app/services/patch-db/data-model.ts @@ -362,52 +362,6 @@ export interface HealthCheckResultFailure { error: string } -export type DependencyError = - | DependencyErrorNotInstalled - | DependencyErrorNotRunning - | DependencyErrorIncorrectVersion - | DependencyErrorConfigUnsatisfied - | DependencyErrorHealthChecksFailed - | DependencyErrorTransitive - -export enum DependencyErrorType { - NotInstalled = 'not-installed', - NotRunning = 'not-running', - IncorrectVersion = 'incorrect-version', - ConfigUnsatisfied = 'config-unsatisfied', - HealthChecksFailed = 'health-checks-failed', - InterfaceHealthChecksFailed = 'interface-health-checks-failed', - Transitive = 'transitive', -} - -export interface DependencyErrorNotInstalled { - type: DependencyErrorType.NotInstalled -} - -export interface DependencyErrorNotRunning { - type: DependencyErrorType.NotRunning -} - -export interface DependencyErrorIncorrectVersion { - type: DependencyErrorType.IncorrectVersion - expected: string // version range - received: string // version -} - -export interface DependencyErrorConfigUnsatisfied { - type: DependencyErrorType.ConfigUnsatisfied - error: string -} - -export interface DependencyErrorHealthChecksFailed { - type: DependencyErrorType.HealthChecksFailed - check: HealthCheckResult -} - -export interface DependencyErrorTransitive { - type: DependencyErrorType.Transitive -} - export interface InstallProgress { readonly size: number | null readonly downloaded: number diff --git a/frontend/projects/ui/src/app/services/pkg-status-rendering.service.ts b/frontend/projects/ui/src/app/services/pkg-status-rendering.service.ts index 58fab27bf..ce415bcbd 100644 --- a/frontend/projects/ui/src/app/services/pkg-status-rendering.service.ts +++ b/frontend/projects/ui/src/app/services/pkg-status-rendering.service.ts @@ -1,11 +1,13 @@ import { isEmptyObject } from '@start9labs/shared' import { + InstalledPackageDataEntry, MainStatusStarting, PackageDataEntry, PackageMainStatus, PackageState, Status, } from 'src/app/services/patch-db/data-model' +import { PackageDependencyErrors } from './dep-error.service' export interface PackageStatus { primary: PrimaryStatus @@ -13,16 +15,21 @@ export interface PackageStatus { health: HealthStatus | null } -export function renderPkgStatus(pkg: PackageDataEntry): PackageStatus { +export function renderPkgStatus( + pkg: PackageDataEntry, + depErrors: PackageDependencyErrors, +): PackageStatus { let primary: PrimaryStatus let dependency: DependencyStatus | null = null let health: HealthStatus | null = null - const hasHealthChecks = !isEmptyObject(pkg.manifest['health-checks']) if (pkg.state === PackageState.Installed && pkg.installed) { primary = getPrimaryStatus(pkg.installed.status) - dependency = getDependencyStatus(pkg) - health = getHealthStatus(pkg.installed.status, hasHealthChecks) + dependency = getDependencyStatus(pkg.installed, depErrors) + health = getHealthStatus( + pkg.installed.status, + !isEmptyObject(pkg.manifest['health-checks']), + ) } else { primary = pkg.state as string as PrimaryStatus } @@ -40,15 +47,13 @@ function getPrimaryStatus(status: Status): PrimaryStatus { } } -function getDependencyStatus(pkg: PackageDataEntry): DependencyStatus | null { - const installed = pkg.installed - if (!installed || isEmptyObject(installed['current-dependencies'])) - return null - - const depErrors = installed.status['dependency-errors'] - const depIds = Object.keys(depErrors).filter(key => !!depErrors[key]) - - return depIds.length ? DependencyStatus.Warning : DependencyStatus.Satisfied +function getDependencyStatus( + pkg: InstalledPackageDataEntry, + depErrors: PackageDependencyErrors, +): DependencyStatus { + return Object.values(depErrors[pkg.manifest.id]).some(err => !!err) + ? DependencyStatus.Warning + : DependencyStatus.Satisfied } function getHealthStatus( diff --git a/frontend/projects/ui/src/app/util/get-package-info.ts b/frontend/projects/ui/src/app/util/get-package-info.ts index 1dde896ae..f98d969fb 100644 --- a/frontend/projects/ui/src/app/util/get-package-info.ts +++ b/frontend/projects/ui/src/app/util/get-package-info.ts @@ -10,9 +10,13 @@ import { import { ProgressData } from 'src/app/types/progress-data' import { Subscription } from 'rxjs' import { packageLoadingProgress } from './package-loading-progress' +import { PackageDependencyErrors } from '../services/dep-error.service' -export function getPackageInfo(entry: PackageDataEntry): PkgInfo { - const statuses = renderPkgStatus(entry) +export function getPackageInfo( + entry: PackageDataEntry, + depErrors: PackageDependencyErrors, +): PkgInfo { + const statuses = renderPkgStatus(entry, depErrors) const primaryRendering = PrimaryRendering[statuses.primary] return { From 6dedae53365e21a2730d6aed28ea3ef9f7471cb4 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Tue, 26 Sep 2023 15:05:05 -0600 Subject: [PATCH 84/89] handle config pointers --- backend/src/backup/mod.rs | 4 +- backend/src/db/model.rs | 3 +- backend/src/dependencies.rs | 70 +++++++++++++++++++--------------- backend/src/install/cleanup.rs | 2 - backend/src/install/mod.rs | 42 ++++++++++++-------- backend/src/manager/mod.rs | 24 +++++++----- 6 files changed, 84 insertions(+), 61 deletions(-) diff --git a/backend/src/backup/mod.rs b/backend/src/backup/mod.rs index dd52aae8b..dc7b2a611 100644 --- a/backend/src/backup/mod.rs +++ b/backend/src/backup/mod.rs @@ -18,6 +18,7 @@ use tracing::instrument; use self::target::PackageBackupInfo; use crate::context::RpcContext; use crate::install::PKG_ARCHIVE_DIR; +use crate::manager::manager_seed::ManagerSeed; use crate::net::interface::InterfaceId; use crate::net::keys::Key; use crate::prelude::*; @@ -28,9 +29,6 @@ use crate::util::serde::{Base32, Base64, IoFormat}; use crate::util::Version; use crate::version::{Current, VersionT}; use crate::volume::{backup_dir, Volume, VolumeId, Volumes, BACKUP_DIR}; -use crate::{ - dependencies::reconfigure_dependents_with_live_pointers, manager::manager_seed::ManagerSeed, -}; use crate::{Error, ErrorKind, ResultExt}; pub mod backup_bulk; diff --git a/backend/src/db/model.rs b/backend/src/db/model.rs index 8dd6e8528..879feb644 100644 --- a/backend/src/db/model.rs +++ b/backend/src/db/model.rs @@ -15,6 +15,7 @@ use serde::{Deserialize, Serialize}; use ssh_key::public::Ed25519PublicKey; use crate::account::AccountInfo; +use crate::config::spec::PackagePointerSpec; use crate::install::progress::InstallProgress; use crate::net::utils::{get_iface_ipv4_addr, get_iface_ipv6_addr}; use crate::prelude::*; @@ -35,7 +36,6 @@ pub struct Database { impl Database { pub fn init(account: &AccountInfo) -> Self { let lan_address = account.hostname.lan_address().parse().unwrap(); - // TODO Database { server_info: ServerInfo { id: account.server_id.clone(), @@ -480,6 +480,7 @@ pub struct StaticDependencyInfo { #[serde(rename_all = "kebab-case")] #[model = "Model"] pub struct CurrentDependencyInfo { + pub pointers: BTreeSet, pub health_checks: BTreeSet, } diff --git a/backend/src/dependencies.rs b/backend/src/dependencies.rs index a175c7f1e..b6239c2f0 100644 --- a/backend/src/dependencies.rs +++ b/backend/src/dependencies.rs @@ -9,6 +9,7 @@ use rpc_toolkit::command; use serde::{Deserialize, Serialize}; use tracing::instrument; +use crate::config::spec::PackagePointerSpec; use crate::config::{action::ConfigRes, not_found}; use crate::config::{Config, ConfigSpec, ConfigureContext}; use crate::context::RpcContext; @@ -260,35 +261,42 @@ pub fn add_dependent_to_current_dependents_lists( Ok(()) } -pub async fn reconfigure_dependents_with_live_pointers( - ctx: &RpcContext, - i: &InstalledPackageInfo, -) -> Result<(), Error> { - // todo!("DR_BONEZ"); - // TODO @dr-bonez - // let dependents = &pde.current_dependents; - // let me = &pde.manifest.id; - // for (dependent_id, dependency_info) in &dependents.0 { - // if dependency_info.pointers.iter().any(|ptr| match ptr { - // // dependency id matches the package being uninstalled - // PackagePointerSpec::TorAddress(ptr) => &ptr.package_id == me && dependent_id != me, - // PackagePointerSpec::LanAddress(ptr) => &ptr.package_id == me && dependent_id != me, - // // we never need to retarget these - // PackagePointerSpec::TorKey(_) => false, - // PackagePointerSpec::Config(_) => false, - // }) { - // let breakages = BTreeMap::new(); - // let overrides = Default::default(); - - // let configure_context = ConfigureContext { - // breakages, - // timeout: None, - // config: None, - // dry_run: false, - // overrides, - // }; - // crate::config::configure(&ctx, dependent_id, configure_context).await?; - // } - // } - Ok(()) +pub fn set_dependents_with_live_pointers_to_needs_config( + db: &mut Peeked, + id: &PackageId, +) -> Result, Error> { + let mut res = Vec::new(); + for (dep, info) in db + .as_package_data() + .as_idx(id) + .or_not_found(id)? + .as_installed() + .or_not_found(id)? + .as_current_dependents() + .de()? + .0 + { + if info.pointers.iter().any(|ptr| match ptr { + // dependency id matches the package being uninstalled + PackagePointerSpec::TorAddress(ptr) => &ptr.package_id == id && &dep != id, + PackagePointerSpec::LanAddress(ptr) => &ptr.package_id == id && &dep != id, + // we never need to retarget these + PackagePointerSpec::TorKey(_) => false, + PackagePointerSpec::Config(_) => false, + }) { + let installed = db + .as_package_data_mut() + .as_idx_mut(&dep) + .or_not_found(&dep)? + .as_installed_mut() + .or_not_found(&dep)?; + let version = installed.as_manifest().as_version().de()?; + let configured = installed.as_status_mut().as_configured_mut(); + if configured.de()? { + configured.ser(&false)?; + res.push((dep, version)); + } + } + } + Ok(res) } diff --git a/backend/src/install/cleanup.rs b/backend/src/install/cleanup.rs index 1023a4a63..e47b7ca6b 100644 --- a/backend/src/install/cleanup.rs +++ b/backend/src/install/cleanup.rs @@ -11,7 +11,6 @@ use crate::db::model::{ CurrentDependencies, Database, PackageDataEntry, PackageDataEntryInstalled, PackageDataEntryMatchModelRef, }; -use crate::dependencies::reconfigure_dependents_with_live_pointers; use crate::error::ErrorCollection; use crate::prelude::*; use crate::s9pk::manifest::PackageId; @@ -175,7 +174,6 @@ where cleanup(ctx, id, &version).await?; cleanup_folder(volume_dir, Arc::new(dependents_paths)).await; remove_tor_keys(secrets, id).await?; - reconfigure_dependents_with_live_pointers(ctx, &entry.as_removing().de()?).await?; ctx.db .mutate(|d| { diff --git a/backend/src/install/mod.rs b/backend/src/install/mod.rs index 089daf91c..6d4ab11fb 100644 --- a/backend/src/install/mod.rs +++ b/backend/src/install/mod.rs @@ -39,7 +39,7 @@ use crate::db::model::{ StaticDependencyInfo, StaticFiles, }; use crate::dependencies::{ - add_dependent_to_current_dependents_lists, reconfigure_dependents_with_live_pointers, + add_dependent_to_current_dependents_lists, set_dependents_with_live_pointers_to_needs_config, }; use crate::install::cleanup::cleanup; use crate::install::progress::{InstallProgress, InstallProgressTracker}; @@ -796,7 +796,6 @@ pub async fn download_install_s9pk( } } -/// TODO @Blu-J @dr-bonez Need to make sure that we end load the db models #[instrument(skip_all)] pub async fn install_s9pk( ctx: RpcContext, @@ -1205,19 +1204,10 @@ pub async fn install_s9pk( .await?; } - if let Some(installed) = db - .as_package_data() - .as_idx(pkg_id) - .and_then(|x| x.as_installed()) - .map(|x| x.de()) - { - let installed = installed?; - reconfigure_dependents_with_live_pointers(&ctx, &installed).await?; - } - sql_tx.commit().await?; - ctx.db + let to_configure = ctx + .db .mutate(|db| { for (package, icon) in dependents_static_dependency_info { db.as_package_data_mut() @@ -1246,14 +1236,36 @@ pub async fn install_s9pk( // TODO: inizialize dependency config errors of dependents if config exists - Ok(()) + set_dependents_with_live_pointers_to_needs_config(db, pkg_id) }) .await?; - if dbg!(auto_start) { + if auto_start { manager.start(); } + for to_configure in to_configure { + if let Err(e) = async { + ctx.managers + .get(&to_configure) + .await + .or_not_found(format!("manager for {}", to_configure.0))? + .configure(ConfigureContext { + breakages: BTreeMap::new(), + timeout: None, + config: None, + overrides: BTreeMap::new(), + dry_run: false, + }) + .await + } + .await + { + tracing::error!("error configuring dependent: {e}"); + tracing::debug!("{e:?}") + } + } + tracing::info!("Install {}@{}: Complete", pkg_id, version); Ok(()) diff --git a/backend/src/manager/mod.rs b/backend/src/manager/mod.rs index 66e58e59c..bd9f2561d 100644 --- a/backend/src/manager/mod.rs +++ b/backend/src/manager/mod.rs @@ -406,14 +406,16 @@ async fn configure( for ptr in spec.pointers(&config)? { match ptr { ValueSpecPointer::Package(pkg_ptr) => { - if current_dependencies - .0 - .get_mut(pkg_ptr.package_id()) - .is_none() - { + if let Some(info) = current_dependencies.0.get_mut(pkg_ptr.package_id()) { + info.pointers.insert(pkg_ptr); + } else { + let id = pkg_ptr.package_id().to_owned(); + let mut pointers = BTreeSet::new(); + pointers.insert(pkg_ptr); current_dependencies.0.insert( - pkg_ptr.package_id().to_owned(), + id, CurrentDependencyInfo { + pointers, health_checks: BTreeSet::new(), }, ); @@ -462,9 +464,13 @@ async fn configure( if let Some(current_dependency) = current_dependencies.0.get_mut(&package_id) { current_dependency.health_checks.extend(health_checks); } else { - current_dependencies - .0 - .insert(package_id, CurrentDependencyInfo { health_checks }); + current_dependencies.0.insert( + package_id, + CurrentDependencyInfo { + pointers: BTreeSet::new(), + health_checks, + }, + ); } } From e522c5d200e9d2488537278631b03ef17f7a4ac0 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Tue, 26 Sep 2023 16:45:55 -0600 Subject: [PATCH 85/89] dependency config errs --- backend/Cargo.lock | 4 +- backend/Cargo.toml | 2 +- backend/src/backup/backup_bulk.rs | 7 ++- backend/src/backup/mod.rs | 6 +- backend/src/backup/restore.rs | 7 ++- backend/src/backup/target/mod.rs | 3 +- backend/src/config/mod.rs | 23 +++---- backend/src/config/spec.rs | 82 +++++++++++++------------ backend/src/config/util.rs | 4 +- backend/src/db/prelude.rs | 3 +- backend/src/dependencies.rs | 67 +++++++++++++++++++-- backend/src/install/mod.rs | 67 ++++++++++----------- backend/src/manager/mod.rs | 99 ++++++------------------------- backend/src/util/config.rs | 7 ++- backend/src/version/v0_3_4_1.rs | 4 +- backend/src/version/v0_3_4_2.rs | 1 - backend/src/version/v0_3_4_3.rs | 1 - backend/src/version/v0_3_4_4.rs | 4 +- libs/helpers/src/lib.rs | 6 +- libs/models/src/lib.rs | 1 - 20 files changed, 201 insertions(+), 197 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 54efbe954..9f0683e75 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -2305,9 +2305,9 @@ dependencies = [ [[package]] name = "jsonpath_lib" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaa63191d68230cccb81c5aa23abd53ed64d83337cacbb25a7b8c7979523774f" +source = "git+https://github.com/Start9Labs/jsonpath.git#1cacbd64afa2e1941a21fef06bad14317ba92f30" dependencies = [ + "imbl-value", "log", "serde", "serde_json", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index b7b9c4b8e..d10c011a1 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -89,7 +89,7 @@ jaq-core = "0.10.0" jaq-std = "0.10.0" josekit = "0.8.1" js_engine = { path = '../libs/js_engine', optional = true } -jsonpath_lib = "0.3.0" +jsonpath_lib = { git = "https://github.com/Start9Labs/jsonpath.git" } lazy_static = "1.4.0" libc = "0.2.126" log = "0.4.17" diff --git a/backend/src/backup/backup_bulk.rs b/backend/src/backup/backup_bulk.rs index 3dea21f08..5bab4bb78 100644 --- a/backend/src/backup/backup_bulk.rs +++ b/backend/src/backup/backup_bulk.rs @@ -1,7 +1,7 @@ +use std::collections::BTreeMap; use std::panic::UnwindSafe; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::sync::Arc; -use std::{collections::BTreeMap, path::Path}; use chrono::Utc; use clap::ArgMatches; @@ -16,6 +16,7 @@ use tracing::instrument; use super::target::BackupTargetId; use super::PackageBackupReport; +use crate::auth::check_password_against_db; use crate::backup::os::OsBackup; use crate::backup::{BackupReport, ServerBackupReport}; use crate::context::RpcContext; @@ -29,9 +30,9 @@ use crate::notifications::NotificationLevel; use crate::prelude::*; use crate::s9pk::manifest::PackageId; use crate::util::display_none; +use crate::util::io::dir_copy; use crate::util::serde::IoFormat; use crate::version::VersionT; -use crate::{auth::check_password_against_db, util::io::dir_copy}; fn parse_comma_separated(arg: &str, _: &ArgMatches) -> Result, Error> { arg.split(',') diff --git a/backend/src/backup/mod.rs b/backend/src/backup/mod.rs index dc7b2a611..670c01c29 100644 --- a/backend/src/backup/mod.rs +++ b/backend/src/backup/mod.rs @@ -1,8 +1,6 @@ +use std::collections::{BTreeMap, BTreeSet}; use std::path::{Path, PathBuf}; -use std::{ - collections::{BTreeMap, BTreeSet}, - sync::Arc, -}; +use std::sync::Arc; use chrono::{DateTime, Utc}; use color_eyre::eyre::eyre; diff --git a/backend/src/backup/restore.rs b/backend/src/backup/restore.rs index a53c71b7f..ac8d07f48 100644 --- a/backend/src/backup/restore.rs +++ b/backend/src/backup/restore.rs @@ -15,7 +15,11 @@ use torut::onion::OnionAddressV3; use tracing::instrument; use super::target::BackupTargetId; +use crate::backup::os::OsBackup; +use crate::backup::BackupMetadata; +use crate::context::rpc::RpcContextConfig; use crate::context::{RpcContext, SetupContext}; +use crate::db::model::{PackageDataEntry, PackageDataEntryRestoring, StaticFiles}; use crate::disk::mount::backup::{BackupMountGuard, PackageBackupMountGuard}; use crate::disk::mount::filesystem::ReadWrite; use crate::disk::mount::guard::TmpMountGuard; @@ -32,9 +36,6 @@ use crate::util::display_none; use crate::util::io::dir_size; use crate::util::serde::IoFormat; use crate::volume::{backup_dir, BACKUP_DIR, PKG_VOLUME_DIR}; -use crate::{backup::os::OsBackup, db::model::PackageDataEntry}; -use crate::{backup::BackupMetadata, db::model::StaticFiles}; -use crate::{context::rpc::RpcContextConfig, db::model::PackageDataEntryRestoring}; fn parse_comma_separated(arg: &str, _: &ArgMatches) -> Result, Error> { arg.split(',') diff --git a/backend/src/backup/target/mod.rs b/backend/src/backup/target/mod.rs index a8ef743ac..80c4a59c6 100644 --- a/backend/src/backup/target/mod.rs +++ b/backend/src/backup/target/mod.rs @@ -24,9 +24,8 @@ use crate::disk::mount::guard::TmpMountGuard; use crate::disk::util::PartitionInfo; use crate::prelude::*; use crate::s9pk::manifest::PackageId; -use crate::util::display_none; use crate::util::serde::{deserialize_from_str, display_serializable, serialize_display}; -use crate::util::Version; +use crate::util::{display_none, Version}; pub mod cifs; diff --git a/backend/src/config/mod.rs b/backend/src/config/mod.rs index 70ddd9264..2d3a30fd7 100644 --- a/backend/src/config/mod.rs +++ b/backend/src/config/mod.rs @@ -1,19 +1,22 @@ use std::collections::BTreeMap; use std::path::PathBuf; +use std::sync::Arc; use std::time::Duration; use color_eyre::eyre::eyre; use indexmap::IndexSet; use itertools::Itertools; use models::{ErrorKind, OptionExt}; +use patch_db::value::InternedString; +use patch_db::Value; use regex::Regex; use rpc_toolkit::command; -use serde_json::Value; use tracing::instrument; use crate::context::RpcContext; +use crate::db::model::CurrentDependencies; use crate::prelude::*; -use crate::s9pk::manifest::PackageId; +use crate::s9pk::manifest::{Manifest, PackageId}; use crate::util::display_none; use crate::util::serde::{display_serializable, parse_stdin_deserializable, IoFormat}; use crate::Error; @@ -28,7 +31,7 @@ use util::NumRange; use self::action::ConfigRes; use self::spec::ValueSpecPointer; -pub type Config = serde_json::Map; +pub type Config = patch_db::value::InOMap; pub trait TypeOf { fn type_of(&self) -> &'static str; } @@ -72,7 +75,7 @@ pub struct TimeoutError; #[derive(Clone, Debug, thiserror::Error)] pub struct NoMatchWithPath { - pub path: Vec, + pub path: Vec, pub error: MatchError, } impl NoMatchWithPath { @@ -82,7 +85,7 @@ impl NoMatchWithPath { error, } } - pub fn prepend(mut self, seg: String) -> Self { + pub fn prepend(mut self, seg: InternedString) -> Self { self.path.push(seg); self } @@ -101,9 +104,9 @@ impl From for Error { #[derive(Clone, Debug, thiserror::Error)] pub enum MatchError { #[error("String {0:?} Does Not Match Pattern {1}")] - Pattern(String, Regex), + Pattern(Arc, Regex), #[error("String {0:?} Is Not In Enum {1:?}")] - Enum(String, IndexSet), + Enum(Arc, IndexSet), #[error("Field Is Not Nullable")] NotNullable, #[error("Length Mismatch: expected {0}, actual: {1}")] @@ -115,11 +118,11 @@ pub enum MatchError { #[error("Number Is Not Integral: {0}")] NonIntegral(f64), #[error("Variant {0:?} Is Not In Union {1:?}")] - Union(String, IndexSet), + Union(Arc, IndexSet), #[error("Variant Is Missing Tag {0:?}")] - MissingTag(String), + MissingTag(InternedString), #[error("Property {0:?} Of Variant {1:?} Conflicts With Union Tag")] - PropertyMatchesUnionTag(String, String), + PropertyMatchesUnionTag(InternedString, String), #[error("Name of Property {0:?} Conflicts With Map Tag Name")] PropertyNameMatchesMapTag(String), #[error("Pointer Is Invalid: {0}")] diff --git a/backend/src/config/spec.rs b/backend/src/config/spec.rs index 9603df2f5..b11cefaf2 100644 --- a/backend/src/config/spec.rs +++ b/backend/src/config/spec.rs @@ -9,14 +9,16 @@ use std::sync::Arc; use std::time::Duration; use async_trait::async_trait; +use imbl::Vector; +use imbl_value::InternedString; use indexmap::{IndexMap, IndexSet}; use itertools::Itertools; use jsonpath_lib::Compiled as CompiledJsonPath; +use patch_db::value::{Number, Value}; use rand::{CryptoRng, Rng}; use regex::Regex; use serde::de::{MapAccess, Visitor}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use serde_json::{Number, Value}; use sqlx::PgPool; use super::util::{self, CharSet, NumRange, UniqueBy, STATIC_NULL}; @@ -547,7 +549,7 @@ impl ValueSpec for ValueSpecEnum { fn matches(&self, val: &Value) -> Result<(), NoMatchWithPath> { match val { Value::String(b) => { - if self.values.contains(b) { + if self.values.contains(&**b) { Ok(()) } else { Err(NoMatchWithPath::new(MatchError::Enum( @@ -589,7 +591,7 @@ impl ValueSpec for ValueSpecEnum { } } impl DefaultableWith for ValueSpecEnum { - type DefaultSpec = String; + type DefaultSpec = Arc; type Error = crate::util::Never; fn gen_with( @@ -627,13 +629,13 @@ where .map(|(i, v)| { self.spec .matches(v) - .map_err(|e| e.prepend(format!("{}", i)))?; + .map_err(|e| e.prepend(InternedString::from_display(&i)))?; if l.iter() .enumerate() .any(|(i2, v2)| i != i2 && self.spec.eq(v, v2)) { Err(NoMatchWithPath::new(MatchError::ListUniquenessViolation) - .prepend(format!("{}", i))) + .prepend(InternedString::from_display(&i))) } else { Ok(()) } @@ -659,11 +661,11 @@ where value: &mut Value, ) -> Result<(), ConfigurationError> { if let Value::Array(ref mut ls) = value { - for (i, val) in ls.into_iter().enumerate() { + for (i, val) in ls.iter_mut().enumerate() { match self.spec.update(ctx, manifest, config_overrides, val).await { - Err(ConfigurationError::NoMatch(e)) => { - Err(ConfigurationError::NoMatch(e.prepend(format!("{}", i)))) - } + Err(ConfigurationError::NoMatch(e)) => Err(ConfigurationError::NoMatch( + e.prepend(InternedString::from_display(&i)), + )), a => a, }?; } @@ -710,9 +712,9 @@ where rng: &mut R, timeout: &Option, ) -> Result { - let mut res = Vec::new(); + let mut res = Vector::new(); for spec_member in spec.iter() { - res.push(self.spec.gen_with(spec_member, rng, timeout)?); + res.push_back(self.spec.gen_with(spec_member, rng, timeout)?); } Ok(Value::Array(res)) } @@ -823,7 +825,7 @@ impl Defaultable for ValueSpecList { ) .contains(&ret.len()) { - ret.push( + ret.push_back( a.inner .inner .spec @@ -1006,11 +1008,11 @@ impl Defaultable for ValueSpecObject { } #[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct ConfigSpec(pub IndexMap); +pub struct ConfigSpec(pub IndexMap); impl ConfigSpec { pub fn matches(&self, value: &Config) -> Result<(), NoMatchWithPath> { for (key, val) in self.0.iter() { - if let Some(v) = value.get(key) { + if let Some(v) = value.get(&**key) { val.matches(v).map_err(|e| e.prepend(key.clone()))?; } else { val.matches(&Value::Null) @@ -1194,7 +1196,7 @@ impl ValueSpec for ValueSpecString { Ok(()) } else { Err(NoMatchWithPath::new(MatchError::Pattern( - s.to_owned(), + s.clone(), pattern.pattern.clone(), ))) } @@ -1276,11 +1278,11 @@ pub enum DefaultString { Entropy(Entropy), } impl DefaultString { - pub fn gen(&self, rng: &mut R) -> String { - match self { + pub fn gen(&self, rng: &mut R) -> Arc { + Arc::new(match self { DefaultString::Literal(s) => s.clone(), DefaultString::Entropy(e) => e.gen(rng), - } + }) } } @@ -1304,7 +1306,7 @@ impl Entropy { #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct UnionTag { - pub id: String, + pub id: InternedString, pub name: String, pub description: Option, pub variant_names: BTreeMap, @@ -1325,7 +1327,7 @@ impl<'de> serde::de::Deserialize<'de> for ValueSpecUnion { #[serde(rename_all = "kebab-case")] #[serde(untagged)] pub enum _UnionTag { - Old(String), + Old(InternedString), New(UnionTag), } #[derive(Deserialize)] @@ -1343,7 +1345,7 @@ impl<'de> serde::de::Deserialize<'de> for ValueSpecUnion { tag: match u.tag { _UnionTag::Old(id) => UnionTag { id: id.clone(), - name: id, + name: id.to_string(), description: None, variant_names: u .variants @@ -1385,10 +1387,10 @@ impl ValueSpec for ValueSpecUnion { fn matches(&self, value: &Value) -> Result<(), NoMatchWithPath> { match value { Value::Object(o) => { - if let Some(Value::String(ref tag)) = o.get(&self.tag.id) { - if let Some(obj_spec) = self.variants.get(tag) { + if let Some(Value::String(ref tag)) = o.get(&*self.tag.id) { + if let Some(obj_spec) = self.variants.get(&**tag) { let mut without_tag = o.clone(); - without_tag.remove(&self.tag.id); + without_tag.remove(&*self.tag.id); obj_spec.matches(&without_tag) } else { Err(NoMatchWithPath::new(MatchError::Union( @@ -1411,7 +1413,7 @@ impl ValueSpec for ValueSpecUnion { } fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { for (name, variant) in &self.variants { - if variant.0.get(&self.tag.id).is_some() { + if variant.0.get(&*self.tag.id).is_some() { return Err(NoMatchWithPath::new(MatchError::PropertyMatchesUnionTag( self.tag.id.clone(), name.clone(), @@ -1429,11 +1431,11 @@ impl ValueSpec for ValueSpecUnion { value: &mut Value, ) -> Result<(), ConfigurationError> { if let Value::Object(o) = value { - match o.get(&self.tag.id) { + match o.get(&*self.tag.id) { None => Err(ConfigurationError::NoMatch(NoMatchWithPath::new( MatchError::MissingTag(self.tag.id.clone()), ))), - Some(Value::String(tag)) => match self.variants.get(tag) { + Some(Value::String(tag)) => match self.variants.get(&**tag) { None => Err(ConfigurationError::NoMatch(NoMatchWithPath::new( MatchError::Union(tag.clone(), self.variants.keys().cloned().collect()), ))), @@ -1452,11 +1454,11 @@ impl ValueSpec for ValueSpecUnion { } fn pointers(&self, value: &Value) -> Result, NoMatchWithPath> { if let Value::Object(o) = value { - match o.get(&self.tag.id) { + match o.get(&*self.tag.id) { None => Err(NoMatchWithPath::new(MatchError::MissingTag( self.tag.id.clone(), ))), - Some(Value::String(tag)) => match self.variants.get(tag) { + Some(Value::String(tag)) => match self.variants.get(&**tag) { None => Err(NoMatchWithPath::new(MatchError::Union( tag.clone(), self.variants.keys().cloned().collect(), @@ -1478,8 +1480,8 @@ impl ValueSpec for ValueSpecUnion { } fn requires(&self, id: &PackageId, value: &Value) -> bool { if let Value::Object(o) = value { - match o.get(&self.tag.id) { - Some(Value::String(tag)) => match self.variants.get(tag) { + match o.get(&*self.tag.id) { + Some(Value::String(tag)) => match self.variants.get(&**tag) { None => false, Some(spec) => spec.requires(id, o), }, @@ -1497,7 +1499,7 @@ impl ValueSpec for ValueSpecUnion { } } impl DefaultableWith for ValueSpecUnion { - type DefaultSpec = String; + type DefaultSpec = Arc; type Error = ConfigurationError; fn gen_with( @@ -1506,7 +1508,7 @@ impl DefaultableWith for ValueSpecUnion { rng: &mut R, timeout: &Option, ) -> Result { - let variant = if let Some(v) = self.variants.get(spec) { + let variant = if let Some(v) = self.variants.get(&**spec) { v } else { return Err(ConfigurationError::NoMatch(NoMatchWithPath::new( @@ -1702,7 +1704,7 @@ impl TorAddressPointer { .and_then(|a| a.as_tor_address().de().transpose()) .transpose() .map_err(|e| ConfigurationError::SystemError(e))?; - Ok(addr.map(Value::String).unwrap_or(Value::Null)) + Ok(addr.map(Arc::new).map(Value::String).unwrap_or(Value::Null)) } } impl fmt::Display for TorAddressPointer { @@ -1745,7 +1747,11 @@ impl LanAddressPointer { .and_then(|a| a.as_lan_address().de().transpose()) .transpose() .map_err(|e| ConfigurationError::SystemError(e))?; - Ok(addr.to_owned().map(Value::String).unwrap_or(Value::Null)) + Ok(addr + .to_owned() + .map(Arc::new) + .map(Value::String) + .unwrap_or(Value::Null)) } } @@ -1823,7 +1829,7 @@ pub struct ConfigSelector { } impl ConfigSelector { fn select(&self, multi: bool, val: &Value) -> Value { - let selected = self.compiled.select(&val).ok().unwrap_or_else(Vec::new); + let selected = self.compiled.select(&val).ok().unwrap_or_else(Vector::new); if multi { Value::Array(selected.into_iter().cloned().collect()) } else { @@ -1902,10 +1908,10 @@ impl TorKeyPointer { ) .await .map_err(ConfigurationError::SystemError)?; - Ok(Value::String(base32::encode( + Ok(Value::String(Arc::new(base32::encode( base32::Alphabet::RFC4648 { padding: false }, &key.tor_key().as_bytes(), - ))) + )))) } } impl fmt::Display for TorKeyPointer { diff --git a/backend/src/config/util.rs b/backend/src/config/util.rs index f6acd092e..359c24476 100644 --- a/backend/src/config/util.rs +++ b/backend/src/config/util.rs @@ -1,9 +1,9 @@ use std::borrow::Cow; use std::ops::{Bound, RangeBounds, RangeInclusive}; +use patch_db::Value; use rand::distributions::Distribution; use rand::Rng; -use serde_json::Value; use super::Config; @@ -321,7 +321,7 @@ impl UniqueBy { match self { UniqueBy::Any(any) => any.iter().any(|u| u.eq(lhs, rhs)), UniqueBy::All(all) => all.iter().all(|u| u.eq(lhs, rhs)), - UniqueBy::Exactly(key) => lhs.get(key) == rhs.get(key), + UniqueBy::Exactly(key) => lhs.get(&**key) == rhs.get(&**key), UniqueBy::NotUnique => false, } } diff --git a/backend/src/db/prelude.rs b/backend/src/db/prelude.rs index 3a26153b4..4fce5fbcb 100644 --- a/backend/src/db/prelude.rs +++ b/backend/src/db/prelude.rs @@ -1,5 +1,6 @@ +use std::collections::BTreeMap; +use std::marker::PhantomData; use std::panic::UnwindSafe; -use std::{collections::BTreeMap, marker::PhantomData}; use patch_db::value::InternedString; pub use patch_db::{HasModel, PatchDb, Value}; diff --git a/backend/src/dependencies.rs b/backend/src/dependencies.rs index b6239c2f0..eade717d0 100644 --- a/backend/src/dependencies.rs +++ b/backend/src/dependencies.rs @@ -9,14 +9,15 @@ use rpc_toolkit::command; use serde::{Deserialize, Serialize}; use tracing::instrument; +use crate::config::action::ConfigRes; use crate::config::spec::PackagePointerSpec; -use crate::config::{action::ConfigRes, not_found}; -use crate::config::{Config, ConfigSpec, ConfigureContext}; +use crate::config::{not_found, Config, ConfigSpec, ConfigureContext}; use crate::context::RpcContext; -use crate::db::model::{CurrentDependencies, Database, InstalledPackageInfo}; +use crate::db::model::{CurrentDependencies, Database}; use crate::prelude::*; use crate::procedure::{NoOutput, PackageProcedure, ProcedureName}; -use crate::s9pk::manifest::PackageId; +use crate::s9pk::manifest::{Manifest, PackageId}; +use crate::status::DependencyConfigErrors; use crate::util::serde::display_serializable; use crate::util::{display_none, Version}; use crate::volume::Volumes; @@ -300,3 +301,61 @@ pub fn set_dependents_with_live_pointers_to_needs_config( } Ok(res) } + +pub async fn compute_dependency_config_errs( + ctx: &RpcContext, + db: &Peeked, + manifest: &Manifest, + current_dependencies: &CurrentDependencies, + dependency_config: &BTreeMap, +) -> Result { + let mut dependency_config_errs = BTreeMap::new(); + for (dependency, _dep_info) in current_dependencies + .0 + .iter() + .filter(|(dep_id, _)| dep_id != &&manifest.id) + { + // check if config passes dependency check + if let Some(cfg) = &manifest + .dependencies + .0 + .get(dependency) + .or_not_found(dependency)? + .config + { + let manifest = db + .as_package_data() + .as_idx(dependency) + .or_not_found(dependency)? + .as_installed() + .or_not_found(dependency)? + .as_manifest() + .de()?; + + if let Err(error) = cfg + .check( + ctx, + &manifest.id, + &manifest.version, + &manifest.volumes, + dependency, + &if let Some(config) = dependency_config.get(dependency) { + config.clone() + } else if let Some(config) = &manifest.config { + config + .get(ctx, &manifest.id, &manifest.version, &manifest.volumes) + .await? + .config + .unwrap_or_default() + } else { + Config::default() + }, + ) + .await? + { + dependency_config_errs.insert(dependency.clone(), error); + } + } + } + Ok(DependencyConfigErrors(dependency_config_errs)) +} diff --git a/backend/src/install/mod.rs b/backend/src/install/mod.rs index 6d4ab11fb..3e3f07fb7 100644 --- a/backend/src/install/mod.rs +++ b/backend/src/install/mod.rs @@ -19,27 +19,26 @@ use reqwest::Url; use rpc_toolkit::command; use rpc_toolkit::yajrc::RpcError; use serde_json::{json, Value}; +use tokio::fs::{File, OpenOptions}; use tokio::io::{AsyncRead, AsyncSeek, AsyncSeekExt, AsyncWriteExt}; use tokio::process::Command; -use tokio::sync::oneshot; -use tokio::{ - fs::{File, OpenOptions}, - sync::Mutex, -}; +use tokio::sync::{oneshot, Mutex}; use tokio_stream::wrappers::ReadDirStream; use tracing::instrument; use self::cleanup::{cleanup_failed, remove_from_current_dependents_lists}; +use crate::config::ConfigureContext; use crate::context::{CliContext, RpcContext}; use crate::core::rpc_continuations::{RequestGuid, RpcContinuation}; use crate::db::model::{ CurrentDependencies, CurrentDependencyInfo, CurrentDependents, InstalledPackageInfo, PackageDataEntry, PackageDataEntryInstalled, PackageDataEntryInstalling, - PackageDataEntryRemoving, PackageDataEntryRestoring, PackageDataEntryUpdating, - StaticDependencyInfo, StaticFiles, + PackageDataEntryMatchModelRef, PackageDataEntryRemoving, PackageDataEntryRestoring, + PackageDataEntryUpdating, StaticDependencyInfo, StaticFiles, }; use crate::dependencies::{ - add_dependent_to_current_dependents_lists, set_dependents_with_live_pointers_to_needs_config, + add_dependent_to_current_dependents_lists, compute_dependency_config_errs, + set_dependents_with_live_pointers_to_needs_config, }; use crate::install::cleanup::cleanup; use crate::install::progress::{InstallProgress, InstallProgressTracker}; @@ -54,7 +53,6 @@ use crate::util::io::{copy_and_shutdown, response_to_reader}; use crate::util::serde::{display_serializable, Port}; use crate::util::{display_none, AsyncFileExt, Version}; use crate::volume::{asset_dir, script_dir}; -use crate::{config::ConfigureContext, db::model::PackageDataEntryMatchModelRef}; use crate::{Error, ErrorKind, ResultExt}; pub mod cleanup; @@ -1097,10 +1095,8 @@ pub async fn install_s9pk( CurrentDependents(deps) }; - let prev = ctx - .db - .peek() - .await? + let peek = ctx.db.peek().await?; + let prev = peek .as_package_data() .as_idx(pkg_id) .or_not_found(pkg_id)? @@ -1110,7 +1106,14 @@ pub async fn install_s9pk( configured: manifest.config.is_none(), main: MainStatus::Stopped, dependency_errors: Default::default(), - dependency_config_errors: DependencyConfigErrors::default(), + dependency_config_errors: compute_dependency_config_errs( + &ctx, + &peek, + &manifest, + ¤t_dependencies, + &Default::default(), + ) + .await?, }, marketplace_url, developer_key, @@ -1138,6 +1141,7 @@ pub async fn install_s9pk( }; let mut auto_start = false; + let mut configured = false; if let PackageDataEntry::Updating(PackageDataEntryUpdating { installed: prev, .. @@ -1173,23 +1177,8 @@ pub async fn install_s9pk( migration.or(prev_migration) }; - let configured = if let Some(f) = viable_migration { - f.await?.configured && prev_is_configured - } else { - false - }; - if configured && manifest.config.is_some() { - let breakages = BTreeMap::new(); - let overrides = Default::default(); - - let configure_context = ConfigureContext { - breakages, - timeout: None, - config: None, - dry_run: false, - overrides, - }; - manager.configure(configure_context).await?; + if let Some(f) = viable_migration { + configured = f.await?.configured && prev_is_configured; } if configured || manifest.config.is_none() { auto_start = prev.status.main.running(); @@ -1234,12 +1223,24 @@ pub async fn install_s9pk( } add_dependent_to_current_dependents_lists(db, pkg_id, ¤t_dependencies)?; - // TODO: inizialize dependency config errors of dependents if config exists - set_dependents_with_live_pointers_to_needs_config(db, pkg_id) }) .await?; + if configured && manifest.config.is_some() { + let breakages = BTreeMap::new(); + let overrides = Default::default(); + + let configure_context = ConfigureContext { + breakages, + timeout: None, + config: None, + dry_run: false, + overrides, + }; + manager.configure(configure_context).await?; + } + if auto_start { manager.start(); } diff --git a/backend/src/manager/mod.rs b/backend/src/manager/mod.rs index bd9f2561d..ceb535302 100644 --- a/backend/src/manager/mod.rs +++ b/backend/src/manager/mod.rs @@ -15,21 +15,24 @@ use persistent_container::PersistentContainer; use rand::SeedableRng; use sqlx::Connection; use start_stop::StartStop; -use tokio::sync::oneshot; -use tokio::sync::{ - watch::{self, Sender}, - Mutex, -}; +use tokio::sync::watch::{self, Sender}; +use tokio::sync::{oneshot, Mutex}; use tracing::instrument; use transition_state::TransitionState; +use crate::backup::target::PackageBackupInfo; +use crate::backup::PackageBackupReport; use crate::config::action::ConfigRes; use crate::config::spec::ValueSpecPointer; use crate::config::ConfigureContext; use crate::context::RpcContext; use crate::db::model::{CurrentDependencies, CurrentDependencyInfo}; +use crate::dependencies::{ + add_dependent_to_current_dependents_lists, compute_dependency_config_errs, +}; use crate::disk::mount::backup::BackupMountGuard; use crate::disk::mount::guard::TmpMountGuard; +use crate::install::cleanup::remove_from_current_dependents_lists; use crate::net::net_controller::NetService; use crate::net::vhost::AlpnInfo; use crate::prelude::*; @@ -41,11 +44,6 @@ use crate::util::docker::{get_container_ip, kill_container}; use crate::util::NonDetachingJoinHandle; use crate::volume::Volume; use crate::Error; -use crate::{ - backup::target::PackageBackupInfo, dependencies::add_dependent_to_current_dependents_lists, - install::cleanup::remove_from_current_dependents_lists, -}; -use crate::{backup::PackageBackupReport, status::DependencyConfigErrors}; pub mod health; mod manager_container; @@ -381,15 +379,16 @@ async fn configure( // TODO Commit or not? spec.update(ctx, &manifest, overrides, &mut config).await?; // dereference pointers in the new config - let dependencies = db + let manifest = db .as_package_data() .as_idx(id) .or_not_found(id)? .as_installed() .or_not_found(id)? .as_manifest() - .as_dependencies() .de()?; + + let dependencies = &manifest.dependencies; let mut current_dependencies: CurrentDependencies = CurrentDependencies( dependencies .0 @@ -425,38 +424,13 @@ async fn configure( } } - let action = db - .as_package_data() - .as_idx(id) - .or_not_found(id)? - .as_installed() - .or_not_found(id)? - .as_manifest() - .as_config() - .de()? - .or_not_found(id)?; - let version = db - .as_package_data() - .as_idx(id) - .or_not_found(id)? - .as_installed() - .or_not_found(id)? - .as_manifest() - .as_version() - .de()?; - let volumes = db - .as_package_data() - .as_idx(id) - .or_not_found(id)? - .as_installed() - .or_not_found(id)? - .as_manifest() - .as_volumes() - .de()?; + let action = manifest.config.as_ref().or_not_found(id)?; + let version = &manifest.version; + let volumes = &manifest.volumes; if !configure_context.dry_run { // run config action let res = action - .set(ctx, id, &version, &dependencies, &volumes, &config) + .set(ctx, id, version, &dependencies, volumes, &config) .await?; // track dependencies with no pointers @@ -489,44 +463,9 @@ async fn configure( }); } - let mut dependency_config_errs = BTreeMap::new(); - for (dependency, _dep_info) in current_dependencies - .0 - .iter() - .filter(|(dep_id, _)| dep_id != &id) - { - // check if config passes dependency check - if let Some(cfg) = db - .as_package_data() - .as_idx(dependency) - .and_then(|pde| pde.as_installed()) - .and_then(|i| i.as_manifest().as_dependencies().as_idx(id)) - .and_then(|d| d.as_config().de().transpose()) - .transpose()? - { - let manifest = db - .as_package_data() - .as_idx(dependency) - .or_not_found(dependency)? - .as_installed() - .or_not_found(dependency)? - .as_manifest() - .de()?; - if let Err(error) = cfg - .check( - ctx, - dependency, - &manifest.version, - &manifest.volumes, - id, - &config, - ) - .await? - { - dependency_config_errs.insert(dependency.clone(), error); - } - } - } + let dependency_config_errs = + compute_dependency_config_errs(&ctx, &db, &manifest, ¤t_dependencies, overrides) + .await?; // cache current config for dependents configure_context @@ -616,7 +555,7 @@ async fn configure( status.as_configured_mut().ser(&true)?; status .as_dependency_config_errors_mut() - .ser(&DependencyConfigErrors(dependency_config_errs))?; + .ser(&dependency_config_errs)?; Ok(configure_context.breakages) }) .await; // add new diff --git a/backend/src/util/config.rs b/backend/src/util/config.rs index ddc6f87c8..f719f563f 100644 --- a/backend/src/util/config.rs +++ b/backend/src/util/config.rs @@ -1,11 +1,12 @@ use std::fs::File; use std::path::{Path, PathBuf}; +use patch_db::Value; use serde::Deserialize; -use serde_json::Value; +use crate::prelude::*; use crate::util::serde::IoFormat; -use crate::{Config, Error, ResultExt}; +use crate::{Config, Error}; pub const DEVICE_CONFIG_PATH: &str = "/media/embassy/config/config.yaml"; pub const CONFIG_PATH: &str = "/etc/embassy/config.yaml"; @@ -37,7 +38,7 @@ pub fn load_config_from_paths<'a, T: for<'de> Deserialize<'de>>( config = merge_configs(config, new); } } - serde_json::from_value(Value::Object(config)).with_kind(crate::ErrorKind::Deserialization) + from_value(Value::Object(config)) } pub fn merge_configs(mut first: Config, second: Config) -> Config { diff --git a/backend/src/version/v0_3_4_1.rs b/backend/src/version/v0_3_4_1.rs index 2d3e5af56..915a47235 100644 --- a/backend/src/version/v0_3_4_1.rs +++ b/backend/src/version/v0_3_4_1.rs @@ -1,8 +1,8 @@ use async_trait::async_trait; use emver::VersionRange; -use super::{v0_3_4::V0_3_0_COMPAT, *}; - +use super::v0_3_4::V0_3_0_COMPAT; +use super::*; use crate::prelude::*; const V0_3_4_1: emver::Version = emver::Version::new(0, 3, 4, 1); diff --git a/backend/src/version/v0_3_4_2.rs b/backend/src/version/v0_3_4_2.rs index 6cfe2b244..5931b2879 100644 --- a/backend/src/version/v0_3_4_2.rs +++ b/backend/src/version/v0_3_4_2.rs @@ -3,7 +3,6 @@ use emver::VersionRange; use super::v0_3_4::V0_3_0_COMPAT; use super::*; - use crate::prelude::*; const V0_3_4_2: emver::Version = emver::Version::new(0, 3, 4, 2); diff --git a/backend/src/version/v0_3_4_3.rs b/backend/src/version/v0_3_4_3.rs index d13f7bca8..d3199e913 100644 --- a/backend/src/version/v0_3_4_3.rs +++ b/backend/src/version/v0_3_4_3.rs @@ -3,7 +3,6 @@ use emver::VersionRange; use super::v0_3_4::V0_3_0_COMPAT; use super::*; - use crate::prelude::*; const V0_3_4_3: emver::Version = emver::Version::new(0, 3, 4, 3); diff --git a/backend/src/version/v0_3_4_4.rs b/backend/src/version/v0_3_4_4.rs index 6056cdcd5..b6345ca4c 100644 --- a/backend/src/version/v0_3_4_4.rs +++ b/backend/src/version/v0_3_4_4.rs @@ -3,10 +3,10 @@ use emver::VersionRange; use models::ResultExt; use sqlx::PgPool; +use super::v0_3_4::V0_3_0_COMPAT; +use super::{v0_3_4_3, VersionT}; use crate::prelude::*; -use super::{v0_3_4::V0_3_0_COMPAT, v0_3_4_3, VersionT}; - const V0_3_4_4: emver::Version = emver::Version::new(0, 3, 4, 4); #[derive(Clone, Debug)] diff --git a/libs/helpers/src/lib.rs b/libs/helpers/src/lib.rs index d5893b0a1..b854f142c 100644 --- a/libs/helpers/src/lib.rs +++ b/libs/helpers/src/lib.rs @@ -1,9 +1,7 @@ +use std::future::Future; +use std::ops::{Deref, DerefMut}; use std::path::{Path, PathBuf}; use std::time::Duration; -use std::{ - future::Future, - ops::{Deref, DerefMut}, -}; use color_eyre::eyre::{eyre, Context, Error}; use futures::future::BoxFuture; diff --git a/libs/models/src/lib.rs b/libs/models/src/lib.rs index 9778a0728..ad9055f24 100644 --- a/libs/models/src/lib.rs +++ b/libs/models/src/lib.rs @@ -5,7 +5,6 @@ mod mime; mod procedure_name; mod version; -pub use data_url::*; pub use data_url::*; pub use errors::*; pub use id::*; From d322fd9c7f5f45794914506655da401c33c31d41 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Wed, 27 Sep 2023 13:56:59 -0600 Subject: [PATCH 86/89] fix compat --- compress-uis.sh | 28 ++++---- system-images/compat/Cargo.lock | 5 +- system-images/compat/Cargo.toml | 1 + system-images/compat/src/config/mod.rs | 4 +- system-images/compat/src/config/rules.rs | 86 +++++++++++++----------- 5 files changed, 67 insertions(+), 57 deletions(-) diff --git a/compress-uis.sh b/compress-uis.sh index d6713d7b9..1a7ea9124 100755 --- a/compress-uis.sh +++ b/compress-uis.sh @@ -4,19 +4,21 @@ set -e rm -rf frontend/dist/static -find frontend/dist/raw -type f -not -name '*.gz' -and -not -name '*.br' | xargs -n 1 -P 0 gzip -kf -find frontend/dist/raw -type f -not -name '*.gz' -and -not -name '*.br' | xargs -n 1 -P 0 brotli -kf +if ! [[ "$ENVIRONMENT" =~ (^|-)dev($|-) ]]; then + find frontend/dist/raw -type f -not -name '*.gz' -and -not -name '*.br' | xargs -n 1 -P 0 gzip -kf + find frontend/dist/raw -type f -not -name '*.gz' -and -not -name '*.br' | xargs -n 1 -P 0 brotli -kf -for file in $(find frontend/dist/raw -type f -not -name '*.gz' -and -not -name '*.br'); do - raw_size=$(du $file | awk '{print $1 * 512}') - gz_size=$(du $file.gz | awk '{print $1 * 512}') - br_size=$(du $file.br | awk '{print $1 * 512}') - if [ $((gz_size * 100 / raw_size)) -gt 70 ]; then - rm $file.gz - fi - if [ $((br_size * 100 / raw_size)) -gt 70 ]; then - rm $file.br - fi -done + for file in $(find frontend/dist/raw -type f -not -name '*.gz' -and -not -name '*.br'); do + raw_size=$(du $file | awk '{print $1 * 512}') + gz_size=$(du $file.gz | awk '{print $1 * 512}') + br_size=$(du $file.br | awk '{print $1 * 512}') + if [ $((gz_size * 100 / raw_size)) -gt 70 ]; then + rm $file.gz + fi + if [ $((br_size * 100 / raw_size)) -gt 70 ]; then + rm $file.br + fi + done +fi cp -r frontend/dist/raw frontend/dist/static \ No newline at end of file diff --git a/system-images/compat/Cargo.lock b/system-images/compat/Cargo.lock index 468598c0d..3e05e0d4b 100644 --- a/system-images/compat/Cargo.lock +++ b/system-images/compat/Cargo.lock @@ -574,6 +574,7 @@ dependencies = [ "dashmap", "emver", "failure", + "imbl-value", "indexmap", "itertools 0.10.5", "lazy_static", @@ -2090,9 +2091,9 @@ dependencies = [ [[package]] name = "jsonpath_lib" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaa63191d68230cccb81c5aa23abd53ed64d83337cacbb25a7b8c7979523774f" +source = "git+https://github.com/Start9Labs/jsonpath.git#1cacbd64afa2e1941a21fef06bad14317ba92f30" dependencies = [ + "imbl-value", "log", "serde", "serde_json", diff --git a/system-images/compat/Cargo.toml b/system-images/compat/Cargo.toml index 735667a36..199b7e1c5 100644 --- a/system-images/compat/Cargo.toml +++ b/system-images/compat/Cargo.toml @@ -17,6 +17,7 @@ emver = { version = "0.1.7", git = "https://github.com/Start9Labs/emver-rs.git", ] } failure = "0.1.8" indexmap = { version = "1.6.2", features = ["serde"] } +imbl-value = { git = "https://github.com/Start9Labs/imbl-value.git" } itertools = "0.10.0" lazy_static = "1.4" linear-map = { version = "1.2", features = ["serde_impl"] } diff --git a/system-images/compat/src/config/mod.rs b/system-images/compat/src/config/mod.rs index 89e4b9282..ce591b06a 100644 --- a/system-images/compat/src/config/mod.rs +++ b/system-images/compat/src/config/mod.rs @@ -79,7 +79,7 @@ pub fn validate_dependency_configuration( if let Some(config) = config { cfgs.insert(name, Cow::Borrowed(&config)) } else { - cfgs.insert(name, Cow::Owned(serde_json::Map::new())) + cfgs.insert(name, Cow::Owned(imbl_value::InOMap::new())) }; let rule_check = rules .into_iter() @@ -104,7 +104,7 @@ pub fn apply_dependency_configuration( cfgs.insert(dependency_id, Cow::Owned(dep_config.clone())); match config { Some(config) => cfgs.insert(package_id, Cow::Owned(config.clone())), - None => cfgs.insert(package_id, Cow::Owned(serde_json::Map::new())), + None => cfgs.insert(package_id, Cow::Owned(imbl_value::InOMap::new())), }; let rule_check = rules .into_iter() diff --git a/system-images/compat/src/config/rules.rs b/system-images/compat/src/config/rules.rs index ea2650237..a74ae195c 100644 --- a/system-images/compat/src/config/rules.rs +++ b/system-images/compat/src/config/rules.rs @@ -1,11 +1,11 @@ use std::borrow::Cow; use std::sync::Arc; +use imbl_value::{InternedString, Value}; use linear_map::LinearMap; use pest::iterators::Pairs; use pest::Parser; use rand::SeedableRng; -use serde_json::Value; use startos::config::util::STATIC_NULL; use startos::config::Config; @@ -382,7 +382,7 @@ fn compile_var_rec(mut ident: Pairs) -> Option { match idx.as_rule() { Rule::list_access_function_first => { let mut pred_iter = idx.into_inner(); - let item_var = pred_iter.next().unwrap().as_str().to_owned(); + let item_var: InternedString = pred_iter.next().unwrap().as_str().into(); let predicate = compile_bool_expr(pred_iter.next().unwrap().into_inner()); Box::new(move |v, cfgs| match v { Value::Array(l) => VarRes::Exactly( @@ -411,7 +411,7 @@ fn compile_var_rec(mut ident: Pairs) -> Option { } Rule::list_access_function_last => { let mut pred_iter = idx.into_inner(); - let item_var = pred_iter.next().unwrap().as_str().to_owned(); + let item_var: InternedString = pred_iter.next().unwrap().as_str().into(); let predicate = compile_bool_expr(pred_iter.next().unwrap().into_inner()); Box::new(move |v, cfgs| match v { Value::Array(l) => VarRes::Exactly( @@ -440,7 +440,7 @@ fn compile_var_rec(mut ident: Pairs) -> Option { } Rule::list_access_function_any => { let mut pred_iter = idx.into_inner(); - let item_var = pred_iter.next().unwrap().as_str().to_owned(); + let item_var: InternedString = pred_iter.next().unwrap().as_str().into(); let predicate = compile_bool_expr(pred_iter.next().unwrap().into_inner()); Box::new(move |v, cfgs| match v { Value::Array(l) => VarRes::Any( @@ -469,7 +469,7 @@ fn compile_var_rec(mut ident: Pairs) -> Option { } Rule::list_access_function_all => { let mut pred_iter = idx.into_inner(); - let item_var = pred_iter.next().unwrap().as_str().to_owned(); + let item_var: InternedString = pred_iter.next().unwrap().as_str().into(); let predicate = compile_bool_expr(pred_iter.next().unwrap().into_inner()); Box::new(move |v, cfgs| match v { Value::Array(l) => VarRes::All( @@ -506,7 +506,7 @@ fn compile_var_rec(mut ident: Pairs) -> Option { let idx = idx.as_str().to_owned(); Box::new(move |v, _| match v { Value::Object(o) => { - VarRes::Exactly(o.get(&idx).unwrap_or(&STATIC_NULL)) + VarRes::Exactly(o.get(&*idx).unwrap_or(&STATIC_NULL)) } _ => VarRes::Exactly(&STATIC_NULL), }) @@ -514,8 +514,9 @@ fn compile_var_rec(mut ident: Pairs) -> Option { Rule::sub_ident_regular_expr => { let idx = compile_str_expr(idx.into_inner().next().unwrap().into_inner()); Box::new(move |v, dep_cfg| match v { - Value::Object(o) => idx(&Config::default(), dep_cfg) - .map(|idx| idx.and_then(|idx| o.get(&idx)).unwrap_or(&STATIC_NULL)), + Value::Object(o) => idx(&Config::default(), dep_cfg).map(|idx| { + idx.and_then(|idx| o.get(&*idx)).unwrap_or(&STATIC_NULL) + }), _ => VarRes::Exactly(&STATIC_NULL), }) } @@ -575,7 +576,7 @@ fn compile_var(mut var: Pairs) -> CompiledExpr> { return VarRes::Exactly(Value::Null); }; } - let val = cfg.get(&first_seg_string).unwrap_or(&STATIC_NULL); + let val = cfg.get(&*first_seg_string).unwrap_or(&STATIC_NULL); if let Some(accessor) = &accessor { accessor(val, cfgs).map(|v| v.clone()) } else { @@ -593,7 +594,7 @@ fn compile_var_mut_rec(mut ident: Pairs) -> Result, fa match idx.as_rule() { Rule::list_access_function_first => { let mut pred_iter = idx.into_inner(); - let item_var = pred_iter.next().unwrap().as_str().to_owned(); + let item_var: InternedString = pred_iter.next().unwrap().as_str().into(); let predicate = compile_bool_expr(pred_iter.next().unwrap().into_inner()); Box::new(move |v, cfgs| match v { Value::Array(l) => l @@ -618,7 +619,7 @@ fn compile_var_mut_rec(mut ident: Pairs) -> Result, fa } Rule::list_access_function_last => { let mut pred_iter = idx.into_inner(); - let item_var = pred_iter.next().unwrap().as_str().to_owned(); + let item_var: InternedString = pred_iter.next().unwrap().as_str().into(); let predicate = compile_bool_expr(pred_iter.next().unwrap().into_inner()); Box::new(move |v, cfgs| match v { Value::Array(l) => l @@ -651,14 +652,14 @@ fn compile_var_mut_rec(mut ident: Pairs) -> Result, fa let idx = idx.into_inner().next().unwrap(); match idx.as_rule() { Rule::sub_ident_regular_base => { - let idx = idx.as_str().to_owned(); + let idx: InternedString = idx.as_str().into(); Box::new(move |v, _| match v { Value::Object(ref mut o) => { - if o.contains_key(&idx) { - o.get_mut(&idx) + if o.contains_key(&*idx) { + o.get_mut(&*idx) } else { o.insert(idx.clone(), Value::Null); - o.get_mut(&idx) + o.get_mut(&*idx) } } _ => None, @@ -669,11 +670,11 @@ fn compile_var_mut_rec(mut ident: Pairs) -> Result, fa Box::new( move |v, dep_cfg| match (v, idx(&Config::default(), dep_cfg)) { (Value::Object(ref mut o), VarRes::Exactly(Some(ref idx))) => { - if o.contains_key(idx) { - o.get_mut(idx) + if o.contains_key(&**idx) { + o.get_mut(&**idx) } else { o.insert(idx.clone(), Value::Null); - o.get_mut(idx) + o.get_mut(&**idx) } } _ => None, @@ -693,7 +694,7 @@ fn compile_var_mut_rec(mut ident: Pairs) -> Result, fa if l.len() > idx { l.get_mut(idx) } else if idx == l.len() { - l.push(Value::Null); + l.push_back(Value::Null); l.get_mut(idx) } else { None @@ -711,7 +712,7 @@ fn compile_var_mut_rec(mut ident: Pairs) -> Result, fa if l.len() > idx { l.get_mut(idx) } else if idx == l.len() { - l.push(Value::Null); + l.push_back(Value::Null); l.get_mut(idx) } else { None @@ -741,11 +742,11 @@ fn compile_var_mut(mut var: Pairs) -> Result) -> CompiledRule { } } -fn compile_str_var(var: Pairs) -> CompiledExpr>> { +fn compile_str_var(var: Pairs) -> CompiledExpr>> { let var = compile_var(var); Box::new(move |cfg, cfgs| { var(cfg, cfgs).map(|a| match a { - Value::String(s) => Some(s), - Value::Number(n) => Some(format!("{}", n)), - Value::Bool(b) => Some(format!("{}", b)), + Value::String(s) => Some(InternedString::from(&*s)), + Value::Number(n) => Some(InternedString::from_display(&n)), + Value::Bool(b) => Some(InternedString::from_display(&b)), _ => None, }) }) } -fn compile_str(str_str: &str) -> CompiledExpr>> { +fn compile_str(str_str: &str) -> CompiledExpr>> { let str_str = &str_str[1..str_str.len() - 1]; let mut out = String::with_capacity(str_str.len()); let mut escape = false; @@ -904,11 +905,11 @@ fn compile_str(str_str: &str) -> CompiledExpr>> { } } } - let res = VarRes::Exactly(Some(out)); + let res = VarRes::Exactly(Some(out.into())); Box::new(move |_, _| res.clone()) } -fn compile_str_expr(pairs: Pairs) -> CompiledExpr>> { +fn compile_str_expr(pairs: Pairs) -> CompiledExpr>> { STR_PREC_CLIMBER.climb( pairs, |pair| match pair.as_rule() { @@ -921,9 +922,9 @@ fn compile_str_expr(pairs: Pairs) -> CompiledExpr>> Rule::add => Box::new(move |cfg, cfgs| { lhs(cfg, cfgs).and_then(|lhs| { rhs(cfg, cfgs).map(|rhs| { - let lhs = lhs.clone()?; + let lhs = lhs.as_ref()?.to_string(); let rhs = rhs?; - Some(lhs + &rhs) + Some(InternedString::from(lhs + &*rhs)) }) }) }), @@ -941,7 +942,7 @@ fn compile_str_cmp_expr(mut pairs: Pairs) -> CompiledRule { lhs(cfg, cfgs) .and_then(|lhs| { rhs(cfg, cfgs).map(|rhs| match (&lhs, &rhs) { - (Some(lhs), Some(rhs)) => rhs.contains(lhs) && lhs.len() < rhs.len(), + (Some(lhs), Some(rhs)) => rhs.contains(&**lhs) && lhs.len() < rhs.len(), _ => false, }) }) @@ -951,7 +952,7 @@ fn compile_str_cmp_expr(mut pairs: Pairs) -> CompiledRule { lhs(cfg, cfgs) .and_then(|lhs| { rhs(cfg, cfgs).map(|rhs| match (&lhs, &rhs) { - (Some(lhs), Some(rhs)) => rhs.contains(lhs), + (Some(lhs), Some(rhs)) => rhs.contains(&**lhs), _ => false, }) }) @@ -983,7 +984,7 @@ fn compile_str_cmp_expr(mut pairs: Pairs) -> CompiledRule { lhs(cfg, cfgs) .and_then(|lhs| { rhs(cfg, cfgs).map(|rhs| match (&lhs, &rhs) { - (Some(lhs), Some(rhs)) => lhs.contains(rhs) && lhs.len() > rhs.len(), + (Some(lhs), Some(rhs)) => lhs.contains(&**rhs) && lhs.len() > rhs.len(), _ => true, }) }) @@ -993,7 +994,7 @@ fn compile_str_cmp_expr(mut pairs: Pairs) -> CompiledRule { lhs(cfg, cfgs) .and_then(|lhs| { rhs(cfg, cfgs).map(|rhs| match (&lhs, &rhs) { - (Some(lhs), Some(rhs)) => lhs.contains(rhs), + (Some(lhs), Some(rhs)) => lhs.contains(&**rhs), _ => true, }) }) @@ -1037,7 +1038,10 @@ fn compile_value_expr(mut pairs: Pairs) -> CompiledExpr> { Rule::str_expr => { let expr = compile_str_expr(expr.into_inner()); Box::new(move |cfg, cfgs| { - expr(cfg, cfgs).map(|s| s.map(Value::String).unwrap_or(Value::Null)) + expr(cfg, cfgs).map(|s| { + s.map(|s| Value::String(Arc::new(s.to_string()))) + .unwrap_or(Value::Null) + }) }) } Rule::num_expr => { @@ -1059,7 +1063,7 @@ fn compile_value_expr(mut pairs: Pairs) -> CompiledExpr> { fn compile_del_action(mut pairs: Pairs) -> Result { let list_mut = compile_var_mut(pairs.next().unwrap().into_inner())?; - let var = pairs.next().unwrap().as_str().to_owned(); + let var: InternedString = pairs.next().unwrap().as_str().into(); let predicate = compile_bool_expr(pairs.next().unwrap().into_inner()); Ok(Box::new(move |cfg, cfgs| match (&list_mut)(cfg, cfgs) { Some(Value::Array(ref mut l)) => { @@ -1093,7 +1097,7 @@ fn compile_push_action(mut pairs: Pairs, value: Value) -> Result a, _ => return, }; - vec.push(value.clone()) + vec.push_back(value.clone()) })) } @@ -1122,7 +1126,9 @@ fn compile_set_action(var: &str, to: &SetVariant) -> Result Date: Wed, 27 Sep 2023 14:10:36 -0600 Subject: [PATCH 87/89] cache docker --- .github/workflows/startos-iso.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/startos-iso.yaml b/.github/workflows/startos-iso.yaml index b495acf5b..c2e568d47 100644 --- a/.github/workflows/startos-iso.yaml +++ b/.github/workflows/startos-iso.yaml @@ -106,6 +106,11 @@ jobs: with: node-version: ${{ env.NODEJS_VERSION }} + - uses: actions/cache@v3 + with: + path: /var/lib/docker + key: ${{ runner.os }}-${{ matrix.platform }}-docker-cache + - name: Get npm cache directory id: npm-cache-dir run: | From 90ba212ad659cb86ca74f93f114ff8608a2c9cf4 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Wed, 27 Sep 2023 14:56:40 -0600 Subject: [PATCH 88/89] fix dependency expectation --- backend/src/dependencies.rs | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/backend/src/dependencies.rs b/backend/src/dependencies.rs index eade717d0..d6ca30c6a 100644 --- a/backend/src/dependencies.rs +++ b/backend/src/dependencies.rs @@ -302,6 +302,7 @@ pub fn set_dependents_with_live_pointers_to_needs_config( Ok(res) } +#[instrument(skip_all)] pub async fn compute_dependency_config_errs( ctx: &RpcContext, db: &Peeked, @@ -323,15 +324,6 @@ pub async fn compute_dependency_config_errs( .or_not_found(dependency)? .config { - let manifest = db - .as_package_data() - .as_idx(dependency) - .or_not_found(dependency)? - .as_installed() - .or_not_found(dependency)? - .as_manifest() - .de()?; - if let Err(error) = cfg .check( ctx, @@ -341,12 +333,22 @@ pub async fn compute_dependency_config_errs( dependency, &if let Some(config) = dependency_config.get(dependency) { config.clone() - } else if let Some(config) = &manifest.config { - config - .get(ctx, &manifest.id, &manifest.version, &manifest.volumes) - .await? - .config - .unwrap_or_default() + } else if let Some(manifest) = db + .as_package_data() + .as_idx(dependency) + .and_then(|pde| pde.as_installed()) + .map(|i| i.as_manifest().de()) + .transpose()? + { + if let Some(config) = &manifest.config { + config + .get(ctx, &manifest.id, &manifest.version, &manifest.volumes) + .await? + .config + .unwrap_or_default() + } else { + Config::default() + } } else { Config::default() }, From a741979ae19bea6cb2cc6ecd8dd8ee626dc1e34e Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Wed, 27 Sep 2023 15:25:15 -0600 Subject: [PATCH 89/89] fix dependency auto-config --- backend/src/dependencies.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/dependencies.rs b/backend/src/dependencies.rs index d6ca30c6a..299518057 100644 --- a/backend/src/dependencies.rs +++ b/backend/src/dependencies.rs @@ -181,10 +181,10 @@ pub async fn configure_logic( let pkg_volumes = pkg.as_manifest().as_volumes().de()?; let dependency = db .as_package_data() - .as_idx(&pkg_id) - .or_not_found(&pkg_id)? + .as_idx(&dependency_id) + .or_not_found(&dependency_id)? .as_installed() - .or_not_found(&pkg_id)?; + .or_not_found(&dependency_id)?; let dependency_config_action = dependency .as_manifest() .as_config()