use crate::git::{GitConfig, ThreadSafeRepository}; use crate::http::response::response_interceptor; use anyhow::{anyhow, Result}; use axum::middleware::from_fn; use bollard::Docker; use clap::Parser; use git2::Config; use std::{net::SocketAddr, path::PathBuf}; use tokio::fs; mod git; mod http; mod nix; mod node; mod route; mod stack; /// GitOps+WebUI for arion-based stacks #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { /// Path to root of stacks #[arg(short = 'd', long = "stack-dir", env = "STAX_DIR")] stack_dir: PathBuf, /// Address:port to bind #[arg(short, long, default_value = "0.0.0.0:3000", env = "STAX_BIND")] bind: SocketAddr, /// Path to arion #[arg( long = "arion-bin", env = "STAX_ARION_BIN", default_value = "/run/current-system/sw/bin/arion" )] arion_binary: PathBuf, #[arg( long = "git-author", default_value = "staxman ", env = "STAX_GIT_AUTHOR" )] git_author: String, } #[derive(Clone)] pub struct AppState { pub stack_dir: PathBuf, pub arion_bin: PathBuf, pub docker: Docker, pub repository: ThreadSafeRepository, } #[tokio::main] async fn main() -> Result<()> { // Initialize logging and env _ = dotenvy::dotenv(); tracing_subscriber::fmt::init(); // Parse args let args = Args::parse(); tracing::info!("listening on {}", &args.bind); // Try to connect to docker server let docker = Docker::connect_with_local_defaults()?; // Ping to make sure it works let version = docker.version().await?; tracing::info!("docker version: {}", version.version.unwrap()); let (author_name, author_email) = parse_author(&args.git_author)?; let gitconfig = GitConfig { author_name, author_email, }; // Fix up local git config for docker scenarios let mut config = Config::open_default()?; // Add "*" to safe.directory config.set_str("safe.directory", "*")?; // Make sure stack arg exists and is properly initialized fs::create_dir_all(&args.stack_dir).await?; let repository = ThreadSafeRepository::ensure_repository(gitconfig, &args.stack_dir)?; let state = AppState { stack_dir: args.stack_dir, arion_bin: args.arion_binary, docker, repository, }; let app = route::router() .with_state(state) .layer(from_fn(response_interceptor)); axum::Server::bind(&args.bind) .serve(app.into_make_service()) .await?; Ok(()) } fn parse_author(git_author: &str) -> Result<(String, String)> { match git_author.split_once('<') { Some((name, email)) => Ok(( name.trim().to_string(), email.trim_end_matches(">").trim().to_string(), )), None => Err(anyhow!( "invalid git author format (email must be specified)" )), } }