From f02fe27a52154b3f234cd295f76669955027d236 Mon Sep 17 00:00:00 2001 From: Miguel Piedrafita Date: Sun, 21 Jul 2024 23:56:34 -0400 Subject: [PATCH] basic deployments work --- .dockerignore | 2 + .env.example | 3 + .gitignore | 4 +- Cargo.lock | 652 +++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 12 +- orbit.example.toml | 6 + src/config.rs | 52 ++++ src/main.rs | 9 +- src/misc.rs | 81 ++++++ src/routes/mod.rs | 2 + src/routes/sites.rs | 62 +++++ src/server.rs | 6 +- src/site.rs | 191 +++++++++++++ 13 files changed, 1073 insertions(+), 9 deletions(-) create mode 100644 .env.example create mode 100644 orbit.example.toml create mode 100644 src/config.rs create mode 100644 src/misc.rs create mode 100644 src/routes/sites.rs create mode 100644 src/site.rs diff --git a/.dockerignore b/.dockerignore index f8802e6..280179b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,7 @@ +tmp .env target .github README.md Dockerfile +orbit.toml diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..eb9456b --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +RUST_LOG="orbit=debug" +ORBIT_CONFIG="./orbit.toml" +GITHUB_TOKEN="" diff --git a/.gitignore b/.gitignore index fedaa2b..5f62dcc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ -/target +tmp .env +/target +orbit.toml diff --git a/Cargo.lock b/Cargo.lock index 6915211..5a496a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,6 +82,16 @@ version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +[[package]] +name = "async-fn-stream" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e71711442f1016c768c259bec59300a10efe753bc3e686ec19e2c6a54a97c29b" +dependencies = [ + "futures-util", + "pin-project-lite", +] + [[package]] name = "async-trait" version = "0.1.81" @@ -93,6 +103,12 @@ dependencies = [ "syn", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.3.0" @@ -195,6 +211,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bit-set" version = "0.5.3" @@ -210,6 +232,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.6.0" @@ -260,12 +288,31 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + [[package]] name = "deranged" version = "0.3.11" @@ -275,6 +322,12 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "deunicode" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00" + [[package]] name = "dotenvy" version = "0.15.7" @@ -293,12 +346,31 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "fancy-regex" version = "0.11.0" @@ -309,12 +381,55 @@ dependencies = [ "regex", ] +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "filetime" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.4.1", + "windows-sys 0.52.0", +] + +[[package]] +name = "flate2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -442,6 +557,25 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +[[package]] +name = "h2" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -509,6 +643,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", + "h2", "http", "http-body", "httparse", @@ -517,6 +652,40 @@ dependencies = [ "pin-project-lite", "smallvec", "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", ] [[package]] @@ -526,12 +695,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" dependencies = [ "bytes", + "futures-channel", "futures-util", "http", "http-body", "hyper", "pin-project-lite", + "socket2", "tokio", + "tower", + "tower-service", + "tracing", ] [[package]] @@ -578,6 +752,12 @@ dependencies = [ "serde", ] +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + [[package]] name = "iso8601" version = "0.6.1" @@ -619,7 +799,7 @@ checksum = "2a071f4f7efc9a9118dfb627a0a94ef247986e1ab8606a4c806ae2b3aa3b6978" dependencies = [ "ahash", "anyhow", - "base64", + "base64 0.21.7", "bytecount", "fancy-regex", "fraction", @@ -651,6 +831,12 @@ version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "lock_api" version = "0.4.12" @@ -720,6 +906,23 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nom" version = "7.1.3" @@ -850,23 +1053,77 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "openssl" +version = "0.10.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2823eb4c6453ed64055057ea8bd416eda38c71018723869dd043a3b1186115e" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "orbit" version = "0.1.0" dependencies = [ "aide", "anyhow", + "async-fn-stream", "axum", "axum-jsonschema", "chrono", "dotenvy", + "flate2", + "futures-util", + "http", + "indexmap", + "reqwest", "schemars", "serde", "serde_json", + "slug", + "tar", "thiserror", "tokio", + "toml", "tracing", "tracing-subscriber", + "uuid", ] [[package]] @@ -893,7 +1150,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.3", "smallvec", "windows-targets 0.52.6", ] @@ -936,6 +1193,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + [[package]] name = "powerfmt" version = "0.2.0" @@ -960,13 +1223,22 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ - "bitflags", + "bitflags 2.6.0", ] [[package]] @@ -1013,12 +1285,123 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +[[package]] +name = "reqwest" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.23.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4828ea528154ae444e5a642dbb7d5623354030dc9822b83fd9bb79683c7399d0" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + +[[package]] +name = "rustls-webpki" +version = "0.102.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.17" @@ -1031,6 +1414,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "schemars" version = "0.8.21" @@ -1062,6 +1454,29 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.204" @@ -1127,6 +1542,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "serde_spanned" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1166,6 +1590,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slug" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bd94acec9c8da640005f8e135a39fc0372e74535e6b368b7a04b875f784c8c4" +dependencies = [ + "deunicode", + "wasm-bindgen", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -1182,6 +1616,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.71" @@ -1205,6 +1651,50 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tar" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb797dad5fb5b76fcf519e702f4a589483b5ef06567f160c392832c1f5e44909" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "thiserror" version = "1.0.63" @@ -1310,6 +1800,74 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac2caab0bf757388c6c0ae23b3293fdb463fee59434529014f85e3263b995c28" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "278f3d518e152219c994ce877758516bca5e118eaed6996192a774fb9fbf0788" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower" version = "0.4.13" @@ -1400,6 +1958,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "unicode-bidi" version = "0.3.15" @@ -1421,6 +1985,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.2" @@ -1437,6 +2007,9 @@ name = "uuid" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +dependencies = [ + "getrandom", +] [[package]] name = "valuable" @@ -1444,12 +2017,27 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1481,6 +2069,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.92" @@ -1510,6 +2110,16 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1680,6 +2290,36 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374ec40a2d767a3c1b4972d9475ecd557356637be906f2cb3f7fe17a6eb5e22f" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "xattr" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +dependencies = [ + "libc", + "linux-raw-sys", + "rustix", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -1699,3 +2339,9 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index a2ce614..45ea09b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,13 +7,23 @@ authors = ["Miguel Piedrafita "] [dependencies] axum = "0.7.5" +http = "1.1.0" +tar = "0.4.41" +slug = "0.1.5" +toml = "0.8.15" anyhow = "1.0.71" serde = "1.0.165" +flate2 = "1.0.30" +indexmap = "2.2.6" dotenvy = "0.15.7" tracing = "0.1.37" +reqwest = "0.12.5" schemars = "0.8.12" -thiserror = "1.0.40" +thiserror = "1.0.63" serde_json = "1.0.99" +futures-util = "0.3.30" +async-fn-stream = "0.2.2" +uuid = { version = "1.10.0", features = ["v7"] } tokio = { version = "1.29.1", features = ["full"] } aide = { version = "0.13.4", features = ["axum", "scalar"] } axum-jsonschema = { version = "0.8.0", features = ["aide"] } diff --git a/orbit.example.toml b/orbit.example.toml new file mode 100644 index 0000000..6bf338e --- /dev/null +++ b/orbit.example.toml @@ -0,0 +1,6 @@ +version = 1 + +[[sites]] +name = "Test Site" +path = "/var/www/test-site" +github_repo = "m1guelpf/laravel-test" diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..e052791 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,52 @@ +use anyhow::Result; +use axum::Extension; +use serde::{Deserialize, Serialize}; +use slug::slugify; +use std::{path::Path, sync::Arc}; + +use crate::site::Site; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Config { + version: usize, + pub sites: Vec, +} + +impl Config { + /// Load the config from a TOML file at the given path. + pub fn load>(path: P) -> Result { + if !path.as_ref().exists() { + return Err(anyhow::anyhow!( + "Could not locate Orbit config file at path {}", + path.as_ref().display() + )); + } + + let file = std::fs::read_to_string(path)?; + let config: Self = toml::from_str(&file)?; + + if config.version != 1 { + return Err(anyhow::anyhow!("Unsupported version: {}", config.version)); + } + + Ok(config) + } + + pub fn extension(self) -> Extension { + Extension(Arc::new(self.sites)) + } +} + +pub type Sites = Arc>; + +pub trait SiteCollectionExt { + fn find(&self, slug: &str) -> Option; +} + +impl SiteCollectionExt for Vec { + fn find(&self, slug: &str) -> Option { + self.iter() + .find(|site| slugify(&site.name) == slug) + .cloned() + } +} diff --git a/src/main.rs b/src/main.rs index 6571f92..4d0ebd6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,19 @@ #![warn(clippy::all, clippy::pedantic, clippy::nursery)] +use std::env; + use anyhow::Result; +use config::Config; use dotenvy::dotenv; use tracing_subscriber::{ prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer, }; +mod config; +mod misc; mod routes; mod server; +mod site; #[tokio::main] async fn main() -> Result<()> { @@ -21,5 +27,6 @@ async fn main() -> Result<()> { ) .init(); - server::start().await + let config = Config::load(env::var("ORBIT_CONFIG")?)?; + server::start(config).await } diff --git a/src/misc.rs b/src/misc.rs new file mode 100644 index 0000000..edcbe53 --- /dev/null +++ b/src/misc.rs @@ -0,0 +1,81 @@ +use aide::{ + openapi::{MediaType, Operation, Response, SchemaObject}, + r#gen::GenContext, +}; +use axum::response::{ + sse::{Event, KeepAlive}, + IntoResponse, +}; +use futures_util::Stream; +use indexmap::IndexMap; +use schemars::JsonSchema; + +#[derive(Debug)] +#[repr(transparent)] +pub struct Sse(axum::response::Sse); + +impl Sse { + /// Create a new [`Sse`] response that will respond with the given stream of + /// [`Event`]s. + /// + /// See the [module docs](self) for more details. + pub fn new(stream: S) -> Self + where + S: futures_util::TryStream + Send + 'static, + S::Error: Into, + { + Self(axum::response::sse::Sse::new(stream)) + } + + /// Configure the interval between keep-alive messages. + /// + /// Defaults to no keep-alive messages. + pub fn keep_alive(mut self, keep_alive: KeepAlive) -> Self { + self.0 = self.0.keep_alive(keep_alive); + + self + } +} + +impl IntoResponse for Sse +where + S: Stream> + Send + 'static, + E: Into, +{ + fn into_response(self) -> axum::response::Response { + self.0.into_response() + } +} + +impl aide::OperationOutput for Sse +where + S: Stream> + Send + 'static, + E: Into, +{ + type Inner = String; + + fn operation_response(ctx: &mut GenContext, _operation: &mut Operation) -> Option { + Some(Response { + description: "An SSE event stream".into(), + content: IndexMap::from_iter([( + "text/event-stream".into(), + MediaType { + schema: Some(SchemaObject { + json_schema: String::json_schema(&mut ctx.schema), + example: None, + external_docs: None, + }), + ..Default::default() + }, + )]), + ..Default::default() + }) + } + + fn inferred_responses( + ctx: &mut aide::gen::GenContext, + operation: &mut Operation, + ) -> Vec<(Option, Response)> { + Self::operation_response(ctx, operation).map_or_else(Vec::new, |res| vec![(Some(200), res)]) + } +} diff --git a/src/routes/mod.rs b/src/routes/mod.rs index a25281f..8c96978 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,10 +1,12 @@ use aide::axum::ApiRouter; mod docs; +mod sites; mod system; pub fn handler() -> ApiRouter { ApiRouter::new() .merge(docs::handler()) + .merge(sites::handler()) .merge(system::handler()) } diff --git a/src/routes/sites.rs b/src/routes/sites.rs new file mode 100644 index 0000000..9fb5d6e --- /dev/null +++ b/src/routes/sites.rs @@ -0,0 +1,62 @@ +use crate::{ + config::SiteCollectionExt, + site::{DeploymentError, DeploymentStage}, +}; +use aide::axum::{routing::post, ApiRouter}; + +use axum::{ + extract::{Path, Query}, + http::StatusCode, + response::sse::{Event, KeepAlive}, + Extension, +}; +use futures_util::{stream::Stream, StreamExt}; +use schemars::JsonSchema; +use serde::Deserialize; + +use crate::{config::Sites, misc::Sse}; + +pub fn handler() -> ApiRouter { + ApiRouter::new().api_route("/sites/:site/deploy", post(deploy_site)) +} + +#[derive(Debug, Deserialize, JsonSchema)] +pub struct DeployConfig { + /// The Git reference to deploy. If not provided, the default branch will be used. + r#ref: Option, +} + +pub async fn deploy_site( + Path(site_id): Path, + Query(config): Query, + Extension(sites): Extension, +) -> Result>>, StatusCode> { + let Some(site) = sites.find(&site_id) else { + return Err(StatusCode::NOT_FOUND); + }; + + let stream = site + .deploy(config.r#ref) + .expect("Missing GitHub token") + .stream() + .map(|result| { + let stage = match result { + Ok(stage) => stage, + Err(e) => { + tracing::error!(e = ?e); + return Event::default().id("error").data(e.to_string()); + }, + }; + + let message = match stage { + DeploymentStage::Deployed => "Deployed site.".to_string(), + DeploymentStage::Starting => "Starting deployment...".to_string(), + DeploymentStage::Downloaded => "Downloaded repository.".to_string(), + }; + + Event::default().id("stage").data(message) + }) + .map(Ok); + + Ok(Sse::new(stream).keep_alive(KeepAlive::default())) +} diff --git a/src/server.rs b/src/server.rs index 93cdab2..41a73c1 100644 --- a/src/server.rs +++ b/src/server.rs @@ -4,10 +4,10 @@ use axum::Extension; use std::{env, net::SocketAddr}; use tokio::{net::TcpListener, signal}; -use crate::routes; +use crate::{config::Config, routes}; #[allow(clippy::redundant_pub_crate)] -pub(crate) async fn start() -> Result<()> { +pub(crate) async fn start(config: Config) -> Result<()> { let mut openapi = OpenApi { info: openapi::Info { title: "Orbit".to_string(), @@ -19,7 +19,7 @@ pub(crate) async fn start() -> Result<()> { let router = routes::handler().finish_api(&mut openapi); - let router = router.layer(Extension(openapi)); + let router = router.layer(config.extension()).layer(Extension(openapi)); let addr = SocketAddr::from(( [0, 0, 0, 0], diff --git a/src/site.rs b/src/site.rs new file mode 100644 index 0000000..1b02cca --- /dev/null +++ b/src/site.rs @@ -0,0 +1,191 @@ +use async_fn_stream::try_fn_stream; +use axum::http::StatusCode; + +use flate2::read::GzDecoder; +use futures_util::Stream; +use http::header; +use serde::{Deserialize, Serialize}; +use std::{ + env::{self, VarError}, + fs, + path::PathBuf, +}; +use uuid::Uuid; + +#[allow(clippy::unsafe_derive_deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Site { + pub name: String, + pub path: PathBuf, + pub github_repo: String, +} + +impl Site { + pub fn deploy(self, r#ref: Option) -> Result { + Deployer::from_site(self, r#ref) + } +} + +#[derive(Debug)] +pub enum DeploymentStage { + Starting, + Downloaded, + Deployed, +} + +#[derive(Debug, thiserror::Error)] +pub enum DeploymentError { + #[error("Failed to clone the repository.")] + Download(#[from] reqwest::Error), + + #[error("Failed to extract the repository contents.")] + Extraction(#[from] std::io::Error), + + #[error("Failed to cleanup old deployments.")] + Cleanup(std::io::Error), + + #[error("Failed to publish the new deployment.")] + Publish(std::io::Error), +} + +pub struct Deployer { + site: Site, + deployment_id: Uuid, + github_token: String, + r#ref: Option, + client: reqwest::Client, +} + +impl Deployer { + fn from_site(site: Site, r#ref: Option) -> Result { + env::var("GITHUB_TOKEN").map(|github_token| Self { + site, + r#ref, + github_token, + deployment_id: Uuid::now_v7(), + client: reqwest::Client::builder() + .user_agent("orbit-deployer") + .build() + .unwrap(), + }) + } + + pub fn stream( + self, + ) -> impl Stream> { + try_fn_stream(|stream| async move { + stream.emit(DeploymentStage::Starting).await; + + self.download_repo().await?; + + stream.emit(DeploymentStage::Downloaded).await; + + // install deps, cache, etc. here + + self.set_live()?; + self.clear_old_deployments()?; + + stream.emit(DeploymentStage::Deployed).await; + + Ok(()) + }) + } + + fn clear_old_deployments(&self) -> Result<(), DeploymentError> { + let deployments = + fs::read_dir(self.site.path.join("deployments")).map_err(DeploymentError::Cleanup)?; + + let mut deployments: Vec<_> = deployments + .map(|entry| { + entry + .and_then(|e| { + let metadata = e.metadata()?; + Ok((e.path(), metadata.modified()?)) + }) + .map_err(DeploymentError::Cleanup) + }) + .collect::>()?; + + deployments.sort_by_key(|(_, modified)| *modified); + deployments + .into_iter() + .filter(|(path, _)| path.is_dir() && path != &self.get_path()) + .rev() + .skip(2) + .try_for_each(|(path, _)| fs::remove_dir_all(path)) + .map_err(DeploymentError::Cleanup)?; + + Ok(()) + } + + async fn download_repo(&self) -> Result<(), DeploymentError> { + let (owner, repo) = self.site.github_repo.split_once('/').unwrap(); + + let tarball = self + .client + .get(format!( + "https://api.github.com/repos/{owner}/{repo}/tarball/{}", + self.r#ref + .as_ref() + .map_or(String::new(), |r#ref| format!("/{ref}")) + )) + .header( + header::AUTHORIZATION, + format!("Bearer {}", self.github_token), + ) + .send() + .await? + .bytes() + .await?; + + let path = &self.get_path(); + let mut tar = tar::Archive::new(GzDecoder::new(tarball.as_ref())); + + for entry in tar.entries()? { + let mut file = entry?; + let file_path = file.path()?.into_owned(); + let file_path = file_path + .strip_prefix(file_path.components().next().unwrap()) + .unwrap() + .to_owned(); + + if file_path.to_str() == Some("") { + continue; + } + + if !file.header().entry_type().is_dir() { + fs::create_dir_all(path.join(&file_path).parent().unwrap())?; + file.unpack(path.join(file_path))?; + } + } + + Ok(()) + } + + fn set_live(&self) -> Result<(), DeploymentError> { + let current_deployment = self.site.path.join("current"); + if current_deployment.exists() { + fs::remove_file(¤t_deployment).map_err(DeploymentError::Publish)?; + } + + std::os::unix::fs::symlink( + format!("deployments/{}", self.deployment_id), + current_deployment, + ) + .map_err(DeploymentError::Publish)?; + + Ok(()) + } + + fn get_path(&self) -> PathBuf { + self.site + .path + .join(format!("deployments/{}", self.deployment_id)) + } +} + +impl axum::response::IntoResponse for DeploymentError { + fn into_response(self) -> axum::response::Response { + (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()).into_response() + } +}