use std::path::PathBuf;
use std::sync::Arc;
use anyhow::{bail, Result};
use clap::{Args, Parser, Subcommand};
use rust_nugitea::storage::Store;
use rust_nugitea::{git_exec_tcp, storage_server};
#[derive(Parser)]
#[command(name = "nugitea-storaged")]
struct Cli {
#[command(subcommand)]
command: Cmd,
}
#[derive(Subcommand)]
enum Cmd {
Serve(ServeArgs),
Repo {
#[command(subcommand)]
cmd: RepoCmd,
},
Hook {
name: String,
#[arg(trailing_var_arg = true)]
rest: Vec<String>,
},
}
#[derive(Args)]
struct ServeArgs {
#[arg(long, default_value = "127.0.0.1:9080")]
http: String,
#[arg(long, default_value = "127.0.0.1:9081")]
tcp: String,
#[arg(long, default_value = "./data")]
repo_root: PathBuf,
}
#[derive(Subcommand)]
enum RepoCmd {
Create { name: String },
List,
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
match cli.command {
Cmd::Serve(args) => cmd_serve(args).await,
Cmd::Repo { cmd } => cmd_repo(cmd).await,
Cmd::Hook { name, rest } => cmd_hook(name, rest).await,
}
}
async fn cmd_serve(args: ServeArgs) -> Result<()> {
let store = Arc::new(Store::new(&args.repo_root)?);
let http_addr: std::net::SocketAddr =
args.http.parse().map_err(|e| anyhow::anyhow!("invalid address {:?}: {e}", args.http))?;
let tcp_addr: std::net::SocketAddr =
args.tcp.parse().map_err(|e| anyhow::anyhow!("invalid address {:?}: {e}", args.tcp))?;
let http_store = store.clone();
let http_task = tokio::spawn(async move {
let app = storage_server::router(http_store);
let listener = tokio::net::TcpListener::bind(http_addr).await?;
println!("storage: listening on {http_addr}");
axum::serve(listener, app).await
});
let tcp_task = tokio::spawn(git_exec_tcp::serve(store, tcp_addr));
tokio::select! {
_ = tokio::signal::ctrl_c() => { println!("shutting down"); }
res = http_task => { res??; }
res = tcp_task => { res??; }
}
Ok(())
}
async fn cmd_repo(cmd: RepoCmd) -> Result<()> {
let store = Store::new(repo_root_from_env())?;
match cmd {
RepoCmd::Create { name } => {
let path = store.init_bare(&name).await?;
println!("{}", path.display());
}
RepoCmd::List => {
for name in store.list()? {
println!("{name}");
}
}
}
Ok(())
}
async fn cmd_hook(name: String, _rest: Vec<String>) -> Result<()> {
match name.as_str() {
"pre-receive" | "post-receive" => {
use tokio::io::AsyncReadExt;
let mut buf = Vec::new();
tokio::io::stdin().read_to_end(&mut buf).await?;
}
"update" => {
}
other => bail!("unknown hook {other:?}"),
}
Ok(())
}
fn repo_root_from_env() -> PathBuf {
std::env::var("NUGITEA_REPO_ROOT").map(PathBuf::from).unwrap_or_else(|_| PathBuf::from("./data"))
}