2023-11-26 20:36:51 +00:00
|
|
|
use crate::git::{GitConfig, ThreadSafeRepository};
|
2023-11-21 12:06:31 +00:00
|
|
|
use crate::http::response::response_interceptor;
|
2023-11-22 09:59:51 +00:00
|
|
|
use anyhow::{anyhow, Result};
|
2023-11-18 15:58:28 +00:00
|
|
|
use axum::middleware::from_fn;
|
2023-11-18 15:35:30 +00:00
|
|
|
use bollard::Docker;
|
|
|
|
use clap::Parser;
|
2023-11-26 21:57:50 +00:00
|
|
|
use git2::Config;
|
2023-11-18 19:51:10 +00:00
|
|
|
use std::{net::SocketAddr, path::PathBuf};
|
2023-11-22 09:59:51 +00:00
|
|
|
use tokio::fs;
|
2023-11-18 11:35:40 +00:00
|
|
|
|
2023-11-26 20:36:51 +00:00
|
|
|
mod git;
|
2023-11-18 15:35:30 +00:00
|
|
|
mod http;
|
2023-11-21 12:06:31 +00:00
|
|
|
mod nix;
|
2023-11-19 14:08:47 +00:00
|
|
|
mod node;
|
2023-11-18 15:35:30 +00:00
|
|
|
mod route;
|
2023-11-26 20:36:51 +00:00
|
|
|
mod stack;
|
2023-11-18 13:29:19 +00:00
|
|
|
|
2023-11-18 15:35:30 +00:00
|
|
|
/// 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")]
|
2023-11-18 19:51:10 +00:00
|
|
|
stack_dir: PathBuf,
|
2023-11-18 15:35:30 +00:00
|
|
|
|
|
|
|
/// Address:port to bind
|
|
|
|
#[arg(short, long, default_value = "0.0.0.0:3000", env = "STAX_BIND")]
|
|
|
|
bind: SocketAddr,
|
2023-11-21 00:36:42 +00:00
|
|
|
|
|
|
|
/// Path to arion
|
|
|
|
#[arg(
|
|
|
|
long = "arion-bin",
|
|
|
|
env = "STAX_ARION_BIN",
|
|
|
|
default_value = "/run/current-system/sw/bin/arion"
|
|
|
|
)]
|
|
|
|
arion_binary: PathBuf,
|
2023-11-22 09:59:51 +00:00
|
|
|
|
|
|
|
#[arg(
|
|
|
|
long = "git-author",
|
|
|
|
default_value = "staxman <staxman@example.tld>",
|
|
|
|
env = "STAX_GIT_AUTHOR"
|
|
|
|
)]
|
|
|
|
git_author: String,
|
2023-11-18 15:35:30 +00:00
|
|
|
}
|
2023-11-18 13:29:19 +00:00
|
|
|
|
2023-11-18 15:35:30 +00:00
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct AppState {
|
2023-11-18 19:51:10 +00:00
|
|
|
pub stack_dir: PathBuf,
|
2023-11-21 00:36:42 +00:00
|
|
|
pub arion_bin: PathBuf,
|
2023-11-18 15:35:30 +00:00
|
|
|
pub docker: Docker,
|
2023-11-22 09:59:51 +00:00
|
|
|
pub repository: ThreadSafeRepository,
|
2023-11-18 11:35:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::main]
|
2023-11-18 15:35:30 +00:00
|
|
|
async fn main() -> Result<()> {
|
|
|
|
// Initialize logging and env
|
|
|
|
_ = dotenvy::dotenv();
|
2023-11-18 13:29:19 +00:00
|
|
|
tracing_subscriber::fmt::init();
|
2023-11-18 11:35:40 +00:00
|
|
|
|
2023-11-25 13:42:36 +00:00
|
|
|
// Parse args
|
|
|
|
let args = Args::parse();
|
|
|
|
tracing::info!("listening on {}", &args.bind);
|
|
|
|
|
2023-11-18 15:35:30 +00:00
|
|
|
// Try to connect to docker server
|
|
|
|
let docker = Docker::connect_with_local_defaults()?;
|
2023-11-25 13:42:36 +00:00
|
|
|
|
2023-11-18 15:35:30 +00:00
|
|
|
// Ping to make sure it works
|
|
|
|
let version = docker.version().await?;
|
|
|
|
tracing::info!("docker version: {}", version.version.unwrap());
|
2023-11-18 11:35:40 +00:00
|
|
|
|
2023-11-22 09:59:51 +00:00
|
|
|
let (author_name, author_email) = parse_author(&args.git_author)?;
|
|
|
|
let gitconfig = GitConfig {
|
|
|
|
author_name,
|
|
|
|
author_email,
|
|
|
|
};
|
|
|
|
|
2023-11-26 21:57:50 +00:00
|
|
|
// Fix up local git config for docker scenarios
|
|
|
|
let mut config = Config::open_default()?;
|
|
|
|
// Add "*" to safe.directory
|
|
|
|
config.set_str("safe.directory", "*")?;
|
|
|
|
|
2023-11-22 09:59:51 +00:00
|
|
|
// Make sure stack arg exists and is properly initialized
|
|
|
|
fs::create_dir_all(&args.stack_dir).await?;
|
2023-11-23 00:38:39 +00:00
|
|
|
let repository = ThreadSafeRepository::ensure_repository(gitconfig, &args.stack_dir)?;
|
2023-11-22 09:59:51 +00:00
|
|
|
|
2023-11-18 15:35:30 +00:00
|
|
|
let state = AppState {
|
|
|
|
stack_dir: args.stack_dir,
|
2023-11-21 00:36:42 +00:00
|
|
|
arion_bin: args.arion_binary,
|
2023-11-18 15:35:30 +00:00
|
|
|
docker,
|
2023-11-22 09:59:51 +00:00
|
|
|
repository,
|
2023-11-18 15:35:30 +00:00
|
|
|
};
|
|
|
|
|
2023-11-18 15:58:28 +00:00
|
|
|
let app = route::router()
|
|
|
|
.with_state(state)
|
2023-11-18 16:52:15 +00:00
|
|
|
.layer(from_fn(response_interceptor));
|
2023-11-18 15:35:30 +00:00
|
|
|
|
|
|
|
axum::Server::bind(&args.bind)
|
2023-11-18 11:35:40 +00:00
|
|
|
.serve(app.into_make_service())
|
2023-11-18 15:35:30 +00:00
|
|
|
.await?;
|
|
|
|
|
|
|
|
Ok(())
|
2023-11-18 11:35:40 +00:00
|
|
|
}
|
2023-11-22 09:59:51 +00:00
|
|
|
|
|
|
|
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)"
|
|
|
|
)),
|
|
|
|
}
|
|
|
|
}
|