From de8055bb026c96a250240a6d81d9b7d26b4806c9 Mon Sep 17 00:00:00 2001 From: Alexander Sieg Date: Tue, 29 Nov 2022 17:59:58 +0100 Subject: [PATCH] Initial commit --- .envrc | 5 + .github/renovate.json | 6 + .gitignore | 9 + Cargo.lock | 2429 +++++++++++++++++ Cargo.toml | 67 + build.rs | 4 + crates/agent/.gitignore | 1 + crates/agent/Cargo.toml | 21 + crates/agent/src/handler.rs | 53 + crates/agent/src/main.rs | 107 + crates/agent/src/state.rs | 37 + crates/rpc/Cargo.toml | 12 + crates/rpc/src/error.rs | 7 + crates/rpc/src/lib.rs | 163 ++ crates/rpc/src/types.rs | 17 + flake.lock | 170 ++ flake.nix | 113 + .../20221009070125_initial_migration.down.sql | 4 + .../20221009070125_initial_migration.up.sql | 34 + sqlx-data.json | 292 ++ src/agent.rs | 176 ++ src/bin/nxy.rs | 1 + src/bin/nxyd.rs | 55 + src/http/agent/mod.rs | 9 + src/http/agent/websocket.rs | 105 + src/http/error.rs | 90 + src/http/flakes.rs | 128 + src/http/mod.rs | 46 + src/lib.rs | 3 + src/nix.rs | 195 ++ 30 files changed, 4359 insertions(+) create mode 100644 .envrc create mode 100644 .github/renovate.json create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 build.rs create mode 100644 crates/agent/.gitignore create mode 100644 crates/agent/Cargo.toml create mode 100644 crates/agent/src/handler.rs create mode 100644 crates/agent/src/main.rs create mode 100644 crates/agent/src/state.rs create mode 100644 crates/rpc/Cargo.toml create mode 100644 crates/rpc/src/error.rs create mode 100644 crates/rpc/src/lib.rs create mode 100644 crates/rpc/src/types.rs create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 migrations/20221009070125_initial_migration.down.sql create mode 100644 migrations/20221009070125_initial_migration.up.sql create mode 100644 sqlx-data.json create mode 100644 src/agent.rs create mode 100644 src/bin/nxy.rs create mode 100644 src/bin/nxyd.rs create mode 100644 src/http/agent/mod.rs create mode 100644 src/http/agent/websocket.rs create mode 100644 src/http/error.rs create mode 100644 src/http/flakes.rs create mode 100644 src/http/mod.rs create mode 100644 src/lib.rs create mode 100644 src/nix.rs diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..37335ef --- /dev/null +++ b/.envrc @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2021 Serokell +# +# SPDX-License-Identifier: MPL-2.0 + +use flake --impure diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000..39a2b6e --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:base" + ] +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa8fab2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2020 Serokell +# +# SPDX-License-Identifier: MPL-2.0 + +.direnv +/target +result* +/examples/system/bare-system.qcow2 +.pg diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..a0c55c0 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2429 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "agent" +version = "0.1.0" +dependencies = [ + "color-eyre", + "futures-util", + "once_cell", + "rpc", + "serde", + "serde_json", + "tokio", + "tokio-tungstenite 0.18.0", + "tracing", + "tracing-error", + "tracing-subscriber", + "uuid", +] + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" + +[[package]] +name = "async-stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" +dependencies = [ + "async-stream-impl", + "futures-core", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atoi" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c57d12312ff59c811c0643f4d80830505833c9ffaebd193d819392b265be8e" +dependencies = [ + "num-traits", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acee9fd5073ab6b045a275b3e709c163dd36c90685219cb21804a147b58dba43" +dependencies = [ + "async-trait", + "axum-core 0.2.9", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit 0.5.0", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde", + "sync_wrapper", + "tokio", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08b108ad2665fa3f6e6a517c3d80ec3e77d224c47d605167aefaa5d7ef97fa48" +dependencies = [ + "async-trait", + "axum-core 0.3.0", + "axum-macros", + "base64", + "bitflags", + "bytes", + "futures-util", + "headers", + "http", + "http-body", + "hyper", + "itoa", + "matchit 0.7.0", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sha-1", + "sync_wrapper", + "tokio", + "tokio-tungstenite 0.17.2", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e5939e02c56fecd5c017c37df4238c0a839fa76b7f97acdd7efb804fd181cc" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b8558f5a0581152dc94dcd289132a1d377494bdeafcd41869b3258e3e2ad92" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4df0fc33ada14a338b799002f7e8657711422b25d4e16afb032708d6b185621" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "backtrace" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide 0.5.4", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" + +[[package]] +name = "cc" +version = "1.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "serde", + "time", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "color-eyre" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "console-api" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57ff02e8ad8e06ab9731d5dc72dc23bef9200778eae1a89d555d8c42e5d4a86" +dependencies = [ + "prost", + "prost-types", + "tonic", + "tracing-core", +] + +[[package]] +name = "console-subscriber" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a3a81dfaf6b66bce5d159eddae701e3a002f194d378cbf7be5f053c281d9be" +dependencies = [ + "console-api", + "crossbeam-channel", + "crossbeam-utils", + "futures", + "hdrhistogram", + "humantime", + "prost-types", + "serde", + "serde_json", + "thread_local", + "tokio", + "tokio-stream", + "tonic", + "tracing", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53757d12b596c16c78b83458d732a5d1a17ab3f53f2f7412f6fb57cc8a140ab3" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d0165d2900ae6778e36e80bbc4da3b5eefccee9ba939761f9c2882a5d9af3ff" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cxx" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a41a86530d0fe7f5d9ea779916b7cadd2d4f9add748b99c2c029cbbdfaf453" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06416d667ff3e3ad2df1cd8cd8afae5da26cf9cec4d0825040f88b5ca659a2f0" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "820a9a2af1669deeef27cb271f476ffd196a2c4b6731336011e0ba63e2c7cf71" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08a6e2fcc370a089ad3b4aaf54db3b1b4cee38ddabce5896b33eb693275f470" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dotenvy" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d8c417d7a8cb362e0c37e5d815f5eb7c37f79ff93707329d5a194e42e54ca0" + +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +dependencies = [ + "serde", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "eyre" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "flate2" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +dependencies = [ + "crc32fast", + "miniz_oxide 0.6.2", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" + +[[package]] +name = "futures-intrusive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" + +[[package]] +name = "futures-macro" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" + +[[package]] +name = "futures-task" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" + +[[package]] +name = "futures-util" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +dependencies = [ + "futures-core", + "futures-macro", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gimli" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" + +[[package]] +name = "h2" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashlink" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "hdrhistogram" +version = "7.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f19b9f54f7c7f55e31401bb647626ce0cf0f67b0004982ce815b3ee72a02aa8" +dependencies = [ + "base64", + "byteorder", + "flate2", + "nom", + "num-traits", +] + +[[package]] +name = "headers" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" +dependencies = [ + "base64", + "bitflags", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" + +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" + +[[package]] +name = "link-cplusplus" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +dependencies = [ + "cc", +] + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" + +[[package]] +name = "matchit" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" + +[[package]] +name = "md-5" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" +dependencies = [ + "digest", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +dependencies = [ + "adler", +] + +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "nxy" +version = "0.1.0" +dependencies = [ + "axum 0.6.1", + "chrono", + "color-eyre", + "console-subscriber", + "futures-util", + "hyper", + "rpc", + "serde", + "serde_json", + "sqlx", + "thiserror", + "tokio", + "tower", + "tower-http", + "tracing", + "tracing-error", + "tracing-subscriber", + "uuid", +] + +[[package]] +name = "object" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "paste" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0841812012b2d4a6145fae9a6af1534873c32aa67fff26bd09f8fa42c83f95a" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "164ae68b6587001ca506d3bf7f1000bfa248d0e1217b618108fba4ec1d0cc306" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747761bc3dc48f9a34553bf65605cf6cb6288ba219f3450b4275dbd81539551a" +dependencies = [ + "bytes", + "prost", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rpc" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "thiserror", + "uuid", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "rustls" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" +dependencies = [ + "base64", +] + +[[package]] +name = "rustversion" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "scratch" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "serde" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "184c643044780f7ceb59104cef98a5a6f12cb2288a7bc701ab93a362b49fd47d" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "sqlformat" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87e292b4291f154971a43c3774364e2cbcaec599d3f5bf6fa9d122885dbc38a" +dependencies = [ + "itertools", + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9249290c05928352f71c077cc44a464d880c63f26f7534728cca008e135c0428" +dependencies = [ + "sqlx-core", + "sqlx-macros", +] + +[[package]] +name = "sqlx-core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbc16ddba161afc99e14d1713a453747a2b07fc097d2009f4c300ec99286105" +dependencies = [ + "ahash", + "atoi", + "base64", + "bitflags", + "byteorder", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "dirs", + "dotenvy", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-util", + "hashlink", + "hex", + "hkdf", + "hmac", + "indexmap", + "itoa", + "libc", + "log", + "md-5", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "rand", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "sha1", + "sha2", + "smallvec", + "sqlformat", + "sqlx-rt", + "stringprep", + "thiserror", + "tokio-stream", + "url", + "uuid", + "webpki-roots", + "whoami", +] + +[[package]] +name = "sqlx-macros" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b850fa514dc11f2ee85be9d055c512aa866746adfacd1cb42d867d68e6a5b0d9" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-rt", + "syn", + "url", +] + +[[package]] +name = "sqlx-rt" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24c5b2d25fa654cc5f841750b8e1cdedbe21189bf9a9382ee90bfa9dd3562396" +dependencies = [ + "once_cell", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "stringprep" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "tracing", + "winapi", +] + +[[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 = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-stream" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite 0.17.3", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite 0.18.0", +] + +[[package]] +name = "tokio-util" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tonic" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55b9af819e54b8f33d453655bef9b9acc171568fb49523078d0cc4e7484200ec" +dependencies = [ + "async-stream", + "async-trait", + "axum 0.5.17", + "base64", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "prost-derive", + "tokio", + "tokio-stream", + "tokio-util", + "tower", + "tower-layer", + "tower-service", + "tracing", + "tracing-futures", +] + +[[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", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "tungstenite" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +dependencies = [ + "base64", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "sha-1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "tungstenite" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" +dependencies = [ + "base64", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "uuid" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "web-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" +dependencies = [ + "webpki", +] + +[[package]] +name = "whoami" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6631b6a2fd59b1841b622e8f1a7ad241ef0a46f2d580464ce8140ac94cbd571" +dependencies = [ + "bumpalo", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..fcb9c19 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,67 @@ +[workspace] +members = ["crates/*"] + +[package] +name = "nxy" +version = "0.1.0" +authors = ["Alexander Sieg "] +edition = "2021" + +[[bin]] +name = "nxy" + +[[bin]] +name = "nxyd" + +[features] +default = [] +tokio-console = ["console-subscriber"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +color-eyre = "0.6.2" +serde = { version = "1.0.147", features = ["derive"] } +serde_json = "1.0.89" +tokio = { version = "1.22.0", features = [ + "process", + "macros", + "sync", + "rt-multi-thread", + "fs", + "time", + "tracing", +] } +tracing = "0.1.37" +tracing-error = "0.2.0" +tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } + +rpc = { path = "crates/rpc" } +sqlx = { version = "0.6.2", features = [ + "runtime-tokio-rustls", + "postgres", + "offline", + "macros", + "migrate", + "json", + "chrono", + "uuid", +] } +chrono = { version = "0.4.23", features = ["serde"] } +futures-util = "0.3.25" +axum = { version = "0.6.1", features = ["ws", "headers", "macros"] } +hyper = { version = "0.14.23", features = [] } +tower = "0.4.13" +tower-http = { version = "0.3.4", features = ["trace"] } +uuid = "1.2.2" +thiserror = "1.0.37" + +console-subscriber = { version = "0.1.8", optional = true } + +[profile.release] +lto = true +opt-level = "s" +codegen-units = 1 + +[profile.dev.package.sqlx-macros] +opt-level = 3 diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..25c3936 --- /dev/null +++ b/build.rs @@ -0,0 +1,4 @@ +fn main() { + // trigger recompilation when a new migration is added + println!("cargo:rerun-if-changed=migrations"); +} diff --git a/crates/agent/.gitignore b/crates/agent/.gitignore new file mode 100644 index 0000000..224b094 --- /dev/null +++ b/crates/agent/.gitignore @@ -0,0 +1 @@ +state.json diff --git a/crates/agent/Cargo.toml b/crates/agent/Cargo.toml new file mode 100644 index 0000000..ded1ac7 --- /dev/null +++ b/crates/agent/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "agent" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +color-eyre = "0.6.2" +futures-util = "0.3.25" +tokio-tungstenite = "0.18.0" +tracing = "0.1.37" +tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } + +rpc = { path = "../rpc" } +tokio = { version = "1.22.0", features = ["rt-multi-thread", "macros", "fs", "time"] } +tracing-error = "0.2.0" +serde_json = "1.0.89" +uuid = { version = "1.2.2", features = ["v4", "serde"] } +serde = { version = "1.0.147", features = ["derive"] } +once_cell = "1.16.0" diff --git a/crates/agent/src/handler.rs b/crates/agent/src/handler.rs new file mode 100644 index 0000000..c6fe054 --- /dev/null +++ b/crates/agent/src/handler.rs @@ -0,0 +1,53 @@ +use std::{io, path::PathBuf}; + +use color_eyre::Result; +use rpc::{ + types::{Status, System}, + ErrorCode, Request, Response, +}; +use serde_json::json; +use tracing::instrument; + +use crate::STATE; + +#[instrument(skip(request))] +pub(super) fn ping(request: &Request) -> Result { + tracing::trace!("PONG"); + Ok(Response::new_ok(request.id, "pong")) +} + +async fn current_system() -> io::Result { + tokio::fs::read_link("/run/current-system").await +} + +async fn booted_system() -> io::Result { + tokio::fs::read_link("/run/booted-system").await +} + +#[instrument(skip(request))] +pub(super) async fn status(request: &Request) -> Result { + let system = System { + current: current_system().await?, + booted: booted_system().await?, + }; + let id = { + let state = STATE.lock().unwrap(); + state.id + }; + let status = Status { + id, + version: env!("CARGO_PKG_VERSION").to_string(), + system, + }; + + Ok(Response::new_ok(request.id, json!(status))) +} + +#[instrument(skip(request))] +pub(super) fn unknown(request: &Request) -> Result { + Ok(Response::new_err( + request.id, + ErrorCode::MethodNotFound as i32, + "pong".to_string(), + )) +} diff --git a/crates/agent/src/main.rs b/crates/agent/src/main.rs new file mode 100644 index 0000000..7dff56b --- /dev/null +++ b/crates/agent/src/main.rs @@ -0,0 +1,107 @@ +use std::{env::args, path::PathBuf, sync::Mutex, time::Duration}; + +use color_eyre::Result; +use futures_util::{SinkExt, TryStreamExt}; +use once_cell::sync::Lazy; +use state::State; +use tokio::net::TcpStream; +use tokio_tungstenite::{connect_async, tungstenite::Message, MaybeTlsStream, WebSocketStream}; +use tracing::instrument; + +use rpc::{JsonRPC, Request, Response}; + +mod handler; +mod state; + +pub static STATE: Lazy> = Lazy::new(|| { + let path = args() + .nth(1) + .map(|p| p.parse::().expect("first arg not a vaild path")); + let state = state::load(path); + Mutex::new(state) +}); + +#[tokio::main] +#[instrument] +async fn main() -> Result<()> { + install_tracing(); + color_eyre::install()?; + + run().await +} + +async fn run() -> Result<()> { + loop { + let (mut ws, _) = connect().await?; + while let Ok(Some(msg)) = ws.try_next().await { + let rpc: JsonRPC = msg.into_text()?.parse()?; + match rpc { + JsonRPC::Request(request) => { + let res: JsonRPC = handle_request(request).await?.into(); + ws.send(Message::Text(res.to_string())).await?; + } + JsonRPC::Response(res) => { + tracing::warn!(?res, "received response, this should happen") + } + JsonRPC::Notification(notification) => tracing::info!(?notification), + } + } + } +} + +async fn connect() -> Result<( + WebSocketStream>, + tokio_tungstenite::tungstenite::http::Response<()>, +)> { + let mut retry_period = Duration::from_millis(500); + loop { + match connect_async("ws://localhost:8080/api/v1/agent/ws").await { + Ok(ws) => return Ok(ws), + Err(e) => { + tracing::warn!( + "unable to astablish connection to server, retrying in {:?}", + retry_period + ); + tracing::debug!(?e); + tokio::time::sleep(retry_period).await; + retry_period = backoff(retry_period); + } + } + } +} + +fn backoff(duration: Duration) -> Duration { + if duration >= Duration::from_secs(4) { + return Duration::from_secs(4); + } + duration * 2 +} + +#[instrument(skip_all, err, fields(id = %request.id, method = request.method))] +async fn handle_request(request: Request) -> Result { + tracing::debug!("start processing request"); + let response = match request.method.as_str() { + "ping" => handler::ping(&request), + "status" => handler::status(&request).await, + _ => handler::unknown(&request), + }; + tracing::debug!("done processing request"); + response +} + +fn install_tracing() { + use tracing_error::ErrorLayer; + use tracing_subscriber::prelude::*; + use tracing_subscriber::{fmt, EnvFilter}; + + let fmt_layer = fmt::layer().pretty(); + let filter_layer = EnvFilter::try_from_default_env() + .or_else(|_| EnvFilter::try_new("info")) + .unwrap(); + + tracing_subscriber::registry() + .with(filter_layer) + .with(fmt_layer) + .with(ErrorLayer::default()) + .init(); +} diff --git a/crates/agent/src/state.rs b/crates/agent/src/state.rs new file mode 100644 index 0000000..9d65301 --- /dev/null +++ b/crates/agent/src/state.rs @@ -0,0 +1,37 @@ +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Debug, Deserialize, Serialize)] +pub struct State { + pub id: Uuid, +} + +impl State { + pub fn new() -> Self { + Self { id: Uuid::new_v4() } + } +} + +impl Default for State { + fn default() -> Self { + Self::new() + } +} + +pub fn load(state_path: Option) -> State { + let state_path = state_path.unwrap_or_default(); + let state_file = state_path.join("state.json"); + if state_file.is_file() { + tracing::info!(file = ?state_file, "Loading state"); + let data = std::fs::read_to_string(state_file).expect("unable to read state file"); + serde_json::from_str(&data).expect("failed to deserialize state file") + } else { + tracing::info!(file = ?state_file, "Creating new state file"); + std::fs::create_dir_all(state_path).unwrap(); + let state = State::new(); + std::fs::write(state_file, serde_json::to_vec_pretty(&state).unwrap()).unwrap(); + state + } +} diff --git a/crates/rpc/Cargo.toml b/crates/rpc/Cargo.toml new file mode 100644 index 0000000..27140a9 --- /dev/null +++ b/crates/rpc/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rpc" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +thiserror = "1.0" +serde = { version = "1.0.147", features = ["derive"] } +serde_json = "1.0.89" +uuid = { version = "1.2.2", features = ["serde"] } diff --git a/crates/rpc/src/error.rs b/crates/rpc/src/error.rs new file mode 100644 index 0000000..4625aab --- /dev/null +++ b/crates/rpc/src/error.rs @@ -0,0 +1,7 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error(transparent)] + SerdeJson(#[from] serde_json::Error), +} diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs new file mode 100644 index 0000000..98090fa --- /dev/null +++ b/crates/rpc/src/lib.rs @@ -0,0 +1,163 @@ +pub mod error; +pub mod types; + +use std::{fmt::Display, str::FromStr}; + +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(untagged)] +pub enum JsonRPC { + Request(Request), + Response(Response), + Notification(Notification), +} + +impl From for JsonRPC { + fn from(request: Request) -> JsonRPC { + JsonRPC::Request(request) + } +} + +impl From for JsonRPC { + fn from(response: Response) -> JsonRPC { + JsonRPC::Response(response) + } +} + +impl From for JsonRPC { + fn from(notification: Notification) -> JsonRPC { + JsonRPC::Notification(notification) + } +} + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[serde(transparent)] +pub struct RequestId(u64); + +impl Display for RequestId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Request { + pub id: RequestId, + pub method: String, + #[serde(default = "serde_json::Value::default")] + #[serde(skip_serializing_if = "serde_json::Value::is_null")] + pub params: serde_json::Value, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Response { + pub id: RequestId, + #[serde(skip_serializing_if = "Option::is_none")] + pub result: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ResponseError { + pub code: i32, + pub message: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub data: Option, +} + +#[derive(Clone, Copy, Debug)] +#[allow(unused)] +#[non_exhaustive] +pub enum ErrorCode { + // Defined by JSON RPC: + ParseError = -32700, + InvalidRequest = -32600, + MethodNotFound = -32601, + InvalidParams = -32602, + InternalError = -32603, + ServerErrorStart = -32099, + ServerErrorEnd = -32000, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Notification { + pub method: String, + #[serde(default = "serde_json::Value::default")] + #[serde(skip_serializing_if = "serde_json::Value::is_null")] + pub params: serde_json::Value, +} + +impl From for RequestId { + fn from(v: u64) -> Self { + RequestId(v) + } +} + +impl FromStr for JsonRPC { + type Err = crate::error::Error; + + fn from_str(s: &str) -> Result { + serde_json::from_str(s).map_err(Into::into) + } +} + +impl Display for JsonRPC { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + #[derive(Serialize)] + struct JsonRpc { + jsonrpc: &'static str, + #[serde(flatten)] + msg: JsonRPC, + } + let text = serde_json::to_string(&JsonRpc { + jsonrpc: "2.0", + msg: self.clone(), + }) + .unwrap(); + write!(f, "{text}") + } +} + +impl Request { + pub fn new, P: Serialize>(id: RequestId, method: S, params: P) -> Request { + let params = serde_json::to_value(params).unwrap(); + Request { + id, + method: method.as_ref().to_string(), + params, + } + } +} + +impl Response { + pub fn new_ok(id: RequestId, result: R) -> Response { + Response { + id, + result: Some(serde_json::to_value(result).unwrap()), + error: None, + } + } + pub fn new_err(id: RequestId, code: i32, message: String) -> Response { + let error = ResponseError { + code, + message, + data: None, + }; + Response { + id, + result: None, + error: Some(error), + } + } +} +impl Notification { + #[allow(unused)] + pub fn new(method: String, params: impl Serialize) -> Notification { + Notification { + method, + params: serde_json::to_value(params).unwrap(), + } + } +} diff --git a/crates/rpc/src/types.rs b/crates/rpc/src/types.rs new file mode 100644 index 0000000..d02d30b --- /dev/null +++ b/crates/rpc/src/types.rs @@ -0,0 +1,17 @@ +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Status { + pub id: Uuid, + pub system: System, + pub version: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct System { + pub current: PathBuf, + pub booted: PathBuf, +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..8b16929 --- /dev/null +++ b/flake.lock @@ -0,0 +1,170 @@ +{ + "nodes": { + "crane": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": [ + "utils" + ], + "nixpkgs": [ + "nixpkgs" + ], + "rust-overlay": "rust-overlay" + }, + "locked": { + "lastModified": 1668993159, + "narHash": "sha256-9BVTtPFrHRh0HbeEm2bmXsoIWRj1tKM6Nvfl7VMK/X8=", + "owner": "ipetkov", + "repo": "crane", + "rev": "c61d98aaea5667607a36bafe5a6fa87fe5bb2c7e", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, + "fenix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1669184726, + "narHash": "sha256-YF3xLbY3eJ3d4x3fkh8pASeJ4Y7P2vTg8spdRKLDnM8=", + "owner": "nix-community", + "repo": "fenix", + "rev": "72b820427fbd59a55368cda4de159134764e3ff6", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1650374568, + "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "b4a34015c698c7793d592d66adbab377907a2be8", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1660459072, + "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1669165918, + "narHash": "sha256-hIVruk2+0wmw/Kfzy11rG3q7ev3VTi/IKVODeHcVjFo=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "3b400a525d92e4085e46141ff48cbf89fd89739e", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "crane": "crane", + "fenix": "fenix", + "gitignore": "gitignore", + "nixpkgs": "nixpkgs", + "utils": "utils" + } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1668974383, + "narHash": "sha256-ULEwMFhcr+0z4r//BSZVFV5Nh1+opwwYBk/ZzEptjqw=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "26562973b3482a635416b2b663a13016d4d90e20", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + }, + "rust-overlay": { + "inputs": { + "flake-utils": [ + "crane", + "flake-utils" + ], + "nixpkgs": [ + "crane", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1667487142, + "narHash": "sha256-bVuzLs1ZVggJAbJmEDVO9G6p8BH3HRaolK70KXvnWnU=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "cf668f737ac986c0a89e83b6b2e3c5ddbd8cf33b", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "utils": { + "locked": { + "lastModified": 1667395993, + "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..d7d427c --- /dev/null +++ b/flake.nix @@ -0,0 +1,113 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + crane = { + url = "github:ipetkov/crane"; + inputs.nixpkgs.follows = "nixpkgs"; + inputs.flake-utils.follows = "utils"; + }; + fenix = { + url = "github:nix-community/fenix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + utils.url = "github:numtide/flake-utils"; + gitignore = { + url = "github:hercules-ci/gitignore.nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { self, nixpkgs, crane, fenix, gitignore, utils, ... }: + { + herculesCI = { + ciSystems = [ "x86_64-linux" ]; + }; + } // + utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { inherit system; }; + inherit (gitignore.lib) gitignoreSource; + + rustToolchain = fenix.packages.${system}.stable; + + craneLib = crane.lib.${system}.overrideToolchain rustToolchain.toolchain; + + commonArgs = { + src = gitignoreSource ./.; + + # enable unstable tokio `tracing` feature + RUSTFLAGS = "--cfg tokio_unstable"; + }; + + cargoArtifacts = craneLib.buildDepsOnly commonArgs; + + in + { + packages = rec { + nxy = craneLib.buildPackage (commonArgs // { + inherit cargoArtifacts; + }); + + nxy-agent = craneLib.buildPackage (commonArgs // { + inherit cargoArtifacts; + cargoEtraArgs = "--package agent"; + } // craneLib.crateNameFromCargoToml { cargoToml = ./crates/agent/Cargo.toml; }); + + default = nxy; + }; + + apps = rec { + nxy = { + type = "app"; + program = "${self.packages."${system}".nxy}/bin/nxy"; + }; + nxy-agent = { + type = "app"; + program = "${self.packages."${system}".nxy-agent}/bin/agent"; + }; + default = nxy; + }; + + devShells.default = + let + xdg_runtime_dir = + if builtins.getEnv "XDG_RUNTIME_DIR" == "" then + ".pg" + else + builtins.getEnv "XDG_RUNTIME_DIR"; + in + pkgs.mkShell { + RUST_SRC_PATH = "${rustToolchain.rust-src}/lib/rustlib/src/rust/library"; + RUSTFLAGS = "--cfg tokio_unstable"; + PGDATA = ".pg/data"; + PGHOST = "${xdg_runtime_dir}/nxy"; + PGDATABASE = "nxy"; + DATABASE_URL = "postgres://"; + inputsFrom = [ self.packages.${system}.nxy ]; + buildInputs = with pkgs; [ + nixUnstable + + # runtime deps + postgresql_14 + + # developer tooling + python3Packages.pgcli + sqlx-cli + websocat + ]; + shellHook = '' + mkdir -p $XDG_RUNTIME_DIR/nxy + if ! [ -d $PGDATA ]; then + initdb + fi + if ! pg_ctl status > /dev/null; then + systemd-run --user --unit=nxy_postgres --service-type=notify \ + --same-dir -E PGDATA=$PGDATA \ + ${pkgs.postgresql_14}/bin/postgres --listen-addresses="" --unix_socket_directories=${xdg_runtime_dir}/nxy + + createdb + fi + ''; + }; + }); +} diff --git a/migrations/20221009070125_initial_migration.down.sql b/migrations/20221009070125_initial_migration.down.sql new file mode 100644 index 0000000..d462e29 --- /dev/null +++ b/migrations/20221009070125_initial_migration.down.sql @@ -0,0 +1,4 @@ +drop table nixos_configurations; +drop table flake_revisions; +drop table flakes; +drop table agents; diff --git a/migrations/20221009070125_initial_migration.up.sql b/migrations/20221009070125_initial_migration.up.sql new file mode 100644 index 0000000..2a786ab --- /dev/null +++ b/migrations/20221009070125_initial_migration.up.sql @@ -0,0 +1,34 @@ +create table flakes ( + flake_id bigint generated always as identity primary key, + flake_url text not null unique +); + +create table flake_revisions ( + flake_revision_id bigint generated always as identity primary key, + flake_id bigint references flakes NOT NULL, + revision text not null, + last_modified timestamp with time zone not null, + url text not null, + metadata jsonb not null +); + +create table nixos_configurations ( + nixos_configuration_id bigint generated always as identity primary key, + flake_id bigint not null references flakes, + name text not null, + + unique (flake_id, name) +); + +create table nixos_configuration_evaluations ( + flake_revision_id bigint references flake_revisions, + nixos_configuration_id bigint references nixos_configurations, + store_path text not null, + + primary key (flake_revision_id, nixos_configuration_id) +); + +create table agents ( + agent_id uuid primary key, + current_system text +); diff --git a/sqlx-data.json b/sqlx-data.json new file mode 100644 index 0000000..cda218d --- /dev/null +++ b/sqlx-data.json @@ -0,0 +1,292 @@ +{ + "db": "PostgreSQL", + "02ccdd5994dc03a27d8ab60d769410589dd9f1f3286245a039076ff43e572485": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Uuid" + ] + } + }, + "query": "insert into agents (agent_id) values ($1)" + }, + "165c6e3db988a5debb7881536b630b1067c5fc33ad957b12ba9e37a23eaaf69d": { + "describe": { + "columns": [ + { + "name": "nixos_configuration_id", + "ordinal": 0, + "type_info": "Int8" + } + ], + "nullable": [ + false + ], + "parameters": { + "Left": [ + "Int8", + "Text" + ] + } + }, + "query": "\n SELECT nixos_configuration_id FROM nixos_configurations\n WHERE flake_id = $1 AND name = $2\n " + }, + "3bd52fe6c283e21c1c5b12f1f69114837d1552afa4efab3128242df3a4edc299": { + "describe": { + "columns": [ + { + "name": "agent_id", + "ordinal": 0, + "type_info": "Uuid" + } + ], + "nullable": [ + false + ], + "parameters": { + "Left": [ + "Uuid" + ] + } + }, + "query": "select agent_id from agents where agent_id = $1" + }, + "47fc82cbeae563a394bf44aabc53ac7f13ee5fc6dc697ca78b1ce981564c2736": { + "describe": { + "columns": [ + { + "name": "flake_id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "flake_url", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "flake_revision_id!", + "ordinal": 2, + "type_info": "Int8" + }, + { + "name": "revision", + "ordinal": 3, + "type_info": "Text" + }, + { + "name": "last_modified", + "ordinal": 4, + "type_info": "Timestamptz" + }, + { + "name": "url", + "ordinal": 5, + "type_info": "Text" + } + ], + "nullable": [ + false, + false, + null, + false, + false, + false + ], + "parameters": { + "Left": [] + } + }, + "query": "\n with last_rev as (\n select flake_id, max(flake_revision_id) as flake_revision_id\n from flake_revisions\n group by flake_id\n )\n select flakes.flake_id, flake_url, flake_revision_id as \"flake_revision_id!\", revision, last_modified, url\n from flakes\n join last_rev using (flake_id)\n join flake_revisions using (flake_revision_id)\n " + }, + "6511574c0bf23e5326ae335549bc11db0e5960f783fb9f7c672d3ac8a003c446": { + "describe": { + "columns": [ + { + "name": "flake_id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "flake_url", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "revision", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "last_modified", + "ordinal": 3, + "type_info": "Timestamptz" + } + ], + "nullable": [ + false, + false, + false, + false + ], + "parameters": { + "Left": [] + } + }, + "query": "\n WITH last_rev AS (\n SELECT flake_id, MAX(flake_revision_id) AS flake_revision_id\n FROM flake_revisions\n GROUP BY flake_id\n )\n SELECT flakes.flake_id, flake_url, revision, last_modified \n FROM flakes\n JOIN last_rev USING (flake_id)\n JOIN flake_revisions USING (flake_revision_id)\n " + }, + "6ef91119dff3cd34d85881a28a59e28c4350b4651d63b50be11051aac0aaa8da": { + "describe": { + "columns": [ + { + "name": "flake_id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "url", + "ordinal": 1, + "type_info": "Text" + } + ], + "nullable": [ + false, + false + ], + "parameters": { + "Left": [ + "Int8" + ] + } + }, + "query": "SELECT flake_id, url FROM flake_revisions WHERE flake_revision_id = $1" + }, + "8cd2b188f73142e0f1029ed187f7671f4ed60104e5aa6028345c347ba4e8a42e": { + "describe": { + "columns": [ + { + "name": "flake_revision_id", + "ordinal": 0, + "type_info": "Int8" + } + ], + "nullable": [ + false + ], + "parameters": { + "Left": [ + "Int8", + "Text", + "Timestamptz", + "Text", + "Jsonb" + ] + } + }, + "query": "\n INSERT INTO flake_revisions (flake_id, revision, last_modified, url, metadata)\n VALUES ($1, $2, $3, $4, $5)\n RETURNING flake_revision_id\n " + }, + "901827a3edfb6b3b09676ea976461d23126921fcc6514213fadc1feaf0131475": { + "describe": { + "columns": [ + { + "name": "flake_id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "flake_url", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "flake_revision_id", + "ordinal": 2, + "type_info": "Int8" + }, + { + "name": "revision", + "ordinal": 3, + "type_info": "Text" + }, + { + "name": "last_modified", + "ordinal": 4, + "type_info": "Timestamptz" + }, + { + "name": "url", + "ordinal": 5, + "type_info": "Text" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + false + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Timestamptz", + "Text", + "Jsonb" + ] + } + }, + "query": "\n with inserted_flake as (\n insert into flakes (flake_url)\n values ($1)\n returning flake_id, flake_url\n ), inserted_revision as (\n insert into flake_revisions (flake_id, revision, last_modified, url, metadata)\n select flake_id, $2, $3, $4, $5\n from inserted_flake\n returning flake_revision_id, revision, last_modified, url\n )\n select flake_id, flake_url, flake_revision_id, revision, last_modified, url\n from inserted_flake, inserted_revision\n " + }, + "bc071afcbbc3d4c41e8aaa90145ae531a55214e15c0f996461aef3ecf60ff824": { + "describe": { + "columns": [ + { + "name": "nixos_configuration_id", + "ordinal": 0, + "type_info": "Int8" + } + ], + "nullable": [ + false + ], + "parameters": { + "Left": [ + "Int8", + "Text" + ] + } + }, + "query": "\n INSERT INTO nixos_configurations (flake_id, name)\n VALUES ($1, $2) \n ON CONFLICT DO NOTHING\n RETURNING nixos_configuration_id\n " + }, + "c591ba80c33c36015e66b3b02373b3ad853e64e032278ae4db76f4e15502570e": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Uuid", + "Text" + ] + } + }, + "query": "update agents set current_system = $2 where agent_id = $1" + }, + "ffc37a9ec8bf0c7560f5d30d3c0cca8ad263f0a62a7b8ad8fe8249e6e528a54a": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Int8", + "Int8", + "Text" + ] + } + }, + "query": "\n INSERT INTO nixos_configuration_evaluations (flake_revision_id, nixos_configuration_id, store_path)\n VALUES ($1, $2, $3) \n " + } +} \ No newline at end of file diff --git a/src/agent.rs b/src/agent.rs new file mode 100644 index 0000000..5598628 --- /dev/null +++ b/src/agent.rs @@ -0,0 +1,176 @@ +use std::{ + collections::HashMap, + sync::{atomic::AtomicU64, Arc, Mutex}, + time::Duration, +}; + +use color_eyre::{eyre::eyre, Result}; +use rpc::{types::Status, JsonRPC, Request, RequestId, Response}; +use serde::Serialize; +use sqlx::PgPool; +use tokio::sync::{mpsc, oneshot}; +use tracing::{instrument, Level}; +use uuid::Uuid; + +#[derive(Debug)] +pub struct AgentManager { + pool: PgPool, + agents: Mutex>, +} + +impl AgentManager { + pub async fn start(pool: PgPool) -> Arc { + let manager = Arc::new(Self { + pool, + agents: Default::default(), + }); + + let manager_c = Arc::clone(&manager); + tokio::spawn(async move { manager_c.heartbeat().await }); + + manager + } + + pub async fn heartbeat(&self) { + loop { + let agents = self.agents.lock().unwrap().clone(); + for agent in agents.values() { + agent.ping().await.unwrap(); + } + tokio::time::sleep(Duration::from_secs(5)).await + } + } + + pub async fn add_agent(&self, agent: Agent) -> Result<()> { + // request agent status to aquire the agent id + let status = agent.status().await?; + + let result = + sqlx::query_scalar!("select agent_id from agents where agent_id = $1", status.id) + .fetch_optional(&self.pool) + .await?; + + if result.is_none() { + tracing::info!(id = ?status.id, "new agent established a connection"); + sqlx::query!("insert into agents (agent_id) values ($1)", status.id) + .execute(&self.pool) + .await?; + } else { + tracing::info!(id = ?status.id, "known agent connected"); + } + sqlx::query!( + "update agents set current_system = $2 where agent_id = $1", + status.id, + status.system.current.to_str().unwrap() + ) + .execute(&self.pool) + .await?; + + { + let mut agents = self.agents.lock().unwrap(); + agents.insert(status.id, agent); + } + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub struct Agent(Arc); + +#[derive(Debug)] +struct AgentInner { + next_request_id: AtomicU64, + pending: Mutex>>, + outbox: mpsc::Sender, + span: tracing::Span, +} + +impl Agent { + pub fn new(inbox: mpsc::Receiver, outbox: mpsc::Sender) -> Self { + let span = tracing::span!(Level::TRACE, "agent connection"); + let agent = Agent(Arc::new(AgentInner { + next_request_id: AtomicU64::new(0), + pending: Default::default(), + outbox, + span, + })); + + let clone = agent.clone(); + tokio::spawn(async move { clone.process_inbox(inbox).await }); + agent + } + + #[instrument(parent = &self.0.span, skip(self, inbox))] + async fn process_inbox(self, mut inbox: mpsc::Receiver) { + while let Some(msg) = inbox.recv().await { + tracing::trace!(?msg, "receiver message"); + match msg { + JsonRPC::Request(request) => { + tracing::warn!(?request, "server received request, this should happen"); + } + JsonRPC::Response(res) => { + tracing::info!("{res:?}"); + + let mut pending = self.0.pending.lock().unwrap(); + if let Some(tx) = pending.remove(&res.id) { + tx.send(res).unwrap(); + } else { + tracing::warn!( + request_id = ?res.id, + "received response for unknown request id" + ) + } + } + JsonRPC::Notification(notification) => { + tracing::warn!( + ?notification, + "server received notification, this should happen" + ); + } + } + } + } + + async fn send_request, P: Serialize>( + &self, + method: S, + params: P, + ) -> oneshot::Receiver { + let id = self + .0 + .next_request_id + .fetch_add(1, std::sync::atomic::Ordering::SeqCst) + .into(); + let request: JsonRPC = Request::new(id, method, params).into(); + + let (sender, receiver) = oneshot::channel(); + + { + let mut pending = self.0.pending.lock().unwrap(); + pending.insert(id, sender); + } + + self.0.outbox.send(request).await.unwrap(); + receiver + } + + pub async fn ping(&self) -> Result<()> { + let res = self.send_request("ping", ()).await.await?; + if let Some(error) = res.error { + Err(eyre!("request error: {:?}", error)) + } else { + Ok(()) + } + } + + pub async fn status(&self) -> Result { + let res = self.send_request("status", ()).await.await?; + if let Some(error) = res.error { + Err(eyre!("request error: {:?}", error)) + } else { + res.result + .ok_or_else(|| eyre!("status result is empty")) + .and_then(|v| serde_json::from_value(v).map_err(Into::into)) + } + } +} diff --git a/src/bin/nxy.rs b/src/bin/nxy.rs new file mode 100644 index 0000000..f328e4d --- /dev/null +++ b/src/bin/nxy.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/src/bin/nxyd.rs b/src/bin/nxyd.rs new file mode 100644 index 0000000..03c338c --- /dev/null +++ b/src/bin/nxyd.rs @@ -0,0 +1,55 @@ +use color_eyre::{eyre::Context, Result}; +use nxy::agent::AgentManager; +use sqlx::postgres::PgPoolOptions; +use tracing::subscriber::Subscriber; +use tracing_subscriber::Layer; + +#[tokio::main] +async fn main() -> Result<()> { + install_tracing(); + color_eyre::install()?; + + let database_url = std::env::var("DATABASE_URL").wrap_err("DATABASE_URL unset")?; + let pool = PgPoolOptions::new().connect(&database_url).await?; + sqlx::migrate!().run(&pool).await?; + + let agent_manager = AgentManager::start(pool.clone()).await; + + nxy::http::serve(pool, agent_manager).await +} + +fn install_tracing() { + use tracing_error::ErrorLayer; + use tracing_subscriber::prelude::*; + use tracing_subscriber::{fmt, EnvFilter}; + + let fmt_layer = fmt::layer().pretty(); + let filter_layer = EnvFilter::try_from_default_env() + .or_else(|_| EnvFilter::try_new("sqlx=warn,info")) + .unwrap(); + + tracing_subscriber::registry() + .with(filter_layer) + .with(fmt_layer) + .with(init_console()) + .with(ErrorLayer::default()) + .init(); +} + +use tracing_subscriber::registry::LookupSpan; + +#[cfg(feature = "tokio-console")] +fn init_console() -> impl Layer +where + S: Subscriber + for<'a> LookupSpan<'a>, +{ + Some(console_subscriber::spawn()) +} + +#[cfg(not(feature = "tokio-console"))] +fn init_console() -> impl Layer +where + S: Subscriber + for<'a> LookupSpan<'a>, +{ + None:: + Send + Sync + 'static>> +} diff --git a/src/http/agent/mod.rs b/src/http/agent/mod.rs new file mode 100644 index 0000000..e97756b --- /dev/null +++ b/src/http/agent/mod.rs @@ -0,0 +1,9 @@ +mod websocket; + +use axum::{routing::get, Router}; + +use super::ApiContext; + +pub(crate) fn router() -> Router { + Router::new().route("/api/v1/agent/ws", get(websocket::ws_handler)) +} diff --git a/src/http/agent/websocket.rs b/src/http/agent/websocket.rs new file mode 100644 index 0000000..c69add1 --- /dev/null +++ b/src/http/agent/websocket.rs @@ -0,0 +1,105 @@ +use axum::{ + extract::{ + ws::{Message, WebSocket}, + State, WebSocketUpgrade, + }, + headers, + response::IntoResponse, + TypedHeader, +}; +use futures_util::{ + stream::{SplitSink, SplitStream}, + SinkExt, StreamExt, +}; +use rpc::{ErrorCode, JsonRPC, Response}; +use tokio::sync::mpsc; +use tracing::instrument; + +use crate::{agent::Agent, http::ApiContext}; + +#[instrument(skip_all)] +pub(super) async fn ws_handler( + ws: WebSocketUpgrade, + ctx: State, + user_agent: Option>, +) -> impl IntoResponse { + if let Some(TypedHeader(user_agent)) = user_agent { + tracing::info!("`{}` connected", user_agent.as_str()); + } + + ws.on_upgrade(|socket| handle_socket(socket, ctx)) +} + +#[instrument(skip_all)] +async fn handle_socket(socket: WebSocket, ctx: State) { + let (inbox_sender, inbox) = mpsc::channel(4096); + let (outbox, outbox_receiver) = mpsc::channel(4096); + let (sink, stream) = socket.split(); + let inbox_handler = tokio::spawn(process_inbox(stream, inbox_sender)); + let outbox_handler = tokio::spawn(process_outbox(sink, outbox_receiver)); + + let agent = Agent::new(inbox, outbox); + ctx.agent_manager.add_agent(agent).await.unwrap(); + + inbox_handler.await.unwrap(); + outbox_handler.await.unwrap(); +} + +#[instrument(skip_all)] +async fn process_outbox( + mut sink: SplitSink, + mut outbox_receiver: mpsc::Receiver, +) { + while let Some(msg) = outbox_receiver.recv().await { + if let Err(err) = sink.send(Message::Text(msg.to_string())).await { + tracing::warn!(?err, "connection closed"); + return; + }; + } +} + +#[instrument(skip_all)] +async fn process_inbox(mut stream: SplitStream, tx: mpsc::Sender) { + while let Some(Ok(msg)) = stream.next().await { + match msg { + Message::Text(t) => { + tracing::debug!("client sent str: {:?}", t); + let msg = match t.parse() { + Ok(rpc) => rpc, + Err(err) => { + // compiler needs a little help with the type signature + let err: rpc::error::Error = err; + Response::new_err( + // we don't have a request id in this case, the standard allow + // that the request id is empty in this case, but our + // implementation doesn't support this. + u64::MAX.into(), + ErrorCode::ParseError as i32, + err.to_string(), + ) + .into() + } + }; + if let Err(err) = tx.send(msg).await { + tracing::warn!( + ?err, + "error sending incomming msg to agent, closing connection" + ); + break; + }; + } + Message::Binary(_) => { + tracing::warn!( + "client sent binary data, this is not supported. Closing connection" + ); + break; + } + // ignore ping and pong axum handles this for us + Message::Ping(_) | Message::Pong(_) => {} + Message::Close(_) => { + break; + } + } + } + tracing::debug!("client disconnected"); +} diff --git a/src/http/error.rs b/src/http/error.rs new file mode 100644 index 0000000..6451287 --- /dev/null +++ b/src/http/error.rs @@ -0,0 +1,90 @@ +use axum::response::{IntoResponse, Response}; +use hyper::StatusCode; +use sqlx::error::DatabaseError; + +/// An API-friendly error type. +#[derive(thiserror::Error, Debug)] +pub enum Error { + /// A SQLx call returned an error. + /// + /// The exact error contents are not reported to the user in order to avoid leaking + /// information about databse internals. + #[error("an internal database error occurred")] + Sqlx(#[from] sqlx::Error), + + /// Similarly, we don't want to report random `anyhow` errors to the user. + #[error("an internal server error occurred")] + Eyre(#[from] color_eyre::Report), +} + +impl Error { + fn status_code(&self) -> StatusCode { + match self { + Self::Sqlx(_) | Self::Eyre(_) => StatusCode::INTERNAL_SERVER_ERROR, + } + } +} + +impl IntoResponse for Error { + fn into_response(self) -> Response { + match self { + Self::Sqlx(ref e) => { + tracing::error!(error = %e, "SQLx error"); + } + + Self::Eyre(ref e) => { + tracing::error!(error = %e, "Generic error"); + } + + // Other errors geht mapped normally. + // we currently don't have any more patterns, but let's keep this as a reminder + #[allow(unreachable_patterns)] + _ => {} + } + + (self.status_code(), self.to_string()).into_response() + } +} + +/// A little helper trait for more easily converting database constraint errors into API errors. +/// +/// ```rust,ignore +/// let user_id = sqlx::query_scalar!( +/// r#"insert into "user" (username, email, password_hash) values ($1, $2, $3) returning user_id"#, +/// username, +/// email, +/// password_hash +/// ) +/// .fetch_one(&ctxt.db) +/// .await +/// .on_constraint("user_username_key", |_| Error::unprocessable_entity([("username", "already taken")]))?; +/// ``` +pub trait ResultExt { + /// If `self` contains a SQLx database constraint error with the given name, + /// transform the error. + /// + /// Otherwise, the result is passed through unchanged. + fn on_constraint( + self, + name: &str, + f: impl FnOnce(Box) -> Error, + ) -> Result; +} + +impl ResultExt for Result +where + E: Into, +{ + fn on_constraint( + self, + name: &str, + map_err: impl FnOnce(Box) -> Error, + ) -> Result { + self.map_err(|e| match e.into() { + Error::Sqlx(sqlx::Error::Database(dbe)) if dbe.constraint() == Some(name) => { + map_err(dbe) + } + e => e, + }) + } +} diff --git a/src/http/flakes.rs b/src/http/flakes.rs new file mode 100644 index 0000000..0539dfd --- /dev/null +++ b/src/http/flakes.rs @@ -0,0 +1,128 @@ +use axum::{extract::State, routing::get, Json, Router}; +use serde::{Deserialize, Serialize}; + +use crate::nix::{self, flake_metadata, process_configurations}; + +use super::{ApiContext, Result}; + +pub(crate) fn router() -> Router { + Router::new().route( + "/api/v1/flake", + get(get_flakes).post(create_flake).put(update_flake), + ) +} + +#[derive(Serialize, Deserialize)] +struct FlakeBody { + flake: T, +} + +#[derive(Serialize)] +struct Flake { + flake_id: i64, + flake_url: String, + lastest_revision: FlakeRevision, +} + +#[derive(Serialize)] +struct FlakeRevision { + flake_revision_id: i64, + revision: String, + last_modified: String, + url: String, +} + +#[derive(Deserialize)] +struct NewFlake { + flake_url: String, +} + +async fn create_flake( + ctx: State, + Json(req): Json>, +) -> Result>> { + // fetch flake metadata, this also validates the flake url + let (metadata, meta) = flake_metadata(&req.flake.flake_url).await?; + + let flake = sqlx::query!( + r#" + with inserted_flake as ( + insert into flakes (flake_url) + values ($1) + returning flake_id, flake_url + ), inserted_revision as ( + insert into flake_revisions (flake_id, revision, last_modified, url, metadata) + select flake_id, $2, $3, $4, $5 + from inserted_flake + returning flake_revision_id, revision, last_modified, url + ) + select flake_id, flake_url, flake_revision_id, revision, last_modified, url + from inserted_flake, inserted_revision + "#, + req.flake.flake_url, + metadata.revision, + metadata.last_modified, + metadata.url, + meta + ) + .fetch_one(&ctx.db) + .await?; + + tokio::spawn(process_configurations( + ctx.db.clone(), + flake.flake_revision_id, + )); + + Ok(Json(FlakeBody { + flake: Flake { + flake_id: flake.flake_id, + flake_url: flake.flake_url, + lastest_revision: FlakeRevision { + flake_revision_id: flake.flake_revision_id, + revision: flake.revision, + last_modified: flake.last_modified.to_string(), + url: flake.url, + }, + }, + })) +} + +async fn get_flakes(ctx: State) -> Result>> { + let flakes = sqlx::query!( + r#" + with last_rev as ( + select flake_id, max(flake_revision_id) as flake_revision_id + from flake_revisions + group by flake_id + ) + select flakes.flake_id, flake_url, flake_revision_id as "flake_revision_id!", revision, last_modified, url + from flakes + join last_rev using (flake_id) + join flake_revisions using (flake_revision_id) + "#, + ) + .fetch_all(&ctx.db).await? + .into_iter() + .map(|row| { + let revision = FlakeRevision { + flake_revision_id: row.flake_revision_id, + revision: row.revision, + last_modified: row.last_modified.to_string(), + url: row.url, + }; + + Flake { + flake_id: row.flake_id, + flake_url: row.flake_url, + lastest_revision: revision, + } + }) + .collect(); + + Ok(Json(flakes)) +} + +async fn update_flake(ctx: State) -> Result<()> { + nix::update_flakes(&ctx.db).await?; + Ok(()) +} diff --git a/src/http/mod.rs b/src/http/mod.rs new file mode 100644 index 0000000..2e0f365 --- /dev/null +++ b/src/http/mod.rs @@ -0,0 +1,46 @@ +mod agent; +mod error; +mod flakes; + +use std::{ + net::{Ipv4Addr, SocketAddr}, + sync::Arc, +}; + +use axum::Router; +use color_eyre::eyre::WrapErr; +use sqlx::PgPool; +use tower_http::trace::TraceLayer; + +use crate::{agent::AgentManager, http::error::Error}; + +pub(crate) type Result = std::result::Result; + +#[derive(Debug, Clone)] +pub(crate) struct ApiContext { + db: PgPool, + agent_manager: Arc, +} + +pub async fn serve(db: PgPool, agent_manager: Arc) -> color_eyre::Result<()> { + let api_context = ApiContext { db, agent_manager }; + + let app = api_router(api_context); + + //TODO: make port configuratable + let addr = SocketAddr::from((Ipv4Addr::UNSPECIFIED, 8080)); + tracing::info!("running on {addr}"); + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .wrap_err("error running HTTP server") +} + +fn api_router(api_context: ApiContext) -> Router<()> { + Router::new() + .merge(flakes::router()) + .merge(agent::router()) + // Enable logging. Use `RUST_LOG=tower_http=debug` + .layer(TraceLayer::new_for_http()) + .with_state(api_context) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..ff3e8f6 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,3 @@ +pub mod agent; +pub mod http; +pub mod nix; diff --git a/src/nix.rs b/src/nix.rs new file mode 100644 index 0000000..db30562 --- /dev/null +++ b/src/nix.rs @@ -0,0 +1,195 @@ +use chrono::{DateTime, Utc}; +use color_eyre::{eyre::eyre, Help, Report, Result, SectionExt}; +use serde::{de::DeserializeOwned, Deserialize}; +use serde_json::Value; +use sqlx::PgPool; +use tokio::process::Command; +use tracing::instrument; + +#[derive(Debug, Clone, Deserialize)] +pub struct FlakeMetadata { + pub revision: String, + #[serde(rename = "lastModified", with = "chrono::serde::ts_seconds")] + pub last_modified: DateTime, + pub url: String, +} + +/// Query flake metadata with `nix flake metadata` +#[instrument(err)] +pub async fn flake_metadata(flake_url: &str) -> Result<(FlakeMetadata, Value)> { + let mut cmd = Command::new("nix"); + cmd.args(["flake", "metadata", "--json", flake_url]); + + let meta: serde_json::Value = json_output(cmd).await?; + let metadata: FlakeMetadata = serde_json::from_value(meta.clone())?; + + Ok((metadata, meta)) +} + +#[instrument(skip_all)] +pub(crate) async fn update_flakes(db: &PgPool) -> Result<()> { + let flakes = sqlx::query!( + r#" + WITH last_rev AS ( + SELECT flake_id, MAX(flake_revision_id) AS flake_revision_id + FROM flake_revisions + GROUP BY flake_id + ) + SELECT flakes.flake_id, flake_url, revision, last_modified + FROM flakes + JOIN last_rev USING (flake_id) + JOIN flake_revisions USING (flake_revision_id) + "# + ) + .fetch_all(db) + .await?; + + for flake in flakes { + tracing::info!("updating {}", flake.flake_url); + let (metadata, meta) = flake_metadata(&flake.flake_url).await?; + if metadata.revision == flake.revision { + continue; + } + let flake_revision_id = sqlx::query_scalar!( + r#" + INSERT INTO flake_revisions (flake_id, revision, last_modified, url, metadata) + VALUES ($1, $2, $3, $4, $5) + RETURNING flake_revision_id + "#, + flake.flake_id, + metadata.revision, + metadata.last_modified, + metadata.url, + meta + ) + .fetch_one(db) + .await?; + + process_configurations(db.clone(), flake_revision_id).await?; + } + Ok(()) +} + +#[instrument(skip(db))] +pub(crate) async fn process_configurations(db: PgPool, flake_revision_id: i64) -> Result<()> { + tracing::info!("foo"); + let revision = sqlx::query!( + "SELECT flake_id, url FROM flake_revisions WHERE flake_revision_id = $1", + flake_revision_id + ) + .fetch_one(&db) + .await?; + + let configs = list_configurations(&revision.url).await?; + for config in configs { + let config_id = upsert_nixos_configuration(&db, revision.flake_id, &config).await?; + + let store_path = config_store_path(&revision.url, &config).await?; + insert_nixos_configutaion_evaluation(&db, flake_revision_id, config_id, &store_path) + .await?; + } + Ok(()) +} + +#[instrument(skip(db))] +async fn insert_nixos_configutaion_evaluation( + db: &PgPool, + flake_revision_id: i64, + config_id: i64, + store_path: &str, +) -> Result<()> { + sqlx::query!( + r#" + INSERT INTO nixos_configuration_evaluations (flake_revision_id, nixos_configuration_id, store_path) + VALUES ($1, $2, $3) + "#, + flake_revision_id, config_id, store_path + ) + .execute(db) + .await?; + + Ok(()) +} + +#[instrument(skip(db))] +async fn upsert_nixos_configuration(db: &PgPool, flake_id: i64, name: &str) -> sqlx::Result { + if let Some(id) = sqlx::query_scalar!( + r#" + INSERT INTO nixos_configurations (flake_id, name) + VALUES ($1, $2) + ON CONFLICT DO NOTHING + RETURNING nixos_configuration_id + "#, + flake_id, + name + ) + .fetch_optional(db) + .await? + { + Ok(id) + } else { + sqlx::query_scalar!( + r#" + SELECT nixos_configuration_id FROM nixos_configurations + WHERE flake_id = $1 AND name = $2 + "#, + flake_id, + name + ) + .fetch_one(db) + .await + } +} + +/// returns names of all nixosConfigurations +#[instrument] +pub(crate) async fn list_configurations(flake_url: &str) -> Result> { + let mut cmd = Command::new("nix"); + cmd.args([ + "eval", + "--json", + format!("{}#nixosConfigurations", flake_url).as_str(), + "--apply", + "builtins.attrNames", + ]); + + json_output(cmd).await +} + +#[instrument] +pub async fn config_store_path(flake_url: &str, name: &str) -> Result { + tracing::info!("evaluating configuration"); + let mut cmd = Command::new("nix"); + cmd.args([ + "path-info", + "--json", + format!("{flake_url}#nixosConfigurations.{name}.config.system.build.toplevel").as_str(), + ]); + + #[derive(Deserialize)] + struct PathInfo { + path: String, + } + let mut res: Vec = json_output(cmd).await?; + tracing::info!("done"); + Ok(res.pop().unwrap().path) +} + +/// Executes `cmd` and parse stdout as json +#[instrument] +async fn json_output(mut cmd: Command) -> Result { + let output = cmd.output().await?; + + let stdout = String::from_utf8_lossy(&output.stdout); + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(eyre!("cmd exited with non-zero status code") + .with_section(move || stdout.trim().to_string().header("Stdout:")) + .with_section(move || stderr.trim().to_string().header("Stderr:"))); + } + + serde_json::from_str(&stdout).map_err(|e| { + Report::new(e).with_section(move || stdout.trim().to_string().header("Stdout:")) + }) +}