diff --git a/.forgejo/workflows/docker.yaml b/.forgejo/workflows/docker.yaml index dfbdb95..a8c493d 100644 --- a/.forgejo/workflows/docker.yaml +++ b/.forgejo/workflows/docker.yaml @@ -4,6 +4,7 @@ on: push: branches: ["main"] paths: + - "Cargo.toml" - ".forgejo/workflows/docker.yaml" - "nix/image/**" @@ -16,10 +17,9 @@ jobs: strategy: matrix: - php-version: ["8.2", "8.3", "8.4"] - variant: [""] + php-version: ["8.0", "8.1", "8.2", "8.3", "8.4", "8.5"] - name: haze-${{ matrix.php-version }}${{ matrix.variant }} + name: haze-${{ matrix.php-version }} steps: - name: Checkout repository diff --git a/Cargo.lock b/Cargo.lock index 4cfe4f8..ccedb17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,18 +18,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] -name = "android-tzdata" -version = "0.1.1" +name = "aes" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ - "libc", + "cfg-if", + "cipher", + "cpufeatures", ] [[package]] @@ -90,13 +86,13 @@ checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" [[package]] name = "async-trait" -version = "0.1.87" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn", ] [[package]] @@ -123,12 +119,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] -name = "axum" -version = "0.8.1" +name = "aws-lc-rs" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8" +checksum = "d9a7b350e3bb1767102698302bc37256cbd48422809984b98d292c40e2579aa9" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.37.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "axum" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ "axum-core", + "axum-macros", "bytes", "form_urlencoded", "futures-util", @@ -143,8 +162,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rustversion", - "serde", + "serde_core", "serde_json", "serde_path_to_error", "serde_urlencoded", @@ -158,24 +176,34 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.0" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", - "futures-util", + "futures-core", "http", "http-body", "http-body-util", "mime", "pin-project-lite", - "rustversion", "sync_wrapper", "tower-layer", "tower-service", "tracing", ] +[[package]] +name = "axum-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "backtrace" version = "0.3.74" @@ -188,7 +216,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -200,6 +228,12 @@ dependencies = [ "backtrace", ] +[[package]] +name = "base16ct" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" + [[package]] name = "base64" version = "0.22.1" @@ -213,10 +247,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] -name = "bollard" -version = "0.18.1" +name = "block-buffer" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ccca1260af6a459d75994ad5acc1651bcabcbdbc41467cc9786519ab854c30" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "bollard" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "227aa051deec8d16bd9c34605e7aaf153f240e35483dd42f6f78903847934738" dependencies = [ "base64", "bollard-stubs", @@ -235,7 +287,6 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "serde_repr", "serde_urlencoded", "thiserror 2.0.12", "tokio", @@ -247,13 +298,13 @@ dependencies = [ [[package]] name = "bollard-stubs" -version = "1.47.1-rc.27.3.1" +version = "1.52.1-rc.29.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f179cfbddb6e77a5472703d4b30436bff32929c0aa8a9008ecf23d1d3cdd0da" +checksum = "0f0a8ca8799131c1837d1282c3f81f31e76ceb0ce426e04a7fe1ccee3287c066" dependencies = [ "serde", + "serde_json", "serde_repr", - "serde_with", ] [[package]] @@ -269,9 +320,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byteorder" @@ -286,25 +337,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" [[package]] -name = "camino" -version = "1.1.9" +name = "bzip2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" dependencies = [ - "serde", + "libbz2-rs-sys", +] + +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +dependencies = [ + "serde_core", ] [[package]] name = "cc" -version = "1.2.16" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.0" @@ -312,16 +379,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "chrono" -version = "0.4.40" +name = "cfg_aliases" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "android-tzdata", - "iana-time-zone", - "num-traits", - "serde", - "windows-link", + "crypto-common 0.1.7", + "inout", ] [[package]] @@ -355,7 +425,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.99", + "syn", ] [[package]] @@ -364,12 +434,66 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "console" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width 0.2.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -377,33 +501,168 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] -name = "crc32fast" -version = "1.4.2" +name = "cpufeatures" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] [[package]] -name = "dbus" -version = "0.9.7" +name = "crossbeam-deque" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ - "libc", - "libdbus-sys", - "winapi", + "crossbeam-epoch", + "crossbeam-utils", ] [[package]] -name = "deranged" -version = "0.3.11" +name = "crossbeam-epoch" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "deflate64" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2" + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", - "serde", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "digest" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "285743a676ccb6b3e116bc14cc69319b957867930ae9c4822f8e0f54509d7243" +dependencies = [ + "block-buffer 0.12.0", + "const-oid", + "crypto-common 0.2.1", ] [[package]] @@ -435,15 +694,27 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "either" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "enum_dispatch" version = "0.3.13" @@ -453,7 +724,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.99", + "syn", ] [[package]] @@ -485,13 +756,20 @@ dependencies = [ ] [[package]] -name = "flate2" -version = "1.1.0" +name = "find-msvc-tools" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -500,6 +778,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -509,6 +793,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures-channel" version = "0.3.31" @@ -520,19 +810,19 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn", ] [[package]] @@ -543,24 +833,33 @@ checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-core", "futures-macro", "futures-task", "pin-project-lite", - "pin-utils", "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -568,8 +867,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasip3", + "wasm-bindgen", +] + +[[package]] +name = "getset" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -580,9 +922,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "git2" -version = "0.20.0" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fda788993cc341f69012feba8bf45c0ba4f3291fcc08e214b4d5a7332d88aff" +checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" dependencies = [ "bitflags", "libc", @@ -591,44 +933,29 @@ dependencies = [ "url", ] -[[package]] -name = "h2" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap 2.7.1", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "haze" -version = "2.1.5" +version = "2.2.2" dependencies = [ "async-trait", "atty", "axum", + "base16ct", "bollard", "camino", "directories-next", @@ -639,6 +966,7 @@ dependencies = [ "hyper", "hyper-reverse-proxy", "hyper-util", + "indicatif", "itertools", "local-ip-address", "maplit", @@ -646,17 +974,21 @@ dependencies = [ "opener", "owo-colors", "petname", + "rayon", "reqwest", "serde", "serde_json", + "sha2 0.11.0-rc.5", "shell-words", "strum", "tar", "termion", "tokio", + "tokio-stream", "toml", "tracing", "tracing-subscriber", + "zip", ] [[package]] @@ -686,6 +1018,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "http" version = "1.2.0" @@ -733,21 +1074,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] -name = "hyper" -version = "1.6.0" +name = "hybrid-array" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "e1b229d73f5803b562cc26e4da0396c8610a4ee209f4fac8fa4f8d709166dc45" dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", - "h2", + "futures-core", "http", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -771,8 +1122,9 @@ dependencies = [ [[package]] name = "hyper-reverse-proxy" version = "0.5.2-dev" -source = "git+https://github.com/chpio/hyper-reverse-proxy?rev=6934877eb74465204f605cc1c05ca5a9772db7c0#6934877eb74465204f605cc1c05ca5a9772db7c0" +source = "git+https://code.betamike.com/micropelago/hyper-reverse-proxy.git?rev=d5a6f799189360d9449ae47ab3cdde511f02cf39#d5a6f799189360d9449ae47ab3cdde511f02cf39" dependencies = [ + "http-body-util", "hyper", "hyper-util", "tokio", @@ -780,17 +1132,37 @@ dependencies = [ ] [[package]] -name = "hyper-util" -version = "0.1.10" +name = "hyper-rustls" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", "bytes", "futures-channel", "futures-util", "http", "http-body", "hyper", + "ipnet", + "libc", + "percent-encoding", "pin-project-lite", "socket2", "tokio", @@ -813,29 +1185,6 @@ dependencies = [ "tower-service", ] -[[package]] -name = "iana-time-zone" -version = "0.1.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - [[package]] name = "icu_collections" version = "1.5.0" @@ -951,9 +1300,21 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.0.3" @@ -977,24 +1338,36 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.3" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ - "autocfg", - "hashbrown 0.12.3", + "equivalent", + "hashbrown 0.16.1", "serde", + "serde_core", ] [[package]] -name = "indexmap" -version = "2.7.1" +name = "indicatif" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "25470f23803092da7d239834776d653104d551bc4d7eacaf31e6837854b8e9eb" dependencies = [ - "equivalent", - "hashbrown 0.15.2", - "serde", + "console", + "portable-atomic", + "unicode-width 0.2.0", + "unit-prefix", + "web-time", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", ] [[package]] @@ -1003,6 +1376,16 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is-terminal" version = "0.4.16" @@ -1041,6 +1424,28 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "jobserver" version = "0.1.32" @@ -1067,26 +1472,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] -name = "libc" -version = "0.2.170" +name = "leb128fmt" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] -name = "libdbus-sys" -version = "0.2.5" +name = "libbz2-rs-sys" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72" -dependencies = [ - "cc", - "pkg-config", -] +checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" + +[[package]] +name = "libc" +version = "0.2.182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "libgit2-sys" -version = "0.18.0+1.9.0" +version = "0.18.3+1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1a117465e7e1597e8febea8bb0c410f1c7fb93b1e1cddf34363f8390367ffec" +checksum = "c9b3acc4b91781bb0b3386669d325163746af5f6e4f73e6d2d630e09a35f3487" dependencies = [ "cc", "libc", @@ -1131,21 +1538,44 @@ checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "local-ip-address" -version = "0.6.5" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "656b3b27f8893f7bbf9485148ff9a65f019e3f33bd5cdc87c83cab16b3fd9ec8" +checksum = "79ef8c257c92ade496781a32a581d43e3d512cf8ce714ecf04ea80f93ed0ff4a" dependencies = [ "libc", "neli", - "thiserror 2.0.12", - "windows-sys 0.59.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", ] [[package]] name = "log" -version = "0.4.26" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lzma-rust2" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47bb1e988e6fb779cf720ad431242d3f03167c1b3f2b1aae7f1a94b2495b36ae" +dependencies = [ + "sha2 0.10.9", +] [[package]] name = "maplit" @@ -1167,9 +1597,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "miette" -version = "7.5.0" +version = "7.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a955165f87b37fd1862df2a59547ac542c77ef6d17c666f619d1ad22dd89484" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" dependencies = [ "backtrace", "backtrace-ext", @@ -1181,19 +1611,18 @@ dependencies = [ "supports-unicode", "terminal_size", "textwrap", - "thiserror 1.0.69", "unicode-width 0.1.14", ] [[package]] name = "miette-derive" -version = "7.5.0" +version = "7.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf45bf44ab49be92fd1227a3be6fc6f617f1a337c06af54981048574d8783147" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn", ] [[package]] @@ -1209,6 +1638,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -1224,27 +1654,31 @@ dependencies = [ [[package]] name = "neli" -version = "0.6.5" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93062a0dce6da2517ea35f301dfc88184ce18d3601ec786a727a87bf535deca9" +checksum = "22f9786d56d972959e1408b6a93be6af13b9c1392036c5c1fafa08a1b0c6ee87" dependencies = [ + "bitflags", "byteorder", + "derive_builder", + "getset", "libc", "log", "neli-proc-macros", + "parking_lot", ] [[package]] name = "neli-proc-macros" -version = "0.1.4" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8034b7fbb6f9455b2a96c19e6edf8dc9fc34c70449938d8ee3b4df363f61fe" +checksum = "05d8d08c6e98f20a62417478ebf7be8e1425ec9acecc6f63e22da633f6b71609" dependencies = [ "either", "proc-macro2", "quote", "serde", - "syn 1.0.109", + "syn", ] [[package]] @@ -1258,28 +1692,18 @@ dependencies = [ [[package]] name = "nu-ansi-term" -version = "0.46.0" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "overload", - "winapi", + "windows-sys 0.61.2", ] [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "numtoa" @@ -1304,32 +1728,64 @@ checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "opener" -version = "0.7.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0812e5e4df08da354c851a3376fead46db31c2214f849d3de356d774d057681" +checksum = "a2fa337e0cf13357c13ef1dc108df1333eb192f75fc170bea03fcf1fd404c2ee" dependencies = [ "bstr", - "dbus", "normpath", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] -name = "overload" -version = "0.1.1" +name = "openssl-probe" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "owo-colors" -version = "4.2.2" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" +checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" dependencies = [ "supports-color 2.1.0", "supports-color 3.0.2", ] +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", + "hmac", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1347,7 +1803,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "rand", + "rand 0.8.5", ] [[package]] @@ -1368,12 +1824,24 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppmd-rust" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24" + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -1383,6 +1851,38 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.94" @@ -1392,6 +1892,62 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.12", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.12", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "1.0.39" @@ -1401,6 +1957,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.8.5" @@ -1408,8 +1970,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", ] [[package]] @@ -1419,7 +1991,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] @@ -1428,7 +2010,36 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", ] [[package]] @@ -1440,19 +2051,13 @@ dependencies = [ "bitflags", ] -[[package]] -name = "redox_termios" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" - [[package]] name = "redox_users" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom", + "getrandom 0.2.15", "libredox", "thiserror 1.0.69", ] @@ -1465,38 +2070,51 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" [[package]] name = "reqwest" -version = "0.12.12" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" dependencies = [ "base64", "bytes", "futures-core", - "futures-util", "http", "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-util", - "ipnet", "js-sys", "log", - "mime", - "once_cell", "percent-encoding", "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", "sync_wrapper", "tokio", + "tokio-rustls", "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "windows-registry", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.15", + "libc", + "untrusted", + "windows-sys 0.52.0", ] [[package]] @@ -1505,6 +2123,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustix" version = "0.38.44" @@ -1518,6 +2142,81 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.20" @@ -1531,35 +2230,99 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] -name = "serde" -version = "1.0.218" +name = "same-file" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.218" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn", ] [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] @@ -1580,16 +2343,16 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn", ] [[package]] name = "serde_spanned" -version = "0.6.8" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -1605,20 +2368,36 @@ dependencies = [ ] [[package]] -name = "serde_with" -version = "3.12.0" +name = "sha1" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ - "base64", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.7.1", - "serde", - "serde_derive", - "serde_json", - "time", + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0-rc.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c5f3b1e2dc8aad28310d8410bd4d7e180eca65fca176c52ab00d364475d0024" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.11.1", ] [[package]] @@ -1632,9 +2411,9 @@ dependencies = [ [[package]] name = "shell-words" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" [[package]] name = "shlex" @@ -1651,6 +2430,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + [[package]] name = "slab" version = "0.4.9" @@ -1668,12 +2453,12 @@ checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" [[package]] name = "socket2" -version = "0.5.8" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -1690,25 +2475,31 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.99", + "syn", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "supports-color" version = "2.1.0" @@ -1742,20 +2533,9 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" [[package]] name = "syn" -version = "1.0.109" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -1779,7 +2559,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn", ] [[package]] @@ -1805,14 +2585,12 @@ dependencies = [ [[package]] name = "termion" -version = "4.0.4" +version = "4.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f359c854fbecc1ea65bc3683f1dcb2dce78b174a1ca7fda37acd1fff81df6ff" +checksum = "f44138a9ae08f0f502f24104d82517ef4da7330c35acd638f1f29d3cd5475ecb" dependencies = [ "libc", - "libredox", "numtoa", - "redox_termios", ] [[package]] @@ -1851,7 +2629,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn", ] [[package]] @@ -1862,7 +2640,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn", ] [[package]] @@ -1877,34 +2655,23 @@ dependencies = [ [[package]] name = "time" -version = "0.3.37" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", - "itoa", + "js-sys", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", - "time-macros", ] [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - -[[package]] -name = "time-macros" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" -dependencies = [ - "num-conv", - "time-core", -] +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "tinystr" @@ -1917,12 +2684,26 @@ dependencies = [ ] [[package]] -name = "tokio" -version = "1.43.0" +name = "tinyvec" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ - "backtrace", "bytes", "libc", "mio", @@ -1930,18 +2711,39 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", ] [[package]] @@ -1959,38 +2761,43 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.20" +version = "1.0.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +checksum = "c7614eaf19ad818347db24addfa201729cf2a9b6fdfd9eb0ab870fcacc606c0c" dependencies = [ - "serde", + "indexmap", + "serde_core", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_parser", + "toml_writer", + "winnow", ] [[package]] name = "toml_datetime" -version = "0.6.8" +version = "1.0.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" dependencies = [ - "serde", + "serde_core", ] [[package]] -name = "toml_edit" -version = "0.22.24" +name = "toml_parser" +version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ - "indexmap 2.7.1", - "serde", - "serde_spanned", - "toml_datetime", "winnow", ] +[[package]] +name = "toml_writer" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" + [[package]] name = "tower" version = "0.5.2" @@ -2007,6 +2814,24 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -2021,9 +2846,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -2033,20 +2858,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn", ] [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -2065,9 +2890,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.19" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" dependencies = [ "nu-ansi-term", "sharded-slab", @@ -2083,6 +2908,18 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -2107,6 +2944,24 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unit-prefix" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" + +[[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.4" @@ -2148,6 +3003,22 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -2163,6 +3034,24 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -2185,7 +3074,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.99", + "syn", "wasm-bindgen-shared", ] @@ -2220,7 +3109,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2234,6 +3123,40 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.2", + "indexmap", + "semver", +] + [[package]] name = "web-sys" version = "0.3.77" @@ -2244,6 +3167,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "winapi" version = "0.3.9" @@ -2260,55 +3202,34 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[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-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-link" -version = "0.1.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "windows-registry" -version = "0.2.0" +name = "windows-sys" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-result", - "windows-strings", - "windows-targets", -] - -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result", - "windows-targets", + "windows-targets 0.42.2", ] [[package]] @@ -2317,7 +3238,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -2326,7 +3247,40 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -2335,58 +3289,159 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -2394,12 +3449,103 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "winnow" -version = "0.7.3" +name = "windows_x86_64_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" dependencies = [ - "memchr", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", ] [[package]] @@ -2445,7 +3591,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn", "synstructure", ] @@ -2467,7 +3613,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn", ] [[package]] @@ -2487,10 +3633,30 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn", "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zerovec" version = "0.10.4" @@ -2510,5 +3676,84 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn", +] + +[[package]] +name = "zip" +version = "8.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e499faf5c6b97a0d086f4a8733de6d47aee2252b8127962439d8d4311a73f72" +dependencies = [ + "aes", + "bzip2", + "constant_time_eq", + "crc32fast", + "deflate64", + "flate2", + "getrandom 0.4.1", + "hmac", + "indexmap", + "lzma-rust2", + "memchr", + "pbkdf2", + "ppmd-rust", + "sha1", + "time", + "typed-path", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zlib-rs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c745c48e1007337ed136dc99df34128b9faa6ed542d80a1c673cf55a6d7236c8" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", ] diff --git a/Cargo.toml b/Cargo.toml index 9ac03ff..6da76a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "haze" -version = "2.1.5" +version = "2.2.2" authors = ["Robin Appelman "] edition = "2021" repository = "https://codeberg.org/icewind/haze" @@ -8,38 +8,44 @@ license = "MIT" description = "Easy setup and management of Nextcloud test instances using docker" [dependencies] -bollard = "0.18.1" +bollard = "0.20.1" maplit = "1.0.2" -camino = { version = "1.1.7", features = ["serde1"] } -tokio = { version = "1.38.0", features = ["fs", "macros", "signal", "rt-multi-thread"] } -futures-util = "0.3.30" -termion = "4.0.1" -opener = "0.7.1" -toml = "0.8.14" +camino = { version = "1.2.2", features = ["serde1"] } +tokio = { version = "1.49.0", features = ["fs", "macros", "signal", "rt-multi-thread"] } +tokio-stream = { version = "0.1.18", features = ["net"] } +futures-util = "0.3.32" +termion = "4.0.6" +opener = "0.8.4" +toml = "1.0.3" directories-next = "2.0.0" -serde = "1.0.203" -serde_json = "1.0.117" +serde = "1.0.228" +serde_json = "1.0.149" petname = "2.0.2" -reqwest = { version = "0.12.4", default-features = false } -tar = "0.4.41" -flate2 = "1.0.30" -async-trait = "0.1.80" +reqwest = { version = "0.13.2", default-features = false, features = ["rustls"] } +tar = "0.4.44" +flate2 = "1.1.9" +async-trait = "0.1.89" enum_dispatch = "0.3.13" -miette = { version = "7.2.0", features = ["fancy"] } -shell-words = "1.1.0" -tracing = "0.1.40" -tracing-subscriber = "0.3.18" +miette = { version = "7.6.0", features = ["fancy"] } +shell-words = "1.1.1" +tracing = "0.1.44" +tracing-subscriber = "0.3.22" atty = "0.2.14" -git2 = { version = "0.20.0", default-features = false } +git2 = { version = "0.20.4", default-features = false } itertools = { version = "0.14.0", features = ["use_alloc"] } -local-ip-address = "0.6.5" -strum = { version = "0.27.2", features = ["derive"] } -owo-colors = { version = "4.2.2", features = ["supports-colors"] } +local-ip-address = "0.6.10" +strum = { version = "0.28.0", features = ["derive"] } +owo-colors = { version = "4.3.0", features = ["supports-colors"] } +zip = "8.1.0" +sha2 = "0.11.0-rc.5" +base16ct = { version = "1.0.0", features = ["alloc"] } +indicatif = "0.18.4" +rayon = "1.12.0" -hyper-reverse-proxy = { version = "0.5.2-dev", git = "https://github.com/chpio/hyper-reverse-proxy", rev = "6934877eb74465204f605cc1c05ca5a9772db7c0" } -hyper = "1.6.0" -hyper-util = "0.1.10" -axum = { version = "0.8.1", features = ["tokio"] } +hyper-reverse-proxy = { version = "0.5.2-dev", git = "https://code.betamike.com/micropelago/hyper-reverse-proxy.git", rev = "d5a6f799189360d9449ae47ab3cdde511f02cf39" } +hyper = "1.8.1" +hyper-util = "0.1.20" +axum = { version = "0.8.8", features = ["tokio", "macros"] } [profile.release] lto = true diff --git a/README.md b/README.md index 2a12c61..026c190 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Easy setup and management of Nextcloud test instances using docker ## What -`haze` provides an easy way to setup Nextcloud test instances with a choice of +`haze` provides an easy way to set up Nextcloud test instances with a choice of php version, database server, optional s3 or ldap setup and more. ## Setup @@ -56,47 +56,52 @@ See the [configuration section](#configuration) for more options. #### Start an instance ```bash -haze start [database] [php-version] [services] +haze start [database] [php-version] [services] [vX.Y.Z] ``` Where `database` is one of `sqlite`, `mysql`, `mariadb`, `pgsql` or `oracle` with an optional version (e.g. `pgsql:12`), defaults to `sqlite`. And -`php-version` is one of `8.2`, `8.3` or `8.4`, defaults to the maximum version -support by the current Nextcloud version. `7.3` till `8.1` are still supported -but the docker images for those versions aren't being updated anymore so they -might be missing some newer features. +`php-version` is one of `8.0`, `8.1`, `8.2`, `8.3`, `8.4` or `8.5`, defaults to +the maximum version support by the current Nextcloud version. -Each php version also comes with a `-dbg` variant that has php compiled in debug -mode and can be used for debugging php itself with gdb. +You can specify a version number (e.g. `v32.0.2`) to use the sources from a +release instead of using the local sources. Additionally, you can use the following options when starting an instance: -- `s3`: setup an S3 server and configure to Nextcloud to use it as primary +- `s3`: set up an S3 server and configure to Nextcloud to use it as primary storage. -- ``: by specifying the path to an app package this package - will be extracted into the apps. directory of the new instance (overwriting - any existing app code). This can be used to quickly test a packaged app. -- `ldap`: setup an LDAP server. -- `office`: setup a Nextcloud Office server. + - `s3s`: enable TLS for the S3 setup. + - `s3mb`: enable multi-bucket S3 setup. + - `s3m`: enable multi-instance S3 setup. +- `ldap`: set up an LDAP server. +- `office`: set up a Nextcloud Office server. - `onlyoffice` setup an onlyoffice document server. -- `push` setup [client push](https://github.com/nextcloud/notify_push). -- `smb`: setup a samba server for external storage use. -- `dav`: setup a WebDAV server for external storage use. -- `sftp`: setup a SFTP server for external storage use. -- `kaspersky`: setup a kaspersky scan engine server in http mode. ( Requires +- `push` set up [client push](https://github.com/nextcloud/notify_push). +- `smb`: set up a samba server for external storage use. +- `dav`: set up a WebDAV server for external storage use. +- `sftp`: set up a SFTP server for external storage use. +- `sftp-key`: set up a SFTP server for external storage use with public key + authentication. +- `kaspersky`: set up a kaspersky scan engine server in http mode. ( Requires [manually setting up the image](https://github.com/icewind1991/kaspersky-docker)) - `kaspersky-icap`: setup a kaspersky scan engine server in ICAP mode. -- `clamav`: setup a local clam av scanner in executable mode. -- `clamav-socket`: setup a clam av scanner in socket mode. -- `clamav-icap`: setup a clam av scanner in ICAP mode. -- `clamav-icap-tls`: setup a clam av scanner in ICAP mode with TLS encryption. +- `clamav`: set up a local clam av scanner in executable mode. +- `clamav-socket`: set up a clam av scanner in socket mode. +- `clamav-icap`: set up a clam av scanner in ICAP mode. +- `clamav-icap-tls`: set up a clam av scanner in ICAP mode with TLS encryption. - `oc`: start an ownCloud instance in the same network. - `imaginary`: start an Imaginary service and configure it for preview generation. -- `mail`: start an [smtp4dev](https://github.com/rnwood/smtp4dev) server and +- `mail`: start a [smtp4dev](https://github.com/rnwood/smtp4dev) server and configure it the mail server. +- `webhook` start a + [webhook tester](https://github.com/tarampampam/webhook-tester) - `redis`: start a separate container for redis. - `redis-tls`: connect to redis over TLS. +- ``: by specifying the path to an app package this package + will be extracted into the apps. directory of the new instance (overwriting + any existing app code). This can be used to quickly test a packaged app. - The name of any configured preset. #### Run tests in a new instance @@ -105,7 +110,7 @@ Additionally, you can use the following options when starting an instance: haze test [database] [php-version] [path] ``` -Where `path` is a file or folder to run phpunit in, relative to the sources +Where `path` is a file or folder to run PHPUnit in, relative to the sources root. ### List running instances @@ -129,7 +134,7 @@ haze clean ## Controlling running instances The following commands run against the most recently started instance and allow -optionally providing a `match` to select a specific instance by it's name. +optionally providing a `match` to select a specific instance by its name. #### Open an instance @@ -206,7 +211,7 @@ haze [match] env [args] Runs the provided command with `NEXTCLOUD_URL`, `DATABASE_URL` and `REDIS_URL` environment variables set for the matched instance. -This is intented to run a local +This is intended to run a local [push daemon](https://github.com/nextcloud/notify_push) against an instance. #### Update the container images @@ -227,7 +232,7 @@ haze [match] edit haze [match] reload ``` -The php configuration can edit changed with `haze edit /php.ini` +The php configuration can edit changed with `haze edit /config/php.ini` #### Checkout a branch for all local apps @@ -254,29 +259,30 @@ Performs a pull in all git repositories within the apps folder. Multiple instances can reach each other by using their instance name as domain name to allow for testing federation between instances. Alternatively, you can -setup the haze proxy and the proxied domains to get https support between +set up the haze proxy and the proxied domains to get https support between instances. ## Proxy By default, instances can be accessed by their IP. In order to get more -memorable urls and allow supporting https, haze comes with a builtin reverse +memorable URLs and allow supporting https, haze comes with a builtin reverse proxy to allow using a wildcard domain. ### Requirements - A domain name you can set wildcard DNS records for -- A reverse proxy like nginx or apache +- A reverse proxy like nginx or Apache - (optionally) a wildcard ssl certificate (can be acquiring using letsencrypt and dns verification) ### Setup - Set a DNS record for `*.haze.exmaple.com` and `haze.example.com` pointing to - your development machine. (127.0.0.1 will not work) -- Set the `proxy` configuration with your domain and desired listen endpoint -- Setup a service to run `haze proxy` in the background as your own user. A - systemd user service is recommended. + your development machine. +- Set the `proxy` configuration with your domain and desired listen endpoint. +- Set up a service to run `haze proxy` in the background as your own user. A + systemd user service is recommended (see [haze.service](./haze.service) for an + example). - Configure your reverse proxy of choice to proxy `*.haze.example.com` and `haze.example.com` to the proxy's listen endpoint - (optional) acquire a wildcard ssl certificate for your domain and set your @@ -286,11 +292,17 @@ proxy to allow using a wildcard domain. ### Usage -When the proxy is configured, generated urls for the instances will use a +When the proxy is configured, generated URLs for the instances will use a subdomain of the configured domain, e.g. the `rolling-bees` instance will be available at `rolling-bees.haze.example.com`. Additionally, `haze.example.com` will automatically point to the last created instance. +Additionally, the proxy allows access to the server containers trough either +`-.haze.example.com` for a specific instance, or +`.haze.example.com` for the last created instance. For example +`rolling-bees-mail.haze.example.com` will give access to the smtp4dev web +interface of the `rolling-bees` instance. + ## Configuration Configuration is loaded from `~/.config/haze/haze.toml` and has the following @@ -298,6 +310,7 @@ options ```toml sources_root = "/path/to/sources" # path of the nextcloud sources. required +app_directories = ["/path/to/sources/more_app"] # paths to additional app directories. work_dir = "/path/to/temp/dir" # path to temporary directory. optional, defaults to "/tmp/haze" [auto_setup] # optional @@ -309,6 +322,7 @@ disable_apps = ["contacts"] # apps to disable after setup, defaults to [] post_setup = [# commands to execute after setup, defaults to [] "occ group:add test", ] +config = { "foo" = "bar" } # configuration options to set before install [[volume]] # optional source = "/tmp/haze-shared" @@ -322,7 +336,7 @@ read_only = true [proxy] # optional address = "haze.example.com" # base domain -https = true # Is the proxy behind an https terminating proxy +https = true # Is the proxy behind a https terminating proxy listen = "/run/haze/haze.sock" # either a unix socket path #listen = "127.0.0.1:8080" # or a socket address diff --git a/redis-certificates/ca.crt b/certificates/redis/ca.crt similarity index 100% rename from redis-certificates/ca.crt rename to certificates/redis/ca.crt diff --git a/redis-certificates/ca.key b/certificates/redis/ca.key similarity index 100% rename from redis-certificates/ca.key rename to certificates/redis/ca.key diff --git a/redis-certificates/ca.txt b/certificates/redis/ca.txt similarity index 100% rename from redis-certificates/ca.txt rename to certificates/redis/ca.txt diff --git a/redis-certificates/client.crt b/certificates/redis/client.crt similarity index 100% rename from redis-certificates/client.crt rename to certificates/redis/client.crt diff --git a/redis-certificates/client.key b/certificates/redis/client.key similarity index 100% rename from redis-certificates/client.key rename to certificates/redis/client.key diff --git a/redis-certificates/openssl.cnf b/certificates/redis/openssl.cnf similarity index 100% rename from redis-certificates/openssl.cnf rename to certificates/redis/openssl.cnf diff --git a/redis-certificates/redis.crt b/certificates/redis/redis.crt similarity index 100% rename from redis-certificates/redis.crt rename to certificates/redis/redis.crt diff --git a/redis-certificates/redis.dh b/certificates/redis/redis.dh similarity index 100% rename from redis-certificates/redis.dh rename to certificates/redis/redis.dh diff --git a/redis-certificates/redis.key b/certificates/redis/redis.key similarity index 100% rename from redis-certificates/redis.key rename to certificates/redis/redis.key diff --git a/redis-certificates/server.crt b/certificates/redis/server.crt similarity index 100% rename from redis-certificates/server.crt rename to certificates/redis/server.crt diff --git a/redis-certificates/server.key b/certificates/redis/server.key similarity index 100% rename from redis-certificates/server.key rename to certificates/redis/server.key diff --git a/certificates/s3/private.key b/certificates/s3/private.key new file mode 100644 index 0000000..939d93e --- /dev/null +++ b/certificates/s3/private.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCQuTk+irdXRGbY +JUu+AFmMM4/CCtBIBBgGIG18tesgQUeHEPdRHbBypvFKhpXvlKQbVbiaZxFjtlvn +L2ReN7gYwjiTjLWuaDgOzGQyObwSJpedlcd5Q957WNlc5OjpoK8zZq9EGnmvjIqe +5VVBne85RZVw6+i4ljEiWoXCiy0iOPIL4jKO8kfO8EmTTh6ge2sLCT5jFT/V63/B +hjEobA7+vPbAmEo+Qs4adnBSrlX9nLtL1j4gqawAQMmGl/Ti75T0uQvNxdq2gddc +n0wyWhyERnZNeMv5sfkwCOTuetNNtGLf/lTkXNdaOCRvW5jJ0EjYYdpQG5R0do1U +XFLH1cZZAgMBAAECggEAR0xpTk2Ku5yASlY9dXK4qyCv3znymLgjmckaB4mcN7zR +X1JVdYn55tImJ8AcV/bTzn+xvaevYn9x0XiAqwYqVVBCDTcSPsUrcObzKedVp1+J +7GHg7vYnwn7oPyKrOIYoKluZVyTv9DN6C4QSN4x2UbHdSM+ATIf51uHf6hMk/ilv +4uw3csxSCpLOqqsYCQarES7SypcETjFpNnIfTPt7q2Y2DbIDttTTjzrm0/GBP0WE +OYNvzZZPZRPJfy3et3r9vJbqWzGHvOttzQ+EFHjwPTMfW4tsHcCsEKSGWwLpG4bN +FPNx0+QCqDiChesdiCHFNSk+u7pRZrHdjuDJEuKSzQKBgQDMDgdCGBQfgaNnkRz/ +aiv3V200/vXegnc0Jz49Dye5AxEVu0X1m2xZpJv+qEwbOx5B+1PV3gfVP/iRf+FK +MAwFbmb7hGcDE8AGNsSpQydjwzKoi/M67YXv7T8dgWKnwz0eyU4K2IOGInGxuFty +Ik7+DTqz+Ikh1RiAoGbKfw9yhwKBgQC1kKhjxB7r/uSLcfOSg1mLcR5lTrNDQAPQ +GnsIje0nD5Tv52/k6U4tk15vjL4t6KZUFo9SJ4O1kM1veyuOJuol2AfPXS+H/Izo +5BjmoZ0jOONOZZiRIB1moQSy1qhTAeZB9S1ORxQ3dIBPqm+oyADPTTsNV67Cwnt4 +woeZRUPYHwKBgQDE0AcKJcVK+jQMUXfBlrsfTvDjO8MTwYyN/gfWxsZOeXnCFyYM +FcO01sMrJVJ6tVOi2nFrB0NQ2Om8FLbMYnlFx82GbJca7bK5i5u1kjLs0zoKPSn1 +vWEBIDhPEhuAqhxKlGk0ps580r4MZz+0XwkHmuTy7xX9TtbaQVvDljflAwKBgDKy +3hJdpTTIzBCUFSuIOezR/WbUfwH8UhQ+ELTmzJ3nn/MNcRU+gHIBgJEtf71aBXfd +hM+v8Ps2H+dNQXBENYWzuRqSLr+OKdquNrXP0w0OyYoOnHeJvCv4MlOt1Pq4wQ8R +40DEYETL5zhXoy5CCtfX/PFQ1p/Tpp6l0y9dRACJAoGALwUbyyDy85b2xRQB6RtU +I+5Vz5cd/1eQdCkoU9mX4qWA/hWpgc7Z2Jd67LW/WWtVjlF9hva/WNDSfGsXo2ew +C8OofvlfIuFDOCXrodYdHE1Q4g5TZdESr0XAqopq+QzBs89qbIy05kM9iuE4yFUo +xeimCY9oDWTeGw/XrLdHZF4= +-----END PRIVATE KEY----- diff --git a/certificates/s3/public.crt b/certificates/s3/public.crt new file mode 100644 index 0000000..f31f379 --- /dev/null +++ b/certificates/s3/public.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDXTCCAkWgAwIBAgIULiChaTwmVx6nRTHohmPuf55/4jUwDQYJKoZIhvcNAQEL +BQAwPjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxDTALBgNVBAoM +BEhhemUxCzAJBgNVBAMMAnMzMB4XDTI2MDMwOTE3NTgwOVoXDTI3MDMwOTE3NTgw +OVowPjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxDTALBgNVBAoM +BEhhemUxCzAJBgNVBAMMAnMzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAkLk5Poq3V0Rm2CVLvgBZjDOPwgrQSAQYBiBtfLXrIEFHhxD3UR2wcqbxSoaV +75SkG1W4mmcRY7Zb5y9kXje4GMI4k4y1rmg4DsxkMjm8EiaXnZXHeUPee1jZXOTo +6aCvM2avRBp5r4yKnuVVQZ3vOUWVcOvouJYxIlqFwostIjjyC+IyjvJHzvBJk04e +oHtrCwk+YxU/1et/wYYxKGwO/rz2wJhKPkLOGnZwUq5V/Zy7S9Y+IKmsAEDJhpf0 +4u+U9LkLzcXatoHXXJ9MMlochEZ2TXjL+bH5MAjk7nrTTbRi3/5U5FzXWjgkb1uY +ydBI2GHaUBuUdHaNVFxSx9XGWQIDAQABo1MwUTAdBgNVHQ4EFgQUJJ8HiT2zmuF5 +6WHHFsTHgkrayxYwHwYDVR0jBBgwFoAUJJ8HiT2zmuF56WHHFsTHgkrayxYwDwYD +VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAUF0lB/qIrxkgZ4sqNrw4 +CInHCK29XVaMoqk1QZyS/KhWDM+zgbA92OxxuhCKw4iJEajZvgg0S9RtGkBNmquU +l0rf0JdALd0jPkWr7+3OeqlcgOs2EH7PTqrrbXTGsR12D+Ot+OerQeWXmO28Zrl8 +4O67TwQtslXwZzeCrtiwAA2DrIYpSLzh+qDtwbY5hMG5zmqqjBM20Ysgxszh4rhl +KR6skXwZwkVVhKpK76qwnU02PIMr8auL1csx8/uBTd/UzX2veqlkOP5V/Gg6eEbI +4fTOzq7k+FyuzSkrEX4Vc9GbWcRvoVZh+qAKUKstqlE2iCrqmZ+Wal6GA8JA5SZ+ +bQ== +-----END CERTIFICATE----- diff --git a/certificates/sftp/id_rsa b/certificates/sftp/id_rsa new file mode 100644 index 0000000..c8dcc4d --- /dev/null +++ b/certificates/sftp/id_rsa @@ -0,0 +1,38 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn +NhAAAAAwEAAQAAAYEA323aWqH6YwRLbCBO94UKOkfnJ2m6Zsic0dMt3TmDnjLU0JzpOt7w +t5+mMZrEKQTpefozyUHo3z+HkmllLAGOupNy3A+jG2O955UUgw0dGfu6j6OOb66Du9jpqt +8BQ6gr3cEYASplPI7B889/cVpJ5l1HiBUgyR7Z16v15qCDtmpFVIECAdICEmPosfmZutt3 +YYl9xLay5WCmUztWS/amPcGs0DOEGrWeCtdxGKWT3TywdBKyQ0PbdYMamgDIT7JV1ZSzZP +aly4sB7E+dpS5AgBFVXmZ61151KN1TJ8gyoUjFhY7ctYEIpncZmyT4PYvyIvxRsbJtvERi +eNH8DoX5DwtqcxbgHK0OwYtdl4ydRXToYo3l+qIidf+g8ADVea/mbkfTPegdToo3LOuThX +OwExDlukpM8obFDpz1Yl1L6rRJAVNO1KmHWhn6to23jtYjBhczA2nkemQXQbVSjc/hItjQ +DIFNMOsLW33P+Y2k9LkpI0TL09ogOxOFZzGZp2tNAAAFgIgMIZ+IDCGfAAAAB3NzaC1yc2 +EAAAGBAN9t2lqh+mMES2wgTveFCjpH5ydpumbInNHTLd05g54y1NCc6Tre8LefpjGaxCkE +6Xn6M8lB6N8/h5JpZSwBjrqTctwPoxtjveeVFIMNHRn7uo+jjm+ug7vY6arfAUOoK93BGA +EqZTyOwfPPf3FaSeZdR4gVIMke2der9eagg7ZqRVSBAgHSAhJj6LH5mbrbd2GJfcS2suVg +plM7Vkv2pj3BrNAzhBq1ngrXcRilk908sHQSskND23WDGpoAyE+yVdWUs2T2pcuLAexPna +UuQIARVV5metdedSjdUyfIMqFIxYWO3LWBCKZ3GZsk+D2L8iL8UbGybbxEYnjR/A6F+Q8L +anMW4BytDsGLXZeMnUV06GKN5fqiInX/oPAA1Xmv5m5H0z3oHU6KNyzrk4VzsBMQ5bpKTP +KGxQ6c9WJdS+q0SQFTTtSph1oZ+raNt47WIwYXMwNp5HpkF0G1Uo3P4SLY0AyBTTDrC1t9 +z/mNpPS5KSNEy9PaIDsThWcxmadrTQAAAAMBAAEAAAGAWCkM/TEnztU9e3M+JX253OhNRe +h6lB75ffOxh7avgAc3oP8hKkkYu6PDnJQgbb0R8T7wGywmGp0DPhrXQGd27ZjLvBhxeBfB +sbTJ7LIKdxu0cAQN6nR2Z3M+NF2dLpiXgn80HRWg76W20yDffRcuzLamyIPptWI2e9rPAw +r4HczOAXuMErLOfXotsbg22BvL/dEWLr4WVdruli32LbArxXd73IVPTYi3TTjYV+zRrPzK +9WoBK/iFClfKcdT4NTY82llQesuUNu640lEJtT2G3Iba8UZnohyzm/S+UbeU65z8DKD5co +P7+QehxQSV+kj2BZnTi0WEwsD+GTznJYR5rvUsJCCAzoISsWrncSSgOQhF2XeW/T4ewvH+ +njLZViEhdG8R3kkdDjJG91OrSgrEqlk6Qhz1xEsv1rCOR69En7EJP3TNNrymPXPASrAnuE +HQkrVgGUfGqyD1sw1e6nBfNWisuw+g99CieIB8EI9WwpxQdKqWNU9Hjx+SAdC3NrPjAAAA +wQCo0hUGjSf6xhcgeaNa0gWSKEVuFhxR/FaCPTKadV7Md0APW4toeQZDujzDFlCZbQTZjC +0723B4lKugDzXfsOgvOTKp4vEjZOu0YGruS00LFWM7Sutdzx68b/ZMFALzITt/myKVMdpv +WpaO+3+PyEYIQH44QrSWw7cKLzNiZ8kt2drPkPktub4o2h5TdIBluEQLJDPMejy8IqQEU8 +aOyJOMvYxAbGAWY7Ck9DGlcJgaFdORROW8d8ZGrHQkyRl41JQAAADBAPeUMsrbI17wPP/s +Tsrkms5ws5yTz0xle2Wn6HwDSzQRSdn5abnIDYb3QSy0nRBvczef6ssH65dl50+2V4BV2L +MwHcmKD6/UoFsWwP/RMf1EoacPFiEAWJGxFbOthNX+rx5BpbUHNoQd8xby+88saDI0e8W6 +36HPBZrAZhQljkMa4OJqZDDCpOJvSndXwkZ789E96uprKopJZGwlLmfMtikQpNXT9R+I0b +SQCJj4yNakcdOE/7UifkOR02u+pgux3wAAAMEA5wdelKwGQ0EdkIF2TM844uLPszo3ZSH+ +Heff/Lbxs1Y+oL0NTJQicwMF0d9WEwBoTZJpuzsQEA1zkfmW0gi2womIRmiY0ZhpxbBuhO +6XePMIhUfQmWWjaUbAkrNB0eJkSTuUGzwxVkMXehrMuj4gYe8GMC8GgULbP0A8FjH01fKk +jFwgg4WAg6zUTpck12bh49NZRFyXIbXNk/jjxJtb0p//5TRTUQ6mR5IloaNTM23EiF6tle +Y6CAchnyhHO0BTAAAACWhhemVAaGF6ZQE= +-----END OPENSSH PRIVATE KEY----- diff --git a/certificates/sftp/id_rsa.pub b/certificates/sftp/id_rsa.pub new file mode 100644 index 0000000..81a5db6 --- /dev/null +++ b/certificates/sftp/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDfbdpaofpjBEtsIE73hQo6R+cnabpmyJzR0y3dOYOeMtTQnOk63vC3n6YxmsQpBOl5+jPJQejfP4eSaWUsAY66k3LcD6MbY73nlRSDDR0Z+7qPo45vroO72Omq3wFDqCvdwRgBKmU8jsHzz39xWknmXUeIFSDJHtnXq/XmoIO2akVUgQIB0gISY+ix+Zm623dhiX3EtrLlYKZTO1ZL9qY9wazQM4QatZ4K13EYpZPdPLB0ErJDQ9t1gxqaAMhPslXVlLNk9qXLiwHsT52lLkCAEVVeZnrXXnUo3VMnyDKhSMWFjty1gQimdxmbJPg9i/Ii/FGxsm28RGJ40fwOhfkPC2pzFuAcrQ7Bi12XjJ1FdOhijeX6oiJ1/6DwANV5r+ZuR9M96B1Oijcs65OFc7ATEOW6SkzyhsUOnPViXUvqtEkBU07UqYdaGfq2jbeO1iMGFzMDaeR6ZBdBtVKNz+Ei2NAMgU0w6wtbfc/5jaT0uSkjRMvT2iA7E4VnMZmna00= haze@haze diff --git a/flake.lock b/flake.lock index 81e86a7..a7766b8 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "crane": { "locked": { - "lastModified": 1760924934, - "narHash": "sha256-tuuqY5aU7cUkR71sO2TraVKK2boYrdW3gCSXUkF4i44=", + "lastModified": 1763938834, + "narHash": "sha256-j8iB0Yr4zAvQLueCZ5abxfk6fnG/SJ5JnGUziETjwfg=", "owner": "ipetkov", "repo": "crane", - "rev": "c6b4d5308293d0d04fcfeee92705017537cad02f", + "rev": "d9e753122e51cee64eb8d2dddfe11148f339f5a2", "type": "github" }, "original": { @@ -15,6 +15,22 @@ "type": "github" } }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1767039857, + "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", + "owner": "NixOS", + "repo": "flake-compat", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "flake-compat", + "type": "github" + } + }, "flakelight": { "inputs": { "nixpkgs": [ @@ -22,11 +38,11 @@ ] }, "locked": { - "lastModified": 1764593611, - "narHash": "sha256-6SdexcO69Dlu14YN2xuB1A6JHWSrcqMj7Na9oK7IT2M=", + "lastModified": 1773062095, + "narHash": "sha256-u+cK9IoJokO4YzQwMc2s8Vti0RL/LVSrROOEn2opc5U=", "owner": "nix-community", "repo": "flakelight", - "rev": "0d63256401341f528dd628f1a8e96d3afecade7a", + "rev": "c99e4d5f40e578cb2d8f460ea2bbd5dc26316d24", "type": "github" }, "original": { @@ -44,11 +60,11 @@ "rust-overlay": "rust-overlay" }, "locked": { - "lastModified": 1763591898, - "narHash": "sha256-aHSMj7CIa9EJYxdf05wOWRGp0KRsT/TAox7uwVSdDb8=", + "lastModified": 1772297202, + "narHash": "sha256-UEzHO/tCmhPhr8RpWtbm1MTa7ABobwt3nCjrcuDAPm0=", "ref": "refs/heads/main", - "rev": "2d9b2da2c9f384f93ef977c48f8ee35ce586529b", - "revCount": 66, + "rev": "8690e1514863b934de12f2a503c9431d186ce30b", + "revCount": 68, "type": "git", "url": "https://codeberg.org/icewind/mill-scale.git" }, @@ -59,11 +75,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1764522689, - "narHash": "sha256-SqUuBFjhl/kpDiVaKLQBoD8TLD+/cTUzzgVFoaHrkqY=", + "lastModified": 1772822230, + "narHash": "sha256-yf3iYLGbGVlIthlQIk5/4/EQDZNNEmuqKZkQssMljuw=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "8bb5646e0bed5dbd3ab08c7a7cc15b75ab4e1d0f", + "rev": "71caefce12ba78d84fe618cf61644dce01cf3a96", "type": "github" }, "original": { @@ -72,11 +88,48 @@ "type": "indirect" } }, + "nixpkgs_2": { + "locked": { + "lastModified": 1772173633, + "narHash": "sha256-MOH58F4AIbCkh6qlQcwMycyk5SWvsqnS/TCfnqDlpj4=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "c0f3d81a7ddbc2b1332be0d8481a672b4f6004d6", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "phps": { + "inputs": { + "flake-compat": "flake-compat", + "nixpkgs": "nixpkgs_2", + "utils": "utils" + }, + "locked": { + "lastModified": 1772365008, + "narHash": "sha256-/ynkWKeZ1dyRIUkQas0AB35semWAwCbTKXu+/q+8MGg=", + "owner": "fossar", + "repo": "nix-phps", + "rev": "f47eb877bf1c219809e4357eec2fdab8e3263b7b", + "type": "github" + }, + "original": { + "owner": "fossar", + "repo": "nix-phps", + "type": "github" + } + }, "root": { "inputs": { "flakelight": "flakelight", "mill-scale": "mill-scale", - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs", + "phps": "phps" } }, "rust-overlay": { @@ -88,11 +141,11 @@ ] }, "locked": { - "lastModified": 1761964689, - "narHash": "sha256-Zo3LQQDz+64EQ9zor/WmeNTFLoZkjmhp0UY3G0D3seE=", + "lastModified": 1764557621, + "narHash": "sha256-kX5PoY8hQZ80+amMQgOO9t8Tc1JZ70gYRnzaVD4AA+o=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "63d22578600f70d293aede6bc737efef60ebd97f", + "rev": "93316876c2229460a5d6f5f052766cc4cef538ce", "type": "github" }, "original": { @@ -100,6 +153,39 @@ "repo": "rust-overlay", "type": "github" } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 641c5fc..9934eb7 100644 --- a/flake.nix +++ b/flake.nix @@ -9,8 +9,15 @@ url = "git+https://codeberg.org/icewind/mill-scale.git"; inputs.flakelight.follows = "flakelight"; }; + phps = { + url = "github:fossar/nix-phps"; + }; }; - outputs = {mill-scale, ...}: + outputs = { + mill-scale, + phps, + ... + }: mill-scale ./. { crossTargets = [ "x86_64-unknown-linux-gnu" @@ -24,17 +31,23 @@ }; extraPaths = [ - ./redis-certificates + ./certificates ]; withOverlays = [ (import ./nix/overlay.nix) + (prev: final: { + inherit (phps.packages.${prev.system}) php81 php80; + }) ]; packages = { + "haze-image-php-8.5" = pkgs: pkgs.haze-image-php-85; "haze-image-php-8.4" = pkgs: pkgs.haze-image-php-84; "haze-image-php-8.3" = pkgs: pkgs.haze-image-php-83; "haze-image-php-8.2" = pkgs: pkgs.haze-image-php-82; + "haze-image-php-8.1" = pkgs: pkgs.haze-image-php-81; + "haze-image-php-8.0" = pkgs: pkgs.haze-image-php-80; }; tools = pkgs: with pkgs; [cargo-edit bacon skopeo]; diff --git a/haze.service b/haze.service new file mode 100644 index 0000000..4b0ff66 --- /dev/null +++ b/haze.service @@ -0,0 +1,10 @@ +[Install] +WantedBy=default.target + +[Service] +ExecStart=/usr/bin/haze proxy +Restart=on-failure +RestartSec=10 + +[Unit] +Description=Haze reverse proxy \ No newline at end of file diff --git a/nix/hm-module.nix b/nix/hm-module.nix index 096d853..7fe684d 100644 --- a/nix/hm-module.nix +++ b/nix/hm-module.nix @@ -9,9 +9,10 @@ with lib; let format = pkgs.formats.toml {}; configFile = format.generate "haze.toml" ({ sources_root = cfg.sourcesRoot; + app_directories = cfg.appDirectories; work_dir = cfg.workDir; auto_setup = { - enabled = cfg.autoSetup.enable; + inherit (cfg.autoSetup) enable config; post_setup = cfg.autoSetup.postSetup; enable_apps = cfg.autoSetup.enableApps; disable_apps = cfg.autoSetup.disableApps; @@ -61,6 +62,12 @@ in { description = "Directory to store instance data"; }; + appDirectories = mkOption { + type = types.listOf types.str; + default = []; + description = "Paths to additional app directories"; + }; + autoSetup = mkOption { type = types.submodule { options = { @@ -84,6 +91,13 @@ in { default = []; description = "Commands to run post-setup"; }; + config = mkOption { + type = types.submodule { + freeformType = format.type; + }; + description = "Configuration options to set before install"; + default = {}; + }; }; }; }; diff --git a/nix/image/bootstrap b/nix/image/bootstrap new file mode 100755 index 0000000..2c9dcff --- /dev/null +++ b/nix/image/bootstrap @@ -0,0 +1,88 @@ +#!/bin/nu + +touch /var/log/nginx/access.log +touch /var/log/nginx/error.log +touch /var/log/cron/owncloud.log + +mkdir /config +echo "# Options in here overwrite the builtin php.ini\n" | save /config/php.ini +echo "# xdebug.mode = debug\n" | save -a /config/php.ini +echo "# xdebug.start_with_request = yes\n\n" | save -a /config/php.ini +chmod 0777 /config/php.ini +let PHP_INI_DIR = php --ini | grep 'Scan' | cut -d ' ' -f7 | tr -d '"' +ln -s /config/php.ini $"($PHP_INI_DIR)/zz_extra.ini" + +let HAZE_UID = $env.HAZE_UID | default "1000" +let HAZE_GID = $env.HAZE_GID | default "1000" + +nc-auto-config +shadow-setup + +echo $"Running as ($HAZE_UID):($HAZE_GID)" + +mkdir /var/www/html/core/skeleton /var/www/html/build/integration/vendor /var/www/html/build/integration/output /var/www/html/build/integration/work /var/www/html/core/skeleton /var/www/.composer/cache /var/www/html/apps/spreed/tests/integration/vendor/composer +chown -R $"($HAZE_UID):($HAZE_GID)" /var/www/html/data /var/www/html/config +chown $"($HAZE_UID):($HAZE_GID)" /var/www/html/core/skeleton /var/www/html/build/integration/vendor /var/www/html/build/integration/composer.lock /var/www/html/build/integration/output /var/www/html/build/integration/work /var/www/html/core/skeleton /var/www/.composer/cache /var/www/html/apps/spreed/tests/integration/vendor/composer + +echo "{}\n" | save -f /var/www/html/build/integration/composer.lock + +echo $"Starting server using ($env.SQL) database…" + +chmod +sx /sbin/sudo + +mkdir /var/log/nginx /tmp /var/run/blackfire +touch /var/log/nginx/access.log +touch /var/log/nginx/error.log + +if ((getent group $HAZE_GID | length) > 0) { + groupadd haze + useradd -u $HAZE_UID -g $HAZE_GID -G haze haze +} else { + groupadd -g $HAZE_GID haze + useradd -u $HAZE_UID -g $HAZE_GID haze +} +chown -R $"haze:($HAZE_GID)" /home/haze +ls -af /etc/home | each {|file| ln -s $file.name $"/home/haze/($file.name | path basename)" } + +if ("/var/run/docker.sock" | path exists) { + let dockerGid = stat --format "%g" /var/run/docker.sock + groupadd docker -g $dockerGid + usermod -a -G docker haze +} + +if ("REDIS_TLS" in $env) { + cp /etc/supervisor/redis-tls.conf /etc/supervisor/enabled/ +} else { + cp /etc/supervisor/redis-plain.conf /etc/supervisor/enabled/ +} + +if ("BLACKFIRE_SERVER_ID" in $env) { + blackfire agent:config --server-id $env.BLACKFIRE_SERVER_ID --server-token $env.BLACKFIRE_SERVER_TOKEN + cp /etc/supervisor/blackfire.conf /etc/supervisor/enabled/ +} + +if ("PROXY_BASE" in $env) { + let UPSTREAM_DNS = cat /etc/resolv.conf | grep nameserver | cut -d' ' -f 2 + let RC = sed '/nameserver/d' /etc/resolv.conf + echo $RC | save -f /etc/resolv.conf + + echo "\nnameserver 127.0.0.22\n" | save -a /etc/resolv.conf + + echo $"s/UPSTREAM_DNS/($UPSTREAM_DNS)" + sed -i $"s/UPSTREAM_DNS/($UPSTREAM_DNS)/" /etc/dnsmasq.conf + echo $"s/PROXY_BASE/($env.PROXY_BASE)" + sed -i $"s/PROXY_BASE/($env.PROXY_BASE)/" /etc/dnsmasq.conf + echo $"s/HOST_IP/($env.HOST_IP)" + sed -i $"s/HOST_IP/($env.HOST_IP)/" /etc/dnsmasq.conf + + cp /etc/supervisor/dnsmasq.conf /etc/supervisor/enabled/ +} + +if ("FRANKENPHP" in $env) { + cp /etc/supervisor/frankenphp.conf /etc/supervisor/enabled/ +} else { + cp /etc/supervisor/php-fpm.conf /etc/supervisor/enabled/ + cp /etc/supervisor/nginx.conf /etc/supervisor/enabled/ +} + +exec supervisord -c /etc/supervisor/supervisord.conf diff --git a/nix/image/bootstrap.sh b/nix/image/bootstrap.sh deleted file mode 100755 index 141affa..0000000 --- a/nix/image/bootstrap.sh +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env bash - -touch /var/log/nginx/access.log -touch /var/log/nginx/error.log -touch /var/log/cron/owncloud.log - -echo "# Options in here overwrite the builtin php.ini" > /php.ini -chmod 0777 /php.ini -PHP_INI_DIR="$(php --ini | grep 'Scan' | cut -d ' ' -f7)" -ln -s /php.ini "$PHP_INI_DIR/zz_extra.ini" - -HAZE_UID=${HAZE_UID:-www-data} -HAZE_GID=${HAZE_GID:-www-data} - -nc-auto-config -shadow-setup - -echo "Running as $HAZE_UID:$HAZE_GID" - -mkdir -p /var/www/html/core/skeleton /var/www/html/build/integration/vendor /var/www/html/build/integration/output /var/www/html/build/integration/work /var/www/html/core/skeleton /var/www/.composer/cache /var/www/html/apps/spreed/tests/integration/vendor/composer -chown -R "$HAZE_UID":"$HAZE_GID" /var/www/html/data /var/www/html/config -chown "$HAZE_UID":"$HAZE_GID" /var/www/html/core/skeleton /var/www/html/build/integration/vendor /var/www/html/build/integration/composer.lock /var/www/html/build/integration/output /var/www/html/build/integration/work /var/www/html/core/skeleton /var/www/.composer/cache /var/www/html/apps/spreed/tests/integration/vendor/composer - -echo "{}" > /var/www/html/build/integration/composer.lock - -echo "Starting server using $SQL database…" - -# tail --follow --retry /var/log/nginx/*.log /var/log/cron/owncloud.log & - -chmod +sx /sbin/sudo - -mkdir -p /var/log/nginx /tmp /var/run/blackfire -touch /var/log/nginx/access.log -touch /var/log/nginx/error.log - -HAZE_UID=${HAZE_UID:-1000} -HAZE_GID=${HAZE_GID:-1000} - -if [ "$(getent group "$HAZE_GID")" ]; then - groupadd haze - EXTRA_GROUP=" -G haze" -else - groupadd -g "$HAZE_GID" haze - EXTRA_GROUP="" -fi - -useradd -u "$HAZE_UID" -g "$HAZE_GID""$EXTRA_GROUP" haze -chown -R haze:"$HAZE_GID" /home/haze - -if [ -f "/var/run/docker.sock" ]; then - groupadd docker -g "$(stat --format "%g" /var/run/docker.sock)" - usermod -a -G docker haze -fi - -if [ -n "${REDIS_TLS:-}" ] -then - redis-server --protected-mode no \ - --tls-port 6379 --port 0 \ - --tls-cert-file /redis-certificates/server.crt \ - --tls-key-file /redis-certificates/server.key \ - --tls-ca-cert-file /redis-certificates/ca.crt & -else - redis-server --protected-mode no & -fi - -if [ -n "${BLACKFIRE_SERVER_ID:-}" ] -then - sh -c ' - yes | blackfire agent:config --server-id=$BLACKFIRE_SERVER_ID --server-token=$BLACKFIRE_SERVER_TOKEN - BLACKFIRE_LOG_LEVEL=4 BLACKFIRE_LOG_FILE=/var/log/agent.log blackfire agent & - '& -fi - -php-fpm --fpm-config /etc/php-fpm.conf& - -nginx -c /etc/nginx.conf \ No newline at end of file diff --git a/nix/image/configs.nix b/nix/image/configs.nix index 1b90bc8..afa06c7 100644 --- a/nix/image/configs.nix +++ b/nix/image/configs.nix @@ -1,12 +1,7 @@ {runCommand}: runCommand "configs" {} '' - mkdir -p $out/etc - mkdir -p $out/etc/sudoers.d - mkdir -p $out/conf - cp ${./configs/cron.conf} $out/etc/oc-cron.conf - cp ${./configs/nginx-app.conf} $out/conf/nginx-app.conf - cp ${./configs/sudoers} $out/etc/sudoers.d/haze - cp -r ${./configs/nc} $out/etc/nc - cp ${./php-fpm.conf} $out/etc/php-fpm.conf - cp ${./nginx.conf} $out/etc/nginx.conf + mkdir -p $out + cp -r ${./configs} $out/etc + chmod -R +w $out/etc + mkdir $out/etc/supervisor/enabled/ '' diff --git a/nix/image/configs/dnsmasq.conf b/nix/image/configs/dnsmasq.conf new file mode 100644 index 0000000..2b1afc5 --- /dev/null +++ b/nix/image/configs/dnsmasq.conf @@ -0,0 +1,6 @@ +listen-address=127.0.0.22 +no-resolv + +address=/PROXY_BASE/HOST_IP + +server=UPSTREAM_DNS \ No newline at end of file diff --git a/nix/image/configs/home/.sqliterc b/nix/image/configs/home/.sqliterc new file mode 100644 index 0000000..0d5fe04 --- /dev/null +++ b/nix/image/configs/home/.sqliterc @@ -0,0 +1 @@ +.mode table diff --git a/nix/image/configs/nc/config.php b/nix/image/configs/nc/config.php index e8dc0af..06de6db 100644 --- a/nix/image/configs/nc/config.php +++ b/nix/image/configs/nc/config.php @@ -1,12 +1,19 @@ - true, - 'appstoreenabled' => false, 'memcache.local' => '\\OC\\Memcache\\APCu', - 'memcache.distributed' => '\\OC\\Memcache\\APCu', - 'memcache.locking' => '\\OC\\Memcache\\APCu', + 'memcache.distributed' => '\\OC\\Memcache\\Redis', + 'memcache.locking' => '\\OC\\Memcache\\Redis', 'allow_local_remote_servers' => true, 'trusted_domains' => ['cloud'], 'profiling.secret' => 'haze', 'profiling.path' => '/tmp/profiling', //PLACEHOLDER -]; +]); diff --git a/nix/image/configs/nc/redis-default.php b/nix/image/configs/nc/redis-default.php index 2ba24aa..71b18a8 100644 --- a/nix/image/configs/nc/redis-default.php +++ b/nix/image/configs/nc/redis-default.php @@ -1,2 +1 @@ 'redis' => ['host' => 'localhost'], -//PLACEHOLDER diff --git a/nix/image/configs/nc/redis-tls.php b/nix/image/configs/nc/redis-tls.php index fecc760..c3b9abe 100644 --- a/nix/image/configs/nc/redis-tls.php +++ b/nix/image/configs/nc/redis-tls.php @@ -2,10 +2,9 @@ 'host' => 'tls://127.0.0.1', 'port' => 6379, 'ssl_context' => [ - 'local_cert' => '/redis-certificates/client.crt', - 'local_pk' => '/redis-certificates/client.key', - 'cafile' => '/redis-certificates/ca.crt', + 'local_cert' => '/certificates/redis/client.crt', + 'local_pk' => '/certificates/redis/client.key', + 'cafile' => '/certificates/redis/ca.crt', 'verify_peer_name' => false, ], ], -//PLACEHOLDER diff --git a/nix/image/configs/nc/s3s.php b/nix/image/configs/nc/s3s.php new file mode 100644 index 0000000..cfbf186 --- /dev/null +++ b/nix/image/configs/nc/s3s.php @@ -0,0 +1,15 @@ + 'objectstore' => [ + 'class' => 'OC\Files\ObjectStore\S3', + 'arguments' => [ + 'bucket' => 'nextcloud', + 'autocreate' => true, + 'key' => 'minio', + 'secret' => 'minio123', + 'hostname' => 's3', + 'port' => 9000, + 'use_ssl' => true, + 'use_path_style' => true, + 'uploadPartSize' => 52428800, + 'use_nextcloud_bundle' => true, + ], + ], diff --git a/nix/image/nginx.conf b/nix/image/configs/nginx.conf similarity index 76% rename from nix/image/nginx.conf rename to nix/image/configs/nginx.conf index 3de81ca..15a57c3 100644 --- a/nix/image/nginx.conf +++ b/nix/image/configs/nginx.conf @@ -69,7 +69,17 @@ http { access_log off; } - include /conf/nginx-app.conf; + location ^~ /store_apps { + root /var/www; + try_files $uri /index.php$request_uri; + access_log off; # Optional: Don't log access to assets + + location ~ \.wasm$ { + default_type application/wasm; + } + } + + include /etc/nginx-app.conf; location ~ \.php(?:$|/) { rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|ocs-provider\/.+|.+\/richdocumentscode\/proxy) /index.php$request_uri; @@ -99,5 +109,16 @@ http { expires 7d; # Cache-Control policy borrowed from `.htaccess` access_log off; # Optional: Don't log access to assets } + + location /fpm-status { + include /conf/fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_pass php-handler; + fastcgi_read_timeout 3600; + proxy_request_buffering off; + fastcgi_request_buffering off; + fastcgi_buffering off; + } } } diff --git a/nix/image/configs/cron.conf b/nix/image/configs/oc-cron.conf similarity index 100% rename from nix/image/configs/cron.conf rename to nix/image/configs/oc-cron.conf diff --git a/nix/image/php-fpm.conf b/nix/image/configs/php-fpm.conf similarity index 77% rename from nix/image/php-fpm.conf rename to nix/image/configs/php-fpm.conf index e6292fb..89379d8 100644 --- a/nix/image/php-fpm.conf +++ b/nix/image/configs/php-fpm.conf @@ -1,11 +1,11 @@ [global] -error_log = /proc/self/fd/2 +error_log = /var/log/php-fpm-error.log daemonize = no [www] -access.log = /proc/self/fd/2 +access.log =/var/log/php-fpm-access.log user = haze group = haze @@ -21,6 +21,7 @@ pm.max_children = 5 pm.start_servers = 2 pm.min_spare_servers = 1 pm.max_spare_servers = 3 +pm.status_path = /fpm-status clear_env = no diff --git a/nix/image/configs/sudoers b/nix/image/configs/sudoers.d/haze similarity index 100% rename from nix/image/configs/sudoers rename to nix/image/configs/sudoers.d/haze diff --git a/nix/image/configs/supervisor/blackfire.conf b/nix/image/configs/supervisor/blackfire.conf new file mode 100644 index 0000000..ca6cd89 --- /dev/null +++ b/nix/image/configs/supervisor/blackfire.conf @@ -0,0 +1,2 @@ +[program:blackfire] +command = blackfire agent \ No newline at end of file diff --git a/nix/image/configs/supervisor/dnsmasq.conf b/nix/image/configs/supervisor/dnsmasq.conf new file mode 100644 index 0000000..d57ed09 --- /dev/null +++ b/nix/image/configs/supervisor/dnsmasq.conf @@ -0,0 +1,2 @@ +[program:dnsmasq] +command = /bin/dnsmasq --keep-in-foreground -u root \ No newline at end of file diff --git a/nix/image/configs/supervisor/frankenphp.conf b/nix/image/configs/supervisor/frankenphp.conf new file mode 100644 index 0000000..8c1b1e5 --- /dev/null +++ b/nix/image/configs/supervisor/frankenphp.conf @@ -0,0 +1,3 @@ +[program:frankenphp] +command = /bin/frankenphp php-server +directory = /var/www/html \ No newline at end of file diff --git a/nix/image/configs/supervisor/nginx.conf b/nix/image/configs/supervisor/nginx.conf new file mode 100644 index 0000000..957e4b3 --- /dev/null +++ b/nix/image/configs/supervisor/nginx.conf @@ -0,0 +1,2 @@ +[program:nginx] +command = /bin/nginx -c /etc/nginx.conf diff --git a/nix/image/configs/supervisor/php-fpm.conf b/nix/image/configs/supervisor/php-fpm.conf new file mode 100644 index 0000000..69418c7 --- /dev/null +++ b/nix/image/configs/supervisor/php-fpm.conf @@ -0,0 +1,2 @@ +[program:php-fpm] +command = /bin/php-fpm --fpm-config /etc/php-fpm.conf diff --git a/nix/image/configs/supervisor/redis-plain.conf b/nix/image/configs/supervisor/redis-plain.conf new file mode 100644 index 0000000..e9284cc --- /dev/null +++ b/nix/image/configs/supervisor/redis-plain.conf @@ -0,0 +1,2 @@ +[program:redis] +command = /bin/redis-server --protected-mode no \ No newline at end of file diff --git a/nix/image/configs/supervisor/redis-tls.conf b/nix/image/configs/supervisor/redis-tls.conf new file mode 100644 index 0000000..d9f86bd --- /dev/null +++ b/nix/image/configs/supervisor/redis-tls.conf @@ -0,0 +1,2 @@ +[program:redis-tls] +command = /bin/redis-server --protected-mode no --tls-port 6379 --port 0 --tls-cert-file /certificates/redis/server.crt --tls-key-file /certificates/redis/server.key --tls-ca-cert-file /certificates/redis/ca.crt \ No newline at end of file diff --git a/nix/image/configs/supervisor/supervisord.conf b/nix/image/configs/supervisor/supervisord.conf new file mode 100644 index 0000000..bed7885 --- /dev/null +++ b/nix/image/configs/supervisor/supervisord.conf @@ -0,0 +1,19 @@ +[supervisord] +logfile = /dev/stdout +logfile_maxbytes = 0 +nodaemon = true +pidfile = /var/run/supervisord.pid +user = root + +[unix_http_server] +file = /var/run/supervisor.sock +chmod = 0777 + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +[supervisorctl] +serverurl = unix:///var/run/supervisor.sock + +[include] +files = enabled/* diff --git a/nix/image/haze.nix b/nix/image/haze.nix index 38bc8f9..5ce1051 100644 --- a/nix/image/haze.nix +++ b/nix/image/haze.nix @@ -7,10 +7,9 @@ blackfire, coreutils, getent, + writers, shadow, - buildEnv, runCommand, - cacert, callPackage, cronie, redis, @@ -33,18 +32,20 @@ writeShellApplication, vim, helix, + python3Packages, + dnsmasq, + frankenphp, + nushell, }: let - inherit (builtins) toString; - inherit (lib) readFile getExe concatStringsSep splitString take; + inherit (builtins) toString compareVersions; + inherit (lib) readFile getExe concatStringsSep splitString take optionals; + + version = (fromTOML (readFile ../../Cargo.toml)).package.version; phpVersion = concatStringsSep "." (take 2 (splitString "." php.version)); phpEnv = callPackage ./php.nix {inherit debug php;}; - bootstrap = writeShellApplication { - name = "bootstrap"; - runtimeInputs = [getent]; - text = readFile ./bootstrap.sh; - }; + bootstrap = writers.writeNuBin "bootstrap" (readFile ./bootstrap); shadowSetupScript = writeShellApplication { name = "shadow-setup"; text = dockerTools.shadowSetup; @@ -70,14 +71,6 @@ php = phpEnv; }; - phpunitWrapped = majorVersion: - writeShellApplication { - name = "phpunit${toString majorVersion}"; - text = '' - ${phpunitUnwrapped (toString majorVersion)}/bin/phpunit "$@" - ''; - }; - phpunit = writeShellApplication { name = "phpunit"; runtimeInputs = [jq]; @@ -104,9 +97,9 @@ ''; }; - redis-certificates = runCommand "scripts" {} '' + certificates = runCommand "scripts" {} '' mkdir -p $out - cp -r ${../../redis-certificates} $out/redis-certificates + cp -r ${../../certificates} $out/certificates ''; clamav-data = runCommand "scripts" {} '' mkdir -p $out/etc @@ -145,6 +138,10 @@ oracle-instantclient vim helix + python3Packages.supervisor + dnsmasq + nushell + getent ]; }; @@ -153,11 +150,20 @@ tag = phpVersion; fromImage = baseImage; - copyToRoot = [ - phpEnv - phpEnv.packages.composer - phpunit - ]; + copyToRoot = + [ + phpEnv + phpEnv.packages.composer + phpunit + ] + ++ optionals ((compareVersions phpVersion "8.2") == 1) [ + (frankenphp.override { + php = php.withExtensions (import ./php-ext.nix { + inherit lib php; + enableBlackfire = false; + }); + }) + ]; }; in dockerTools.buildLayeredImage { @@ -170,7 +176,7 @@ in bootstrap configs scripts - redis-certificates + certificates clamav-data shadowSetupScript ]; @@ -185,7 +191,14 @@ in ''; config = { Cmd = [(getExe bootstrap)]; - Env = ["EDITOR=hx" "WEBROOT=/var/www/html"]; + Env = [ + "EDITOR=hx" + "WEBROOT=/var/www/html" + "HAZE_IMAGE_VERSION=${toString version}" + ]; WorkingDir = "/var/www/html"; + Labels = { + "nl.icewind.haze.version" = toString version; + }; }; } diff --git a/nix/image/php-ext.nix b/nix/image/php-ext.nix new file mode 100644 index 0000000..0a7e53e --- /dev/null +++ b/nix/image/php-ext.nix @@ -0,0 +1,45 @@ +{ + lib, + php, + debug ? false, + enableBlackfire ? true, +}: let + inherit (builtins) compareVersions; + inherit (lib) optionals; + withBlackfire = enableBlackfire && !debug && ((compareVersions php.version "8.1.0") == 1); +in + { + enabled, + all, + }: + enabled + ++ (with all; + [ + xdebug + excimer + inotify + redis + oci8 + zip + pdo + pdo_pgsql + pdo_sqlite + pdo_mysql + pgsql + intl + curl + mbstring + pcntl + ldap + exif + gmp + apcu + ffi + imagick + ] + ++ optionals (!debug) [ + # smbclient # this breaks the build for no apparent reason + ] + ++ optionals withBlackfire [ + blackfire + ]) diff --git a/nix/image/php.nix b/nix/image/php.nix index 852b478..8ef3d31 100644 --- a/nix/image/php.nix +++ b/nix/image/php.nix @@ -2,56 +2,23 @@ lib, php, debug ? false, -}: let - inherit (lib) optionals; -in - php.buildEnv { - extensions = { - enabled, - all, - }: - enabled - ++ (with all; - [ - xdebug - excimer - inotify - redis - oci8 - zip - pdo - pdo_pgsql - pdo_sqlite - pdo_mysql - pgsql - intl - curl - mbstring - pcntl - ldap - exif - gmp - apcu - ffi - ] - ++ optionals (!debug) [ - smbclient # this breaks the build for no apparent reason - blackfire - ]); - extraConfig = '' - xdebug.mode=debug,trace,profile - xdebug.start_with_request=trigger - xdebug.discover_client_host=false - xdebug.client_host=hazehost - xdebug.log_level=0 - xdebug.output_dir=/tmp/xdebug +}: +php.buildEnv { + extensions = import ./php-ext.nix {inherit lib php debug;}; + extraConfig = '' + xdebug.mode=debug,trace,profile + xdebug.start_with_request=trigger + xdebug.discover_client_host=false + xdebug.client_host=hazehost + xdebug.log_level=0 + xdebug.output_dir=/tmp/xdebug - memory_limit=512M + memory_limit=512M - post_max_size 10G - upload_max_filesize 10G + post_max_size 10G + upload_max_filesize 10G - apc.enable_cli=1 - opcache.enable_cli=1 - ''; - } + apc.enable_cli=1 + opcache.enable_cli=1 + ''; +} diff --git a/nix/image/scripts/install b/nix/image/scripts/install index d216118..35d3f00 100755 --- a/nix/image/scripts/install +++ b/nix/image/scripts/install @@ -1,20 +1,22 @@ -#!/bin/sh +#!/bin/nu -USER=$1 -PASSWORD=$2 +def main [username: string, password: string] { + cd $env.WEBROOT; + let sql = match $env.SQL { + "oracle" => "oci" + "mariadb" => "mysql" + _ => $env.SQL + } + let dbName = match $env.SQL { + "oracle" => "xe" + _ => "haze" + } + let dbUser = match $env.SQL { + "oracle" => "system" + _ => "haze" + } + let dbPass = "haze" + let dbHost = $env.SQL -if [ -z "$USER" ] || [ -z "$PASSWORD" ]; then - echo "Usage: install \$USER \$PASSWORD" - exit; -fi - -cd $WEBROOT - -if [ "$SQL" = "oci" ]; then - # oracle is a special snowflake - occ maintenance:install --admin-user=$USER --admin-pass=$PASSWORD --database=$SQL --database-name=xe --database-host=$SQL --database-user=system --database-pass=haze -elif [ "$SQL" = "mariadb" ]; then - occ maintenance:install --admin-user=$USER --admin-pass=$PASSWORD --database=mysql --database-name=haze --database-host=$SQL --database-user=haze --database-pass=haze -else - occ maintenance:install --admin-user=$USER --admin-pass=$PASSWORD --database=$SQL --database-name=haze --database-host=$SQL --database-user=haze --database-pass=haze -fi; + occ maintenance:install --admin-user $username --admin-pass $password --database $sql --database-name $dbName --database-host $dbHost --database-user $dbUser --database-pass $dbPass +} diff --git a/nix/image/scripts/integration b/nix/image/scripts/integration index 4f73aef..d8187d9 100755 --- a/nix/image/scripts/integration +++ b/nix/image/scripts/integration @@ -1,4 +1,22 @@ -#!/bin/sh +#!/bin/nu -cd $WEBROOT/build/integration -./run.sh "$@" +def --wrapped main [feature: path, ...rest] { + mut feature = $feature; + mut workdir = $"($env.WEBROOT)/build/integration" + if ($feature | str starts-with "apps/") { + let parts = $feature | split row '/' + occ app:enable $parts.1 + let parts = $feature | split row -n 2 '/features/' + $workdir = $parts.0 + $feature = $"features/($parts.1)" + } else if ($feature | str starts-with "build/integration/") { + $feature = $feature | str replace "build/integration/" "" + } + + # don't use the proxy urls for generated urls + occ config:system:delete overwritehost + occ config:system:delete overwriteprotocol + + cd $workdir + bash run.sh $feature ...$rest +} diff --git a/nix/image/scripts/nc-auto-config b/nix/image/scripts/nc-auto-config index 6a803d9..b7a2c07 100755 --- a/nix/image/scripts/nc-auto-config +++ b/nix/image/scripts/nc-auto-config @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/bin/nu touch /var/log/nginx/access.log touch /var/log/nginx/error.log @@ -7,59 +7,30 @@ touch /var/log/cron/owncloud.log cp /etc/nc/config.php /var/www/html/config/config.php chmod 0755 /var/www/html/config/config.php -if [ "$SQL" = "mysql" ] -then - cp /etc/nc/autoconfig_mysql.php /var/www/html/config/autoconfig.php -fi +let configName = match $env.SQL { + "oracle" => "oci" + _ => $env.SQL -if [ "$SQL" = "mariadb" ] -then - cp /etc/nc/autoconfig_mariadb.php /var/www/html/config/autoconfig.php -fi +} +let configPath = $"/etc/nc/autoconfig_($configName).php" -if [ "$SQL" = "pgsql" ] -then - cp /etc/nc/autoconfig_pgsql.php /var/www/html/config/autoconfig.php -fi +if ($configPath | path exists) { + cp $configPath /var/www/html/config/autoconfig.php +} -if [ "$SQL" = "oci" ] -then - cp /etc/nc/autoconfig_oci.php /var/www/html/config/autoconfig.php -fi +def loadExtraConfig [name: string] { + sed -i $'/\/\/PLACEHOLDER/ r /etc/nc/($name).php' /var/www/html/config/config.php +} -if [ -n "${S3:-}" ] -then - sed -i '/\/\/PLACEHOLDER/ r /etc/nc/s3.php' /var/www/html/config/config.php -fi +let extraConfigs = ["S3", "S3S", "S3MB", "S3M", "SWIFT", "SWIFTV3", "AZURE"]; +$extraConfigs | each { + if ($in in $env) { + loadExtraConfig ($in | str downcase) + } +} -if [ -n "${S3MB:-}" ] -then - sed -i '/\/\/PLACEHOLDER/ r /etc/nc/s3mb.php' /var/www/html/config/config.php -fi - -if [ -n "${S3M:-}" ] -then - sed -i '/\/\/PLACEHOLDER/ r /etc/nc/s3m.php' /var/www/html/config/config.php -fi - -if [ -n "${SWIFT:-}" ] -then - sed -i '/\/\/PLACEHOLDER/ r /etc/nc/swift.php' /var/www/html/config/config.php -fi - -if [ -n "${SWIFTV3:-}" ] -then - sed -i '/\/\/PLACEHOLDER/ r /etc/nc/swiftv3.php' /var/www/html/config/config.php -fi - -if [ -n "${AZURE:-}" ] -then - sed -i '/\/\/PLACEHOLDER/ r /etc/nc/azure.php' /var/www/html/config/config.php -fi - -if [ -n "${REDIS_TLS:-}" ] -then - sed -i '/\/\/PLACEHOLDER/ r /etc/nc/redis-tls.php' /var/www/html/config/config.php -else - sed -i '/\/\/PLACEHOLDER/ r /etc/nc/redis-default.php' /var/www/html/config/config.php -fi +if ("REDIS_TLS" in $env) { + loadExtraConfig "redis-tls" +} else { + loadExtraConfig "redis-default" +} diff --git a/nix/image/scripts/occ b/nix/image/scripts/occ index cf7d6fa..07a443b 100755 --- a/nix/image/scripts/occ +++ b/nix/image/scripts/occ @@ -1,5 +1,5 @@ -#!/bin/sh +#!/bin/nu -export XDEBUG_SESSION=haze - -php $WEBROOT/occ "$@" +def --wrapped main [...rest] { + XDEBUG_SESSION=haze php $"($env.WEBROOT)/occ" ...$rest +} diff --git a/nix/image/scripts/tests b/nix/image/scripts/tests index 70691c0..868f225 100755 --- a/nix/image/scripts/tests +++ b/nix/image/scripts/tests @@ -1,7 +1,7 @@ -#!/bin/sh +#!/bin/nu -cd $WEBROOT +def --wrapped main [...rest] { + cd $env.WEBROOT -export XDEBUG_SESSION=haze - -phpunit --configuration $WEBROOT/tests/phpunit-autotest.xml $@ + XDEBUG_SESSION=haze phpunit --configuration $"($env.WEBROOT)/tests/phpunit-autotest.xml" ...$rest +} diff --git a/nix/overlay.nix b/nix/overlay.nix index b08daca..276b70e 100644 --- a/nix/overlay.nix +++ b/nix/overlay.nix @@ -1,7 +1,9 @@ final: prev: { haze = final.callPackage ./package.nix {}; + haze-image-php-85 = final.callPackage ./image/haze.nix {php = final.php85;}; haze-image-php-84 = final.callPackage ./image/haze.nix {php = final.php84;}; haze-image-php-83 = final.callPackage ./image/haze.nix {php = final.php83;}; haze-image-php-82 = final.callPackage ./image/haze.nix {php = final.php82;}; haze-image-php-81 = final.callPackage ./image/haze.nix {php = final.php81;}; + haze-image-php-80 = final.callPackage ./image/haze.nix {php = final.php80;}; } diff --git a/nix/package.nix b/nix/package.nix index d79a216..c6166f0 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -1,16 +1,15 @@ { rustPlatform, - pkg-config, lib, git, }: let inherit (lib) getExe; inherit (lib.sources) sourceByRegex; inherit (builtins) fromTOML readFile; - src = sourceByRegex ../. ["Cargo.*" "(src|redis-certificates)(/.*)?"]; + src = sourceByRegex ../. ["Cargo.*" "(src|certificates)(/.*)?"]; version = (fromTOML (readFile ../Cargo.toml)).package.version; in - rustPlatform.buildRustPackage rec { + rustPlatform.buildRustPackage { pname = "haze"; inherit src version; @@ -20,7 +19,7 @@ in cargoLock = { lockFile = ../Cargo.lock; outputHashes = { - "hyper-reverse-proxy-0.5.2-dev" = "sha256-+ebi4FVVkiOpf75e8K5oGkHJBYQjLNJhUPNj+78zd7Q="; + "hyper-reverse-proxy-0.5.2-dev" = "sha256-awmj5aLFTea+kj81cwmfP1HWlWezwEKfyQSUJWjtamk="; }; }; } diff --git a/src/args.rs b/src/args.rs index bcf9365..9b7a2cc 100644 --- a/src/args.rs +++ b/src/args.rs @@ -124,9 +124,12 @@ pub enum GitOperation { /// /// "main" and "master" can be used interchangeably. #[strum(props( - Args = "[branch] branch to checkout, defaults to the branch matching the current server version" + Args = "[branch] branch to checkout, defaults to the branch matching the current server version [-v] verbose" ))] - Checkout { branch: Option }, + Checkout { + branch: Option, + verbose: bool, + }, } impl SubCommand for GitOperation { @@ -320,18 +323,24 @@ impl HazeArgs { HazeCommand::Checkout => { let branch = args.next().map(S::into); Ok(HazeArgs::Git { - operation: GitOperation::Checkout { branch }, + operation: GitOperation::Checkout { + branch, + verbose: false, + }, }) } HazeCommand::Git => { + let mut args = args.peekable(); let operation = args .next() .ok_or_else(|| Report::msg("No git operation provided"))?; match operation.as_ref() { "checkout" => { + let verbose = args.next_if(|arg| arg.as_ref() == "-v").is_some(); let branch = args.next().map(S::into); + let verbose = verbose | args.next_if(|arg| arg.as_ref() == "-v").is_some(); Ok(HazeArgs::Git { - operation: GitOperation::Checkout { branch }, + operation: GitOperation::Checkout { branch, verbose }, }) } "pull" => Ok(HazeArgs::Git { @@ -469,7 +478,7 @@ pub enum HazeCommand { Edit, /// Reload the php configuration in the instance #[strum(props( - Details = "note: you can overwrite php.ini settings with haze [filter] edit /php.ini" + Details = "note: you can overwrite php.ini settings with haze [filter] edit /config/php.ini" ))] Reload, } @@ -505,6 +514,7 @@ impl SubCommand for HazeCommand { fn test_arg_parse() { let config = HazeConfig { sources_root: Default::default(), + app_directories: Default::default(), work_dir: Default::default(), auto_setup: Default::default(), volume: vec![], diff --git a/src/cloud.rs b/src/cloud.rs index 70ee931..afa01cf 100644 --- a/src/cloud.rs +++ b/src/cloud.rs @@ -1,13 +1,14 @@ use crate::config::{HazeConfig, HazeVolumeConfig}; use crate::database::Database; use crate::exec::{exec, exec_io, exec_tty, ExitCode}; -use crate::mapping::{default_mappings, Mapping}; +use crate::mapping::{for_config, Mapping}; use crate::php::{PhpVersion, PHP_MEMORY_LIMIT}; use crate::service::Service; use crate::service::ServiceTrait; -use bollard::container::{ListContainersOptions, RemoveContainerOptions, UpdateContainerOptions}; -use bollard::models::ContainerState; -use bollard::network::CreateNetworkOptions; +use crate::sources::download_nc; +use bollard::config::NetworkCreateRequest; +use bollard::models::{ContainerState, ContainerUpdateBody}; +use bollard::query_parameters::{ListContainersOptions, RemoveContainerOptions}; use bollard::Docker; use camino::{Utf8Path, Utf8PathBuf}; use flate2::read::GzDecoder; @@ -19,9 +20,9 @@ use std::borrow::Cow; use std::collections::HashMap; use std::fmt::Display; use std::fs; -use std::fs::read_to_string; +use std::fs::{read_to_string, write}; use std::io::{stdout, Cursor, Read, Stdout, Write}; -use std::iter::Peekable; +use std::iter::{once, Peekable}; use std::net::IpAddr; use std::os::unix::fs::MetadataExt; use std::str::FromStr; @@ -57,6 +58,7 @@ pub struct CloudOptions { pub php: PhpVersion, pub services: Vec, pub app_packages: Vec, + pub version: Option, } impl CloudOptions { @@ -69,6 +71,7 @@ impl CloudOptions { db: Database::default(), services: vec![], app_packages: vec![], + version: None, } } @@ -82,6 +85,7 @@ impl CloudOptions { let mut name = None; let mut services = Vec::new(); let mut app_package = Vec::new(); + let mut version = None; while let Some(option) = args.peek() { if let Ok(db_option) = Database::from_str(option.as_ref()) { @@ -96,6 +100,9 @@ impl CloudOptions { } else if option.as_ref().ends_with(".tar.gz") { app_package.push(option.to_string().into()); let _ = args.next(); + } else if let Some(v) = option.as_ref().strip_prefix("v") { + version = Some(v.into()); + let _ = args.next(); } else if option.as_ref() == "--name" { let _ = args.next(); name = args.next().map(|s| s.into()); @@ -112,6 +119,7 @@ impl CloudOptions { .unwrap_or_default(), services, app_packages: app_package, + version, }) } } @@ -144,44 +152,44 @@ fn test_option_parse() { ..Default::default() } ); - let mut args = vec!["7"].into_iter().peekable(); + let mut args = vec!["8"].into_iter().peekable(); assert_eq!( CloudOptions::parse(&config, &mut args).unwrap(), CloudOptions { - php: PhpVersion::Php74, + php: PhpVersion::Php80, ..Default::default() } ); - let mut args = vec!["7", "pgsql", "rest"].into_iter().peekable(); + let mut args = vec!["8.1", "pgsql", "rest"].into_iter().peekable(); assert_eq!( CloudOptions::parse(&config, &mut args).unwrap(), CloudOptions { - php: PhpVersion::Php74, + php: PhpVersion::Php81, db: Database::Postgres, ..Default::default() } ); - let mut args = vec!["7", "ldap", "pgsql"].into_iter().peekable(); + let mut args = vec!["8", "ldap", "pgsql"].into_iter().peekable(); assert_eq!( CloudOptions::parse(&config, &mut args).unwrap(), CloudOptions { - php: PhpVersion::Php74, + php: PhpVersion::Php80, db: Database::Postgres, services: vec![Service::Ldap(Ldap), Service::LdapAdmin(LdapAdmin)], ..Default::default() } ); - let mut args = vec!["7", "pgsql", "ldap"].into_iter().peekable(); + let mut args = vec!["8", "pgsql", "ldap"].into_iter().peekable(); assert_eq!( CloudOptions::parse(&config, &mut args).unwrap(), CloudOptions { - php: PhpVersion::Php74, + php: PhpVersion::Php80, db: Database::Postgres, services: vec![Service::Ldap(Ldap), Service::LdapAdmin(LdapAdmin)], ..Default::default() } ); - let mut args = vec!["7", "pgsql", "ldap", "mypreset"] + let mut args = vec!["8", "pgsql", "ldap", "mypreset"] .into_iter() .peekable(); @@ -198,7 +206,7 @@ fn test_option_parse() { assert_eq!( CloudOptions::parse(&config, &mut args).unwrap(), CloudOptions { - php: PhpVersion::Php74, + php: PhpVersion::Php80, db: Database::Postgres, services: vec![ Service::Ldap(Ldap), @@ -210,7 +218,7 @@ fn test_option_parse() { ); } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Cloud { pub id: String, pub network: String, @@ -245,6 +253,12 @@ impl Cloud { .wrap_err("Failed to create directory for app packages")?; } + let source_root = if let Some(version) = options.version.as_deref() { + download_nc(config, version).await? + } else { + config.sources_root.clone() + }; + let app_volumes = options .app_packages .iter() @@ -272,11 +286,8 @@ impl Cloud { }) }) .collect::>>()?; - let mappings = config - .volume - .iter() - .map(Mapping::from) - .chain(default_mappings()) + + let mappings = for_config(config) .chain(app_volumes.iter().map(Mapping::from)) .collect::>(); for mapping in &mappings { @@ -286,9 +297,56 @@ impl Cloud { .wrap_err_with(|| format!("Failed to setup work directory {}", mapping.source))?; } + let mut nc_config = Value::Object( + config + .auto_setup + .config + .clone() + .into_iter() + .map(|(key, value)| (key, serde_json::to_value(value).unwrap())) + .collect(), + ); + nc_config["apps_paths"] = Value::Array( + once("apps") + .chain( + config + .app_directories + .iter() + .filter_map(|dir| dir.file_name()), + ) + .map(|name| { + [ + ( + String::from("path"), + Value::from(format!("/var/www/html/{}", name)), + ), + (String::from("url"), Value::from(format!("/{}", name))), + (String::from("writable"), Value::from(false)), + ] + .into_iter() + .collect() + }) + .chain(once( + [ + (String::from("path"), Value::from("/var/www/store_apps")), + (String::from("url"), Value::from("/store_apps")), + (String::from("writable"), Value::from(true)), + ] + .into_iter() + .collect(), + )) + .collect(), + ); + write( + workdir.join("config/nextcloud.json"), + serde_json::to_string_pretty(&nc_config).unwrap(), + ) + .into_diagnostic() + .wrap_err("Failed to write config json")?; + let network = docker - .create_network(CreateNetworkOptions { - name: id.as_str(), + .create_network(NetworkCreateRequest { + name: id.clone(), ..Default::default() }) .await @@ -296,7 +354,7 @@ impl Cloud { .id; let network_info = docker - .inspect_network::(&network, None) + .inspect_network(&network, None) .await .into_diagnostic()?; let gateway = network_info @@ -327,7 +385,7 @@ impl Cloud { ]; let volumes: Vec = mappings .into_iter() - .filter_map(|mapping| mapping.get_volume_arg(&id, config)) + .filter_map(|mapping| mapping.get_volume_arg(&id, config, &source_root)) .collect(); if let Some(db_name) = options @@ -382,6 +440,7 @@ impl Cloud { gateway, &options.services, &config.proxy, + options.version.as_deref(), ) .await .wrap_err("Failed to start php container") @@ -439,9 +498,26 @@ impl Cloud { } }; + for pre_setup in options + .services + .iter() + .flat_map(|service| service.pre_setup(docker, &id, config).into_iter().flatten()) + { + exec( + docker, + &container, + &uid.to_string(), + pre_setup, + vec!["NC_IS_CONFIG_READ_ONLY=1"], + Some(stdout()), + ) + .await?; + } + containers.push(container); let options_clone = options.clone(); + let proxy_config = config.proxy.clone(); let cloud_id = id.clone(); let docker_clone = docker.clone(); spawn(async move { @@ -455,7 +531,10 @@ impl Cloud { return; } for service in options_clone.services { - match service.start_message(&docker_clone, &cloud_id).await { + match service + .start_message(&docker_clone, &cloud_id, &proxy_config) + .await + { Ok(Some(msg)) => { println!("{}", msg); } @@ -484,6 +563,9 @@ impl Cloud { pub async fn destroy(self, docker: &Docker) -> Result<()> { for container in self.containers { + let _ = docker + .kill_container(container.trim_start_matches('/'), None) + .await; docker .remove_container( container.trim_start_matches('/'), @@ -583,7 +665,7 @@ impl Cloud { config: &HazeConfig, ) -> Result> { let containers = docker - .list_containers::(Some(ListContainersOptions { + .list_containers(Some(ListContainersOptions { all: true, ..Default::default() })) @@ -620,6 +702,7 @@ impl Cloud { let labels = cloud.labels?; let db = labels.get("haze-db")?.parse().ok()?; let php = labels.get("haze-php")?.parse().ok()?; + let version = labels.get("haze-version").cloned(); let found_services = labels .get("haze-services")? @@ -665,6 +748,7 @@ impl Cloud { db, services: found_services, app_packages: vec![], + version, }, pinned, address, @@ -735,9 +819,9 @@ impl Cloud { docker .update_container( &self.id, - UpdateContainerOptions:: { + ContainerUpdateBody { memory: Some(PHP_MEMORY_LIMIT + 1), - ..UpdateContainerOptions::default() + ..ContainerUpdateBody::default() }, ) .await @@ -749,9 +833,9 @@ impl Cloud { docker .update_container( &self.id, - UpdateContainerOptions:: { + ContainerUpdateBody { memory: Some(PHP_MEMORY_LIMIT), - ..UpdateContainerOptions::default() + ..ContainerUpdateBody::default() }, ) .await @@ -777,20 +861,25 @@ impl Cloud { format!("/var/www/html/{path}").into() }; - let mut mappings = config - .volume - .iter() - .map(Mapping::from) - .chain(default_mappings()) - .collect::>(); + let mut mappings = for_config(config).collect::>(); mappings.sort_by_key(|mapping| usize::MAX - mapping.target.as_str().len()); for mapping in mappings { if let Some(rel_path) = path.strip_prefix(mapping.target.as_str()) { let rel_path = rel_path.trim_matches('/'); - return Some(mapping.source(&self.id, config).join(rel_path)); + return Some( + mapping + .source(&self.id, config, &config.sources_root) + .join(rel_path), + ); } } None } } + +impl PartialEq for Cloud { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} diff --git a/src/config.rs b/src/config.rs index 07cfcef..9e4548f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,15 +4,17 @@ use miette::{IntoDiagnostic, Report, Result, WrapErr}; use serde::Deserialize; use std::collections::HashMap; use std::convert::TryFrom; -use std::env::var; +use std::env::home_dir; use std::fs::read_to_string; use std::net::IpAddr; +use toml::map::Map; use toml::Value; #[derive(Debug, Deserialize, Default)] #[serde(from = "RawHazeConfig")] pub struct HazeConfig { pub sources_root: Utf8PathBuf, + pub app_directories: Vec, pub work_dir: Utf8PathBuf, pub auto_setup: HazeAutoSetupConfig, pub volume: Vec, @@ -27,6 +29,8 @@ pub struct RawHazeConfig { #[serde(default = "default_work_dir")] pub work_dir: Utf8PathBuf, #[serde(default)] + pub app_directories: Vec, + #[serde(default)] pub auto_setup: HazeAutoSetupConfig, #[serde(default)] pub volume: Vec, @@ -42,7 +46,11 @@ impl From for HazeConfig { fn from(raw: RawHazeConfig) -> Self { fn normalize_path(path: Utf8PathBuf) -> Utf8PathBuf { if path.starts_with("~") { - let home = var("HOME").expect("HOME not set"); + let home = home_dir().expect("can't detect home directory"); + let home = home + .into_os_string() + .into_string() + .expect("non-utf8 home directory"); format!("{}{}", home, &path.as_str()[1..]).into() } else { path @@ -51,6 +59,11 @@ impl From for HazeConfig { HazeConfig { sources_root: normalize_path(raw.sources_root), + app_directories: raw + .app_directories + .into_iter() + .map(normalize_path) + .collect(), work_dir: normalize_path(raw.work_dir), auto_setup: raw.auto_setup, volume: raw.volume, @@ -75,6 +88,8 @@ pub struct HazeAutoSetupConfig { pub disable_apps: Vec, #[serde(default)] pub post_setup: Vec, + #[serde(default)] + pub config: Map, } impl Default for HazeAutoSetupConfig { @@ -86,6 +101,7 @@ impl Default for HazeAutoSetupConfig { enable_apps: Vec::default(), disable_apps: Vec::default(), post_setup: Vec::default(), + config: Map::default(), } } } @@ -169,7 +185,7 @@ fn load_secret(name: &str, path: Option, raw: Option) -> Result< } } -#[derive(Default, Deserialize, Debug)] +#[derive(Default, Deserialize, Debug, Clone)] pub struct ProxyConfig { pub listen: String, #[serde(default)] diff --git a/src/database.rs b/src/database.rs index d621e84..ff969a3 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,7 +1,8 @@ use crate::exec::{exec, exec_tty, ExitCode}; use crate::image::pull_image; -use bollard::container::{Config, CreateContainerOptions, NetworkingConfig}; -use bollard::models::{EndpointSettings, HostConfig}; +use bollard::config::ContainerCreateBody; +use bollard::models::{EndpointSettings, HostConfig, NetworkingConfig}; +use bollard::query_parameters::CreateContainerOptions; use bollard::Docker; use maplit::hashmap; use miette::{IntoDiagnostic, Report, Result, WrapErr}; @@ -193,35 +194,35 @@ impl Database { .wrap_err("Failed to pull database image")?; } let options = Some(CreateContainerOptions { - name: format!("{}-db{}", cloud_id, postfix), + name: Some(format!("{}-db{}", cloud_id, postfix)), ..CreateContainerOptions::default() }); - let config = Config { - image: Some(self.image()), - env: Some(self.env()), + let config = ContainerCreateBody { + image: Some(self.image().into()), + env: Some(self.env().into_iter().map(String::from).collect()), host_config: Some(HostConfig { network_mode: Some(network.to_string()), ..Default::default() }), labels: Some(hashmap! { - "haze-type" => "db", - "haze-cloud-id" => cloud_id + "haze-type".into() => "db".into(), + "haze-cloud-id".into() => cloud_id.into() }), networking_config: Some(NetworkingConfig { - endpoints_config: hashmap! { - network => EndpointSettings { + endpoints_config: Some(hashmap! { + String::from(network) => EndpointSettings { aliases: Some(vec![ format!("{}{}", self.name(), postfix), format!("db{}", postfix), ]), ..Default::default() } - }, + }), }), cmd: if self.image() == "mysql:8" { Some(vec![ - "--default-authentication-plugin", - "mysql_native_password", + "--default-authentication-plugin".into(), + "mysql_native_password".into(), ]) } else { None @@ -233,10 +234,7 @@ impl Database { .await .into_diagnostic()? .id; - docker - .start_container::(&id, None) - .await - .into_diagnostic()?; + docker.start_container(&id, None).await.into_diagnostic()?; Ok(Some(id)) } diff --git a/src/exec.rs b/src/exec.rs index ec67333..8d07508 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -1,5 +1,5 @@ -use bollard::container::LogsOptions; use bollard::exec::{CreateExecOptions, ResizeExecOptions, StartExecResults}; +use bollard::query_parameters::LogsOptions; use bollard::Docker; use futures_util::StreamExt; use miette::{IntoDiagnostic, Report, Result, WrapErr}; @@ -189,7 +189,7 @@ pub async fn container_logs( count: usize, follow: bool, ) -> Result<()> { - let mut stream = docker.logs::( + let mut stream = docker.logs( container, Some(LogsOptions { stdout: true, diff --git a/src/git.rs b/src/git.rs index d08f2f3..fc31255 100644 --- a/src/git.rs +++ b/src/git.rs @@ -1,22 +1,33 @@ +use crate::config::HazeConfig; use crate::Result; use git2::build::CheckoutBuilder; use git2::{Branch, BranchType, Repository, RepositoryState}; +use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use miette::{Context, IntoDiagnostic}; +use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; +use rayon::ThreadPoolBuilder; use std::fs::read_dir; -use std::path::{Path, PathBuf}; +use std::iter::once; +use std::path::PathBuf; use std::process::Command; +use std::time::Duration; -fn find_app_repos(root: impl AsRef) -> Result> { - let apps_dir = root.as_ref().join("apps"); - Ok(read_dir(apps_dir) - .into_diagnostic()? +fn find_app_repos(config: &HazeConfig) -> Result> { + let apps_dirs = once(config.sources_root.as_path().join("apps")) + .chain(config.app_directories.iter().cloned()); + let dir_handles = apps_dirs + .map(|dir| read_dir(dir).into_diagnostic()) + .collect::>>()?; + Ok(dir_handles + .into_iter() + .flatten() .flatten() .filter(|app| app.path().join(".git").is_dir()) .map(|app| app.path())) } -fn longest_app_branch(root: impl AsRef) -> Result<(usize, usize)> { - Ok(find_app_repos(root)? +fn longest_app_branch(config: &HazeConfig) -> Result<(usize, usize)> { + Ok(find_app_repos(config)? .filter_map(|app_dir| { let app_name = app_dir.file_name()?.to_str()?; let repo = Repository::init(&app_dir).ok()?; @@ -27,27 +38,32 @@ fn longest_app_branch(root: impl AsRef) -> Result<(usize, usize)> { .unwrap_or_default()) } -pub fn checkout_all>(sources_root: P, mut name: &str) -> Result<()> { +pub fn checkout_all(config: &HazeConfig, mut name: &str, verbose: bool) -> Result<()> { // "main" and "master" are interchangeable if name == "main" { name = "master"; } - for app_dir in find_app_repos(sources_root)? { + for app_dir in find_app_repos(config)? { let repo = Repository::init(&app_dir) .into_diagnostic() .wrap_err_with(|| format!("Failed to open repository {}", app_dir.display()))?; + let app_name = app_dir.file_name().unwrap().to_string_lossy(); if let Some(branch) = get_branch(&repo, name)? { if !branch.is_head() { let is_remote = branch.get().is_remote(); - print!("{}", app_dir.file_name().unwrap().to_string_lossy()); + print!("{app_name}"); if let Err(e) = checkout(&repo, &branch, is_remote.then_some(name)) { println!(": {:#} ❌", e); } else { println!(" ✓"); } + } else if verbose { + println!("{app_name} -"); } + } else if verbose { + println!("{app_name} 🛇 Branch not found"); }; } Ok(()) @@ -68,42 +84,69 @@ const GIT_BINARY: &str = match option_env!("GIT_BINARY") { None => "git", }; -pub fn pull_all>(sources_root: P) -> Result<()> { - let sources_root = sources_root.as_ref(); - let (max_app, max_branch) = longest_app_branch(sources_root)?; +pub fn pull_all(config: &HazeConfig) -> Result<()> { + let (max_app, max_branch) = longest_app_branch(config)?; - for app_dir in find_app_repos(sources_root)? { - let app_name = app_dir.file_name().unwrap().to_string_lossy(); - let repo = Repository::init(&app_dir) - .into_diagnostic() - .wrap_err_with(|| format!("Failed to open repository {}", app_dir.display()))?; - let branch_name = current_branch_name(&repo).unwrap_or("unknown".into()); + let progress = MultiProgress::new(); + let pull_style = ProgressStyle::with_template("{spinner:.green} {msg}").unwrap(); - print!( - "{app_name:>(); - if repo.state() != RepositoryState::Clean { - println!(": repository not clean ❌"); - continue; - } + pool.install(|| { + repos.par_iter().for_each(|app_dir| { + let app_name = app_dir.file_name().unwrap().to_string_lossy(); + let Ok(repo) = Repository::init(app_dir) else { + return; + }; + let branch_name = current_branch_name(&repo).unwrap_or("unknown".into()); - let output = Command::new(GIT_BINARY) - .arg("pull") - .current_dir(&app_dir) - .output() - .into_diagnostic() - .wrap_err_with(|| format!("Failed to run git pull for {}", app_dir.display()))?; + let bar = ProgressBar::new_spinner().with_style(pull_style.clone()); + bar.enable_steady_tick(Duration::from_millis(100)); + let bar = progress.add(bar); + + let msg = |state: &str| { + format!( + "{app_name: output, + Err(error) => { + bar.set_message(msg(&format!(" ⨯ {error}"))); + return; + } + }; + + if output.status.success() { + bar.set_message(msg(" ✓")); + } else { + let err = String::from_utf8_lossy(&output.stderr); + let err_line = err.lines().next().unwrap(); + bar.set_message(msg(&format!(" ⨯ {err_line}"))); + } + bar.finish(); + }); + }); - if output.status.success() { - println!(" ✓"); - } else { - println!(" ❌"); - eprintln!("{}", String::from_utf8_lossy(&output.stderr)) - } - } Ok(()) } diff --git a/src/help.rs b/src/help.rs index f868bd6..97af8c8 100644 --- a/src/help.rs +++ b/src/help.rs @@ -88,6 +88,7 @@ fn subcommand_help(command: &dyn SubCommand) { print!(" {}", "[php version]".green()); print!(" {}", "[database type]".green()); print!(" {}", "[services]".green()); + print!(" {}", "[vX.Y.Z]".green()); } let args = if let Some(args) = command.get_str("Args") { diff --git a/src/image.rs b/src/image.rs index 2ea9b7d..e24abfd 100644 --- a/src/image.rs +++ b/src/image.rs @@ -1,17 +1,52 @@ -use bollard::image::CreateImageOptions; use bollard::models::CreateImageInfo; +use bollard::query_parameters::CreateImageOptions; use bollard::Docker; use futures_util::StreamExt; +use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use miette::{IntoDiagnostic, Result, WrapErr}; use std::collections::HashMap; -use std::io::stdout; -use std::io::Write; -use termion::cursor; +use std::fmt::{Display, Formatter}; +use std::str::FromStr; + +#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)] +pub struct ImageVersion { + pub major: u8, + pub minor: u8, + pub patch: u8, +} + +impl FromStr for ImageVersion { + type Err = (); + + fn from_str(s: &str) -> std::result::Result { + let mut parts = s.split('.'); + let major = parts.next().ok_or(())?.parse().map_err(|_| ())?; + let minor = parts.next().ok_or(())?.parse().map_err(|_| ())?; + let patch = parts.next().ok_or(())?.parse().map_err(|_| ())?; + Ok(ImageVersion { + major, + minor, + patch, + }) + } +} + +impl Display for ImageVersion { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}.{}.{}", self.major, self.minor, self.patch) + } +} pub async fn image_exists(docker: &Docker, image: &str) -> bool { docker.inspect_image(image).await.is_ok() } +pub async fn image_version(docker: &Docker, image: &str) -> Option { + let labels = docker.inspect_image(image).await.ok()?.config?.labels?; + let label = labels.get("nl.icewind.haze.version")?; + ImageVersion::from_str(label).ok() +} + pub async fn update_image(docker: &Docker, image: &str) -> Result<()> { if image_exists(docker, image).await { force_pull_image(docker, image).await?; @@ -32,9 +67,9 @@ pub async fn force_pull_image(docker: &Docker, image: &str) -> Result<()> { let mut info_stream = docker.create_image( Some(CreateImageOptions { from_image: if image.contains(':') { - image.to_string() + Some(image.to_string()) } else { - format!("{}:latest", image) + Some(format!("{}:latest", image)) }, ..Default::default() }), @@ -42,35 +77,33 @@ pub async fn force_pull_image(docker: &Docker, image: &str) -> Result<()> { None, ); - let mut bars: HashMap = HashMap::new(); + let bar_style = ProgressStyle::with_template( + "{spinner:.green} {msg} [{elapsed_precise}] [{bar:.cyan/blue}] {bytes:>12}/{total_bytes}", + ) + .unwrap(); + let mut bars: HashMap = HashMap::new(); + let mp = MultiProgress::new(); - let mut stdout = stdout(); while let Some(info) = info_stream.next().await { let info: CreateImageInfo = info .into_diagnostic() .wrap_err_with(|| format!("Error while pulling image {}", image))?; - if let (Some(id), Some(status), Some(progress)) = (info.id, info.status, info.progress) { - match bars.get(&id) { - Some(pos) => { - let offset = bars.len() as u16 - pos; - write!( - stdout, - "{}{}{} - {:12} {}{}", - cursor::Save, - cursor::Up(offset), - id, - status, - progress, - cursor::Restore - ) - .into_diagnostic()?; - } - None => { - writeln!(stdout, "{} - {:12} {}", id, status, progress).into_diagnostic()?; - bars.insert(id, bars.len() as u16); - } + if let (Some(id), Some(status), Some(progress)) = + (info.id, info.status, info.progress_detail) + { + let bar = bars.entry(id.clone()).or_insert_with(|| { + let bar = ProgressBar::new(progress.total.unwrap_or_default() as u64) + .with_style(bar_style.clone()) + .with_message(format!("{id:20} - {status:10}")); + mp.add(bar) + }); + bar.set_message(format!("{id:10} - {status:20}")); + if let Some(total) = progress.total { + bar.set_length(total as u64); + } + if let Some(current) = progress.current { + bar.set_position(current as u64); } - stdout.flush().into_diagnostic()?; } } Ok(()) diff --git a/src/main.rs b/src/main.rs index 091afd0..a58e922 100644 --- a/src/main.rs +++ b/src/main.rs @@ -103,13 +103,18 @@ async fn main() -> Result { services.push(cloud.db().name()); let services = services.join(", "); let pin = if cloud.pinned { "*" } else { "" }; + let version = match cloud.options.version.as_ref() { + Some(version) => format!(", v{version}"), + None => String::new(), + }; println!( - "Cloud {}{}, {}, {}, running on {}", + "Cloud {}{}, {}, {}{}, running on {}", cloud.id, pin, cloud.php().name(), services, - cloud.address + version, + cloud.address, ); } } @@ -269,26 +274,8 @@ async fn main() -> Result { return Ok(result.into()); } HazeArgs::Integration { options, mut args } => { - let cloud = Cloud::create(&docker, options, &config).await?; - println!("Waiting for servers to start"); - cloud.wait_for_start(&docker).await?; - println!("Installing"); - if let Err(e) = cloud - .exec( - &docker, - vec![ - "install", - &config.auto_setup.username, - &config.auto_setup.password, - ], - false, - Vec::::default(), - ) - .await - { - cloud.destroy(&docker).await?; - return Err(e); - } + let cloud = setup(&docker, options, &config).await?; + args.insert(0, "integration".to_string()); cloud.exec(&docker, args, false, get_forward_env()).await?; cloud.destroy(&docker).await?; @@ -386,15 +373,16 @@ async fn main() -> Result { proxy(docker, config).await?; } HazeArgs::Git { operation } => match operation { - GitOperation::Checkout { branch } => { + GitOperation::Checkout { branch, verbose } => { let sources = Sources::new(&config.sources_root)?; checkout_all( - &config.sources_root, + &config, &branch.unwrap_or_else(|| sources.get_server_version_branch()), + verbose, )?; } GitOperation::Pull => { - pull_all(&config.sources_root)?; + pull_all(&config)?; } }, HazeArgs::Env { @@ -433,16 +421,16 @@ async fn main() -> Result { ); if cloud.services().contains(&Service::RedisTls(RedisTls)) { - create_dir_all(config.work_dir.join("redis_certificates")) + create_dir_all(config.work_dir.join("certificates/redis")) .into_diagnostic() .wrap_err("Failed to create redis certificate directory")?; - let redis_cert_path = config.work_dir.join("redis_certificates/client.cert"); - let redis_key_path = config.work_dir.join("redis_certificates/client.key"); - let redis_ca_path = config.work_dir.join("redis_certificates/ca.cert"); + let redis_cert_path = config.work_dir.join("certificates/redis/client.cert"); + let redis_key_path = config.work_dir.join("certificates/redis/client.key"); + let redis_ca_path = config.work_dir.join("certificates/redis/ca.cert"); if !redis_cert_path.exists() { write( &redis_cert_path, - include_bytes!("../redis-certificates/client.crt"), + include_bytes!("../certificates/redis/client.crt"), ) .into_diagnostic() .wrap_err("Failed to write redis client certificate")?; @@ -450,7 +438,7 @@ async fn main() -> Result { if !redis_key_path.exists() { write( &redis_key_path, - include_bytes!("../redis-certificates/client.key"), + include_bytes!("../certificates/redis/client.key"), ) .into_diagnostic() .wrap_err("Failed to write redis client key")?; @@ -458,7 +446,7 @@ async fn main() -> Result { if !redis_ca_path.exists() { write( &redis_ca_path, - include_bytes!("../redis-certificates/ca.crt"), + include_bytes!("../certificates/redis/ca.crt"), ) .into_diagnostic() .wrap_err("Failed to write redis ca certificate")?; @@ -645,12 +633,7 @@ async fn setup(docker: &Docker, options: CloudOptions, config: &HazeConfig) -> R for service in cloud.services() { for cmd in service.post_setup(docker, &cloud.id, config).await? { cloud - .exec( - docker, - shell_words::split(&cmd).into_diagnostic()?, - false, - Vec::::default(), - ) + .exec(docker, cmd, false, Vec::::default()) .await?; } } diff --git a/src/mapping.rs b/src/mapping.rs index f2a93a3..9c01da0 100644 --- a/src/mapping.rs +++ b/src/mapping.rs @@ -1,13 +1,14 @@ use crate::config::{HazeConfig, HazeVolumeConfig}; use camino::{Utf8Path, Utf8PathBuf}; use miette::{IntoDiagnostic, Result}; +use std::borrow::Cow; use tokio::fs::{create_dir_all, write}; #[derive(Debug)] pub struct Mapping<'a> { source_type: MappingSourceType, - pub source: &'a Utf8Path, - pub target: &'a Utf8Path, + pub source: Cow<'a, Utf8Path>, + pub target: Cow<'a, Utf8Path>, mapping_type: MappingType, read_only: bool, map: bool, @@ -23,6 +24,26 @@ impl<'a> Mapping<'a> { where Target: Into<&'a Utf8Path>, Source: Into<&'a Utf8Path>, + { + Mapping { + source_type, + source: Cow::Borrowed(source.into()), + target: Cow::Borrowed(target.into()), + mapping_type: MappingType::Folder, + read_only: false, + map: true, + create: true, + } + } + + pub fn owned( + source_type: MappingSourceType, + source: Source, + target: Target, + ) -> Self + where + Target: Into>, + Source: Into>, { Mapping { source_type, @@ -65,10 +86,10 @@ impl<'a> Mapping<'a> { return Ok(()); } let source = match self.source_type { - MappingSourceType::WorkDir => config.work_dir.join(id).join(self.source), - MappingSourceType::GlobalWorkDir => config.work_dir.join(self.source), + MappingSourceType::WorkDir => config.work_dir.join(id).join(self.source.as_ref()), + MappingSourceType::GlobalWorkDir => config.work_dir.join(self.source.as_ref()), MappingSourceType::Sources => return Ok(()), - MappingSourceType::Absolute => self.source.into(), + MappingSourceType::Absolute => self.source.as_ref().into(), }; match self.mapping_type { MappingType::Folder => create_dir_all(source).await.into_diagnostic()?, @@ -78,20 +99,25 @@ impl<'a> Mapping<'a> { Ok(()) } - pub fn source(&self, id: &str, config: &HazeConfig) -> Utf8PathBuf { + pub fn source(&self, id: &str, config: &HazeConfig, source_root: &Utf8Path) -> Utf8PathBuf { match self.source_type { - MappingSourceType::WorkDir => config.work_dir.join(id).join(self.source), - MappingSourceType::GlobalWorkDir => config.work_dir.join(self.source), - MappingSourceType::Sources => config.sources_root.join(self.source), - MappingSourceType::Absolute => self.source.into(), + MappingSourceType::WorkDir => config.work_dir.join(id).join(self.source.as_ref()), + MappingSourceType::GlobalWorkDir => config.work_dir.join(self.source.as_ref()), + MappingSourceType::Sources => source_root.join(self.source.as_ref()), + MappingSourceType::Absolute => self.source.as_ref().into(), } } - pub fn get_volume_arg(&self, id: &str, config: &HazeConfig) -> Option { + pub fn get_volume_arg( + &self, + id: &str, + config: &HazeConfig, + source_root: &Utf8Path, + ) -> Option { if !self.map { return None; } - let source = self.source(id, config); + let source = self.source(id, config, source_root); Some(if self.read_only { format!("{}:{}:ro", source, self.target) } else { @@ -107,6 +133,7 @@ pub fn default_mappings<'a>() -> impl IntoIterator> { Mapping::new(Sources, "", "/var/www/html"), Mapping::new(WorkDir, "data", "/var/www/html/data"), Mapping::new(WorkDir, "config", "/var/www/html/config"), + Mapping::new(WorkDir, "store_apps", "/var/www/store_apps"), Mapping::new(WorkDir, "data-autotest", "/var/www/html/data-autotest"), Mapping::new(WorkDir, "skeleton", "/var/www/html/core/skeleton"), Mapping::new( @@ -161,11 +188,32 @@ pub fn default_mappings<'a>() -> impl IntoIterator> { .dont_create(), Mapping::new(WorkDir, "xdebug", "/tmp/xdebug"), Mapping::new(WorkDir, "profiling", "/tmp/profiling"), - Mapping::new(WorkDir, "php.ini", "/php.ini").file(), + Mapping::new(WorkDir, "php-config", "/config"), ]; + IntoIterator::into_iter(mappings) } +pub fn for_config<'a>(config: &'a HazeConfig) -> impl Iterator> { + let app_dir_mappings = config.app_directories.iter().map(|dir| { + Mapping::owned( + MappingSourceType::Absolute, + dir.as_path(), + Cow::Owned(Utf8PathBuf::from(format!( + "/var/www/html/{}", + dir.file_name().unwrap() + ))), + ) + }); + + config + .volume + .iter() + .map(Mapping::from) + .chain(app_dir_mappings) + .chain(default_mappings()) +} + #[derive(Debug, Copy, Clone)] pub enum MappingSourceType { Sources, @@ -189,8 +237,8 @@ impl<'a> From<&'a HazeVolumeConfig> for Mapping<'a> { }; Mapping { source_type: MappingSourceType::Absolute, - source: config.source.as_path(), - target: config.target.as_path(), + source: Cow::Borrowed(config.source.as_path()), + target: Cow::Borrowed(config.target.as_path()), mapping_type: ty, read_only: config.read_only, map: true, diff --git a/src/network.rs b/src/network.rs index ea589ec..0b25f2c 100644 --- a/src/network.rs +++ b/src/network.rs @@ -1,11 +1,11 @@ use crate::cloud::Cloud; -use bollard::network::CreateNetworkOptions; +use bollard::config::NetworkCreateRequest; use bollard::Docker; use miette::{IntoDiagnostic, Result, WrapErr}; pub async fn clear_networks(docker: &Docker, instances: &[Cloud]) -> Result<()> { let networks = docker - .list_networks::<&str>(None) + .list_networks(None) .await .into_diagnostic() .wrap_err("Failed to list docker networks")?; @@ -23,7 +23,7 @@ pub async fn clear_networks(docker: &Docker, instances: &[Cloud]) -> Result<()> async fn get_network_id(docker: &Docker, name: &str) -> Result> { let networks = docker - .list_networks::<&str>(None) + .list_networks(None) .await .into_diagnostic() .wrap_err("Failed to list docker networks")?; @@ -41,9 +41,8 @@ pub async fn ensure_network_exists(docker: &Docker, name: &str) -> Result Result { match s { - "7" => Ok(PhpVersion::Php74), - "7.3" => Ok(PhpVersion::Php73), - "7.4" => Ok(PhpVersion::Php74), - "8" => Ok(PhpVersion::Php81), + "8" => Ok(PhpVersion::Php80), "8.0" => Ok(PhpVersion::Php80), "8.1" => Ok(PhpVersion::Php81), "8.2" => Ok(PhpVersion::Php82), "8.3" => Ok(PhpVersion::Php83), "8.4" => Ok(PhpVersion::Php84), - "7-dbg" => Ok(PhpVersion::Php74Dbg), - "7.3-dbg" => Ok(PhpVersion::Php73Dbg), - "7.4-dbg" => Ok(PhpVersion::Php74Dbg), - "8-dbg" => Ok(PhpVersion::Php80Dbg), - "8.0-dbg" => Ok(PhpVersion::Php80Dbg), - "8.1-dbg" => Ok(PhpVersion::Php81Dbg), - "8.2-dbg" => Ok(PhpVersion::Php82Dbg), - "8.3-dbg" => Ok(PhpVersion::Php83Dbg), - "8.4-dbg" => Ok(PhpVersion::Php84Dbg), + "8.5" => Ok(PhpVersion::Php85), _ => Err(()), } } @@ -73,53 +55,35 @@ impl PhpVersion { } pub fn image(&self) -> &'static str { - // for now only 7.4 match self { - PhpVersion::Php73 => "icewind1991/haze:7.3", - PhpVersion::Php74 => "icewind1991/haze:7.4", PhpVersion::Php80 => "icewind1991/haze:8.0", PhpVersion::Php81 => "icewind1991/haze:8.1", PhpVersion::Php82 => "icewind1991/haze:8.2", PhpVersion::Php83 => "icewind1991/haze:8.3", PhpVersion::Php84 => "icewind1991/haze:8.4", - PhpVersion::Php73Dbg => "icewind1991/haze:7.3-dbg", - PhpVersion::Php74Dbg => "icewind1991/haze:7.4-dbg", - PhpVersion::Php80Dbg => "icewind1991/haze:8.0-dbg", - PhpVersion::Php81Dbg => "icewind1991/haze:8.1-dbg", - PhpVersion::Php82Dbg => "icewind1991/haze:8.2-dbg", - PhpVersion::Php83Dbg => "icewind1991/haze:8.3-dbg", - PhpVersion::Php84Dbg => "icewind1991/haze:8.4-dbg", + PhpVersion::Php85 => "icewind1991/haze:8.5", } } pub fn name(&self) -> &'static str { match self { - PhpVersion::Php73 => "7.3", - PhpVersion::Php74 => "7.4", PhpVersion::Php80 => "8.0", PhpVersion::Php81 => "8.1", PhpVersion::Php82 => "8.2", PhpVersion::Php83 => "8.3", PhpVersion::Php84 => "8.4", - PhpVersion::Php73Dbg => "7.3-dbg", - PhpVersion::Php74Dbg => "7.4-dbg", - PhpVersion::Php80Dbg => "8.0-dbg", - PhpVersion::Php81Dbg => "8.1-dbg", - PhpVersion::Php82Dbg => "8.2-dbg", - PhpVersion::Php83Dbg => "8.3-dbg", - PhpVersion::Php84Dbg => "8.4-dbg", + PhpVersion::Php85 => "8.4", } } pub fn from_number(major: u8, minor: u8) -> Option { match (major, minor) { - (7, 3) => Some(PhpVersion::Php73), - (7, 4) => Some(PhpVersion::Php74), (8, 0) => Some(PhpVersion::Php80), (8, 1) => Some(PhpVersion::Php81), (8, 2) => Some(PhpVersion::Php82), (8, 3) => Some(PhpVersion::Php83), (8, 4) => Some(PhpVersion::Php84), + (8, 5) => Some(PhpVersion::Php85), _ => None, } } @@ -127,27 +91,19 @@ impl PhpVersion { pub fn max_minor(major: u8) -> u8 { match major { 7 => 4, - 8 => 4, + 8 => 5, _ => 0, } } pub fn all() -> impl Iterator { [ - PhpVersion::Php73, - PhpVersion::Php74, PhpVersion::Php80, PhpVersion::Php81, PhpVersion::Php82, PhpVersion::Php83, PhpVersion::Php84, - PhpVersion::Php73Dbg, - PhpVersion::Php74Dbg, - PhpVersion::Php80Dbg, - PhpVersion::Php81Dbg, - PhpVersion::Php82Dbg, - PhpVersion::Php83Dbg, - PhpVersion::Php84Dbg, + PhpVersion::Php85, ] .into_iter() } @@ -164,11 +120,23 @@ impl PhpVersion { host: &str, services: &[Service], proxy_config: &ProxyConfig, + version: Option<&str>, ) -> Result { ensure_network_exists(docker, "haze").await?; pull_image(docker, self.image()).await?; + + let image_version = image_version(docker, self.image()).await; + let haze_version = ImageVersion::from_str(env!("CARGO_PKG_VERSION")); + if let (Some(image_version), Ok(haze_version)) = (image_version, haze_version) { + if image_version < haze_version { + eprintln!("{}: image version is out of date, run {} to update.", "Warning".red(), "haze update".blue()); + eprintln!(" Haze version: {}", haze_version.bright_yellow()); + eprintln!(" Image version: {}", image_version.bright_yellow()); + } + } + let options = Some(CreateContainerOptions { - name: id.to_string(), + name: Some(id.to_string()), ..CreateContainerOptions::default() }); let clean_id = id.strip_prefix("haze-").unwrap_or(id); @@ -192,7 +160,23 @@ impl PhpVersion { proxy_config.addr(id, IpAddr::V4(Ipv4Addr::LOCALHOST)) )); - let config = Config { + env.push(format!("HOST_IP={host}")); + if !proxy_config.address.is_empty() { + env.push(format!("PROXY_BASE={}", proxy_config.address)); + } + + let mut labels = hashmap! { + "haze-type".to_string() => "cloud".to_string(), + "haze-db".to_string() => db.name().to_string(), + "haze-php".to_string() => self.name().to_string(), + "haze-cloud-id".to_string() => id.to_string(), + "haze-services".to_string() => services.iter().map(|s| s.name()).join(","), + }; + if let Some(version) = version { + labels.insert("haze-version".to_string(), version.to_string()); + } + + let config = ContainerCreateBody { image: Some(self.image().to_string()), env: Some(env), host_config: Some(HostConfig { @@ -204,20 +188,14 @@ impl PhpVersion { ..Default::default() }), networking_config: Some(NetworkingConfig { - endpoints_config: hashmap! { + endpoints_config: Some(hashmap! { network.to_string() => EndpointSettings { aliases: Some(vec!["cloud".to_string()]), ..Default::default() } - }, - }), - labels: Some(hashmap! { - "haze-type".to_string() => "cloud".to_string(), - "haze-db".to_string() => db.name().to_string(), - "haze-php".to_string() => self.name().to_string(), - "haze-cloud-id".to_string() => id.to_string(), - "haze-services".to_string() => services.iter().map(|s| s.name()).join(","), + }), }), + labels: Some(labels), ..Default::default() }; @@ -227,11 +205,7 @@ impl PhpVersion { .into_diagnostic()? .id; - if let Err(e) = docker - .start_container::(&id, None) - .await - .into_diagnostic() - { + if let Err(e) = docker.start_container(&id, None).await.into_diagnostic() { docker.remove_container(&id, None).await.ok(); return Err(e); } @@ -239,12 +213,12 @@ impl PhpVersion { if let Err(e) = docker .connect_network( "haze", - ConnectNetworkOptions { - container: id.as_str(), - endpoint_config: EndpointSettings { + NetworkConnectRequest { + container: id.to_string(), + endpoint_config: Some(EndpointSettings { aliases: Some(vec![id.to_string()]), ..Default::default() - }, + }), }, ) .await diff --git a/src/proxy.rs b/src/proxy.rs index ee867c9..4784a18 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -1,34 +1,43 @@ -use crate::service::ServiceTrait; +use crate::service::{ServiceTrait, ServiceType}; use crate::Result; use crate::{Cloud, HazeConfig}; use axum::http::header::HOST; use axum::http::HeaderValue; use axum::{ body::Body, - extract::{Request, State}, + extract::Request, response::{IntoResponse, Response}, - Router, }; use bollard::Docker; +use futures_util::StreamExt; +use hyper::body::Incoming; +use hyper::server::conn::http1; +use hyper::service::service_fn; use hyper::StatusCode; +use hyper_util::rt::TokioIo; use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor}; use miette::{miette, IntoDiagnostic}; use std::collections::HashMap; +use std::convert::Infallible; use std::fs::{create_dir_all, set_permissions}; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::os::unix::fs::PermissionsExt; use std::path::PathBuf; +use std::pin::pin; +use std::str::FromStr; use std::sync::{Arc, Mutex}; use std::time::Duration; +use tokio::io::{AsyncRead, AsyncWrite}; use tokio::net::UnixListener; use tokio::signal::ctrl_c; use tokio::spawn; use tokio::time::sleep; +use tokio_stream::wrappers::{TcpListenerStream, UnixListenerStream}; use tracing::{debug, error, info}; struct ActiveInstances { known: Mutex>, - last: Mutex>, + last: Mutex>, docker: Docker, config: HazeConfig, } @@ -48,15 +57,9 @@ impl ActiveInstances { return Some(ip); } - // service proxy - let addr = if name.matches('-').count() == 2 { - let (name, service_name) = name.rsplit_once('-').unwrap(); - let cloud = Cloud::get_by_filter(&self.docker, Some(name.into()), &self.config) - .await - .ok()?; - let service = cloud - .services() - .find(|service| service.name() == service_name)?; + let addr = if ServiceType::from_str(name).is_ok() { + let cloud = self.last()?; + let service = cloud.services().find(|service| service.name() == name)?; let ip = service .get_ips(&self.docker, &cloud.id) .await @@ -64,13 +67,34 @@ impl ActiveInstances { .next()?; SocketAddr::new(ip, service.proxy_port()) } else { - SocketAddr::new( - Cloud::get_by_filter(&self.docker, Some(name.into()), &self.config) - .await - .ok()? - .ip?, - 80, - ) + match name.matches('-').count() { + // instance + 1 => SocketAddr::new( + Cloud::get_by_filter(&self.docker, Some(name.into()), &self.config) + .await + .ok()? + .ip?, + 80, + ), + // service with instance + 2.. => { + let service_name = name.splitn(3, '-').last()?; + let name = &name[0..(name.len() - service_name.len() - 1)]; + let cloud = Cloud::get_by_filter(&self.docker, Some(name.into()), &self.config) + .await + .ok()?; + let service = cloud + .services() + .find(|service| service.name() == service_name)?; + let ip = service + .get_ips(&self.docker, &cloud.id) + .await + .ok()? + .next()?; + SocketAddr::new(ip, service.proxy_port()) + } + _ => return None, + } }; println!("{name} => {addr}"); @@ -79,18 +103,31 @@ impl ActiveInstances { Some(addr) } - pub fn last(&self) -> Option { - *self.last.lock().unwrap() + pub fn last_addr(&self) -> Option { + self.last + .lock() + .unwrap() + .as_ref() + .and_then(|cloud| Some(SocketAddr::new(cloud.ip?, 80))) + } + + pub fn last(&self) -> Option { + self.last.lock().unwrap().clone() } async fn update_last(&self) { let last = Cloud::get_by_filter(&self.docker, None, &self.config) .await - .ok() - .and_then(|cloud| Some(SocketAddr::new(cloud.ip?, 80))); + .ok(); let mut old = self.last.lock().unwrap(); if old.as_ref() != last.as_ref() { info!(instance = ?last, "Found new instance"); + + // remove cached base-service mappings + self.known + .lock() + .unwrap() + .retain(|key, _| ServiceType::from_str(key).is_err()); *old = last; } } @@ -134,20 +171,26 @@ async fn serve(instances: ActiveInstances, listen: String, base_address: String) ctrl_c().await.ok(); }; - let app = Router::new().fallback(handler).with_state(AppState { + let state = AppState { instances: instances.clone(), base_address: base_address.clone(), proxy_client: Arc::new(proxy_client), - }); + }; if !listen.starts_with('/') { let addr: SocketAddr = listen.parse().into_diagnostic()?; let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); println!("listening on {}", listener.local_addr().unwrap()); - axum::serve(listener, app) - .with_graceful_shutdown(cancel) - .await - .unwrap(); + let mut connections = pin!(TcpListenerStream::new(listener).take_until(cancel)); + + while let Some(stream) = connections.next().await { + match stream { + Ok(stream) => handle_connection(state.clone(), stream), + Err(error) => { + error!(%error, "connection failed"); + } + } + } } else { let listen: PathBuf = listen.into(); if let Some(parent) = listen.parent() { @@ -158,18 +201,42 @@ async fn serve(instances: ActiveInstances, listen: String, base_address: String) } let _ = tokio::fs::remove_file(&listen).await; - let uds = UnixListener::bind(&listen).unwrap(); + let listener = UnixListener::bind(&listen).unwrap(); + println!("listening on {}", listen.display()); set_permissions(&listen, PermissionsExt::from_mode(0o666)).into_diagnostic()?; - axum::serve(uds, app) - .with_graceful_shutdown(cancel) - .await - .unwrap(); + let mut connections = pin!(UnixListenerStream::new(listener).take_until(cancel)); + + while let Some(stream) = connections.next().await { + match stream { + Ok(stream) => handle_connection(state.clone(), stream), + Err(error) => { + error!(%error, "connection failed"); + } + } + } } Ok(()) } +fn handle_connection( + state: AppState, + stream: I, +) { + let io = TokioIo::new(stream); + // Spawn a tokio task to serve multiple connections concurrently + tokio::task::spawn(async move { + if let Err(err) = http1::Builder::new() + .serve_connection(io, service_fn(move |req| handler(state.clone(), req))) + .with_upgrades() + .await + { + eprintln!("Error serving connection: {:?}", err); + } + }); +} + async fn get_remote( host: Option<&HeaderValue>, instances: &ActiveInstances, @@ -181,7 +248,7 @@ async fn get_remote( }; let ip = if host == base_address { instances - .last() + .last_addr() .ok_or_else(|| String::from("No running instance known")) } else { let requested_instance = host.split('.').next().unwrap(); @@ -203,9 +270,9 @@ async fn get_remote( } } -type Client = hyper_util::client::legacy::Client; +type Client = hyper_util::client::legacy::Client; -async fn handler(State(state): State, mut req: Request) -> Result { +async fn handler(state: AppState, mut req: Request) -> Result { let host = req.headers().get(HOST).cloned(); let remote = match get_remote(host.as_ref(), &state.instances, &state.base_address).await { Ok(remote) => remote, @@ -230,13 +297,13 @@ async fn handler(State(state): State, mut req: Request) -> Result Ok(response.map(Body::new)), Err(error) => { - error!(%error, "error while proxying request"); + error!(?error, "error while proxying request"); Ok(StatusCode::BAD_REQUEST.into_response()) } } diff --git a/src/service.rs b/src/service.rs index 62b84d4..cb517cb 100644 --- a/src/service.rs +++ b/src/service.rs @@ -14,9 +14,10 @@ mod sftp; mod redis; mod sharded; mod smb; +mod webhook; use crate::cloud::CloudOptions; -use crate::config::{HazeConfig, Preset}; +use crate::config::{HazeConfig, Preset, ProxyConfig}; pub use crate::service::clam::{Clam, ClamIcap, ClamIcapTls, ClamSocket}; use crate::service::dav::Dav; use crate::service::imaginary::Imaginary; @@ -29,9 +30,10 @@ pub use crate::service::office::Office; pub use crate::service::onlyoffice::OnlyOffice; pub use crate::service::push::NotifyPush; use crate::service::redis::Redis; -use crate::service::sftp::Sftp; +use crate::service::sftp::{Sftp, SftpKey}; use crate::service::sharded::{Sharding, ShardingMigrate, ShardingMigrateUnset, SingleShard}; use crate::service::smb::Smb; +use crate::service::webhook::Webhook; use bollard::models::ContainerState; use bollard::Docker; use enum_dispatch::enum_dispatch; @@ -78,7 +80,12 @@ pub trait ServiceTrait { None } - async fn start_message(&self, _docker: &Docker, _cloud_id: &str) -> Result> { + async fn start_message( + &self, + _docker: &Docker, + _cloud_id: &str, + _proxy: &ProxyConfig, + ) -> Result> { Ok(None) } @@ -95,12 +102,21 @@ pub trait ServiceTrait { Ok(HashMap::default()) } + fn pre_setup( + &self, + _docker: &Docker, + _cloud_id: &str, + _config: &HazeConfig, + ) -> Result>> { + Ok(Vec::new()) + } + async fn post_setup( &self, _docker: &Docker, _cloud_id: &str, _config: &HazeConfig, - ) -> Result> { + ) -> Result>> { Ok(Vec::new()) } @@ -142,7 +158,7 @@ pub trait ServiceTrait { return Ok(Box::new(empty())); }; docker - .start_container::(&container, None) + .start_container(&container, None) .await .into_diagnostic()?; self.wait_for_running(docker, cloud_id).await?; @@ -193,6 +209,19 @@ impl ServiceTrait for RedisTls { } } +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct FrankenPhp; + +impl ServiceTrait for FrankenPhp { + fn name(&self) -> &str { + "franken-php" + } + + fn env(&self) -> &[&str] { + &["FRANKENPHP=1"] + } +} + #[derive( Copy, Clone, Debug, PartialEq, EnumString, EnumMessage, EnumIter, IntoStaticStr, Display, )] @@ -200,6 +229,8 @@ impl ServiceTrait for RedisTls { pub enum ServiceType { /// S3 Primary storage and external storage S3, + /// S3 Primary storage with TLS + S3s, /// S3 multi-object store Primary storage and external storage S3m, /// S3 multi-bucket Primary storage and external storage @@ -208,6 +239,8 @@ pub enum ServiceType { Azure, /// Ldap user backend Ldap, + /// Ldap admin interface + LdapAdmin, /// OnlyOffice OnlyOffice, /// Libre office online @@ -234,6 +267,8 @@ pub enum ServiceType { Dav, /// Sftp external storage Sftp, + /// Sftp external storage with public key authentication + SftpKey, /// ownCloud instance for migration Oc, /// Imaginary for preview generation @@ -263,6 +298,10 @@ pub enum ServiceType { Redis, /// External redis instance with TLS RedisTls, + /// Use FrankenPHP instead of PHP-FPM + FrankenPhp, + /// Webhook test listener + Webhook, } #[enum_dispatch] @@ -281,6 +320,7 @@ pub enum Service { ShardingMigrate(ShardingMigrate), ShardingMigrateUnset(ShardingMigrateUnset), Sftp(Sftp), + SftpKey(SftpKey), Kaspersky(Kaspersky), KasperskyIcap(KasperskyIcap), Clam(Clam), @@ -292,6 +332,8 @@ pub enum Service { Mail(Mail), Redis(Redis), RedisTls(RedisTls), + FrankenPhp(FrankenPhp), + Webhook(Webhook), Preset(PresetService), } @@ -300,10 +342,14 @@ impl Service { if let Ok(ty) = ServiceType::from_str(ty) { match ty { ServiceType::S3 => Some(vec![Service::ObjectStore(ObjectStore::S3)]), + ServiceType::S3s => Some(vec![Service::ObjectStore(ObjectStore::S3s)]), ServiceType::S3m => Some(vec![Service::ObjectStore(ObjectStore::S3m)]), ServiceType::S3mb => Some(vec![Service::ObjectStore(ObjectStore::S3mb)]), ServiceType::Azure => Some(vec![Service::ObjectStore(ObjectStore::Azure)]), ServiceType::Ldap => Some(vec![Service::Ldap(Ldap), Service::LdapAdmin(LdapAdmin)]), + ServiceType::LdapAdmin => { + Some(vec![Service::Ldap(Ldap), Service::LdapAdmin(LdapAdmin)]) + } ServiceType::OnlyOffice => Some(vec![Service::OnlyOffice(OnlyOffice)]), ServiceType::Office => Some(vec![Service::Office(Office)]), ServiceType::Push => Some(vec![Service::Push(NotifyPush)]), @@ -318,6 +364,7 @@ impl Service { } ServiceType::Dav => Some(vec![Service::Dav(Dav)]), ServiceType::Sftp => Some(vec![Service::Sftp(Sftp)]), + ServiceType::SftpKey => Some(vec![Service::SftpKey(SftpKey)]), ServiceType::Oc => Some(vec![Service::Oc(Oc)]), ServiceType::Imaginary => Some(vec![Service::Imaginary(Imaginary)]), ServiceType::Kaspersky => Some(vec![Service::Kaspersky(Kaspersky)]), @@ -330,6 +377,8 @@ impl Service { ServiceType::Mail => Some(vec![Service::Mail(Mail)]), ServiceType::Redis => Some(vec![Service::Redis(Redis)]), ServiceType::RedisTls => Some(vec![Service::RedisTls(RedisTls)]), + ServiceType::FrankenPhp => Some(vec![Service::FrankenPhp(FrankenPhp)]), + ServiceType::Webhook => Some(vec![Service::Webhook(Webhook)]), } } else { presets @@ -392,15 +441,29 @@ impl ServiceTrait for PresetService { _docker: &Docker, _cloud_id: &str, config: &HazeConfig, - ) -> Result> { + ) -> Result>> { let preset = get_preset(&config.preset, &self.0).ok_or_else(|| Report::msg("invalid preset"))?; let mut commands: Vec<_> = preset .apps .iter() - .map(|app| format!("occ app:enable {app} --force")) + .map(|app| { + vec![ + "occ".into(), + "app:enable".into(), + app.clone(), + "--force".into(), + ] + }) .collect(); - commands.extend_from_slice(&preset.commands); + for cmnd in &preset.commands { + commands.push(shell_words::split(cmnd).into_diagnostic()?); + } + Ok(commands) } } + +fn split_cmnd(s: &str) -> Vec { + s.split(' ').map(String::from).collect() +} diff --git a/src/service/clam.rs b/src/service/clam.rs index 924abd6..7221309 100644 --- a/src/service/clam.rs +++ b/src/service/clam.rs @@ -2,10 +2,10 @@ use crate::cloud::CloudOptions; use crate::config::HazeConfig; use crate::exec::exec; use crate::image::pull_image; -use crate::service::ServiceTrait; +use crate::service::{split_cmnd, ServiceTrait}; use crate::Result; -use bollard::container::{Config, CreateContainerOptions, NetworkingConfig}; -use bollard::models::{EndpointSettings, HostConfig}; +use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig}; +use bollard::query_parameters::CreateContainerOptions; use bollard::Docker; use maplit::hashmap; use miette::{IntoDiagnostic, WrapErr}; @@ -40,26 +40,26 @@ impl ServiceTrait for ClamIcap { let image = "ghcr.io/icewind1991/icap-clamav-service-tls"; pull_image(docker, image).await?; let options = Some(CreateContainerOptions { - name: self.container_name(cloud_id).unwrap(), + name: self.container_name(cloud_id), ..CreateContainerOptions::default() }); - let config = Config { - image: Some(image), + let config = ContainerCreateBody { + image: Some(image.into()), host_config: Some(HostConfig { network_mode: Some(network.to_string()), ..Default::default() }), labels: Some(hashmap! { - "haze-type" => self.name(), - "haze-cloud-id" => cloud_id + "haze-type".into() => self.name().into(), + "haze-cloud-id".into() => cloud_id.into() }), networking_config: Some(NetworkingConfig { - endpoints_config: hashmap! { - network => EndpointSettings { + endpoints_config: Some(hashmap! { + network.into() => EndpointSettings { aliases: Some(vec![self.name().to_string()]), ..Default::default() } - }, + }), }), ..Default::default() }; @@ -68,10 +68,7 @@ impl ServiceTrait for ClamIcap { .await .into_diagnostic()? .id; - docker - .start_container::(&id, None) - .await - .into_diagnostic()?; + docker.start_container(&id, None).await.into_diagnostic()?; Ok(vec![id]) } @@ -88,14 +85,13 @@ impl ServiceTrait for ClamIcap { _docker: &Docker, _cloud_id: &str, _config: &HazeConfig, - ) -> Result> { + ) -> Result>> { Ok(vec![ - "occ config:app:set files_antivirus av_mode --value=icap".into(), - "occ config:app:set files_antivirus av_host --value=clamav-icap".into(), - "occ config:app:set files_antivirus av_port --value=1344".into(), - "occ config:app:set files_antivirus av_icap_request_service --value=avscan".into(), - "occ config:app:set files_antivirus av_icap_response_header --value=X-Infection-Found" - .into(), + split_cmnd("occ config:app:set files_antivirus av_mode --value=icap"), + split_cmnd("occ config:app:set files_antivirus av_host --value=clamav-icap"), + split_cmnd("occ config:app:set files_antivirus av_port --value=1344"), + split_cmnd("occ config:app:set files_antivirus av_icap_request_service --value=avscan"), + split_cmnd("occ config:app:set files_antivirus av_icap_response_header --value=X-Infection-Found"), ]) } } @@ -129,26 +125,26 @@ impl ServiceTrait for ClamIcapTls { let image = "ghcr.io/icewind1991/icap-clamav-service-tls"; pull_image(docker, image).await?; let options = Some(CreateContainerOptions { - name: self.container_name(cloud_id).unwrap(), + name: self.container_name(cloud_id), ..CreateContainerOptions::default() }); - let config = Config { - image: Some(image), + let config = ContainerCreateBody { + image: Some(image.into()), host_config: Some(HostConfig { network_mode: Some(network.to_string()), ..Default::default() }), labels: Some(hashmap! { - "haze-type" => self.name(), - "haze-cloud-id" => cloud_id + "haze-type".into() => self.name().into(), + "haze-cloud-id".into() => cloud_id.into() }), networking_config: Some(NetworkingConfig { - endpoints_config: hashmap! { - network => EndpointSettings { + endpoints_config: Some(hashmap! { + network.into() => EndpointSettings { aliases: Some(vec![self.name().to_string()]), ..Default::default() } - }, + }), }), ..Default::default() }; @@ -157,10 +153,7 @@ impl ServiceTrait for ClamIcapTls { .await .into_diagnostic()? .id; - docker - .start_container::(&id, None) - .await - .into_diagnostic()?; + docker.start_container(&id, None).await.into_diagnostic()?; Ok(vec![id]) } @@ -177,7 +170,7 @@ impl ServiceTrait for ClamIcapTls { docker: &Docker, cloud_id: &str, config: &HazeConfig, - ) -> Result> { + ) -> Result>> { let mut cert = Vec::new(); exec( docker, @@ -197,14 +190,13 @@ impl ServiceTrait for ClamIcapTls { .wrap_err("Failed to write icap certificate")?; Ok(vec![ - "occ config:app:set files_antivirus av_mode --value=icap".into(), - "occ config:app:set files_antivirus av_icap_tls --value=1".into(), - "occ config:app:set files_antivirus av_host --value=clamav-icap-tls".into(), - "occ config:app:set files_antivirus av_port --value=1345".into(), - "occ config:app:set files_antivirus av_icap_request_service --value=avscan".into(), - "occ config:app:set files_antivirus av_icap_response_header --value=X-Infection-Found" - .into(), - "occ security:certificates:import data/icap-cert.pem".into(), + split_cmnd("occ config:app:set files_antivirus av_mode --value=icap"), + split_cmnd("occ config:app:set files_antivirus av_icap_tls --value=1"), + split_cmnd("occ config:app:set files_antivirus av_host --value=clamav-icap-tls"), + split_cmnd("occ config:app:set files_antivirus av_port --value=1345"), + split_cmnd("occ config:app:set files_antivirus av_icap_request_service --value=avscan"), + split_cmnd("occ config:app:set files_antivirus av_icap_response_header --value=X-Infection-Found"), + split_cmnd("occ security:certificates:import data/icap-cert.pem"), ]) } } @@ -227,10 +219,10 @@ impl ServiceTrait for Clam { _docker: &Docker, _cloud_id: &str, _config: &HazeConfig, - ) -> Result> { + ) -> Result>> { Ok(vec![ - "occ config:app:set files_antivirus av_mode --value=executable".into(), - "occ config:app:set files_antivirus av_path --value=/bin/clamscan".into(), + split_cmnd("occ config:app:set files_antivirus av_mode --value=executable"), + split_cmnd("occ config:app:set files_antivirus av_path --value=/bin/clamscan"), ]) } } @@ -255,26 +247,26 @@ impl ServiceTrait for ClamSocket { let image = "clamav/clamav"; pull_image(docker, image).await?; let options = Some(CreateContainerOptions { - name: self.container_name(cloud_id).unwrap(), + name: self.container_name(cloud_id), ..CreateContainerOptions::default() }); - let config = Config { - image: Some(image), + let config = ContainerCreateBody { + image: Some(image.into()), host_config: Some(HostConfig { network_mode: Some(network.to_string()), ..Default::default() }), labels: Some(hashmap! { - "haze-type" => self.name(), - "haze-cloud-id" => cloud_id + "haze-type".into() => self.name().into(), + "haze-cloud-id".into() => cloud_id.into() }), networking_config: Some(NetworkingConfig { - endpoints_config: hashmap! { - network => EndpointSettings { + endpoints_config: Some(hashmap! { + network.into() => EndpointSettings { aliases: Some(vec![self.name().to_string()]), ..Default::default() } - }, + }), }), ..Default::default() }; @@ -283,10 +275,7 @@ impl ServiceTrait for ClamSocket { .await .into_diagnostic()? .id; - docker - .start_container::(&id, None) - .await - .into_diagnostic()?; + docker.start_container(&id, None).await.into_diagnostic()?; Ok(vec![id]) } @@ -303,10 +292,12 @@ impl ServiceTrait for ClamSocket { _docker: &Docker, _cloud_id: &str, _config: &HazeConfig, - ) -> Result> { + ) -> Result>> { Ok(vec![ - "occ config:app:set files_antivirus av_mode --value=socket".into(), - "occ config:app:set files_antivirus av_socket --value=tcp://clamav-socket:3310".into(), + split_cmnd("occ config:app:set files_antivirus av_mode --value=socket"), + split_cmnd( + "occ config:app:set files_antivirus av_socket --value=tcp://clamav-socket:3310", + ), ]) } } diff --git a/src/service/dav.rs b/src/service/dav.rs index bfd4904..e0b20f3 100644 --- a/src/service/dav.rs +++ b/src/service/dav.rs @@ -1,10 +1,11 @@ use crate::cloud::CloudOptions; use crate::config::HazeConfig; use crate::image::pull_image; -use crate::service::ServiceTrait; +use crate::service::{split_cmnd, ServiceTrait}; use crate::Result; -use bollard::container::{Config, CreateContainerOptions, NetworkingConfig}; -use bollard::models::{EndpointSettings, HostConfig}; +use bollard::config::ContainerCreateBody; +use bollard::models::{EndpointSettings, HostConfig, NetworkingConfig}; +use bollard::query_parameters::CreateContainerOptions; use bollard::Docker; use maplit::hashmap; use miette::IntoDiagnostic; @@ -29,27 +30,27 @@ impl ServiceTrait for Dav { let image = "ugeek/webdav:amd64"; pull_image(docker, image).await?; let options = Some(CreateContainerOptions { - name: self.container_name(cloud_id).unwrap(), + name: self.container_name(cloud_id), ..CreateContainerOptions::default() }); - let config = Config { - image: Some(image), + let config = ContainerCreateBody { + image: Some(image.into()), host_config: Some(HostConfig { network_mode: Some(network.to_string()), ..Default::default() }), - env: Some(vec!["USERNAME=test", "PASSWORD=test"]), + env: Some(vec!["USERNAME=test".into(), "PASSWORD=test".into()]), labels: Some(hashmap! { - "haze-type" => self.name(), - "haze-cloud-id" => cloud_id + "haze-type".into() => self.name().into(), + "haze-cloud-id".into() => cloud_id.into() }), networking_config: Some(NetworkingConfig { - endpoints_config: hashmap! { - network => EndpointSettings { + endpoints_config: Some(hashmap! { + network.into() => EndpointSettings { aliases: Some(vec![self.name().to_string()]), ..Default::default() } - }, + }), }), ..Default::default() }; @@ -58,10 +59,7 @@ impl ServiceTrait for Dav { .await .into_diagnostic()? .id; - docker - .start_container::(&id, None) - .await - .into_diagnostic()?; + docker.start_container(&id, None).await.into_diagnostic()?; Ok(vec![id]) } @@ -78,12 +76,12 @@ impl ServiceTrait for Dav { _docker: &Docker, _cloud_id: &str, _config: &HazeConfig, - ) -> Result> { + ) -> Result>> { Ok(vec![ - "occ files_external:create dav dav password::password".into(), - "occ files_external:config 1 host dav".into(), - "occ files_external:config 1 user test".into(), - "occ files_external:config 1 password test".into(), + split_cmnd("occ files_external:create dav dav password::password"), + split_cmnd("occ files_external:config 1 host dav"), + split_cmnd("occ files_external:config 1 user test"), + split_cmnd("occ files_external:config 1 password test"), ]) } } diff --git a/src/service/imaginary.rs b/src/service/imaginary.rs index 986b242..4ec4d96 100644 --- a/src/service/imaginary.rs +++ b/src/service/imaginary.rs @@ -1,10 +1,11 @@ use crate::cloud::CloudOptions; use crate::config::HazeConfig; use crate::image::pull_image; -use crate::service::ServiceTrait; +use crate::service::{split_cmnd, ServiceTrait}; use crate::Result; -use bollard::container::{Config, CreateContainerOptions, NetworkingConfig}; -use bollard::models::{EndpointSettings, HostConfig}; +use bollard::config::NetworkingConfig; +use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig}; +use bollard::query_parameters::CreateContainerOptions; use bollard::Docker; use maplit::hashmap; use miette::IntoDiagnostic; @@ -29,26 +30,26 @@ impl ServiceTrait for Imaginary { let image = "nextcloud/aio-imaginary:latest"; pull_image(docker, image).await?; let options = Some(CreateContainerOptions { - name: self.container_name(cloud_id).unwrap(), + name: self.container_name(cloud_id), ..CreateContainerOptions::default() }); - let config = Config { - image: Some(image), + let config = ContainerCreateBody { + image: Some(image.into()), host_config: Some(HostConfig { network_mode: Some(network.to_string()), ..Default::default() }), labels: Some(hashmap! { - "haze-type" => self.name(), - "haze-cloud-id" => cloud_id + "haze-type".into() => self.name().into(), + "haze-cloud-id".into() => cloud_id.into() }), networking_config: Some(NetworkingConfig { - endpoints_config: hashmap! { - network => EndpointSettings { + endpoints_config: Some(hashmap! { + network.into() => EndpointSettings { aliases: Some(vec![self.name().to_string()]), ..Default::default() } - }, + }), }), ..Default::default() }; @@ -57,10 +58,7 @@ impl ServiceTrait for Imaginary { .await .into_diagnostic()? .id; - docker - .start_container::(&id, None) - .await - .into_diagnostic()?; + docker.start_container(&id, None).await.into_diagnostic()?; Ok(vec![id]) } @@ -73,11 +71,14 @@ impl ServiceTrait for Imaginary { _docker: &Docker, _cloud_id: &str, _config: &HazeConfig, - ) -> Result> { + ) -> Result>> { Ok(vec![ - "occ config:system:set enabledPreviewProviders 0 --value='OC\\Preview\\Imaginary'" - .into(), - "occ config:system:set preview_imaginary_url --value='http://imaginary:9000'".into(), + split_cmnd( + "occ config:system:set enabledPreviewProviders 0 --value='OC\\Preview\\Imaginary'", + ), + split_cmnd( + "occ config:system:set preview_imaginary_url --value='http://imaginary:9000'", + ), ]) } } diff --git a/src/service/kaspersky.rs b/src/service/kaspersky.rs index 8199429..1c0a67f 100644 --- a/src/service/kaspersky.rs +++ b/src/service/kaspersky.rs @@ -2,10 +2,10 @@ use crate::cloud::CloudOptions; use crate::config::HazeConfig; use crate::exec::exec; use crate::image::{image_exists, pull_image}; -use crate::service::ServiceTrait; +use crate::service::{split_cmnd, ServiceTrait}; use crate::Result; -use bollard::container::{Config, CreateContainerOptions, NetworkingConfig}; -use bollard::models::{EndpointSettings, HostConfig}; +use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig}; +use bollard::query_parameters::CreateContainerOptions; use bollard::Docker; use maplit::hashmap; use miette::{bail, IntoDiagnostic}; @@ -38,26 +38,26 @@ impl ServiceTrait for Kaspersky { } pull_image(docker, image).await?; let options = Some(CreateContainerOptions { - name: self.container_name(cloud_id).unwrap(), + name: self.container_name(cloud_id), ..CreateContainerOptions::default() }); - let config = Config { - image: Some(image), + let config = ContainerCreateBody { + image: Some(image.into()), host_config: Some(HostConfig { network_mode: Some(network.to_string()), ..Default::default() }), labels: Some(hashmap! { - "haze-type" => self.name(), - "haze-cloud-id" => cloud_id + "haze-type".into() => self.name().into(), + "haze-cloud-id".into() => cloud_id.into() }), networking_config: Some(NetworkingConfig { - endpoints_config: hashmap! { - network => EndpointSettings { + endpoints_config: Some(hashmap! { + network.into() => EndpointSettings { aliases: Some(vec![self.name().to_string()]), ..Default::default() } - }, + }), }), ..Default::default() }; @@ -66,10 +66,7 @@ impl ServiceTrait for Kaspersky { .await .into_diagnostic()? .id; - docker - .start_container::(&id, None) - .await - .into_diagnostic()?; + docker.start_container(&id, None).await.into_diagnostic()?; Ok(vec![id]) } @@ -104,11 +101,11 @@ impl ServiceTrait for Kaspersky { _docker: &Docker, _cloud_id: &str, _config: &HazeConfig, - ) -> Result> { + ) -> Result>> { Ok(vec![ - "occ config:app:set files_antivirus av_mode --value=kaspersky".into(), - "occ config:app:set files_antivirus av_host --value=kaspersky".into(), - "occ config:app:set files_antivirus av_port --value=80".into(), + split_cmnd("occ config:app:set files_antivirus av_mode --value=kaspersky"), + split_cmnd("occ config:app:set files_antivirus av_host --value=kaspersky"), + split_cmnd("occ config:app:set files_antivirus av_port --value=80"), ]) } } @@ -145,26 +142,26 @@ impl ServiceTrait for KasperskyIcap { } pull_image(docker, image).await?; let options = Some(CreateContainerOptions { - name: self.container_name(cloud_id).unwrap(), + name: self.container_name(cloud_id), ..CreateContainerOptions::default() }); - let config = Config { - image: Some(image), + let config = ContainerCreateBody { + image: Some(image.into()), host_config: Some(HostConfig { network_mode: Some(network.to_string()), ..Default::default() }), labels: Some(hashmap! { - "haze-type" => self.name(), - "haze-cloud-id" => cloud_id + "haze-type".into() => self.name().into(), + "haze-cloud-id".into() => cloud_id.into(), }), networking_config: Some(NetworkingConfig { - endpoints_config: hashmap! { - network => EndpointSettings { + endpoints_config: Some(hashmap! { + network.into() => EndpointSettings { aliases: Some(vec![self.name().to_string()]), ..Default::default() } - }, + }), }), ..Default::default() }; @@ -173,10 +170,7 @@ impl ServiceTrait for KasperskyIcap { .await .into_diagnostic()? .id; - docker - .start_container::(&id, None) - .await - .into_diagnostic()?; + docker.start_container(&id, None).await.into_diagnostic()?; Ok(vec![id]) } @@ -193,13 +187,15 @@ impl ServiceTrait for KasperskyIcap { _docker: &Docker, _cloud_id: &str, _config: &HazeConfig, - ) -> Result> { + ) -> Result>> { Ok(vec![ - "occ config:app:set files_antivirus av_mode --value=icap".into(), - "occ config:app:set files_antivirus av_host --value=kaspersky-icap".into(), - "occ config:app:set files_antivirus av_port --value=1344".into(), - "occ config:app:set files_antivirus av_icap_request_service --value=req".into(), - "occ config:app:set files_antivirus av_icap_response_header --value=X-Virus-ID".into(), + split_cmnd("occ config:app:set files_antivirus av_mode --value=icap"), + split_cmnd("occ config:app:set files_antivirus av_host --value=kaspersky-icap"), + split_cmnd("occ config:app:set files_antivirus av_port --value=1344"), + split_cmnd("occ config:app:set files_antivirus av_icap_request_service --value=req"), + split_cmnd( + "occ config:app:set files_antivirus av_icap_response_header --value=X-Virus-ID", + ), ]) } } diff --git a/src/service/ldap.rs b/src/service/ldap.rs index 4868ffe..4f317e6 100644 --- a/src/service/ldap.rs +++ b/src/service/ldap.rs @@ -1,13 +1,16 @@ use crate::cloud::CloudOptions; -use crate::config::HazeConfig; +use crate::config::{HazeConfig, ProxyConfig}; use crate::image::pull_image; -use crate::service::ServiceTrait; +use crate::service::{split_cmnd, ServiceTrait}; use crate::Result; -use bollard::container::{Config, CreateContainerOptions, NetworkingConfig}; -use bollard::models::{ContainerState, EndpointSettings, HostConfig}; +use bollard::config::NetworkingConfig; +use bollard::models::{ContainerCreateBody, ContainerState, EndpointSettings, HostConfig}; +use bollard::query_parameters::CreateContainerOptions; use bollard::Docker; use maplit::hashmap; use miette::{IntoDiagnostic, Report}; +use std::net::IpAddr; +use std::str::FromStr; #[derive(Debug, Clone, Eq, PartialEq)] pub struct Ldap; @@ -33,29 +36,29 @@ impl ServiceTrait for Ldap { let image = "icewind1991/haze-ldap"; pull_image(docker, image).await?; let options = Some(CreateContainerOptions { - name: self.container_name(cloud_id).unwrap(), + name: self.container_name(cloud_id), ..CreateContainerOptions::default() }); - let config = Config { - image: Some(image), - env: Some(vec!["LDAP_ADMIN_PASSWORD=haze"]), + let config = ContainerCreateBody { + image: Some(image.into()), + env: Some(vec!["LDAP_ADMIN_PASSWORD=haze".into()]), host_config: Some(HostConfig { network_mode: Some(network.to_string()), ..Default::default() }), labels: Some(hashmap! { - "haze-type" => self.name(), - "haze-cloud-id" => cloud_id + "haze-type".into() => self.name().into(), + "haze-cloud-id".into() => cloud_id.into() }), networking_config: Some(NetworkingConfig { - endpoints_config: hashmap! { - network => EndpointSettings { + endpoints_config: Some(hashmap! { + network.into() => EndpointSettings { aliases: Some(vec![self.name().to_string()]), ..Default::default() } - }, + }), }), - cmd: Some(vec!["--copy-service"]), + cmd: Some(vec!["--copy-service".into()]), ..Default::default() }; let id = docker @@ -63,10 +66,7 @@ impl ServiceTrait for Ldap { .await .into_diagnostic()? .id; - docker - .start_container::(&id, None) - .await - .into_diagnostic()?; + docker.start_container(&id, None).await.into_diagnostic()?; Ok(vec![id]) } @@ -86,6 +86,46 @@ impl ServiceTrait for Ldap { ) -> Result { self.is_running(docker, cloud_id).await } + + async fn post_setup( + &self, + _docker: &Docker, + _cloud_id: &str, + _config: &HazeConfig, + ) -> Result>> { + Ok(vec![ + split_cmnd("occ ldap:create-empty-config"), + split_cmnd("occ ldap:set-config s01 ldapHost ldap://ldap"), + split_cmnd("occ ldap:set-config s01 ldapPort 389"), + split_cmnd("occ ldap:set-config s01 ldapAgentName cn=admin,dc=example,dc=org"), + split_cmnd("occ ldap:set-config s01 ldapAgentPassword haze"), + split_cmnd("occ ldap:set-config s01 ldapBase dc=example,dc=org"), + split_cmnd("occ ldap:set-config s01 ldapBaseUsers dc=example,dc=org"), + split_cmnd("occ ldap:set-config s01 ldapBaseGroups dc=example,dc=org"), + split_cmnd("occ ldap:set-config s01 ldapLoginFilter (&(&(objectclass=inetOrgPerson))(uid=%uid))"), + split_cmnd("occ ldap:set-config s01 ldapUserFilter ((objectclass=inetOrgPerson))"), + split_cmnd("occ ldap:set-config s01 ldapUserFilterMode 0"), + split_cmnd("occ ldap:set-config s01 ldapUserDisplayName sn"), + split_cmnd("occ ldap:set-config s01 ldapUserFilterObjectclass inetOrgPerson"), + split_cmnd("occ ldap:set-config s01 ldapGroupFilter (&(|(objectclass=posixGroup)))"), + split_cmnd("occ ldap:set-config s01 ldapGroupFilterObjectclass posixGroup"), + split_cmnd("occ ldap:set-config s01 ldapEmailAttribute email"), + split_cmnd("occ ldap:set-config s01 ldapUuidUserAttribute email"), + split_cmnd("occ ldap:set-config s01 ldapUuidUserAttribute auto"), + split_cmnd("occ ldap:set-config s01 ldapUuidGroupAttribute auto"), + split_cmnd("occ ldap:set-config s01 ldapLoginFilterUsername 1"), + split_cmnd("occ ldap:set-config s01 ldapConfigurationActive 1"), + ]) + } + + async fn start_message( + &self, + _docker: &Docker, + _cloud_id: &str, + _proxy: &ProxyConfig, + ) -> Result> { + Ok(Some("\nLdap users provisioned:\n\t'cn=admin,dc=example,dc=org' and password 'haze'\n\t'cn=ldaptest,dc=example,dc=org' and password 'test'\n\nldaptest is available for login\n".into())) + } } #[derive(Debug, Clone, Eq, PartialEq)] @@ -112,29 +152,32 @@ impl ServiceTrait for LdapAdmin { let image = "osixia/phpldapadmin"; pull_image(docker, image).await?; let options = Some(CreateContainerOptions { - name: self.container_name(cloud_id).unwrap(), + name: self.container_name(cloud_id), ..CreateContainerOptions::default() }); - let config = Config { - image: Some(image), - env: Some(vec!["PHPLDAPADMIN_LDAP_HOSTS=ldap"]), + let config = ContainerCreateBody { + image: Some(image.into()), + env: Some(vec![ + "PHPLDAPADMIN_LDAP_HOSTS=ldap".into(), + "PHPLDAPADMIN_HTTPS=false".into(), + ]), host_config: Some(HostConfig { network_mode: Some(network.to_string()), ..Default::default() }), labels: Some(hashmap! { - "haze-type" => self.name(), - "haze-cloud-id" => cloud_id + "haze-type".into() => self.name().into(), + "haze-cloud-id".into() => cloud_id.into(), }), networking_config: Some(NetworkingConfig { - endpoints_config: hashmap! { - network => EndpointSettings { + endpoints_config: Some(hashmap! { + network.into() => EndpointSettings { aliases: Some(vec![self.name().to_string()]), ..Default::default() } - }, + }), }), - cmd: Some(vec!["--copy-service"]), + cmd: Some(vec!["--copy-service".into()]), ..Default::default() }; let id = docker @@ -142,10 +185,7 @@ impl ServiceTrait for LdapAdmin { .await .into_diagnostic()? .id; - docker - .start_container::(&id, None) - .await - .into_diagnostic()?; + docker.start_container(&id, None).await.into_diagnostic()?; Ok(vec![id]) } @@ -153,9 +193,15 @@ impl ServiceTrait for LdapAdmin { Some(format!("{}-ldap-admin", cloud_id)) } - async fn start_message(&self, docker: &Docker, cloud_id: &str) -> Result> { + async fn start_message( + &self, + docker: &Docker, + cloud_id: &str, + proxy: &ProxyConfig, + ) -> Result> { + let id = self.container_name(cloud_id).unwrap(); let info = docker - .inspect_container(&self.container_name(cloud_id).unwrap(), None) + .inspect_container(&id, None) .await .into_diagnostic()?; let ip = if matches!( @@ -178,9 +224,7 @@ impl ServiceTrait for LdapAdmin { } else { return Err(Report::msg("ldap admin not started")); }; - Ok(Some(format!( - "Ldap admin running at: https://{} with 'cn=admin,dc=example,dc=org' and password 'haze'", - ip - ))) + let addr = proxy.addr(&id, IpAddr::from_str(&ip).unwrap()); + Ok(Some(format!("Ldap admin running at: {addr}"))) } } diff --git a/src/service/mail.rs b/src/service/mail.rs index 93a411d..ad40471 100644 --- a/src/service/mail.rs +++ b/src/service/mail.rs @@ -1,10 +1,10 @@ use crate::cloud::CloudOptions; use crate::config::HazeConfig; use crate::image::pull_image; -use crate::service::ServiceTrait; +use crate::service::{split_cmnd, ServiceTrait}; use crate::Result; -use bollard::container::{Config, CreateContainerOptions, NetworkingConfig}; -use bollard::models::{EndpointSettings, HostConfig}; +use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig}; +use bollard::query_parameters::CreateContainerOptions; use bollard::Docker; use maplit::hashmap; use miette::IntoDiagnostic; @@ -29,26 +29,26 @@ impl ServiceTrait for Mail { let image = "rnwood/smtp4dev"; pull_image(docker, image).await?; let options = Some(CreateContainerOptions { - name: self.container_name(cloud_id).unwrap(), + name: self.container_name(cloud_id), ..CreateContainerOptions::default() }); - let config = Config { - image: Some(image), + let config = ContainerCreateBody { + image: Some(image.into()), host_config: Some(HostConfig { network_mode: Some(network.to_string()), ..Default::default() }), labels: Some(hashmap! { - "haze-type" => self.name(), - "haze-cloud-id" => cloud_id + "haze-type".into() => self.name().into(), + "haze-cloud-id".into() => cloud_id.into(), }), networking_config: Some(NetworkingConfig { - endpoints_config: hashmap! { - network => EndpointSettings { + endpoints_config: Some(hashmap! { + network.into() => EndpointSettings { aliases: Some(vec![self.name().to_string()]), ..Default::default() } - }, + }), }), ..Default::default() }; @@ -57,10 +57,7 @@ impl ServiceTrait for Mail { .await .into_diagnostic()? .id; - docker - .start_container::(&id, None) - .await - .into_diagnostic()?; + docker.start_container(&id, None).await.into_diagnostic()?; Ok(vec![id]) } @@ -73,14 +70,14 @@ impl ServiceTrait for Mail { _docker: &Docker, _cloud_id: &str, _config: &HazeConfig, - ) -> Result> { + ) -> Result>> { Ok(vec![ - "occ config:system:set mail_smtpmode --value smtp".into(), - "occ config:system:set mail_sendmailmode --value smtp".into(), - "occ config:system:set mail_domain --value haze".into(), - "occ config:system:set mail_smtphost --value mail".into(), - "occ config:system:set mail_smtpport --value 25".into(), - "occ user:setting admin settings email admin@haze".into(), + split_cmnd("occ config:system:set mail_smtpmode --value smtp"), + split_cmnd("occ config:system:set mail_sendmailmode --value smtp"), + split_cmnd("occ config:system:set mail_domain --value haze"), + split_cmnd("occ config:system:set mail_smtphost --value mail"), + split_cmnd("occ config:system:set mail_smtpport --value 25"), + split_cmnd("occ user:setting admin settings email admin@haze"), ]) } } diff --git a/src/service/objectstore.rs b/src/service/objectstore.rs index 6522207..20e586c 100644 --- a/src/service/objectstore.rs +++ b/src/service/objectstore.rs @@ -2,17 +2,23 @@ use crate::cloud::CloudOptions; use crate::config::HazeConfig; use crate::exec::exec; use crate::image::pull_image; -use crate::service::ServiceTrait; +use crate::service::{split_cmnd, ServiceTrait}; use crate::Result; -use bollard::container::{Config, CreateContainerOptions, NetworkingConfig}; -use bollard::models::{ContainerState, EndpointSettings, HostConfig}; +use bollard::models::{ + ContainerCreateBody, ContainerState, EndpointSettings, HostConfig, NetworkingConfig, +}; +use bollard::query_parameters::CreateContainerOptions; use bollard::Docker; use maplit::hashmap; -use miette::IntoDiagnostic; +use miette::{IntoDiagnostic, WrapErr}; +use serde_json::Value; +use std::collections::HashMap; +use std::fs::{create_dir_all, write}; #[derive(Debug, Clone, Eq, PartialEq)] pub enum ObjectStore { S3, + S3s, S3m, S3mb, Azure, @@ -21,7 +27,7 @@ pub enum ObjectStore { impl ObjectStore { fn image(&self) -> &str { match self { - ObjectStore::S3 | ObjectStore::S3m | ObjectStore::S3mb => { + ObjectStore::S3 | ObjectStore::S3m | ObjectStore::S3mb | ObjectStore::S3s => { "minio/minio:RELEASE.2024-07-16T23-46-41Z" } ObjectStore::Azure => "arafato/azurite:2.6.5", @@ -30,7 +36,7 @@ impl ObjectStore { fn self_env(&self) -> Vec<&str> { match self { - ObjectStore::S3 | ObjectStore::S3m | ObjectStore::S3mb => { + ObjectStore::S3 | ObjectStore::S3m | ObjectStore::S3mb | ObjectStore::S3s => { vec!["MINIO_ACCESS_KEY=minio", "MINIO_SECRET_KEY=minio123"] } ObjectStore::Azure => vec![], @@ -39,17 +45,54 @@ impl ObjectStore { fn host_name(&self) -> &str { match self { - ObjectStore::S3 | ObjectStore::S3m | ObjectStore::S3mb => "s3", + ObjectStore::S3 | ObjectStore::S3m | ObjectStore::S3mb | ObjectStore::S3s => "s3", ObjectStore::Azure => "azure", } } fn args(&self) -> &[&str] { match self { - ObjectStore::S3 | ObjectStore::S3m | ObjectStore::S3mb => &["server", "/data"], + ObjectStore::S3 | ObjectStore::S3m | ObjectStore::S3mb | ObjectStore::S3s => { + &["server", "/data"] + } _ => &[], } } + + fn volumes(&self, config: &HazeConfig) -> Option> { + match self { + ObjectStore::S3s => { + let cert_dir = config.work_dir.join("certificates/s3"); + create_dir_all(&cert_dir) + .into_diagnostic() + .wrap_err("Failed to create redis certificate directory") + .unwrap(); + let s3_cert_path = config.work_dir.join("certificates/s3/public.crt"); + let s3_key_path = config.work_dir.join("certificates/s3/private.key"); + if !s3_cert_path.exists() { + write( + &s3_cert_path, + include_bytes!("../../certificates/s3/public.crt"), + ) + .into_diagnostic() + .wrap_err("Failed to write s3 certificate") + .unwrap(); + } + if !s3_key_path.exists() { + write( + &s3_key_path, + include_bytes!("../../certificates/s3/private.key"), + ) + .into_diagnostic() + .wrap_err("Failed to write s3 key") + .unwrap(); + } + + Some(vec![format!("{cert_dir}:/root/.minio/certs:ro")]) + } + _ => None, + } + } } #[async_trait::async_trait] @@ -57,6 +100,7 @@ impl ServiceTrait for ObjectStore { fn name(&self) -> &str { match self { ObjectStore::S3 => "s3", + ObjectStore::S3s => "s3s", ObjectStore::S3m => "s3m", ObjectStore::S3mb => "s3mb", ObjectStore::Azure => "azure", @@ -66,8 +110,9 @@ impl ServiceTrait for ObjectStore { fn env(&self) -> &[&str] { match self { ObjectStore::S3 => &["S3=1"], + ObjectStore::S3s => &["S3S=1"], ObjectStore::S3m => &["S3M=1"], - ObjectStore::S3mb => &["S3MB=1"], + ObjectStore::S3mb => &["S3MB =1"], ObjectStore::Azure => &["AZURE=1"], } } @@ -77,33 +122,34 @@ impl ServiceTrait for ObjectStore { docker: &Docker, cloud_id: &str, network: &str, - _config: &HazeConfig, + config: &HazeConfig, _options: &CloudOptions, ) -> Result> { pull_image(docker, self.image()).await?; let options = Some(CreateContainerOptions { - name: format!("{}-object", cloud_id), + name: Some(format!("{}-object", cloud_id)), ..CreateContainerOptions::default() }); - let config = Config { - image: Some(self.image()), - env: Some(self.self_env()), + let config = ContainerCreateBody { + image: Some(self.image().into()), + env: Some(self.self_env().into_iter().map(String::from).collect()), host_config: Some(HostConfig { network_mode: Some(network.to_string()), + binds: self.volumes(config), ..Default::default() }), labels: Some(hashmap! { - "haze-type" => self.name(), - "haze-cloud-id" => cloud_id + "haze-type".into() => self.name().into(), + "haze-cloud-id".into() => cloud_id.into(), }), - cmd: Some(self.args().into()), + cmd: Some(self.args().iter().copied().map(String::from).collect()), networking_config: Some(NetworkingConfig { - endpoints_config: hashmap! { - network => EndpointSettings { + endpoints_config: Some(hashmap! { + network.into() => EndpointSettings { aliases: Some(vec![self.host_name().to_string()]), ..Default::default() } - }, + }), }), ..Default::default() }; @@ -112,10 +158,7 @@ impl ServiceTrait for ObjectStore { .await .into_diagnostic()? .id; - docker - .start_container::(&id, None) - .await - .into_diagnostic()?; + docker.start_container(&id, None).await.into_diagnostic()?; Ok(vec![id]) } @@ -166,26 +209,76 @@ impl ServiceTrait for ObjectStore { &["files_external"] } + fn config( + &self, + _docker: &Docker, + _cloud_id: &str, + _config: &HazeConfig, + ) -> Result> { + match self { + ObjectStore::S3s => Ok(hashmap![ + "default_certificates_bundle_path".into() => Value::String("/var/www/html/data/ca-bundle.crt".into()), + ]), + _ => Ok(HashMap::default()), + } + } + + fn pre_setup( + &self, + _docker: &Docker, + _cloud_id: &str, + _config: &HazeConfig, + ) -> Result>> { + match self { + ObjectStore::S3s => Ok(vec![ + vec!["mkdir".into(), "-p".into(), "/var/www/html/data".into()], + vec![ + "sh".into(), + "-c".into(), + "cat /var/www/html/resources/config/ca-bundle.crt /certificates/s3/public.crt > /var/www/html/data/ca-bundle.crt".into(), + ], + ]), + _ => Ok(Vec::new()), + } + } + async fn post_setup( &self, _docker: &Docker, _cloud_id: &str, _config: &HazeConfig, - ) -> Result> { - if *self == ObjectStore::S3 { - Ok(vec![ - "occ files_external:create s3 amazons3 amazons3::accesskey".into(), - "occ files_external:config 1 bucket ext".into(), - "occ files_external:config 1 hostname s3".into(), - "occ files_external:config 1 port 9000".into(), - "occ files_external:config 1 use_ssl false".into(), - "occ files_external:config 1 use_path_style true".into(), - "occ files_external:config 1 key minio".into(), - "occ files_external:config 1 secret minio123".into(), - "mc alias set s3 http://s3:9000 minio minio123".into(), - ]) - } else { - Ok(Vec::new()) + ) -> Result>> { + match self { + ObjectStore::S3 => Ok(vec![ + split_cmnd("occ files_external:create s3 amazons3 amazons3::accesskey"), + split_cmnd("occ files_external:config 1 bucket ext"), + split_cmnd("occ files_external:config 1 hostname s3"), + split_cmnd("occ files_external:config 1 port 9000"), + split_cmnd("occ files_external:config 1 use_ssl false"), + split_cmnd("occ files_external:config 1 use_path_style true"), + split_cmnd("occ files_external:config 1 key minio"), + split_cmnd("occ files_external:config 1 secret minio123"), + split_cmnd("mc alias set s3 http://s3:9000 minio minio123"), + ]), + // ObjectStore::S3s => Ok(vec![ + // "occ files_external:create s3 amazons3 amazons3::accesskey".into(), + // "occ files_external:config 1 bucket ext".into(), + // "occ files_external:config 1 hostname s3".into(), + // "occ files_external:config 1 port 9000".into(), + // "occ files_external:config 1 use_ssl true".into(), + // "occ files_external:config 1 use_path_style true".into(), + // "occ files_external:config 1 key minio".into(), + // "occ files_external:config 1 secret minio123".into(), + // "mc alias set s3 https://s3:9000 minio minio123".into(), + // ]), + _ => Ok(Vec::new()), + } + } + + fn proxy_port(&self) -> u16 { + match self { + ObjectStore::S3 | ObjectStore::S3m | ObjectStore::S3mb | ObjectStore::S3s => 9000, + ObjectStore::Azure => 10000, } } } diff --git a/src/service/oc.rs b/src/service/oc.rs index fcff9e6..296b2a5 100644 --- a/src/service/oc.rs +++ b/src/service/oc.rs @@ -4,8 +4,9 @@ use crate::exec::exec; use crate::image::pull_image; use crate::service::ServiceTrait; use crate::Result; -use bollard::container::{Config, CreateContainerOptions, NetworkingConfig}; -use bollard::models::{EndpointSettings, HostConfig}; +use bollard::config::NetworkingConfig; +use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig}; +use bollard::query_parameters::CreateContainerOptions; use bollard::Docker; use maplit::hashmap; use miette::IntoDiagnostic; @@ -33,7 +34,7 @@ impl ServiceTrait for Oc { let image = "owncloud/server:10.12.2"; pull_image(docker, image).await?; let options = Some(CreateContainerOptions { - name: self.container_name(cloud_id).unwrap(), + name: self.container_name(cloud_id), ..CreateContainerOptions::default() }); let addr = config.proxy.addr( @@ -43,24 +44,24 @@ impl ServiceTrait for Oc { let domain = addr.split_once("://").unwrap().1; let env_trusted_domain = format!("OWNCLOUD_TRUSTED_DOMAINS={domain}"); let env_domain = format!("OWNCLOUD_DOMAIN={domain}"); - let config = Config { - image: Some(image), + let config = ContainerCreateBody { + image: Some(image.into()), host_config: Some(HostConfig { network_mode: Some(network.to_string()), ..Default::default() }), - env: Some(vec![&env_trusted_domain, &env_domain]), + env: Some(vec![env_trusted_domain, env_domain]), labels: Some(hashmap! { - "haze-type" => self.name(), - "haze-cloud-id" => cloud_id + "haze-type".into() => self.name().into(), + "haze-cloud-id".into() => cloud_id.into(), }), networking_config: Some(NetworkingConfig { - endpoints_config: hashmap! { - network => EndpointSettings { + endpoints_config: Some(hashmap! { + network.into() => EndpointSettings { aliases: Some(vec![self.name().to_string()]), ..Default::default() } - }, + }), }), ..Default::default() }; @@ -69,10 +70,7 @@ impl ServiceTrait for Oc { .await .into_diagnostic()? .id; - docker - .start_container::(&id, None) - .await - .into_diagnostic()?; + docker.start_container(&id, None).await.into_diagnostic()?; Ok(vec![id]) } @@ -85,7 +83,7 @@ impl ServiceTrait for Oc { docker: &Docker, cloud_id: &str, config: &HazeConfig, - ) -> Result> { + ) -> Result>> { if let Some(ip) = self.get_ips(docker, cloud_id).await?.next() { let container = self.container_name(cloud_id).unwrap(); let addr = config.proxy.addr(&container, ip); diff --git a/src/service/office.rs b/src/service/office.rs index 87266a6..e7db10d 100644 --- a/src/service/office.rs +++ b/src/service/office.rs @@ -3,8 +3,10 @@ use crate::config::HazeConfig; use crate::image::pull_image; use crate::service::ServiceTrait; use crate::Result; -use bollard::container::{Config, CreateContainerOptions, NetworkingConfig}; -use bollard::models::{ContainerState, EndpointSettings, HostConfig}; +use bollard::models::{ + ContainerCreateBody, ContainerState, EndpointSettings, HostConfig, NetworkingConfig, +}; +use bollard::query_parameters::CreateContainerOptions; use bollard::Docker; use maplit::hashmap; use miette::{IntoDiagnostic, Report}; @@ -30,48 +32,68 @@ impl ServiceTrait for Office { config: &HazeConfig, _options: &CloudOptions, ) -> Result> { + let network_info = docker + .inspect_network(network, None) + .await + .into_diagnostic()?; + let gateway = network_info + .ipam + .as_ref() + .ok_or_else(|| Report::msg("Network has no ip info"))? + .config + .as_deref() + .ok_or_else(|| Report::msg("Network has no ip info"))? + .first() + .ok_or_else(|| Report::msg("Network has no ip info"))? + .gateway + .as_deref() + .ok_or_else(|| Report::msg("Network has no ip info"))?; + let image = "collabora/code"; pull_image(docker, image).await?; let container_id = self.container_name(cloud_id).unwrap(); let options = Some(CreateContainerOptions { - name: container_id.clone(), + name: Some(container_id.clone()), ..CreateContainerOptions::default() }); - let mut env = vec!["extra_params=--o:ssl.enable=false --o:ssl.termination=true"]; + let mut env = + vec![r#"extra_params=--o:ssl.enable=false --o:ssl.termination=true --o:net.frame_ancestors=*"#.to_string()]; + let proxy_base = &config.proxy.address; let clean_id = container_id.strip_prefix("haze-").unwrap_or(&container_id); - let server_name_opt = match (&config.proxy.address, config.proxy.https) { - (public, true) if !public.is_empty() => { - format!("server_name={clean_id}.{public}") - } - (public, false) if !public.is_empty() => { - format!("server_name={clean_id}.{public}") - } - _ => "".to_string(), - }; - - if !server_name_opt.is_empty() { - env.push(&server_name_opt); + if !proxy_base.is_empty() { + env.push(format!("server_name={clean_id}.{}", config.proxy.address)); } - let config = Config { - image: Some(image), + let clean_cloud_id = cloud_id.strip_prefix("haze-").unwrap_or(cloud_id); + let hosts = if proxy_base.is_empty() { + vec![] + } else { + vec![ + format!("{proxy_base}:{gateway}"), + format!("{clean_cloud_id}.{proxy_base}:{gateway}"), + ] + }; + + let config = ContainerCreateBody { + image: Some(image.into()), env: Some(env), host_config: Some(HostConfig { network_mode: Some(network.to_string()), + extra_hosts: Some(hosts), ..Default::default() }), labels: Some(hashmap! { - "haze-type" => self.name(), - "haze-cloud-id" => cloud_id + "haze-type".into() => self.name().into(), + "haze-cloud-id".into() => cloud_id.into(), }), networking_config: Some(NetworkingConfig { - endpoints_config: hashmap! { - network => EndpointSettings { + endpoints_config: Some(hashmap! { + network.into() => EndpointSettings { aliases: Some(vec![self.name().to_string()]), ..Default::default() } - }, + }), }), ..Default::default() }; @@ -80,10 +102,7 @@ impl ServiceTrait for Office { .await .into_diagnostic()? .id; - docker - .start_container::(&id, None) - .await - .into_diagnostic()?; + docker.start_container(&id, None).await.into_diagnostic()?; Ok(vec![id]) } @@ -100,7 +119,7 @@ impl ServiceTrait for Office { docker: &Docker, cloud_id: &str, config: &HazeConfig, - ) -> Result> { + ) -> Result>> { let container = &self.container_name(cloud_id).unwrap(); let info = docker .inspect_container(container, None) @@ -128,19 +147,27 @@ impl ServiceTrait for Office { } else { return Err(Report::msg("office not started")); }; + let public = config + .proxy + .addr_with_port(container, ip, self.proxy_port()); + Ok(vec![ - format!( - r#"occ config:app:set richdocuments wopi_url --value="http://{}:9980""#, - ip - ), - format!( - r#"occ config:app:set richdocuments public_wopi_url --value="{}""#, - config.proxy.addr_with_port(container, ip, 9980) - ), - format!( - r#"occ config:app:set richdocuments wopi_root --value="http://{}""#, - cloud_id - ), + vec![ + "occ".into(), + "config:app:set".into(), + "richdocuments".into(), + "public_wopi_url".into(), + "--value".into(), + public, + ], + vec![ + "occ".into(), + "richdocuments:setup".into(), + "--wopi-url".into(), + "http://office:9980".into(), + "--callback-url".into(), + "http://cloud".into(), + ], ]) } diff --git a/src/service/onlyoffice.rs b/src/service/onlyoffice.rs index f262c8d..fa12c2c 100644 --- a/src/service/onlyoffice.rs +++ b/src/service/onlyoffice.rs @@ -2,10 +2,12 @@ use crate::cloud::CloudOptions; use crate::config::HazeConfig; use crate::exec::exec; use crate::image::pull_image; -use crate::service::ServiceTrait; +use crate::service::{split_cmnd, ServiceTrait}; use crate::Result; -use bollard::container::{Config, CreateContainerOptions, NetworkingConfig}; -use bollard::models::{ContainerState, EndpointSettings, HostConfig}; +use bollard::models::{ + ContainerCreateBody, ContainerState, EndpointSettings, HostConfig, NetworkingConfig, +}; +use bollard::query_parameters::CreateContainerOptions; use bollard::Docker; use maplit::hashmap; use miette::{IntoDiagnostic, Report}; @@ -35,26 +37,26 @@ impl ServiceTrait for OnlyOffice { let image = "onlyoffice/documentserver"; pull_image(docker, image).await?; let options = Some(CreateContainerOptions { - name: self.container_name(cloud_id).unwrap(), + name: self.container_name(cloud_id), ..CreateContainerOptions::default() }); - let config = Config { - image: Some(image), + let config = ContainerCreateBody { + image: Some(image.into()), host_config: Some(HostConfig { network_mode: Some(network.to_string()), ..Default::default() }), labels: Some(hashmap! { - "haze-type" => self.name(), - "haze-cloud-id" => cloud_id + "haze-type".into() => self.name().into(), + "haze-cloud-id".into() => cloud_id.into(), }), networking_config: Some(NetworkingConfig { - endpoints_config: hashmap! { - network => EndpointSettings { + endpoints_config: Some(hashmap! { + network.into() => EndpointSettings { aliases: Some(vec![self.name().to_string()]), ..Default::default() } - }, + }), }), ..Default::default() }; @@ -63,10 +65,7 @@ impl ServiceTrait for OnlyOffice { .await .into_diagnostic()? .id; - docker - .start_container::(&id, None) - .await - .into_diagnostic()?; + docker.start_container(&id, None).await.into_diagnostic()?; Ok(vec![id]) } @@ -83,7 +82,7 @@ impl ServiceTrait for OnlyOffice { docker: &Docker, cloud_id: &str, config: &HazeConfig, - ) -> Result> { + ) -> Result>> { let info = docker .inspect_container(&self.container_name(cloud_id).unwrap(), None) .await @@ -138,16 +137,44 @@ impl ServiceTrait for OnlyOffice { ); Ok(vec![ - format!("occ config:app:set onlyoffice DocumentServerUrl --value {addr}/"), - format!("occ config:app:set onlyoffice jwt_secret --value {secret}"), - "occ onlyoffice:documentserver --check".into(), + vec![ + "occ".into(), + "config:app:set".into(), + "onlyoffice".into(), + "DocumentServerUrl".into(), + "--value".into(), + addr, + ], + vec![ + "occ".into(), + "config:app:set".into(), + "onlyoffice".into(), + "jwt_secret".into(), + "--value".into(), + secret.into(), + ], + split_cmnd("occ onlyoffice:documentserver --check"), ]) } else { Ok(vec![ - format!("occ config:app:set onlyoffice DocumentServerUrl --value https://{ip}/"), - "occ config:app:set onlyoffice verify_peer_off --value true".into(), - format!("occ config:app:set onlyoffice jwt_secret --value {secret}"), - "occ onlyoffice:documentserver --check".into(), + vec![ + "occ".into(), + "config:app:set".into(), + "onlyoffice".into(), + "DocumentServerUrl".into(), + "--value".into(), + format!("https://{ip}/"), + ], + split_cmnd("occ config:app:set onlyoffice verify_peer_off --value true"), + vec![ + "occ".into(), + "config:app:set".into(), + "onlyoffice".into(), + "jwt_secret".into(), + "--value".into(), + secret.into(), + ], + split_cmnd("occ onlyoffice:documentserver --check"), ]) } } diff --git a/src/service/push.rs b/src/service/push.rs index ed4bf57..b0bbab2 100644 --- a/src/service/push.rs +++ b/src/service/push.rs @@ -2,8 +2,8 @@ use crate::cloud::CloudOptions; use crate::config::HazeConfig; use crate::image::pull_image; use crate::service::ServiceTrait; -use bollard::container::{Config, CreateContainerOptions, NetworkingConfig}; -use bollard::models::{EndpointSettings, HostConfig}; +use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig}; +use bollard::query_parameters::CreateContainerOptions; use bollard::Docker; use local_ip_address::list_afinet_netifas; use maplit::hashmap; @@ -33,11 +33,11 @@ impl ServiceTrait for NotifyPush { let image = "icewind1991/notify_push"; pull_image(docker, image).await?; let options = Some(CreateContainerOptions { - name: self.container_name(cloud_id).unwrap(), + name: self.container_name(cloud_id), ..CreateContainerOptions::default() }); - let config = Config { - image: Some(image), + let config = ContainerCreateBody { + image: Some(image.into()), host_config: Some(HostConfig { network_mode: Some(network.to_string()), binds: Some(vec![ @@ -47,23 +47,23 @@ impl ServiceTrait for NotifyPush { ..Default::default() }), env: Some(vec![ - "NEXTCLOUD_URL=http://cloud/", - "LOG=debug", - "REDIS_URL=redis://cloud/", + "NEXTCLOUD_URL=http://cloud/".into(), + "LOG=debug".into(), + "REDIS_URL=redis://cloud/".into(), ]), labels: Some(hashmap! { - "haze-type" => self.name(), - "haze-cloud-id" => cloud_id + "haze-type".into() => self.name().into(), + "haze-cloud-id".into() => cloud_id.into(), }), networking_config: Some(NetworkingConfig { - endpoints_config: hashmap! { - network => EndpointSettings { + endpoints_config: Some(hashmap! { + network.into() => EndpointSettings { aliases: Some(vec![self.name().to_string()]), ..Default::default() } - }, + }), }), - cmd: Some(vec!["/notify_push", "/config/config.php"]), + cmd: Some(vec!["/notify_push".into(), "/config/config.php".into()]), ..Default::default() }; let id = docker @@ -87,7 +87,7 @@ impl ServiceTrait for NotifyPush { docker: &Docker, cloud_id: &str, config: &HazeConfig, - ) -> Result> { + ) -> Result>> { let mut ips: Vec<_> = self.get_ips(docker, cloud_id).await?.collect(); if let Ok(local_interfaces) = list_afinet_netifas() { ips.extend(local_interfaces.into_iter().map(|(_, ip)| ip)); @@ -97,10 +97,14 @@ impl ServiceTrait for NotifyPush { .iter() .enumerate() .map(|(i, ip)| { - format!( - "occ config:system:set trusted_proxies {} --value {ip}", - i + 1 - ) + vec![ + "occ".into(), + "config:system:set".into(), + "trusted_proxies".into(), + (i + 1).to_string(), + "--value".into(), + ip.to_string(), + ] }) .collect(); @@ -108,7 +112,7 @@ impl ServiceTrait for NotifyPush { config .proxy .addr_with_port(&self.container_name(cloud_id).unwrap(), ips[0], 7867); - commands.push(format!("occ notify_push:setup {}", addr)); + commands.push(vec!["occ".into(), "notify_push:setup".into(), addr]); Ok(commands) } diff --git a/src/service/redis.rs b/src/service/redis.rs index 1712b9b..db69667 100644 --- a/src/service/redis.rs +++ b/src/service/redis.rs @@ -1,10 +1,10 @@ use crate::cloud::CloudOptions; use crate::config::HazeConfig; use crate::image::pull_image; -use crate::service::ServiceTrait; +use crate::service::{split_cmnd, ServiceTrait}; use crate::Result; -use bollard::container::{Config, CreateContainerOptions, NetworkingConfig}; -use bollard::models::{EndpointSettings, HostConfig}; +use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig}; +use bollard::query_parameters::CreateContainerOptions; use bollard::Docker; use maplit::hashmap; use miette::IntoDiagnostic; @@ -29,26 +29,26 @@ impl ServiceTrait for Redis { let image = "redis:8-alpine"; pull_image(docker, image).await?; let options = Some(CreateContainerOptions { - name: self.container_name(cloud_id).unwrap(), + name: self.container_name(cloud_id), ..CreateContainerOptions::default() }); - let config = Config { - image: Some(image), + let config = ContainerCreateBody { + image: Some(image.into()), host_config: Some(HostConfig { network_mode: Some(network.to_string()), ..Default::default() }), labels: Some(hashmap! { - "haze-type" => self.name(), - "haze-cloud-id" => cloud_id + "haze-type".into() => self.name().into(), + "haze-cloud-id".into() => cloud_id.into(), }), networking_config: Some(NetworkingConfig { - endpoints_config: hashmap! { - network => EndpointSettings { + endpoints_config: Some(hashmap! { + network.into() => EndpointSettings { aliases: Some(vec![self.name().to_string()]), ..Default::default() } - }, + }), }), ..Default::default() }; @@ -57,10 +57,7 @@ impl ServiceTrait for Redis { .await .into_diagnostic()? .id; - docker - .start_container::(&id, None) - .await - .into_diagnostic()?; + docker.start_container(&id, None).await.into_diagnostic()?; Ok(vec![id]) } @@ -73,7 +70,9 @@ impl ServiceTrait for Redis { _docker: &Docker, _cloud_id: &str, _config: &HazeConfig, - ) -> Result> { - Ok(vec!["occ config:system:set redis host --value redis".into()]) + ) -> Result>> { + Ok(vec![split_cmnd( + "occ config:system:set redis host --value redis", + )]) } } diff --git a/src/service/sftp.rs b/src/service/sftp.rs index 84502c1..1f47a40 100644 --- a/src/service/sftp.rs +++ b/src/service/sftp.rs @@ -1,13 +1,14 @@ use crate::cloud::CloudOptions; use crate::config::HazeConfig; use crate::image::pull_image; -use crate::service::ServiceTrait; +use crate::service::{split_cmnd, ServiceTrait}; use crate::Result; -use bollard::container::{Config, CreateContainerOptions, NetworkingConfig}; -use bollard::models::{EndpointSettings, HostConfig}; +use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig}; +use bollard::query_parameters::CreateContainerOptions; use bollard::Docker; use maplit::hashmap; -use miette::IntoDiagnostic; +use miette::{Context, IntoDiagnostic}; +use std::fs::{create_dir_all, write}; #[derive(Debug, Clone, Eq, PartialEq)] pub struct Sftp; @@ -29,28 +30,31 @@ impl ServiceTrait for Sftp { let image = "atmoz/sftp:alpine"; pull_image(docker, image).await?; let options = Some(CreateContainerOptions { - name: self.container_name(cloud_id).unwrap(), + name: self.container_name(cloud_id), ..CreateContainerOptions::default() }); - let config = Config { - image: Some(image), + let config = ContainerCreateBody { + image: Some(image.into()), host_config: Some(HostConfig { network_mode: Some(network.to_string()), ..Default::default() }), labels: Some(hashmap! { - "haze-type" => self.name(), - "haze-cloud-id" => cloud_id + "haze-type".into() => self.name().into(), + "haze-cloud-id".into() => cloud_id.into(), }), networking_config: Some(NetworkingConfig { - endpoints_config: hashmap! { - network => EndpointSettings { + endpoints_config: Some(hashmap! { + network.into() => EndpointSettings { aliases: Some(vec![self.name().to_string()]), ..Default::default() } - }, + }), }), - cmd: Some(vec!["test:test:::data"]), + cmd: Some(vec![ + "test:test:::data".into(), + "ldaptest:test:::data".into(), + ]), ..Default::default() }; let id = docker @@ -58,10 +62,7 @@ impl ServiceTrait for Sftp { .await .into_diagnostic()? .id; - docker - .start_container::(&id, None) - .await - .into_diagnostic()?; + docker.start_container(&id, None).await.into_diagnostic()?; Ok(vec![id]) } @@ -78,13 +79,119 @@ impl ServiceTrait for Sftp { _docker: &Docker, _cloud_id: &str, _config: &HazeConfig, - ) -> Result> { + ) -> Result>> { Ok(vec![ - "occ files_external:create sftp sftp password::password".into(), - "occ files_external:config 1 host sftp".into(), - "occ files_external:config 1 user test".into(), - "occ files_external:config 1 root data".into(), - "occ files_external:config 1 password test".into(), + split_cmnd("occ files_external:create sftp sftp password::password"), + split_cmnd("occ files_external:config 1 host sftp"), + split_cmnd("occ files_external:config 1 user test"), + split_cmnd("occ files_external:config 1 root data"), + split_cmnd("occ files_external:config 1 password test"), + ]) + } +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct SftpKey; + +#[async_trait::async_trait] +impl ServiceTrait for SftpKey { + fn name(&self) -> &str { + "sftp-key" + } + + async fn spawn( + &self, + docker: &Docker, + cloud_id: &str, + network: &str, + config: &HazeConfig, + _options: &CloudOptions, + ) -> Result> { + let image = "atmoz/sftp:alpine"; + pull_image(docker, image).await?; + let options = Some(CreateContainerOptions { + name: self.container_name(cloud_id), + ..CreateContainerOptions::default() + }); + let key_dir = config.work_dir.join("certificates/sftp"); + create_dir_all(&key_dir) + .into_diagnostic() + .wrap_err("Failed to create sftp certificate directory")?; + let private_path = key_dir.join("id_rsa"); + let public_path = key_dir.join("id_rsa.pub"); + let private_key = include_str!("../../certificates/sftp/id_rsa"); + let public_key = include_str!("../../certificates/sftp/id_rsa.pub"); + if !private_path.exists() { + write(&private_path, private_key) + .into_diagnostic() + .wrap_err("Failed to write sftp client certificate")?; + } + if !public_path.exists() { + write(&public_path, public_key) + .into_diagnostic() + .wrap_err("Failed to write sftp client key")?; + } + + let volumes = vec![format!("{public_path}:/home/test/.ssh/keys/id_rsa:ro")]; + + let config = ContainerCreateBody { + image: Some(image.into()), + host_config: Some(HostConfig { + network_mode: Some(network.to_string()), + binds: Some(volumes), + ..Default::default() + }), + labels: Some(hashmap! { + "haze-type".into() => self.name().into(), + "haze-cloud-id".into() => cloud_id.into(), + }), + networking_config: Some(NetworkingConfig { + endpoints_config: Some(hashmap! { + network.into() => EndpointSettings { + aliases: Some(vec![self.name().to_string()]), + ..Default::default() + } + }), + }), + cmd: Some(vec!["test::::data".into()]), + ..Default::default() + }; + let id = docker + .create_container(options, config) + .await + .into_diagnostic()? + .id; + docker.start_container(&id, None).await.into_diagnostic()?; + Ok(vec![id]) + } + + fn container_name(&self, cloud_id: &str) -> Option { + Some(format!("{}-sftp-key", cloud_id)) + } + + fn apps(&self) -> &'static [&'static str] { + &["files_external"] + } + + async fn post_setup( + &self, + _docker: &Docker, + _cloud_id: &str, + _config: &HazeConfig, + ) -> Result>> { + Ok(vec![ + split_cmnd("occ files_external:create sftp sftp publickey::rsa_private"), + split_cmnd("occ files_external:config 1 host sftp-key"), + split_cmnd("occ files_external:config 1 user test"), + split_cmnd("occ files_external:config 1 root data"), + vec![ + "occ".into(), + "files_external:config".into(), + "--value-from-file".into(), + "1".into(), + "private_key".into(), + "/certificates/sftp/id_rsa".into(), + ], ]) } } diff --git a/src/service/smb.rs b/src/service/smb.rs index 5f4dec9..be1143b 100644 --- a/src/service/smb.rs +++ b/src/service/smb.rs @@ -1,10 +1,10 @@ use crate::cloud::CloudOptions; use crate::config::HazeConfig; use crate::image::pull_image; -use crate::service::ServiceTrait; +use crate::service::{split_cmnd, ServiceTrait}; use crate::Result; -use bollard::container::{Config, CreateContainerOptions, NetworkingConfig}; -use bollard::models::{EndpointSettings, HostConfig}; +use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig}; +use bollard::query_parameters::CreateContainerOptions; use bollard::Docker; use maplit::hashmap; use miette::IntoDiagnostic; @@ -29,31 +29,33 @@ impl ServiceTrait for Smb { let image = "ghcr.io/servercontainers/samba:smbd-only-a3.18.0-s4.18.2-r0"; pull_image(docker, image).await?; let options = Some(CreateContainerOptions { - name: self.container_name(cloud_id).unwrap(), + name: self.container_name(cloud_id), ..CreateContainerOptions::default() }); - let config = Config { - image: Some(image), + let config = ContainerCreateBody { + image: Some(image.into()), host_config: Some(HostConfig { network_mode: Some(network.to_string()), ..Default::default() }), env: Some(vec![ - "ACCOUNT_test=test", - "UID_test=1000", - "SAMBA_VOLUME_CONFIG_test=[test]; path=/tmp; valid users = test; guest ok = no; read only = no; browseable = yes", + "ACCOUNT_test=test".into(), + "ACCOUNT_ldaptest=test".into(), + "UID_test=1000".into(), + "SAMBA_VOLUME_CONFIG_test=[test]; path=/tmp; valid users = test; guest ok = no; read only = no; browseable = yes".into(), + "SAMBA_VOLUME_CONFIG_ldaptest=[ldaptest]; path=/tmp; valid users = ldaptest; guest ok = no; read only = no; browseable = yes".into(), ]), labels: Some(hashmap! { - "haze-type" => self.name(), - "haze-cloud-id" => cloud_id + "haze-type".into() => self.name().into(), + "haze-cloud-id".into() => cloud_id.into(), }), networking_config: Some(NetworkingConfig { - endpoints_config: hashmap! { - network => EndpointSettings { + endpoints_config: Some(hashmap! { + network.into() => EndpointSettings { aliases: Some(vec![self.name().to_string()]), ..Default::default() } - }, + }), }), ..Default::default() }; @@ -62,10 +64,7 @@ impl ServiceTrait for Smb { .await .into_diagnostic()? .id; - docker - .start_container::(&id, None) - .await - .into_diagnostic()?; + docker.start_container(&id, None).await.into_diagnostic()?; Ok(vec![id]) } @@ -82,13 +81,13 @@ impl ServiceTrait for Smb { _docker: &Docker, _cloud_id: &str, _config: &HazeConfig, - ) -> Result> { + ) -> Result>> { Ok(vec![ - "occ files_external:create smb smb password::password".into(), - "occ files_external:config 1 host smb".into(), - "occ files_external:config 1 user test".into(), - "occ files_external:config 1 password test".into(), - "occ files_external:config 1 share test".into(), + split_cmnd("occ files_external:create smb smb password::password"), + split_cmnd("occ files_external:config 1 host smb"), + split_cmnd("occ files_external:config 1 user test"), + split_cmnd("occ files_external:config 1 password test"), + split_cmnd("occ files_external:config 1 share test"), ]) } } diff --git a/src/service/webhook.rs b/src/service/webhook.rs new file mode 100644 index 0000000..3967483 --- /dev/null +++ b/src/service/webhook.rs @@ -0,0 +1,71 @@ +use crate::cloud::CloudOptions; +use crate::config::HazeConfig; +use crate::image::pull_image; +use crate::service::ServiceTrait; +use crate::Result; +use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig}; +use bollard::query_parameters::CreateContainerOptions; +use bollard::Docker; +use maplit::hashmap; +use miette::IntoDiagnostic; + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Webhook; + +#[async_trait::async_trait] +impl ServiceTrait for Webhook { + fn name(&self) -> &str { + "webhook" + } + + async fn spawn( + &self, + docker: &Docker, + cloud_id: &str, + network: &str, + _config: &HazeConfig, + _options: &CloudOptions, + ) -> Result> { + let image = "ghcr.io/tarampampam/webhook-tester"; + pull_image(docker, image).await?; + let options = Some(CreateContainerOptions { + name: self.container_name(cloud_id), + ..CreateContainerOptions::default() + }); + let config = ContainerCreateBody { + image: Some(image.into()), + host_config: Some(HostConfig { + network_mode: Some(network.to_string()), + ..Default::default() + }), + labels: Some(hashmap! { + "haze-type".into() => self.name().into(), + "haze-cloud-id".into() => cloud_id.into(), + }), + networking_config: Some(NetworkingConfig { + endpoints_config: Some(hashmap! { + network.into() => EndpointSettings { + aliases: Some(vec![self.name().to_string()]), + ..Default::default() + } + }), + }), + ..Default::default() + }; + let id = docker + .create_container(options, config) + .await + .into_diagnostic()? + .id; + docker.start_container(&id, None).await.into_diagnostic()?; + Ok(vec![id]) + } + + fn container_name(&self, cloud_id: &str) -> Option { + Some(format!("{}-webhook", cloud_id)) + } + + fn proxy_port(&self) -> u16 { + 8080 + } +} diff --git a/src/sources.rs b/src/sources.rs index 0a19e08..ccfe564 100644 --- a/src/sources.rs +++ b/src/sources.rs @@ -1,6 +1,17 @@ +use crate::config::HazeConfig; +use camino::Utf8PathBuf; +use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use miette::{Context, IntoDiagnostic, Report, Result}; +use reqwest::header::HeaderName; +use reqwest::{Client, IntoUrl, Response}; +use sha2::{Digest, Sha512}; use std::fs::read_to_string; +use std::io::Cursor; use std::path::{Path, PathBuf}; +use std::time::Duration; +use tokio::fs::create_dir_all; +use zip::read::root_dir_common_filter; +use zip::ZipArchive; pub struct Sources { #[allow(dead_code)] @@ -49,3 +60,116 @@ impl Sources { } } } + +pub async fn download_nc(config: &HazeConfig, version: &str) -> Result { + if !version.chars().all(|c| c.is_ascii_digit() || c == '.') { + return Err(Report::msg(format!("Invalid version: {version}"))); + } + let root = config.work_dir.join("sources"); + create_dir_all(&root) + .await + .into_diagnostic() + .wrap_err("failed to create parent directory for sources")?; + let dest = root.join(version); + if !dest.exists() { + let progress = MultiProgress::new(); + let download_style = ProgressStyle::with_template("{spinner:.green} {msg} [{elapsed_precise}] [{bar:.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})") + .unwrap(); + let download_bar = ProgressBar::new(0) + .with_message("Downloading") + .with_style(download_style.clone()); + let download_bar = progress.add(download_bar); + + let archive = download_url( + format!("https://download.nextcloud.com/server/releases/nextcloud-{version}.zip"), + |size| { + download_bar.set_length(size); + }, + |count| { + download_bar.inc(count); + }, + ) + .await + .wrap_err_with(|| format!("Failed to download archive for {}", version))?; + download_bar.finish(); + + let expected_hash = download_text(format!( + "https://download.nextcloud.com/server/releases/nextcloud-{version}.zip.sha512" + )) + .await + .wrap_err_with(|| format!("Failed to download hash for {}", version))?; + let expected_hash = expected_hash + .split_once(' ') + .map(|(hash, _)| hash) + .unwrap_or(expected_hash.trim()); + + let hash_bar = ProgressBar::new(download_bar.length().unwrap_or_default()) + .with_message("Validating") + .with_style(download_style.clone()); + let hash_bar = progress.add(hash_bar); + let mut hasher = Sha512::new(); + for chunk in archive.chunks(1014 * 1024) { + hash_bar.inc(chunk.len() as u64); + hasher.update(chunk); + } + let hash = hasher.finalize(); + + let hash = base16ct::lower::encode_string(&hash); + if expected_hash != hash { + return Err(Report::msg(format!( + "Invalid hash for downloaded: {version}, expected {expected_hash} but got {hash}" + ))); + } + hash_bar.finish(); + + let extract_bar = ProgressBar::new_spinner().with_message("Extracting"); + extract_bar.enable_steady_tick(Duration::from_millis(100)); + let extract_bar = progress.add(extract_bar); + let mut archive = ZipArchive::new(Cursor::new(archive)).into_diagnostic()?; + archive + .extract_unwrapped_root_dir(&dest, root_dir_common_filter) + .into_diagnostic() + .wrap_err("Failed to extract archive")?; + extract_bar.finish(); + } + Ok(dest) +} + +async fn download_url( + url: U, + size: SizeFN, + progress: ProgressFN, +) -> Result> { + let mut res = get_url(url).await?.error_for_status().into_diagnostic()?; + let mut buff = Vec::new(); + + size(res.content_length().unwrap_or_default()); + while let Some(chunk) = res.chunk().await.into_diagnostic()? { + progress(chunk.len() as u64); + buff.extend(chunk); + } + Ok(buff) +} +async fn download_text(url: U) -> Result { + get_url(url) + .await? + .error_for_status() + .into_diagnostic()? + .text() + .await + .into_diagnostic() +} + +async fn get_url(url: U) -> Result { + Client::builder() + .build() + .into_diagnostic()? + .get(url) + .header( + HeaderName::from_static("user-agent"), + format!("haze {}", env!("CARGO_PKG_VERSION")), + ) + .send() + .await + .into_diagnostic() +}