diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b5b0389 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,14 @@ + + +gitvol_socket/ +gitvol_volumes/ + +tests/ +target/ + +.git/ +.github/ +*.log +tmp/ +build/ +build.sh diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 7690033..90aa563 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -5,6 +5,7 @@ on: push: branches: - master + - plugin pull_request: branches: - master diff --git a/.gitignore b/.gitignore index 0d6f974..b82496f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /target -/gitvol_volumes \ No newline at end of file +/gitvol_volumes +/gitvol_socket +/build/rootfs/ diff --git a/Cargo.lock b/Cargo.lock index fc45577..9752b2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,9 +78,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "axum" @@ -153,9 +159,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.9.1" +version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" [[package]] name = "block-buffer" @@ -180,9 +186,47 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "clap" +version = "4.5.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_derive" +version = "4.5.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "colorchoice" @@ -287,9 +331,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -372,9 +416,9 @@ version = "0.1.0" dependencies = [ "anyhow", "axum", + "clap", "env_logger", "git-url-parse", - "hyper", "log", "once_cell", "serde", @@ -441,19 +485,21 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "http", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", ] @@ -562,9 +608,9 @@ dependencies = [ [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -583,9 +629,9 @@ dependencies = [ [[package]] name = "io-uring" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ "bitflags", "cfg-if", @@ -726,9 +772,9 @@ checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" @@ -768,9 +814,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.96" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beef09f85ae72cea1ef96ba6870c51e6382ebfa4f0e85b643459331f3daa5be0" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -792,9 +838,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "regex" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" dependencies = [ "aho-corasick", "memchr", @@ -804,9 +850,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" dependencies = [ "aho-corasick", "memchr", @@ -815,9 +861,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "rustc-demangle" @@ -881,9 +927,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.142" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" dependencies = [ "itoa", "memchr", @@ -1063,9 +1109,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.104" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -1091,15 +1137,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.20.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" dependencies = [ "fastrand", "getrandom", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1241,13 +1287,14 @@ checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "url" -version = "2.5.4" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] @@ -1264,9 +1311,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" dependencies = [ "getrandom", "js-sys", diff --git a/Cargo.toml b/Cargo.toml index 14a1ae6..408413b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,14 +12,20 @@ serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.142" sha2 = "0.10.9" tokio = { version = "1.47.1", features = ["rt", "rt-multi-thread", "fs", "macros", "sync", "process"] } +tokio-stream = "0.1.17" +clap = { version = "4.5.45", default-features = false, features = ["derive", "std", "help"] } # broken deps git-url-parse = "0.4.5" url = { version = "2.5.4" } -tokio-stream = "0.1.17" -hyper = { version = "1.6.0", features = ["server"] } [dev-dependencies] once_cell = "1.21.3" tempfile = "3.20.0" uuid = { version = "1.17.0", features = ["v4"] } + +[profile.release] +strip = true +opt-level = 3 +lto = true +codegen-units = 1 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..05d404f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM rust:alpine AS builder +RUN apk add --no-cache musl-dev + +WORKDIR /gitvol +COPY . . +RUN cargo build --release + +FROM alpine:latest +WORKDIR /gitvol +COPY --from=builder /gitvol/target/release/gitvol /gitvol/gitvol + +RUN apk add --no-cache git + +RUN ["/gitvol/gitvol", "--version"] + +CMD [ "-s", "/run/docker/plugins/gitvol.sock", "-m", "/gitvol/volumes" ] +ENTRYPOINT [ "/gitvol/gitvol" ] \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..bc7eea9 --- /dev/null +++ b/build.sh @@ -0,0 +1,103 @@ +#!/bin/sh + +echo "[INFO] Starting plugin assembly" + +print_err() { + echo "[ERROR] $1" + exit 1 +} + +PLUGIN_NAME="gitvol" +VERSION=$(cargo run --quiet -- --version | awk '{split($0,a," "); print a[2]}') || print_err "Failed to get version from cargo" +BUILD_IMAGE="${PLUGIN_NAME}_rootfs_image" +BUILD_PATH="$PWD/build" +ROOTFS_PATH="$BUILD_PATH/rootfs" + +clean_rootfs_image() { + echo "[INFO] Cleaning old rootfs image: $BUILD_IMAGE" + exists_image_id=$(docker images "$BUILD_IMAGE" -q) + if [ -n "$exists_image_id" ]; then + echo "[INFO] Found image ID: $exists_image_id" + + runned_containers=$(docker ps -q --filter "ancestor=$BUILD_IMAGE") + if [ -n "$runned_containers" ]; then + echo "[INFO] Stopping running containers: $runned_containers" + docker stop $runned_containers || print_err "Failed to stop containers" + fi + + containers=$(docker ps -qa --filter "ancestor=$BUILD_IMAGE") + if [ -n "$containers" ]; then + echo "[INFO] Removing containers: $containers" + docker rm -vf $containers || print_err "Failed to remove containers" + fi + + echo "[INFO] Removing old image: $exists_image_id" + docker rmi -f "$exists_image_id" || print_err "Failed to remove image" + else + echo "[INFO] No old image found" + fi +} + +build_rootfs_image() { + echo "build docker image: $BUILD_IMAGE" + clean_rootfs_image + docker build -t ${BUILD_IMAGE} . || print_err "Failed to build image" +} + +clean_rootfs() { + if [ -d "$ROOTFS_PATH" ]; then + echo "[INFO] Cleaning rootfs directory: $ROOTFS_PATH" + rm -rf $ROOTFS_PATH || print_err "Failed to clean rootfs" + else + echo "[INFO] No rootfs directory to clean" + fi +} + +update_rootfs() { + echo "[INFO] Checking and updating rootfs" + clean_rootfs + mkdir $ROOTFS_PATH || print_err "Failed to create rootfs directory" +} + +clean_plugin() { + pn="${PLUGIN_NAME}:${1}" + echo "[INFO] Cleaning plugin: $pn" + plugin_info=$(docker plugin ls | grep "$pn") + if [ -n "$plugin_info" ]; then + echo "[INFO] Plugin $pn already exists" + plugin_enabled=$(echo $plugin_info | awk '{split($0,a," "); print a[4]}') + if [ "$plugin_enabled" = "true" ]; then + echo "[INFO] Disabling enabled plugin: $pn" + docker plugin disable -f "$pn" || print_err "Failed to disable plugin" + fi + echo "[INFO] Removing plugin: $pn" + docker plugin rm -f "$pn" || print_err "Failed to remove plugin" + else + echo "[INFO] No plugin found: $pn" + fi +} + +create_plugin() { + echo "\n[INFO] building plugin $PLUGIN_NAME with tag $1" + clean_plugin $1 + update_rootfs + echo "[INFO] Creating container from image: $BUILD_IMAGE" + ID=$(docker create ${BUILD_IMAGE}) || print_err "Failed to create container" + echo "[INFO] Container ID: $ID" + echo "[INFO] Exporting rootfs" + docker export $ID | tar -x -C $ROOTFS_PATH || print_err "Failed to export rootfs" + docker rm -vf $ID || print_err "Failed to remove ($ID) container" + echo "[INFO] Creating plugin: ${PLUGIN_NAME}:${1}" + docker plugin create "${PLUGIN_NAME}:${1}" "$BUILD_PATH" || print_err "Failed to create plugin" +} + + + +build_rootfs_image +create_plugin $VERSION +create_plugin latest +clean_rootfs_image +clean_rootfs + +echo "[INFO] Plugin assembly completed successfully" +docker plugin ls diff --git a/build/config.json b/build/config.json new file mode 100644 index 0000000..382e671 --- /dev/null +++ b/build/config.json @@ -0,0 +1,13 @@ +{ + "description": "gitvol", + "documentation": "https://example.com/docs", + "interface": { + "types": ["docker.volumedriver/1.0"], + "socket": "gitvol.sock" + }, + "entrypoint": ["/gitvol/gitvol", "-s", "/run/docker/plugins/gitvol.sock", "-m", "/gitvol/volumes"], + "PropagatedMount": "/gitvol/volumes", + "network": { + "type": "host" + } +} \ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..f156629 --- /dev/null +++ b/config.json @@ -0,0 +1,11 @@ +{ + "description": "Your Volume Plugin", + "documentation": "https://example.com/docs", + "interface": { + "types": ["docker.volumedriver/1.0"], + "socket": "gitvol.sock" + }, + "entrypoint": ["/gitvol", "-s /run/docker/plugins/gitvol.sock", "-m /gitvol/volumes"], + "workdir": "/gitvol", + "PropagatedMount": "/gitvol/volumes" +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b626cf4 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3' +services: + filebrowser: + volumes: + - 'ttt:/srv/ttt' + - 'zzz:/srv/zzz' + ports: + - '8080:80' + image: 'filebrowser/filebrowser:latest' + + +volumes: + ttt: + driver: gitvol:latest + driver_opts: + url: https://github.com/nerjs/nlogs.git + tag: v2.4.4 + zzz: + driver: gitvol:latest + driver_opts: + url: https://github.com/nerjs/nlogs.git + tag: v1.1.3 diff --git a/src/app/handlers.rs b/src/app/handlers.rs index e7d3169..46d8a5a 100644 --- a/src/app/handlers.rs +++ b/src/app/handlers.rs @@ -54,7 +54,9 @@ pub(super) async fn get_volume( volume: GetMp { name, mountpoint, - status: volume.status.clone(), + status: MpStatus { + status: volume.status.clone(), + }, }, }) } diff --git a/src/app/mod.rs b/src/app/mod.rs index c76ef83..7a3002a 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -4,8 +4,17 @@ mod shared; mod tests; use crate::state::GitvolState; -use axum::{Router, routing::post}; +use axum::{ + Router, + body::Bytes, + extract::Request, + http::{HeaderValue, StatusCode, header::CONTENT_TYPE}, + middleware::{self, Next}, + response::Response, + routing::post, +}; use handlers::*; +use log::{debug, kv}; pub fn create(state: GitvolState) -> Router { Router::new() @@ -18,5 +27,19 @@ pub fn create(state: GitvolState) -> Router { .route("/VolumeDriver.Remove", post(remove_volume)) .route("/VolumeDriver.Mount", post(mount_volume_to_container)) .route("/VolumeDriver.Unmount", post(unmount_volume_by_container)) + .layer(middleware::from_fn(transform_headers)) .with_state(state) } + +async fn transform_headers(mut request: Request, next: Next) -> Response { + let mut headers = request.headers_mut(); + headers.append(CONTENT_TYPE, HeaderValue::from_static("application/json")); + let mut response = next.run(request).await; + let mut response_headers = response.headers_mut(); + response_headers.append( + CONTENT_TYPE, + HeaderValue::from_static("application/vnd.docker.plugin.v1+json"), + ); + + response +} diff --git a/src/app/shared.rs b/src/app/shared.rs index 97e76f8..da9a933 100644 --- a/src/app/shared.rs +++ b/src/app/shared.rs @@ -119,6 +119,12 @@ pub(super) struct OptionalMp { pub(super) mountpoint: Option, } +#[cfg_attr(test, derive(Debug, PartialEq))] +#[derive(Serialize)] +pub(super) struct MpStatus { + pub(super) status: RepoStatus, +} + #[cfg_attr(test, derive(Debug, PartialEq))] #[derive(Serialize)] #[serde(rename_all = "PascalCase")] @@ -126,7 +132,8 @@ pub(super) struct GetMp { pub(super) name: String, #[serde(skip_serializing_if = "Option::is_none")] pub(super) mountpoint: Option, - pub(super) status: RepoStatus, + // TODO: format must be an object/ example: {"CreatedAt": "2025-08-24T19:44:31", "Size": "10GB", "Available": "5GB"} + pub(super) status: MpStatus, } #[cfg_attr(test, derive(Debug))] diff --git a/src/app/tests.rs b/src/app/tests.rs index 44c67ea..16f7fee 100644 --- a/src/app/tests.rs +++ b/src/app/tests.rs @@ -209,7 +209,9 @@ mod by_state_reactions { GetMp { name: VOLUME_NAME.to_string(), mountpoint: Some(path), - status: RepoStatus::Created, + status: MpStatus { + status: RepoStatus::Created + }, } ); } @@ -477,9 +479,13 @@ mod usecase { let volume_from_get = get_response.unwrap().volume; assert_eq!( - status, volume_from_get.status, + MpStatus { + status: status.clone() + }, + volume_from_get.status, "The volume status does not match the expected status. Current status: {:?}. Expected status: {:?}.", - volume_from_get.status, status + volume_from_get.status, + status ); let mountpoint_from_list = list_item.unwrap().mountpoint.clone(); diff --git a/src/main.rs b/src/main.rs index ddfa916..6e8c898 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,18 @@ +#![allow(warnings)] mod app; mod git; mod macros; mod state; -use anyhow::Context; +use std::{fmt::Debug, os::unix::fs::FileTypeExt, path::PathBuf}; + +use anyhow::{Context, Result}; use axum::serve; -use log::{debug, info}; +use clap::Parser; +use log::{ + debug, info, + kv::{self, ToValue}, +}; use tokio::{fs, net::UnixListener}; use crate::state::GitvolState; @@ -14,36 +21,97 @@ use crate::state::GitvolState; async fn main() -> anyhow::Result<()> { env_logger::builder() .filter_level(log::LevelFilter::Debug) + .format_timestamp(None) + .target(env_logger::Target::Stdout) .init(); - let volumes_dir = std::env::current_dir() - .context("getting the current directory to create a volumes directory")? - .join("gitvol_volumes"); - - if !volumes_dir.exists() { - fs::create_dir_all(&volumes_dir).await.with_context(|| { - format!("Failed to create volumes directory at '{:?}'", &volumes_dir) - })?; - debug!(path = volumes_dir.to_str(); "Created volumes directory"); - } + let settings = Settings::parse().await.context("parsing arguments")?; git::ensure_git_exists() .await .context("Failed check git exists")?; - let socket_path = volumes_dir.join("plugin.sock"); - if socket_path.exists() { - fs::remove_file(&socket_path) + if settings.socket.exists() { + fs::remove_file(&settings.socket) .await .context("remove old socket")?; } - let state = GitvolState::new(volumes_dir); + let state = GitvolState::new(settings.mount_path); let app = app::create(state); - let listener = UnixListener::bind(socket_path)?; + let listener = UnixListener::bind(settings.socket)?; info!("listening on {:?}", listener.local_addr().unwrap()); serve(listener, app.into_make_service()).await?; Ok(()) } + +#[derive(Debug, clap::Parser)] +#[command(version, about)] +struct Args { + #[arg(short, long)] + socket: Option, + + #[arg(short, long)] + mount_path: Option, +} + +#[derive(Debug)] +struct Settings { + socket: PathBuf, + mount_path: PathBuf, +} + +impl Settings { + async fn parse() -> Result { + let args = Args::parse(); + debug!(args = kv::Value::from_debug(&args); "parsing cli args."); + + let current_dir = + std::env::current_dir().context("Failed getting current dir for start settings.")?; + + let mut socket = args + .socket + .unwrap_or_else(|| current_dir.join("gitvol_socket/plugin.sock")); + if !socket.is_absolute() { + socket = current_dir.join(socket); + debug!(socket = kv::Value::from_debug(&socket); "Relative socket path. fixed this."); + } + + let mut mount_path = args + .mount_path + .unwrap_or_else(|| current_dir.join("gitvol_volumes")); + if !mount_path.is_absolute() { + mount_path = current_dir.join(mount_path); + debug!(mount_path = kv::Value::from_debug(&mount_path); "Relative mount path. fixed this."); + } + + if socket.exists() { + let socket_metadata = fs::metadata(socket.clone()).await?.file_type(); + if !socket_metadata.is_socket() { + anyhow::bail!("The path to the socket {:?} is not a socket.", socket); + } + debug!("Socket already exists."); + } else { + let Some(socket_parent) = socket.parent() else { + anyhow::bail!("Incorrect socket path {socket:?}."); + }; + debug!(socket_parent = kv::Value::from_debug(&socket_parent); "Trying to create socket parent dir."); + fs::create_dir_all(socket_parent) + .await + .context("create socket parent directory.")?; + } + + if mount_path.exists() { + anyhow::ensure!(mount_path.is_dir(), "Mounting path is not directory."); + } else { + debug!(mount_path = kv::Value::from_debug(&mount_path); "Trying to create mount dir."); + fs::create_dir_all(&mount_path) + .await + .context("create mounting directory.")?; + } + + Ok(Self { socket, mount_path }) + } +}