diff --git a/Cargo.lock b/Cargo.lock index d49a76d..34402b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -156,6 +156,51 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.31", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper 0.1.2", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.74" @@ -171,6 +216,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base58ck" version = "0.1.0" @@ -292,6 +343,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.6.0" @@ -552,6 +609,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -609,7 +678,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b035a542cf7abf01f2e3c4d5a7acbaebfefe120ae4efc7bde3df98186e4b8af7" dependencies = [ - "bitflags", + "bitflags 2.6.0", "proc-macro2", "proc-macro2-diagnostics", "quote", @@ -628,6 +697,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dlv-list" version = "0.5.2" @@ -643,6 +723,20 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + [[package]] name = "either" version = "1.13.0" @@ -652,6 +746,25 @@ dependencies = [ "serde", ] +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encoding_rs" version = "0.8.35" @@ -718,6 +831,36 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +[[package]] +name = "fedimint-tonic-lnd" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df03ca33b5116de3051c1e233fe341e23b04c4913c7b16042497924559bc2a2e" +dependencies = [ + "hex", + "http-body 0.4.6", + "hyper 0.14.31", + "hyper-rustls 0.24.2", + "prost", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", + "tokio", + "tokio-stream", + "tonic", + "tonic-build", + "tower", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + [[package]] name = "figment" version = "0.10.19" @@ -732,6 +875,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flume" version = "0.11.1" @@ -881,6 +1030,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -908,6 +1058,17 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "h2" version = "0.3.26" @@ -920,7 +1081,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -939,13 +1100,19 @@ dependencies = [ "futures-core", "futures-sink", "http 1.1.0", - "indexmap", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.5" @@ -1170,6 +1337,20 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.31", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", +] + [[package]] name = "hyper-rustls" version = "0.27.3" @@ -1180,13 +1361,25 @@ dependencies = [ "http 1.1.0", "hyper 1.5.0", "hyper-util", - "rustls", + "rustls 0.23.15", "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.0", "tower-service", ] +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper 0.14.31", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + [[package]] name = "hyper-tls" version = "0.6.0" @@ -1246,13 +1439,152 @@ dependencies = [ ] [[package]] -name = "idna" -version = "0.5.0" +name = "icu_collections" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", ] [[package]] @@ -1311,6 +1643,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -1375,6 +1716,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + [[package]] name = "lnvps" version = "0.1.0" @@ -1383,6 +1730,7 @@ dependencies = [ "base64 0.22.1", "chrono", "config", + "fedimint-tonic-lnd", "lnvps_db", "log", "nostr", @@ -1391,7 +1739,9 @@ dependencies = [ "rocket", "serde", "serde_json", + "ssh-key", "tokio", + "urlencoding", ] [[package]] @@ -1445,6 +1795,12 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "md-5" version = "0.10.6" @@ -1513,6 +1869,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" + [[package]] name = "native-tls" version = "0.2.12" @@ -1678,7 +2040,7 @@ version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ - "bitflags", + "bitflags 2.6.0", "cfg-if", "foreign-types", "libc", @@ -1732,6 +2094,44 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p521" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" +dependencies = [ + "base16ct", + "ecdsa", + "elliptic-curve", + "primeorder", + "rand_core", + "sha2", +] + [[package]] name = "parking" version = "2.2.1" @@ -1877,6 +2277,36 @@ dependencies = [ "sha2", ] +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap 2.6.0", +] + +[[package]] +name = "pin-project" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.15" @@ -1952,6 +2382,25 @@ dependencies = [ "log", ] +[[package]] +name = "prettyplease" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro2" version = "1.0.89" @@ -1974,6 +2423,59 @@ dependencies = [ "yansi", ] +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" +dependencies = [ + "bytes", + "heck", + "itertools", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +dependencies = [ + "prost", +] + [[package]] name = "quote" version = "1.0.37" @@ -2019,7 +2521,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags", + "bitflags 2.6.0", ] [[package]] @@ -2102,7 +2604,7 @@ dependencies = [ "http-body 1.0.1", "http-body-util", "hyper 1.5.0", - "hyper-rustls", + "hyper-rustls 0.27.3", "hyper-tls", "hyper-util", "ipnet", @@ -2113,11 +2615,11 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile", + "rustls-pemfile 2.2.0", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.1", "system-configuration", "tokio", "tokio-native-tls", @@ -2129,6 +2631,16 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.17.8" @@ -2158,7 +2670,7 @@ dependencies = [ "either", "figment", "futures", - "indexmap", + "indexmap 2.6.0", "log", "memchr", "multer", @@ -2190,7 +2702,7 @@ checksum = "575d32d7ec1a9770108c879fc7c47815a80073f96ca07ff9525a94fcede1dd46" dependencies = [ "devise", "glob", - "indexmap", + "indexmap 2.6.0", "proc-macro2", "quote", "rocket_http", @@ -2210,7 +2722,7 @@ dependencies = [ "futures", "http 0.2.12", "hyper 0.14.31", - "indexmap", + "indexmap 2.6.0", "log", "memchr", "pear", @@ -2233,7 +2745,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64 0.21.7", - "bitflags", + "bitflags 2.6.0", "serde", "serde_derive", ] @@ -2252,6 +2764,7 @@ dependencies = [ "pkcs1", "pkcs8", "rand_core", + "sha2", "signature", "spki", "subtle", @@ -2280,13 +2793,25 @@ version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ - "bitflags", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + [[package]] name = "rustls" version = "0.23.15" @@ -2295,11 +2820,20 @@ checksum = "5fbb44d7acc4e873d613422379f69f237a1b141928c02f6bc6ccfddddc2d7993" dependencies = [ "once_cell", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.102.8", "subtle", "zeroize", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + [[package]] name = "rustls-pemfile" version = "2.2.0" @@ -2315,6 +2849,16 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustls-webpki" version = "0.102.8" @@ -2380,6 +2924,30 @@ dependencies = [ "sha2", ] +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "secp256k1" version = "0.29.1" @@ -2407,7 +2975,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -2450,7 +3018,7 @@ version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ - "indexmap", + "indexmap 2.6.0", "itoa", "memchr", "ryu", @@ -2626,7 +3194,7 @@ dependencies = [ "hashbrown 0.14.5", "hashlink 0.9.1", "hex", - "indexmap", + "indexmap 2.6.0", "log", "memchr", "once_cell", @@ -2691,7 +3259,7 @@ checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" dependencies = [ "atoi", "base64 0.22.1", - "bitflags", + "bitflags 2.6.0", "byteorder", "bytes", "chrono", @@ -2734,7 +3302,7 @@ checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" dependencies = [ "atoi", "base64 0.22.1", - "bitflags", + "bitflags 2.6.0", "byteorder", "chrono", "crc", @@ -2789,6 +3357,47 @@ dependencies = [ "url", ] +[[package]] +name = "ssh-cipher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caac132742f0d33c3af65bfcde7f6aa8f62f0e991d80db99149eb9d44708784f" +dependencies = [ + "cipher", + "ssh-encoding", +] + +[[package]] +name = "ssh-encoding" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9242b9ef4108a78e8cd1a2c98e193ef372437f8c22be363075233321dd4a15" +dependencies = [ + "base64ct", + "pem-rfc7468", + "sha2", +] + +[[package]] +name = "ssh-key" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b86f5297f0f04d08cabaa0f6bff7cb6aec4d9c3b49d87990d63da9d9156a8c3" +dependencies = [ + "p256", + "p384", + "p521", + "rand_core", + "rsa", + "sec1", + "sha2", + "signature", + "ssh-cipher", + "ssh-encoding", + "subtle", + "zeroize", +] + [[package]] name = "stable-pattern" version = "0.1.0" @@ -2798,6 +3407,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "state" version = "0.6.0" @@ -2835,6 +3450,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "sync_wrapper" version = "1.0.1" @@ -2844,13 +3465,24 @@ dependencies = [ "futures-core", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "system-configuration" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags", + "bitflags 2.6.0", "core-foundation", "system-configuration-sys", ] @@ -2957,6 +3589,16 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -2989,6 +3631,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-macros" version = "2.4.0" @@ -3010,13 +3662,23 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls", + "rustls 0.23.15", "rustls-pki-types", "tokio", ] @@ -3072,13 +3734,82 @@ version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime", "winnow", ] +[[package]] +name = "tonic" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.21.7", + "bytes", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.31", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", + "tokio", + "tokio-rustls 0.24.1", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d021fc044c18582b9a2408cd0dd05b1596e3ecdb5c4df822bb0183545683889" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "quote", + "syn", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" @@ -3247,9 +3978,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.2" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -3257,6 +3988,24 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "valuable" version = "0.1.0" @@ -3619,6 +4368,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "yaml-rust2" version = "0.8.1" @@ -3639,6 +4400,30 @@ dependencies = [ "is-terminal", ] +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -3660,8 +4445,51 @@ dependencies = [ "syn", ] +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 6c87c4c..71e32d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,6 @@ rocket = { version = "0.5.1", features = ["json"] } chrono = { version = "0.4.38", features = ["serde"] } nostr = { version = "0.36.0", default-features = false, features = ["std"] } base64 = "0.22.1" +ssh-key = "0.6.7" +urlencoding = "2.1.3" +fedimint-tonic-lnd = { version = "0.2.0", default-features = false, features = ["invoicesrpc"] } diff --git a/config.yaml b/config.yaml index 5ee4c72..46ee591 100644 --- a/config.yaml +++ b/config.yaml @@ -1,2 +1,6 @@ # MySQL database connection string -db: "mysql://root:root@localhost:3376/lnvps" \ No newline at end of file +db: "mysql://root:root@localhost:3376/lnvps" +lnd: + url: "https://127.0.0.1:10003" + cert: "/home/kieran/.polar/networks/2/volumes/lnd/alice/tls.cert" + macaroon: "/home/kieran/.polar/networks/2/volumes/lnd/alice/data/chain/bitcoin/regtest/admin.macaroon" \ No newline at end of file diff --git a/dev_setup.sql b/dev_setup.sql index 0eb5e2a..496c6eb 100644 --- a/dev_setup.sql +++ b/dev_setup.sql @@ -1,15 +1,62 @@ insert ignore into vm_host_region(id,name,enabled) values(1,"uat",1); insert -ignore into vm_host(id,kind,region_id,name,ip,cpu,memory,enabled,api_token) values(1, 0, 1, "lab", "https://185.18.221.8:8006", 4, 4096*1024, 1, "root@pam!tester=c82f8a57-f876-4ca4-8610-c086d8d9d51c"); +ignore into vm_host(id,kind,region_id,name,ip,cpu,memory,enabled,api_token) +values(1, 0, 1, "lab", "https://185.18.221.8:8006", 4, 4096*1024, 1, "root@pam!tester=c82f8a57-f876-4ca4-8610-c086d8d9d51c"); insert -ignore into vm_host_disk(id,host_id,name,size,kind,interface,enabled) values(1,1,"local-lvm",1000*1000*1000*1000, 0, 0, 1); +ignore into vm_host_disk(id,host_id,name,size,kind,interface,enabled) +values(1,1,"local-lvm",1000*1000*1000*1000, 0, 0, 1); insert -ignore into vm_os_image(id,name,distribution,flavour,version,enabled,url) values(1,0,"Server","24.04",1,"https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img"); +ignore into vm_os_image(id,distribution,flavour,version,enabled,url,release_date) +values(1, 0,"Server","24.04",1,"https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img","2024-04-25"); insert -ignore into ip_range(id,cidr,enabled) values(1,"185.18.221.80/28",1); +ignore into vm_os_image(id,distribution,flavour,version,enabled,url,release_date) +values(2, 0,"Server","22.04",1,"https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img","2022-04-21"); insert -ignore into vm_cost_plan(id,name,amount,currency,interval_amount,interval_type) values(1,"tiny_monthly",3,"EUR",1,1); +ignore into vm_os_image(id,distribution,flavour,version,enabled,url,release_date) +values(3, 0,"Server","20.04",1,"https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img","2020-04-23"); +insert +ignore into vm_os_image(id,distribution,flavour,version,enabled,url,release_date) +values(4, 1,"Server","12",1,"https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.raw","2023-06-10"); +insert +ignore into vm_os_image(id,distribution,flavour,version,enabled,url,release_date) +values(5, 1,"Server","11",1,"https://cloud.debian.org/images/cloud/bullseye/latest/debian-11-genericcloud-amd64.raw","2021-08-14"); +insert +ignore into ip_range(id,cidr,enabled) +values(1,"185.18.221.80/28",1); +insert +ignore into vm_cost_plan(id,name,amount,currency,interval_amount,interval_type) +values(1,"tiny_monthly",2,"EUR",1,1); +insert +ignore into vm_cost_plan(id,name,amount,currency,interval_amount,interval_type) +values(2,"small_monthly",4,"EUR",1,1); +insert +ignore into vm_cost_plan(id,name,amount,currency,interval_amount,interval_type) +values(3,"medium_monthly",8,"EUR",1,1); +insert +ignore into vm_cost_plan(id,name,amount,currency,interval_amount,interval_type) +values(4,"large_monthly",17,"EUR",1,1); +insert +ignore into vm_cost_plan(id,name,amount,currency,interval_amount,interval_type) +values(5,"xlarge_monthly",30,"EUR",1,1); +insert +ignore into vm_cost_plan(id,name,amount,currency,interval_amount,interval_type) +values(6,"xxlarge_monthly",45,"EUR",1,1); insert ignore into vm_template(id,name,enabled,cpu,memory,disk_size,disk_type,disk_interface,cost_plan_id,region_id) -values(1,"Tiny",1,2,1024*1024*2,1000*1000*1000*80,1,2,1,1); \ No newline at end of file +values(1,"Tiny",1,1,1024*1024*1024*1,1024*1024*1024*40,1,2,1,1); +insert +ignore into vm_template(id,name,enabled,cpu,memory,disk_size,disk_type,disk_interface,cost_plan_id,region_id) +values(2,"Small",1,2,1024*1024*1024*2,1024*1024*1024*80,1,2,2,1); +insert +ignore into vm_template(id,name,enabled,cpu,memory,disk_size,disk_type,disk_interface,cost_plan_id,region_id) +values(3,"Medium",1,4,1024*1024*1024*4,1024*1024*1024*160,1,2,3,1); +insert +ignore into vm_template(id,name,enabled,cpu,memory,disk_size,disk_type,disk_interface,cost_plan_id,region_id) +values(4,"Large",1,8,1024*1024*1024*8,1024*1024*1024*400,1,2,4,1); +insert +ignore into vm_template(id,name,enabled,cpu,memory,disk_size,disk_type,disk_interface,cost_plan_id,region_id) +values(5,"X-Large",1,12,1024*1024*1024*16,1024*1024*1024*800,1,2,5,1); +insert +ignore into vm_template(id,name,enabled,cpu,memory,disk_size,disk_type,disk_interface,cost_plan_id,region_id) +values(6,"XX-Large",1,20,1024*1024*1024*24,1024*1024*1024*1000,1,2,6,1); \ No newline at end of file diff --git a/lnvps_db/migrations/20241103155733_init.sql b/lnvps_db/migrations/20241103155733_init.sql index 41c314d..9eadd19 100644 --- a/lnvps_db/migrations/20241103155733_init.sql +++ b/lnvps_db/migrations/20241103155733_init.sql @@ -6,7 +6,7 @@ create table users email varchar(200), contact_nip4 bit(1) not null, contact_nip17 bit(1) not null, - contact_email bit(1) not null, + contact_email bit(1) not null ); create unique index ix_user_pubkey on users (pubkey); create unique index ix_user_email on users (email); @@ -55,14 +55,14 @@ create table vm_host_disk create table vm_os_image ( id integer unsigned not null auto_increment primary key, - name varchar(200) not null, distribution smallint unsigned not null, flavour varchar(50) not null, version varchar(50) not null, enabled bit(1) not null, - url varchar(1024) not null, + release_date timestamp not null, + url varchar(1024) not null ); -create unique index ix_vm_os_image_name on vm_os_image (name); +create unique index ix_vm_os_image on vm_os_image (distribution, flavour, version); create table ip_range ( id integer unsigned not null auto_increment primary key, @@ -72,6 +72,7 @@ create table ip_range constraint fk_ip_range_region foreign key (region_id) references vm_host_region (id) ); +create unique index ix_ip_range_cidr on ip_range (cidr); create table vm_cost_plan ( id integer unsigned not null auto_increment primary key, diff --git a/lnvps_db/src/hydrate.rs b/lnvps_db/src/hydrate.rs new file mode 100644 index 0000000..b93bb7f --- /dev/null +++ b/lnvps_db/src/hydrate.rs @@ -0,0 +1,48 @@ +use crate::{LNVpsDb, Vm, VmTemplate}; +use anyhow::Result; +use async_trait::async_trait; + +#[async_trait] +pub trait Hydrate { + /// Load parent resources + async fn hydrate_up(&mut self, db: &Box) -> Result<()>; + + /// Load child resources + async fn hydrate_down(&mut self, db: &Box) -> Result<()>; +} + +#[async_trait] +impl Hydrate for Vm { + async fn hydrate_up(&mut self, db: &Box) -> Result<()> { + let image = db.get_os_image(self.image_id).await?; + let template = db.get_vm_template(self.template_id).await?; + let ssh_key = db.get_user_ssh_key(self.ssh_key_id).await?; + + self.image = Some(image); + self.template = Some(template); + self.ssh_key = Some(ssh_key); + Ok(()) + } + + async fn hydrate_down(&mut self, db: &Box) -> Result<()> { + let payments = db.list_vm_payment(self.id).await?; + + self.payments = Some(payments); + Ok(()) + } +} + +#[async_trait] +impl Hydrate for VmTemplate { + async fn hydrate_up(&mut self, db: &Box) -> Result<()> { + let cost_plan = db.get_cost_plan(self.cost_plan_id).await?; + let region = db.get_host_region(self.region_id).await?; + self.cost_plan = Some(cost_plan); + self.region = Some(region); + Ok(()) + } + + async fn hydrate_down(&mut self, db: &Box) -> Result<()> { + todo!() + } +} \ No newline at end of file diff --git a/lnvps_db/src/lib.rs b/lnvps_db/src/lib.rs index 2147728..3f7e95b 100644 --- a/lnvps_db/src/lib.rs +++ b/lnvps_db/src/lib.rs @@ -4,6 +4,7 @@ use async_trait::async_trait; mod model; #[cfg(feature = "mysql")] mod mysql; +pub mod hydrate; pub use model::*; #[cfg(feature = "mysql")] @@ -27,7 +28,7 @@ pub trait LNVpsDb: Sync + Send { async fn delete_user(&self, id: u64) -> Result<()>; /// Insert a new user ssh key - async fn insert_user_ssh_key(&self, new_key: UserSshKey) -> Result; + async fn insert_user_ssh_key(&self, new_key: &UserSshKey) -> Result; /// Get user ssh key by id async fn get_user_ssh_key(&self, id: u64) -> Result; @@ -45,11 +46,14 @@ pub trait LNVpsDb: Sync + Send { async fn list_hosts(&self) -> Result>; /// Update host resources (usually from [auto_discover]) - async fn update_host(&self, host: VmHost) -> Result<()>; + async fn update_host(&self, host: &VmHost) -> Result<()>; /// List VM's owned by a specific user async fn list_host_disks(&self, host_id: u64) -> Result>; + /// Get OS image by id + async fn get_os_image(&self, id: u64) -> Result; + /// List available OS images async fn list_os_image(&self) -> Result>; @@ -59,14 +63,20 @@ pub trait LNVpsDb: Sync + Send { /// Get a VM cost plan by id async fn get_cost_plan(&self, id: u64) -> Result; + /// Get VM template by id + async fn get_vm_template(&self, id: u64) -> Result; + /// List VM templates async fn list_vm_templates(&self) -> Result>; /// List VM's owned by a specific user async fn list_user_vms(&self, id: u64) -> Result>; + /// Get a VM by id + async fn get_vm(&self, vm_id: u64) -> Result; + /// Insert a new VM record - async fn insert_vm(&self, vm: Vm) -> Result; + async fn insert_vm(&self, vm: &Vm) -> Result; /// List VM ip assignments async fn get_vm_ip_assignments(&self, vm_id: u64) -> Result>; @@ -75,8 +85,8 @@ pub trait LNVpsDb: Sync + Send { async fn list_vm_payment(&self, vm_id: u64) -> Result>; /// Insert a new VM payment record - async fn insert_vm_payment(&self, vm_payment: VmPayment) -> Result; + async fn insert_vm_payment(&self, vm_payment: &VmPayment) -> Result; /// Update a VM payment record - async fn update_vm_payment(&self, vm_payment: VmPayment) -> Result<()>; + async fn update_vm_payment(&self, vm_payment: &VmPayment) -> Result<()>; } \ No newline at end of file diff --git a/lnvps_db/src/model.rs b/lnvps_db/src/model.rs index c3a92e6..ce3c671 100644 --- a/lnvps_db/src/model.rs +++ b/lnvps_db/src/model.rs @@ -18,12 +18,13 @@ pub struct User { pub contact_email: bool, } -#[derive(Serialize, Deserialize, FromRow, Clone, Debug)] +#[derive(Serialize, Deserialize, FromRow, Clone, Debug, Default)] pub struct UserSshKey { pub id: u64, pub name: String, pub user_id: u64, pub created: DateTime, + #[serde(skip_serializing)] pub key_data: String, #[sqlx(skip)] @@ -79,24 +80,29 @@ pub struct VmHostDisk { pub enabled: bool, } -#[derive(Serialize, Deserialize, Clone, Debug, sqlx::Type)] +#[derive(Serialize, Deserialize, Clone, Debug, sqlx::Type, Default)] +#[serde(rename_all = "lowercase")] #[repr(u16)] pub enum DiskType { + #[default] HDD = 0, SSD = 1, } -#[derive(Serialize, Deserialize, Clone, Debug, sqlx::Type)] +#[derive(Serialize, Deserialize, Clone, Debug, sqlx::Type, Default)] +#[serde(rename_all = "lowercase")] #[repr(u16)] pub enum DiskInterface { + #[default] SATA = 0, SCSI = 1, PCIe = 2, } -#[derive(Serialize, Deserialize, Clone, Debug, sqlx::Type)] +#[derive(Serialize, Deserialize, Clone, Debug, sqlx::Type, Default)] #[repr(u16)] pub enum OsDistribution { + #[default] Ubuntu = 0, Debian = 1, } @@ -106,11 +112,12 @@ pub enum OsDistribution { #[derive(Serialize, Deserialize, FromRow, Clone, Debug)] pub struct VmOsImage { pub id: u64, - pub name: String, pub distribution: OsDistribution, pub flavour: String, pub version: String, pub enabled: bool, + pub release_date: DateTime, + #[serde(skip_serializing)] /// URL location of cloud image pub url: String, } @@ -124,6 +131,7 @@ pub struct IpRange { } #[derive(Serialize, Deserialize, Clone, Debug, sqlx::Type)] +#[serde(rename_all = "lowercase")] #[repr(u16)] pub enum VmCostPlanIntervalType { Day = 0, @@ -144,7 +152,7 @@ pub struct VmCostPlan { /// Offers. /// These are the same as the offers visible to customers -#[derive(Serialize, Deserialize, FromRow, Clone, Debug)] +#[derive(Serialize, Deserialize, FromRow, Clone, Debug, Default)] pub struct VmTemplate { pub id: u64, pub name: String, @@ -154,6 +162,7 @@ pub struct VmTemplate { pub expires: Option>, pub cpu: u16, pub memory: u64, + pub disk_size: u64, pub disk_type: DiskType, pub disk_interface: DiskInterface, pub cost_plan_id: u64, @@ -167,7 +176,7 @@ pub struct VmTemplate { pub region: Option, } -#[derive(Serialize, Deserialize, FromRow, Clone, Debug)] +#[derive(Serialize, Deserialize, FromRow, Clone, Debug, Default)] pub struct Vm { /// Unique VM ID (Same in proxmox) pub id: u64, @@ -193,6 +202,19 @@ pub struct Vm { pub disk_size: u64, /// The [VmHostDisk] this VM is on pub disk_id: u64, + + #[sqlx(skip)] + #[serde(skip_serializing_if = "Option::is_none")] + pub image: Option, + #[sqlx(skip)] + #[serde(skip_serializing_if = "Option::is_none")] + pub template: Option, + #[sqlx(skip)] + #[serde(skip_serializing_if = "Option::is_none")] + pub ssh_key: Option, + #[sqlx(skip)] + #[serde(skip_serializing_if = "Option::is_none")] + pub payments: Option>, } #[derive(Serialize, Deserialize, FromRow, Clone, Debug)] diff --git a/lnvps_db/src/mysql.rs b/lnvps_db/src/mysql.rs index 4dc7797..cbff71f 100644 --- a/lnvps_db/src/mysql.rs +++ b/lnvps_db/src/mysql.rs @@ -25,11 +25,11 @@ impl LNVpsDbMysql { #[async_trait] impl LNVpsDb for LNVpsDbMysql { - async fn migrate(&self) -> anyhow::Result<()> { + async fn migrate(&self) -> Result<()> { sqlx::migrate!().run(&self.db).await.map_err(Error::new) } - async fn upsert_user(&self, pubkey: &[u8; 32]) -> anyhow::Result { + async fn upsert_user(&self, pubkey: &[u8; 32]) -> Result { let res = sqlx::query("insert ignore into users(pubkey) values(?) returning id") .bind(pubkey.as_slice()) .fetch_optional(&self.db) @@ -46,7 +46,11 @@ impl LNVpsDb for LNVpsDbMysql { } async fn get_user(&self, id: u64) -> Result { - todo!() + sqlx::query_as("select * from users where id=?") + .bind(id) + .fetch_one(&self.db) + .await + .map_err(Error::new) } async fn update_user(&self, user: &User) -> Result<()> { @@ -57,12 +61,23 @@ impl LNVpsDb for LNVpsDbMysql { todo!() } - async fn insert_user_ssh_key(&self, new_key: UserSshKey) -> Result { - todo!() + async fn insert_user_ssh_key(&self, new_key: &UserSshKey) -> Result { + Ok(sqlx::query("insert into user_ssh_key(name,user_id,key_data) values(?, ?, ?) returning id") + .bind(&new_key.name) + .bind(&new_key.user_id) + .bind(&new_key.key_data) + .fetch_one(&self.db) + .await + .map_err(Error::new)? + .try_get(0)?) } async fn get_user_ssh_key(&self, id: u64) -> Result { - todo!() + sqlx::query_as("select * from user_ssh_key where id=?") + .bind(id) + .fetch_one(&self.db) + .await + .map_err(Error::new) } async fn delete_user_ssh_key(&self, id: u64) -> Result<()> { @@ -70,21 +85,29 @@ impl LNVpsDb for LNVpsDbMysql { } async fn list_user_ssh_key(&self, user_id: u64) -> Result> { - todo!() + sqlx::query_as("select * from user_ssh_key where user_id = ?") + .bind(user_id) + .fetch_all(&self.db) + .await + .map_err(Error::new) } async fn get_host_region(&self, id: u64) -> Result { - todo!() + sqlx::query_as("select * from vm_host_region where id=?") + .bind(id) + .fetch_one(&self.db) + .await + .map_err(Error::new) } - async fn list_hosts(&self) -> anyhow::Result> { + async fn list_hosts(&self) -> Result> { sqlx::query_as("select * from vm_host") .fetch_all(&self.db) .await .map_err(Error::new) } - async fn update_host(&self, host: VmHost) -> anyhow::Result<()> { + async fn update_host(&self, host: &VmHost) -> Result<()> { sqlx::query("update vm_host set name = ?, cpu = ?, memory = ? where id = ?") .bind(&host.name) .bind(&host.cpu) @@ -95,7 +118,7 @@ impl LNVpsDb for LNVpsDbMysql { Ok(()) } - async fn list_host_disks(&self, host_id: u64) -> anyhow::Result> { + async fn list_host_disks(&self, host_id: u64) -> Result> { sqlx::query_as("select * from vm_host_disk where host_id = ?") .bind(&host_id) .fetch_all(&self.db) @@ -103,20 +126,46 @@ impl LNVpsDb for LNVpsDbMysql { .map_err(Error::new) } + async fn get_os_image(&self, id: u64) -> Result { + sqlx::query_as("select * from vm_os_image where id=?") + .bind(id) + .fetch_one(&self.db) + .await + .map_err(Error::new) + } + async fn list_os_image(&self) -> Result> { - todo!() + sqlx::query_as("select * from vm_os_image") + .fetch_all(&self.db) + .await + .map_err(Error::new) } async fn list_ip_range(&self) -> Result> { - todo!() + sqlx::query_as("select * from ip_range") + .fetch_all(&self.db) + .await + .map_err(Error::new) } async fn get_cost_plan(&self, id: u64) -> Result { - todo!() + sqlx::query_as("select * from vm_cost_plan where id=?") + .bind(id) + .fetch_one(&self.db) + .await + .map_err(Error::new) } - async fn list_vm_templates(&self) -> anyhow::Result> { - sqlx::query_as("select * from vm_template where enabled = 1 and (expires is null or expires > now())") + async fn get_vm_template(&self, id: u64) -> Result { + sqlx::query_as("select * from vm_template where id=?") + .bind(id) + .fetch_one(&self.db) + .await + .map_err(Error::new) + } + + async fn list_vm_templates(&self) -> Result> { + sqlx::query_as("select * from vm_template") .fetch_all(&self.db) .await .map_err(Error::new) @@ -130,23 +179,71 @@ impl LNVpsDb for LNVpsDbMysql { .map_err(Error::new) } - async fn insert_vm(&self, vm: Vm) -> Result { - todo!() + async fn get_vm(&self, vm_id: u64) -> Result { + sqlx::query_as("select * from vm where id = ?") + .bind(&vm_id) + .fetch_one(&self.db) + .await + .map_err(Error::new) + } + + async fn insert_vm(&self, vm: &Vm) -> Result { + Ok(sqlx::query("insert into vm(host_id,user_id,image_id,template_id,ssh_key_id,created,expires,cpu,memory,disk_size,disk_id) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) returning id") + .bind(&vm.host_id) + .bind(&vm.user_id) + .bind(&vm.image_id) + .bind(&vm.template_id) + .bind(&vm.ssh_key_id) + .bind(&vm.created) + .bind(&vm.expires) + .bind(&vm.cpu) + .bind(&vm.memory) + .bind(&vm.disk_size) + .bind(&vm.disk_id) + .fetch_one(&self.db) + .await + .map_err(Error::new)? + .try_get(0)?) } async fn get_vm_ip_assignments(&self, vm_id: u64) -> Result> { - todo!() + sqlx::query_as("select * from vm_ip_assignment where vm_id=?") + .bind(vm_id) + .fetch_all(&self.db) + .await + .map_err(Error::new) } async fn list_vm_payment(&self, vm_id: u64) -> Result> { - todo!() + sqlx::query_as("select * from vm_payment where vm_id=?") + .bind(vm_id) + .fetch_all(&self.db) + .await + .map_err(Error::new) } - async fn insert_vm_payment(&self, vm_payment: VmPayment) -> Result { - todo!() + async fn insert_vm_payment(&self, vm_payment: &VmPayment) -> Result { + Ok(sqlx::query("insert into vm_payment(vm_id,created,expires,amount,invoice,time_value,is_paid) values(?,?,?,?,?,?,?) returning id") + .bind(&vm_payment.vm_id) + .bind(&vm_payment.created) + .bind(&vm_payment.expires) + .bind(&vm_payment.amount) + .bind(&vm_payment.invoice) + .bind(&vm_payment.time_value) + .bind(&vm_payment.is_paid) + .fetch_one(&self.db) + .await + .map_err(Error::new)? + .try_get(0)?) } - async fn update_vm_payment(&self, vm_payment: VmPayment) -> Result<()> { - todo!() + async fn update_vm_payment(&self, vm_payment: &VmPayment) -> Result<()> { + sqlx::query("update vm_payment set is_paid = ? where id = ?") + .bind(&vm_payment.is_paid) + .bind(&vm_payment.id) + .execute(&self.db) + .await + .map_err(Error::new)?; + Ok(()) } } \ No newline at end of file diff --git a/src/api.rs b/src/api.rs index d6d4cde..8ff4ef6 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,13 +1,22 @@ use crate::nip98::Nip98Auth; use crate::provisioner::Provisioner; -use anyhow::Error; -use lnvps_db::{LNVpsDb, Vm, VmTemplate}; +use lnvps_db::hydrate::Hydrate; +use lnvps_db::{LNVpsDb, UserSshKey, Vm, VmOsImage, VmPayment, VmTemplate}; use rocket::serde::json::Json; use rocket::{get, post, routes, Responder, Route, State}; use serde::{Deserialize, Serialize}; +use ssh_key::PublicKey; pub fn routes() -> Vec { - routes![v1_list_vms, v1_list_vm_templates, v1_provision_vm] + routes![ + v1_list_vms, + v1_list_vm_templates, + v1_list_vm_images, + v1_list_ssh_keys, + v1_add_ssh_key, + v1_create_vm_order, + v1_renew_vm + ] } type ApiResult = Result>, ApiError>; @@ -21,6 +30,9 @@ impl ApiData { pub fn ok(data: T) -> ApiResult { Ok(Json::from(ApiData { data })) } + pub fn err(msg: &str) -> ApiResult { + Err(msg.into()) + } } #[derive(Responder)] @@ -29,30 +41,86 @@ struct ApiError { pub error: String, } -impl From for ApiError { - fn from(value: Error) -> Self { +impl ApiError { + pub fn new(error: &str) -> Self { + Self { + error: error.to_owned(), + } + } +} + +impl From for ApiError { + fn from(value: T) -> Self { Self { error: value.to_string(), } } } -#[get("/api/v1/vms")] +#[get("/api/v1/vm")] async fn v1_list_vms(auth: Nip98Auth, db: &State>) -> ApiResult> { let pubkey = auth.event.pubkey.to_bytes(); let uid = db.upsert_user(&pubkey).await?; - let vms = db.list_user_vms(uid).await?; + let mut vms = db.list_user_vms(uid).await?; + for vm in &mut vms { + vm.hydrate_up(db).await?; + } + ApiData::ok(vms) +} + +#[get("/api/v1/image")] +async fn v1_list_vm_images(db: &State>) -> ApiResult> { + let vms = db.list_os_image().await?; ApiData::ok(vms) } #[get("/api/v1/vm/templates")] async fn v1_list_vm_templates(db: &State>) -> ApiResult> { - let vms = db.list_vm_templates().await?; + let mut vms = db.list_vm_templates().await?; + for vm in &mut vms { + vm.hydrate_up(db).await?; + } ApiData::ok(vms) } +#[get("/api/v1/ssh-key")] +async fn v1_list_ssh_keys( + auth: Nip98Auth, + db: &State>, +) -> ApiResult> { + let uid = db.upsert_user(&auth.event.pubkey.to_bytes()).await?; + let keys = db.list_user_ssh_key(uid).await?; + ApiData::ok(keys) +} + +#[post("/api/v1/ssh-key", data = "", format = "json")] +async fn v1_add_ssh_key( + auth: Nip98Auth, + db: &State>, + req: Json, +) -> ApiResult { + let uid = db.upsert_user(&auth.event.pubkey.to_bytes()).await?; + + let pk: PublicKey = req.key_data.parse()?; + let key_name = if !req.name.is_empty() { + &req.name + } else { + pk.comment() + }; + let mut new_key = UserSshKey { + name: key_name.to_string(), + user_id: uid, + key_data: pk.to_openssh()?, + ..Default::default() + }; + let key_id = db.insert_user_ssh_key(&new_key).await?; + new_key.id = key_id; + + ApiData::ok(new_key) +} + #[post("/api/v1/vm", data = "", format = "json")] -async fn v1_provision_vm( +async fn v1_create_vm_order( auth: Nip98Auth, db: &State>, provisioner: &State>, @@ -62,15 +130,50 @@ async fn v1_provision_vm( let uid = db.upsert_user(&pubkey).await?; let req = req.0; - let rsp = provisioner.provision(req.into()).await?; + let mut rsp = provisioner + .provision(uid, req.template_id, req.image_id, req.ssh_key_id) + .await?; + rsp.hydrate_up(db).await?; + + ApiData::ok(rsp) +} + +#[get("/api/v1/vm//renew")] +async fn v1_renew_vm( + auth: Nip98Auth, + db: &State>, + provisioner: &State>, + id: u64, +) -> ApiResult { + let pubkey = auth.event.pubkey.to_bytes(); + let uid = db.upsert_user(&pubkey).await?; + let vm = db.get_vm(id).await?; + if uid != vm.user_id { + return ApiData::err("VM does not belong to you"); + } + + let rsp = provisioner.renew(id).await?; ApiData::ok(rsp) } #[derive(Deserialize)] -pub struct CreateVmRequest {} +struct CreateVmRequest { + template_id: u64, + image_id: u64, + ssh_key_id: u64, +} -impl Into for CreateVmRequest { - fn into(self) -> VmTemplate { - todo!() +impl From for VmTemplate { + fn from(val: CreateVmRequest) -> Self { + VmTemplate { + id: val.template_id, + ..Default::default() + } } } + +#[derive(Deserialize)] +struct CreateSshKey { + name: String, + key_data: String, +} diff --git a/src/bin/api.rs b/src/bin/api.rs index d36941b..70512fe 100644 --- a/src/bin/api.rs +++ b/src/bin/api.rs @@ -1,15 +1,25 @@ use anyhow::Error; use config::{Config, File}; +use fedimint_tonic_lnd::connect; use lnvps::api; use lnvps::cors::CORS; use lnvps::provisioner::{LNVpsProvisioner, Provisioner}; use lnvps_db::{LNVpsDb, LNVpsDbMysql}; use log::error; use serde::{Deserialize, Serialize}; +use std::path::PathBuf; #[derive(Debug, Deserialize, Serialize)] pub struct Settings { pub db: String, + pub lnd: LndConfig, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct LndConfig { + pub url: String, + pub cert: PathBuf, + pub macaroon: PathBuf, } #[rocket::main] @@ -24,7 +34,8 @@ async fn main() -> Result<(), Error> { let db = LNVpsDbMysql::new(&config.db).await?; db.migrate().await?; - let provisioner = LNVpsProvisioner::new(db.clone()); + let lnd = connect(config.lnd.url, config.lnd.cert, config.lnd.macaroon).await?; + let provisioner = LNVpsProvisioner::new(db.clone(), lnd.clone()); #[cfg(debug_assertions)] { let setup_script = include_str!("../../dev_setup.sql"); diff --git a/src/host/proxmox.rs b/src/host/proxmox.rs index 5aeb06e..5620e3e 100644 --- a/src/host/proxmox.rs +++ b/src/host/proxmox.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use log::info; use reqwest::{ClientBuilder, Url}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; @@ -42,46 +43,56 @@ impl ProxmoxClient { Ok(rsp.data) } + pub async fn get_vm_status(&self, node: &str, vm_id: i32) -> Result { + let rsp: ResponseBase = self + .get(&format!( + "/api2/json/nodes/{node}/qemu/{vm_id}/status/current" + )) + .await?; + Ok(rsp.data) + } + pub async fn list_vms(&self, node: &str, full: bool) -> Result> { let rsp: ResponseBase> = self.get(&format!("/api2/json/nodes/{node}/qemu")).await?; Ok(rsp.data) } + pub async fn list_storage(&self) -> Result> { let rsp: ResponseBase> = self.get("/api2/json/storage").await?; Ok(rsp.data) } - pub async fn create_vm(&self, node: &str, req: CreateVm) -> Result { - let rsp: ResponseBase = self - .post(&format!("/api2/json/nodes/{node}/qemu"), req) + pub async fn create_vm(&self, req: CreateVm) -> Result { + info!("{}", serde_json::to_string_pretty(&req)?); + let _rsp: ResponseBase> = self + .post(&format!("/api2/json/nodes/{}/qemu", req.node), &req) .await?; - Ok(rsp.data) + self.get_vm_status(&req.node, req.vm_id).await } async fn get(&self, path: &str) -> Result { - Ok(self - .client + self.client .get(self.base.join(path)?) .header("Authorization", format!("PVEAPIToken={}", self.token)) .send() .await? .json::() .await - .map_err(|e| anyhow::Error::new(e))?) + .map_err(anyhow::Error::new) } async fn post(&self, path: &str, body: R) -> Result { - Ok(self + let rsp = self .client .post(self.base.join(path)?) .header("Authorization", format!("PVEAPIToken={}", self.token)) - .json(&body) + .json::(&body) .send() - .await? - .error_for_status()? - .json() - .await?) + .await?; + let rsp = rsp.text().await?; + info!("<< {}", rsp); + Ok(serde_json::from_str(&rsp)?) } } diff --git a/src/provisioner.rs b/src/provisioner.rs index 04e6ef6..82c9e13 100644 --- a/src/provisioner.rs +++ b/src/provisioner.rs @@ -1,22 +1,42 @@ -use crate::host::proxmox::{CreateVm, ProxmoxClient, VmBios}; +use crate::host::proxmox::ProxmoxClient; use anyhow::{bail, Result}; -use lnvps_db::{LNVpsDb, Vm, VmTemplate}; +use chrono::{Days, Months, Utc}; +use fedimint_tonic_lnd::lnrpc::Invoice; +use fedimint_tonic_lnd::Client; +use lnvps_db::{LNVpsDb, Vm, VmCostPlanIntervalType, VmOsImage, VmPayment}; use log::{info, warn}; use rocket::async_trait; +use rocket::yansi::Paint; +use std::ops::Add; +use std::path::PathBuf; +use std::time::Duration; #[async_trait] pub trait Provisioner: Send + Sync { /// Provision a new VM - async fn provision(&self, spec: VmTemplate) -> Result; + async fn provision( + &self, + user_id: u64, + template_id: u64, + image_id: u64, + ssh_key_id: u64, + ) -> Result; + + /// Create a renewal payment + async fn renew(&self, vm_id: u64) -> Result; } pub struct LNVpsProvisioner { db: Box, + lnd: Client, } impl LNVpsProvisioner { - pub fn new(db: impl LNVpsDb + 'static) -> Self { - Self { db: Box::new(db) } + pub fn new(db: impl LNVpsDb + 'static, lnd: Client) -> Self { + Self { + db: Box::new(db), + lnd, + } } /// Auto-discover resources @@ -35,7 +55,7 @@ impl LNVpsProvisioner { host.cpu = node.max_cpu.unwrap_or(host.cpu); host.memory = node.max_mem.unwrap_or(host.memory); info!("Patching host: {:?}", host); - self.db.update_host(host).await?; + self.db.update_host(&host).await?; } // Update disk info let storages = api.list_storage().await?; @@ -61,67 +81,113 @@ impl LNVpsProvisioner { Ok(()) } + + fn map_os_image(image: &VmOsImage) -> PathBuf { + PathBuf::from("/var/lib/vz/images/").join(format!( + "{:?}_{}_{}.img", + image.distribution, image.flavour, image.version + )) + } } #[async_trait] impl Provisioner for LNVpsProvisioner { - async fn provision(&self, spec: VmTemplate) -> Result { + async fn provision( + &self, + user_id: u64, + template_id: u64, + image_id: u64, + ssh_key_id: u64, + ) -> Result { + let user = self.db.get_user(user_id).await?; + let template = self.db.get_vm_template(template_id).await?; + let image = self.db.get_os_image(image_id).await?; + let ssh_key = self.db.get_user_ssh_key(ssh_key_id).await?; let hosts = self.db.list_hosts().await?; - // try any host // TODO: impl resource usage based provisioning - for host in hosts { - let api = ProxmoxClient::new(host.ip.parse()?).with_api_token(&host.api_token); + let pick_host = if let Some(h) = hosts.first() { + h + } else { + bail!("No host found") + }; + let host_disks = self.db.list_host_disks(pick_host.id).await?; + let pick_disk = if let Some(hd) = host_disks.first() { + hd + } else { + bail!("No host disk found") + }; - let nodes = api.list_nodes().await?; - let node = if let Some(n) = nodes.iter().find(|n| n.name == host.name) { - n - } else { - continue; + let mut new_vm = Vm { + host_id: pick_host.id, + user_id: user.id, + image_id: image.id, + template_id: template.id, + ssh_key_id: ssh_key.id, + created: Utc::now(), + expires: Utc::now(), + cpu: template.cpu, + memory: template.memory, + disk_size: template.disk_size, + disk_id: pick_disk.id, + ..Default::default() + }; + + let new_id = self.db.insert_vm(&new_vm).await?; + new_vm.id = new_id; + Ok(new_vm) + } + + async fn renew(&self, vm_id: u64) -> Result { + let vm = self.db.get_vm(vm_id).await?; + let template = self.db.get_vm_template(vm.template_id).await?; + let cost_plan = self.db.get_cost_plan(template.cost_plan_id).await?; + + // push the expiration forward by cost plan interval amount + let new_expire = match cost_plan.interval_type { + VmCostPlanIntervalType::Day => vm.expires.add(Days::new(cost_plan.interval_amount)), + VmCostPlanIntervalType::Month => vm + .expires + .add(Months::new(cost_plan.interval_amount as u32)), + VmCostPlanIntervalType::Year => vm + .expires + .add(Months::new((12 * cost_plan.interval_amount) as u32)), + }; + + const BTC_MILLI_SATS: u64 = 100_000_000_000; + const INVOICE_EXPIRE: i64 = 3600; + + let cost = cost_plan.amount + * match cost_plan.currency.as_str() { + "EUR" => 1_100_000, //TODO: rates + "BTC" => 1, // BTC amounts are always millisats + c => bail!("Unknown currency {c}"), }; - let host_disks = self.db.list_host_disks(host.id).await?; - let disk_name = if let Some(d) = host_disks.first() { - d - } else { - continue; - }; - let next_id = 101; - let vm_result = api - .create_vm( - &node.name, - CreateVm { - vm_id: next_id, - bios: Some(VmBios::OVMF), - boot: Some("order=scsi0".to_string()), - cores: Some(spec.cpu as i32), - cpu: Some("kvm64".to_string()), - memory: Some((spec.memory / 1024 / 1024).to_string()), - machine: Some("q35".to_string()), - scsi_hw: Some("virtio-scsi-pci".to_string()), - efi_disk_0: Some(format!("{}:vm-{next_id}-efi,size=1M", &disk_name.name)), - net: Some("virtio=auto,bridge=vmbr0,tag=100".to_string()), - ip_config: Some(format!("ip=auto,ipv6=auto")), - ..Default::default() - }, - ) - .await?; + info!("Creating invoice for {vm_id} for {cost} mSats"); + let mut lnd = self.lnd.clone(); + let invoice = lnd + .lightning() + .add_invoice(Invoice { + memo: format!("VM renewal {vm_id} to {new_expire}"), + value_msat: cost as i64, + expiry: INVOICE_EXPIRE, + ..Default::default() + }) + .await?; - return Ok(Vm { - id: 0, - host_id: 0, - user_id: 0, - image_id: 0, - template_id: 0, - ssh_key_id: 0, - created: Default::default(), - expires: Default::default(), - cpu: 0, - memory: 0, - disk_size: 0, - disk_id: 0, - }); - } + let mut vm_payment = VmPayment { + id: 0, + vm_id, + created: Utc::now(), + expires: Utc::now().add(Duration::from_secs(INVOICE_EXPIRE as u64)), + amount: cost, + invoice: invoice.into_inner().payment_request, + time_value: (new_expire - vm.expires).num_seconds() as u64, + is_paid: false, + }; + let payment_id = self.db.insert_vm_payment(&vm_payment).await?; + vm_payment.id = payment_id; - bail!("Failed to create VM") + Ok(vm_payment) } }