From 125d50530d4f38fe6ab4ac5adece2aa7b6f47925 Mon Sep 17 00:00:00 2001 From: JMARyA Date: Mon, 28 Apr 2025 23:33:11 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20mqtt=20connections?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit almost working, need encryption --- .gitignore | 7 +- Cargo.lock | 848 +++++++++++++++++++++++++++++++- Cargo.toml | 3 + docker-compose.yml | 12 + homeserver/config.toml | 1 + justfile | 3 + mosquitto/config/mosquitto.conf | 17 + src/api.rs | 177 +++++++ src/server.rs | 96 +++- src/server_core/config.rs | 7 +- src/server_core/model.rs | 4 +- src/server_core/route.rs | 12 +- src/sheepd.rs | 54 +- src/sheepd_core/cmd.rs | 42 +- src/sheepd_core/config.rs | 18 +- 15 files changed, 1259 insertions(+), 42 deletions(-) create mode 100644 docker-compose.yml create mode 100644 homeserver/config.toml create mode 100644 justfile create mode 100644 mosquitto/config/mosquitto.conf diff --git a/.gitignore b/.gitignore index c41cc9e..37997be 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ -/target \ No newline at end of file +/target +/mosquitto/data +/mosquitto/log +/homeserver/age.key +/homeserver/sign.key +/homeserver/db \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 0b8b398..3132729 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,95 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "age" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57fc171f4874fa10887e47088f81a55fcf030cd421aa31ec2b370cafebcc608a" +dependencies = [ + "aes", + "aes-gcm", + "age-core", + "base64 0.21.7", + "bcrypt-pbkdf", + "bech32", + "cbc", + "chacha20poly1305", + "cipher", + "cookie-factory", + "ctr", + "curve25519-dalek", + "futures", + "hmac", + "i18n-embed", + "i18n-embed-fl", + "lazy_static", + "memchr", + "nom", + "num-traits", + "pin-project", + "rand 0.8.5", + "rsa", + "rust-embed", + "scrypt", + "sha2", + "subtle", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "age-core" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2bf6a89c984ca9d850913ece2da39e1d200563b0a94b002b253beee4c5acf99" +dependencies = [ + "base64 0.21.7", + "chacha20poly1305", + "cookie-factory", + "hkdf", + "io_tee", + "nom", + "rand 0.8.5", + "secrecy", + "sha2", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -106,6 +195,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + [[package]] name = "argh" version = "0.1.13" @@ -171,6 +266,35 @@ dependencies = [ "syn", ] +[[package]] +name = "async-tungstenite" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cca750b12e02c389c1694d35c16539f88b8bbaa5945934fdc1b41a776688589" +dependencies = [ + "futures-io", + "futures-util", + "log", + "pin-project-lite", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite", +] + +[[package]] +name = "async_io_stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +dependencies = [ + "futures", + "pharos", + "rustc_version", + "tokio", +] + [[package]] name = "atoi" version = "2.0.0" @@ -348,6 +472,15 @@ dependencies = [ "walkdir", ] +[[package]] +name = "basic-toml" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" +dependencies = [ + "serde", +] + [[package]] name = "bcrypt" version = "0.16.0" @@ -361,6 +494,23 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bcrypt-pbkdf" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aeac2e1fe888769f34f05ac343bbef98b14d1ffb292ab69d4608b3abc86f2a2" +dependencies = [ + "blowfish", + "pbkdf2", + "sha2", +] + +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + [[package]] name = "binascii" version = "0.1.4" @@ -391,6 +541,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "blowfish" version = "0.9.1" @@ -425,6 +584,15 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.2.20" @@ -440,6 +608,30 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.40" @@ -463,6 +655,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", + "zeroize", ] [[package]] @@ -506,6 +699,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "cookie-factory" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" +dependencies = [ + "futures", +] + [[package]] name = "cookie_store" version = "0.21.1" @@ -636,9 +838,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] +[[package]] +name = "ct-codecs" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b916ba8ce9e4182696896f015e8a5ae6081b305f74690baa8465e35f5a142ea4" + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dashmap" version = "6.1.0" @@ -848,6 +1092,12 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "figment" version = "0.10.19" @@ -857,7 +1107,7 @@ dependencies = [ "atomic 0.6.0", "pear", "serde", - "toml", + "toml 0.8.21", "uncased", "version_check", ] @@ -874,6 +1124,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "find-crate" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" +dependencies = [ + "toml 0.5.11", +] + [[package]] name = "flate2" version = "1.1.1" @@ -884,6 +1143,50 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fluent" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb74634707bebd0ce645a981148e8fb8c7bccd4c33c652aeffd28bf2f96d555a" +dependencies = [ + "fluent-bundle", + "unic-langid", +] + +[[package]] +name = "fluent-bundle" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe0a21ee80050c678013f82edf4b705fe2f26f1f9877593d13198612503f493" +dependencies = [ + "fluent-langneg", + "fluent-syntax", + "intl-memoizer", + "intl_pluralrules", + "rustc-hash", + "self_cell 0.10.3", + "smallvec", + "unic-langid", +] + +[[package]] +name = "fluent-langneg" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94" +dependencies = [ + "unic-langid", +] + +[[package]] +name = "fluent-syntax" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a530c4694a6a8d528794ee9bbd8ba0122e779629ac908d15ad5a7ae7763a33d" +dependencies = [ + "thiserror 1.0.69", +] + [[package]] name = "flume" version = "0.11.1" @@ -1108,8 +1411,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1124,6 +1429,16 @@ dependencies = [ "wasi 0.14.2+wasi-0.2.4", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.31.1" @@ -1396,6 +1711,72 @@ dependencies = [ "tower-service", ] +[[package]] +name = "i18n-config" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e88074831c0be5b89181b05e6748c4915f77769ecc9a4c372f88b169a8509c9" +dependencies = [ + "basic-toml", + "log", + "serde", + "serde_derive", + "thiserror 1.0.69", + "unic-langid", +] + +[[package]] +name = "i18n-embed" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "669ffc2c93f97e6ddf06ddbe999fcd6782e3342978bb85f7d3c087c7978404c4" +dependencies = [ + "arc-swap", + "fluent", + "fluent-langneg", + "fluent-syntax", + "i18n-embed-impl", + "intl-memoizer", + "log", + "parking_lot", + "rust-embed", + "thiserror 1.0.69", + "unic-langid", + "walkdir", +] + +[[package]] +name = "i18n-embed-fl" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04b2969d0b3fc6143776c535184c19722032b43e6a642d710fa3f88faec53c2d" +dependencies = [ + "find-crate", + "fluent", + "fluent-syntax", + "i18n-config", + "i18n-embed", + "proc-macro-error2", + "proc-macro2", + "quote", + "strsim", + "syn", + "unic-langid", +] + +[[package]] +name = "i18n-embed-impl" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2cc0e0523d1fe6fc2c6f66e5038624ea8091b3e7748b5e8e0c84b1698db6c2" +dependencies = [ + "find-crate", + "i18n-config", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "iana-time-zone" version = "0.1.63" @@ -1582,9 +1963,35 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ + "block-padding", "generic-array", ] +[[package]] +name = "intl-memoizer" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe22e020fce238ae18a6d5d8c502ee76a52a6e880d99477657e6acc30ec57bda" +dependencies = [ + "type-map", + "unic-langid", +] + +[[package]] +name = "intl_pluralrules" +version = "7.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972" +dependencies = [ + "unic-langid", +] + +[[package]] +name = "io_tee" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b3f7cef34251886990511df1c61443aa928499d598a9473929ab5a90a527304" + [[package]] name = "ipnet" version = "2.11.0" @@ -1798,6 +2205,24 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "minisign" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26541387415a1e829df5d532aad019fb11bc723e2b5bc99edefa4cf5bfad0de7" +dependencies = [ + "ct-codecs", + "getrandom 0.2.16", + "rpassword", + "scrypt", +] + [[package]] name = "miniz_oxide" version = "0.8.8" @@ -1854,6 +2279,16 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nonempty" version = "0.7.0" @@ -1948,6 +2383,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "openssl" version = "0.10.72" @@ -2073,6 +2514,16 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "pear" version = "0.2.9" @@ -2111,6 +2562,36 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pharos" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" +dependencies = [ + "futures", + "rustc_version", +] + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -2150,6 +2631,29 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "portable-atomic" version = "1.11.0" @@ -2203,6 +2707,28 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.95" @@ -2594,6 +3120,17 @@ dependencies = [ "uncased", ] +[[package]] +name = "rpassword" +version = "7.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d4c8b64f049c6721ec8ccec37ddfc3d641c4a7fca57e8f2a89de509c73df39" +dependencies = [ + "libc", + "rtoolbox", + "windows-sys 0.59.0", +] + [[package]] name = "rsa" version = "0.9.8" @@ -2614,6 +3151,72 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rtoolbox" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7cc970b249fbe527d6e02e0a227762c9108b2f49d81094fe357ffc6d14d7f6f" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "rumqttc" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1568e15fab2d546f940ed3a21f48bbbd1c494c90c99c4481339364a497f94a9" +dependencies = [ + "async-tungstenite", + "bytes", + "flume", + "futures-util", + "http 1.3.1", + "log", + "rustls-native-certs", + "rustls-pemfile 2.2.0", + "rustls-webpki 0.102.8", + "thiserror 1.0.69", + "tokio", + "tokio-rustls", + "url", + "ws_stream_tungstenite", +] + +[[package]] +name = "rust-embed" +version = "8.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5fbc0ee50fcb99af7cebb442e5df7b5b45e9460ffa3f8f549cd26b862bec49d" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bf418c9a2e3f6663ca38b8a7134cc2c2167c9d69688860e8961e3faa731702e" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d55b95147fe01265d06b3955db798bdaed52e60e2211c41137701b3aba8e21" +dependencies = [ + "sha2", + "walkdir", +] + [[package]] name = "rust-fuzzy-search" version = "0.1.1" @@ -2626,6 +3229,21 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.0.5" @@ -2639,6 +3257,20 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring 0.17.14", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + [[package]] name = "rustls" version = "0.23.26" @@ -2649,11 +3281,24 @@ dependencies = [ "once_cell", "ring 0.17.14", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.103.1", "subtle", "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -2678,6 +3323,17 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring 0.17.14", + "rustls-pki-types", + "untrusted 0.9.0", +] + [[package]] name = "rustls-webpki" version = "0.103.1" @@ -2701,6 +3357,15 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + [[package]] name = "same-file" version = "1.0.6" @@ -2731,6 +3396,26 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "pbkdf2", + "salsa20", + "sha2", +] + +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "zeroize", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -2754,6 +3439,27 @@ dependencies = [ "libc", ] +[[package]] +name = "self_cell" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14e4d63b804dc0c7ec4a1e52bcb63f02c7ac94476755aa579edac21e01f915d" +dependencies = [ + "self_cell 1.2.0", +] + +[[package]] +name = "self_cell" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + [[package]] name = "serde" version = "1.0.219" @@ -2861,6 +3567,7 @@ dependencies = [ name = "sheepd" version = "0.1.0" dependencies = [ + "age", "argh", "axum", "axum-client-ip", @@ -2868,12 +3575,14 @@ dependencies = [ "hex", "http2", "log", + "minisign", "owl", "rand 0.9.1", + "rumqttc", "serde", "serde_json", "tokio", - "toml", + "toml 0.8.21", "tracing", "tracing-subscriber", "ureq", @@ -3190,6 +3899,12 @@ dependencies = [ "unicode-properties", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" @@ -3418,6 +4133,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.17" @@ -3442,6 +4168,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "toml" version = "0.8.21" @@ -3579,6 +4314,36 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.3.1", + "httparse", + "log", + "rand 0.8.5", + "rustls 0.22.4", + "rustls-pki-types", + "sha1", + "thiserror 1.0.69", + "url", + "utf-8", +] + +[[package]] +name = "type-map" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb68604048ff8fa93347f02441e4487594adc20bb8a084f9e564d2b827a0a9f" +dependencies = [ + "rustc-hash", +] + [[package]] name = "typenum" version = "1.18.0" @@ -3615,6 +4380,25 @@ dependencies = [ "version_check", ] +[[package]] +name = "unic-langid" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dd9d1e72a73b25e07123a80776aae3e7b0ec461ef94f9151eed6ec88005a44" +dependencies = [ + "unic-langid-impl", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a5422c1f65949306c99240b81de9f3f15929f5a8bfe05bb44b034cc8bf593e5" +dependencies = [ + "serde", + "tinystr", +] + [[package]] name = "unicase" version = "2.8.1" @@ -3670,6 +4454,16 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.7.1" @@ -3693,7 +4487,7 @@ dependencies = [ "flate2", "log", "percent-encoding", - "rustls", + "rustls 0.23.26", "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", @@ -4236,6 +5030,38 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "ws_stream_tungstenite" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a198f414f083fb19fcc1bffcb0fa0cf46d33ccfa229adf248cac12c180e91609" +dependencies = [ + "async-tungstenite", + "async_io_stream", + "bitflags 2.9.0", + "futures-core", + "futures-io", + "futures-sink", + "futures-util", + "pharos", + "rustc_version", + "tokio", + "tracing", + "tungstenite", +] + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "serde", + "zeroize", +] + [[package]] name = "yansi" version = "1.0.1" @@ -4315,6 +5141,20 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "zerovec" diff --git a/Cargo.toml b/Cargo.toml index b3f6246..b6ac753 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,3 +33,6 @@ rand = "0.9.1" based = { git = "https://git.hydrar.de/jmarya/based", branch = "owl" } http2 = "0.4.21" ureq = { version = "3.0.11", features = ["json"] } +rumqttc = { version = "0.24.0", features = ["url", "websocket"] } +age = { version = "0.11.1", features = ["aes", "aes-gcm", "armor", "async", "ssh"] } +minisign = "0.7.9" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..06250dd --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +services: + mosquitto: + image: eclipse-mosquitto:2 + container_name: mosquitto + ports: + #- "1883:1883" # MQTT (classic TCP) + - "8083:8083" # MQTT over WebSocket + volumes: + - ./mosquitto/config:/mosquitto/config + - ./mosquitto/data:/mosquitto/data + - ./mosquitto/log:/mosquitto/log + restart: unless-stopped diff --git a/homeserver/config.toml b/homeserver/config.toml new file mode 100644 index 0000000..d628305 --- /dev/null +++ b/homeserver/config.toml @@ -0,0 +1 @@ +mqtt = "ws://127.0.0.1:8083" \ No newline at end of file diff --git a/justfile b/justfile new file mode 100644 index 0000000..ba6b7a6 --- /dev/null +++ b/justfile @@ -0,0 +1,3 @@ + +server: + cargo run --bin homeserver --features homeserver diff --git a/mosquitto/config/mosquitto.conf b/mosquitto/config/mosquitto.conf new file mode 100644 index 0000000..7a52a28 --- /dev/null +++ b/mosquitto/config/mosquitto.conf @@ -0,0 +1,17 @@ +# mosquitto.conf + +# Standard MQTT listener (for regular clients, optional) +listener 1883 +protocol mqtt + +# WebSocket MQTT listener +listener 8083 +protocol websockets + +# Allow anonymous connections (testing) +allow_anonymous true + +max_packet_size 10485760 # 10 MB + +# Logging +log_dest stdout diff --git a/src/api.rs b/src/api.rs index febdb69..b05ecf2 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,8 +1,185 @@ +use age::{Recipient, secrecy::ExposeSecret}; +use based::auth::{Sessions, User}; +use minisign::PublicKey; use owl::{Deserialize, Serialize}; +use owl::{prelude::*, query, save, set_global_db}; +use rumqttc::{AsyncClient, Event, EventLoop, MqttOptions, Packet, Transport}; +use std::io::{Read, Seek}; +use std::time::Duration; +use std::{ + io::{BufReader, Cursor}, + net::SocketAddr, + path::PathBuf, + str::FromStr, +}; +use tokio::time::sleep; #[derive(Deserialize, Serialize)] pub struct JoinParams { pub join_token: Option, pub machine_id: String, pub hostname: String, + pub identity: (String, String), +} + +#[derive(Deserialize, Serialize)] +pub struct JoinResponse { + pub token: String, + pub identity: (String, String), + pub mqtt: String, +} + +pub fn mqtt_connect(machine_id: &str, mqtt: &str) -> (rumqttc::AsyncClient, rumqttc::EventLoop) { + let mqttoptions = if mqtt.starts_with("ws://") { + log::warn!("Using unencrypted WebSocket connection"); + let mut mqttoptions = MqttOptions::new( + machine_id, + &format!("ws://{}", mqtt.trim_start_matches("ws://")), + 8000, + ); + + mqttoptions.set_transport(Transport::Ws); + mqttoptions.set_keep_alive(Duration::from_secs(60)); + mqttoptions + } else { + log::info!("Using encrypted WebSocket connection"); + let mut mqttoptions = MqttOptions::new( + machine_id, + &format!("wss://{}", mqtt.trim_start_matches("wss://")), + 8000, + ); + + mqttoptions.set_transport(Transport::wss_with_default_config()); + mqttoptions.set_keep_alive(Duration::from_secs(60)); + mqttoptions + }; + + AsyncClient::new(mqttoptions, 10) +} + +pub async fn run_event_loop(mut eventloop: EventLoop) { + log::info!("Handling MQTT events"); + loop { + match eventloop.poll().await { + Ok(Event::Incoming(incoming)) => { + log::trace!("Incoming = {:?}", incoming); + match incoming { + Packet::Publish(publish) => { + let s = publish.payload; + // TODO : client decryption here + println!("got payload {}", String::from_utf8(s.to_vec()).unwrap()); + } + _ => {} + } + } + Ok(Event::Outgoing(outgoing)) => { + log::trace!("Outgoing = {:?}", outgoing); + } + Err(e) => { + log::error!("MQTT eventloop error = {:?}", e); + sleep(Duration::from_secs(1)).await; + } + } + } +} + +pub struct Identity { + pub age: age::x25519::Identity, + pub sign: minisign::KeyPair, +} + +fn save_parts(part1: &[u8], part2: &[u8]) -> Vec { + let mut vec = Vec::new(); + + let offset = part1.len() as u32; // assume it fits into u32 + vec.extend_from_slice(&offset.to_le_bytes()); // or to_be_bytes() for big-endian + + vec.extend_from_slice(part1); + vec.extend_from_slice(part2); + + vec +} + +fn load_parts(data: &[u8]) -> (Vec, Vec) { + let offset_bytes: [u8; 4] = data[..4].try_into().unwrap(); + let offset = u32::from_le_bytes(offset_bytes) as usize; + + let part1 = data[4..4 + offset].to_vec(); + let part2 = data[4 + offset..].to_vec(); + + (part1, part2) +} + +impl Identity { + pub fn new() -> Self { + let age = age::x25519::Identity::generate(); + let sign = minisign::KeyPair::generate_encrypted_keypair(Some(String::new())).unwrap(); + Self { age, sign } + } + + pub fn public(&self) -> (String, String) { + (self.pub_key_age(), self.pub_key_sign()) + } + + pub fn pub_key_age(&self) -> String { + self.age.to_public().to_string() + } + + pub fn pub_key_sign(&self) -> String { + self.sign.pk.to_box().unwrap().to_string() + } + + pub fn save(&self, dir: &PathBuf) { + let age_key = self.age.to_string(); + std::fs::write(dir.join("age.key"), age_key.expose_secret().to_string()).unwrap(); + + let kbx = self.sign.sk.to_box(None).unwrap(); + + std::fs::write(dir.join("sign.key"), kbx.into_string()).unwrap(); + log::info!("Saved identity to {dir:?}"); + } + + pub fn try_load(dir: &PathBuf) -> Option { + let age = + age::x25519::Identity::from_str(&std::fs::read_to_string(dir.join("age.key")).ok()?) + .unwrap(); + let kbx = minisign::SecretKeyBox::from_string( + &std::fs::read_to_string(dir.join("sign.key")).ok()?, + ) + .unwrap(); + let sign = kbx.into_secret_key(Some(String::new())).unwrap(); + let pk = minisign::PublicKey::from_secret_key(&sign).ok()?; + let kp = minisign::KeyPair { pk: pk, sk: sign }; + log::info!("Loaded identity from {dir:?}"); + Some(Self { age, sign: kp }) + } + + pub fn encrypt(&self, data: &[u8], recipient: &impl Recipient) -> Vec { + let signed = + minisign::sign(None, &self.sign.sk, data, Some("mynameisanubis"), None).unwrap(); + let signed = save_parts(&signed.to_bytes(), data); + let enc = age::encrypt(recipient, &signed).unwrap(); + enc + } + + pub fn decrypt(&self, data: &[u8], pk: &PublicKey) -> Option> { + let dec = age::decrypt(&self.age, data).unwrap(); + let (sig, data) = load_parts(&dec); + let sign_box = + minisign::SignatureBox::from_string(&String::from_utf8(sig).unwrap()).unwrap(); + if minisign::verify( + pk, + &sign_box, + BufReader::new(Cursor::new(data.clone())), + true, + false, + false, + ) + .is_ok() + { + return Some(data); + } + + None + } } diff --git a/src/server.rs b/src/server.rs index 3f151cd..66629ca 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,3 +1,4 @@ +use age::{Recipient, secrecy::ExposeSecret}; use axum::{ Json, Router, http::StatusCode, @@ -5,14 +6,32 @@ use axum::{ }; use axum_client_ip::{ClientIp, ClientIpSource}; use based::auth::{Sessions, User}; -use owl::{prelude::*, save, set_global_db}; +use minisign::PublicKey; +use owl::{prelude::*, query, save, set_global_db}; use rand::RngCore; +use rumqttc::{AsyncClient, Event}; use serde::{Deserialize, Serialize}; use serde_json::json; -use std::net::SocketAddr; +use std::{ + io::{BufReader, Cursor}, + net::SocketAddr, + path::PathBuf, + str::FromStr, + time::Duration, +}; mod api; +use std::io::{Read, Seek}; mod server_core; -use server_core::route::{join_device, login_user}; +use crate::api::Identity; +use server_core::model::Machine; +use server_core::{ + config::Config, + route::{join_device, login_user}, +}; +use tokio::sync::OnceCell; + +pub static IDENTITY: OnceCell = OnceCell::const_new(); +pub static CONFIG: OnceCell = OnceCell::const_new(); fn generate_token() -> String { let mut rng = rand::rng(); @@ -25,13 +44,22 @@ fn generate_token() -> String { #[tokio::main] async fn main() { tracing_subscriber::fmt::init(); + let i = if let Some(i) = Identity::try_load(&PathBuf::from("./homeserver")) { + i + } else { + let i = Identity::new(); + i.save(&PathBuf::from("./homeserver")); + i + }; + let _ = crate::IDENTITY.set(i); - let db = Database::in_memory(); + let config = Config::default(); + let _ = crate::CONFIG.set(config); + + let db = Database::filesystem("./homeserver/db"); set_global_db!(db); - User::create("admin".to_string(), "admin", based::auth::UserRole::Admin) - .await - .unwrap(); + let _ = User::create("admin".to_string(), "admin", based::auth::UserRole::Admin).await; let device = Router::new() .route("/join", post(join_device)) @@ -44,11 +72,51 @@ async fn main() { log::info!("Starting server"); - let listener = tokio::net::TcpListener::bind("0.0.0.0:8000").await.unwrap(); - axum::serve( - listener, - app.into_make_service_with_connect_info::(), - ) - .await - .unwrap(); + let (client, mut eventloop) = api::mqtt_connect("server", &crate::CONFIG.get().unwrap().mqtt); + + tokio::spawn(async move { + loop { + let machines: Vec> = query!(|_| true); + for machine in machines { + let machine_id = machine.read().id.to_string(); + let machine_id = machine_id.trim(); + log::info!("Pushing topic to {machine_id}"); + client + .publish( + format!("{machine_id}/cmd"), + rumqttc::QoS::AtMostOnce, + false, + "Hello World".as_bytes(), + ) + .await + .unwrap(); + } + tokio::time::sleep(Duration::from_secs(5)).await; + } + }); + + tokio::spawn(async { + let listener = tokio::net::TcpListener::bind("0.0.0.0:8000").await.unwrap(); + axum::serve( + listener, + app.into_make_service_with_connect_info::(), + ) + .await + .unwrap(); + }); + + api::run_event_loop(eventloop).await; +} + +pub fn send_rpc(client: AsyncClient, Machine: &Model) { + // TODO : pub encryption here + client + .publish( + format!("{machine_id}/cmd"), + rumqttc::QoS::AtMostOnce, + true, + "Hello World".as_bytes(), + ) + .await + .unwrap(); } diff --git a/src/server_core/config.rs b/src/server_core/config.rs index 2a9f94b..757fc11 100644 --- a/src/server_core/config.rs +++ b/src/server_core/config.rs @@ -1,10 +1,13 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] -pub struct Config {} +pub struct Config { + /// Public MQTT endpoint + pub mqtt: String, +} impl Default for Config { fn default() -> Self { - toml::from_str(&std::fs::read_to_string("./config.toml").unwrap()).unwrap() + toml::from_str(&std::fs::read_to_string("./homeserver/config.toml").unwrap()).unwrap() } } diff --git a/src/server_core/model.rs b/src/server_core/model.rs index 99c161f..ceaef09 100644 --- a/src/server_core/model.rs +++ b/src/server_core/model.rs @@ -7,14 +7,16 @@ pub struct Machine { pub id: Id, pub hostname: String, pub token: String, + pub identity: (String, String), pub next_token: Option, } impl Machine { pub fn from_join_param(join: api::JoinParams) -> Self { Self { - id: Id::String(join.machine_id), + id: Id::String(join.machine_id.trim().to_string()), hostname: join.hostname.trim().to_string(), + identity: join.identity, token: generate_token(), next_token: None, } diff --git a/src/server_core/route.rs b/src/server_core/route.rs index 0fb9d2e..459e739 100644 --- a/src/server_core/route.rs +++ b/src/server_core/route.rs @@ -1,4 +1,5 @@ use crate::api; +use crate::api::JoinResponse; use crate::server_core::model::Machine; use axum::Json; use axum::http::StatusCode; @@ -44,5 +45,14 @@ pub async fn join_device( save!(machine); - (StatusCode::OK, Json(json!({"ok": new_token}))) + let i = crate::IDENTITY.get().unwrap(); + + ( + StatusCode::OK, + Json(json!(JoinResponse { + token: new_token, + identity: i.public(), + mqtt: crate::CONFIG.get().unwrap().mqtt.clone() + })), + ) } diff --git a/src/sheepd.rs b/src/sheepd.rs index e8fcd62..17b709b 100644 --- a/src/sheepd.rs +++ b/src/sheepd.rs @@ -1,8 +1,22 @@ -use sheepd_core::args::{SheepdArgs, SheepdCommand}; +use api::Identity; +use sheepd_core::{ + args::{SheepdArgs, SheepdCommand}, + config::AgentConfig, +}; mod api; mod sheepd_core; +use rumqttc::{AsyncClient, Event, MqttOptions, QoS, Transport}; +use std::{error::Error, path::PathBuf, time::Duration}; +use tokio::{ + sync::OnceCell, + task, + time::{self, sleep}, +}; -fn main() { +pub static MQTT: OnceCell = OnceCell::const_new(); + +#[tokio::main] +async fn main() { tracing_subscriber::fmt::init(); let args: SheepdArgs = argh::from_env(); @@ -13,6 +27,40 @@ fn main() { } else { log::info!("Starting sheepd"); - // TODO : daemon loop + let conf = AgentConfig::try_load(); + if conf.is_none() { + log::error!("No config file at /etc/sheepd/config.toml"); + std::process::exit(1); + } + + let i = if let Some(i) = Identity::try_load(&PathBuf::from("/etc/sheepd")) { + i + } else { + let i = Identity::new(); + i.save(&PathBuf::from("/etc/sheepd")); + i + }; + + let conf = conf.unwrap(); + let machine_id = std::fs::read_to_string("/etc/machine-id").unwrap(); + let machine_id = machine_id.trim(); + + log::info!("Connecting to MQTT as {machine_id}"); + + let (client, mut eventloop) = api::mqtt_connect(machine_id, &conf.mqtt); + + crate::MQTT.set(client).unwrap(); + + log::info!("Connection done"); + + log::info!("Start sub for {}", format!("{machine_id}/cmd")); + crate::MQTT + .get() + .unwrap() + .subscribe(format!("{machine_id}/cmd"), QoS::AtMostOnce) + .await + .unwrap(); + + api::run_event_loop(eventloop).await; } } diff --git a/src/sheepd_core/cmd.rs b/src/sheepd_core/cmd.rs index d26c9cc..49aac4b 100644 --- a/src/sheepd_core/cmd.rs +++ b/src/sheepd_core/cmd.rs @@ -1,37 +1,53 @@ -use crate::{api, sheepd_core::config::AgentConfig}; +use std::path::PathBuf; + +use crate::{ + api::{self, Identity, JoinResponse}, + sheepd_core::config::AgentConfig, +}; use super::args::JoinCommand; +fn domain(host: &str) -> String { + if host.starts_with("http") { + return host.to_string(); + } else { + format!("https://{host}") + } +} + pub fn join(conf: JoinCommand) { // TODO : check for root // TODO : check if joined somewhere already log::info!("Joining to {}", conf.home); - let url = format!("http://{}/join", conf.home); + let _ = std::fs::create_dir_all("/etc/sheepd"); + + let i = if let Some(i) = Identity::try_load(&PathBuf::from("/etc/sheepd")) { + i + } else { + let i = Identity::new(); + i.save(&PathBuf::from("/etc/sheepd")); + i + }; + + let url = format!("{}/join", domain(&conf.home)); println!("{url}"); let mut res = ureq::post(url) .send_json(&api::JoinParams { join_token: None, machine_id: std::fs::read_to_string("/etc/machine-id").unwrap(), hostname: std::fs::read_to_string("/etc/hostname").unwrap(), + identity: i.public(), }) .unwrap(); - let res: serde_json::Value = res.body_mut().read_json().unwrap(); - - let token = res - .as_object() - .unwrap() - .get("ok") - .unwrap() - .as_str() - .unwrap(); + let res: JoinResponse = res.body_mut().read_json().unwrap(); log::info!("Joined {} successfully", conf.home); std::fs::write( - "/etc/sheepd.toml", - toml::to_string(&AgentConfig::new(&conf.home, token)).unwrap(), + "/etc/sheepd/config.toml", + toml::to_string(&AgentConfig::new(&conf.home, res)).unwrap(), ) .unwrap(); } diff --git a/src/sheepd_core/config.rs b/src/sheepd_core/config.rs index 78bcad8..277a9c5 100644 --- a/src/sheepd_core/config.rs +++ b/src/sheepd_core/config.rs @@ -1,16 +1,28 @@ use owl::{Deserialize, Serialize}; +use crate::api::JoinResponse; + #[derive(Serialize, Deserialize)] pub struct AgentConfig { pub home: String, pub token: String, + pub mqtt: String, + pub server_age: String, + pub server_sign: String, } impl AgentConfig { - pub fn new(home_server: &str, token: &str) -> Self { + pub fn try_load() -> Option { + toml::from_str(&std::fs::read_to_string("/etc/sheepd/config.toml").ok()?).ok() + } + + pub fn new(home: &str, join: JoinResponse) -> Self { Self { - home: home_server.to_string(), - token: token.to_string(), + token: join.token, + mqtt: join.mqtt, + home: home.to_string(), + server_age: join.identity.0, + server_sign: join.identity.1, } } }