diff --git a/Cargo.lock b/Cargo.lock index 745c1af..abb6633 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -166,7 +166,7 @@ checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ "async-trait", "axum-core", - "bitflags", + "bitflags 1.3.2", "bytes", "futures-util", "http", @@ -243,6 +243,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + [[package]] name = "bollard" version = "0.15.0" @@ -303,6 +309,7 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ + "jobserver", "libc", ] @@ -517,6 +524,19 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +[[package]] +name = "git2" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf97ba92db08df386e10c8ede66a2a0369bd277090afd8710e19e38de9ec0cd" +dependencies = [ + "bitflags 2.4.1", + "libc", + "libgit2-sys", + "log", + "url", +] + [[package]] name = "h2" version = "0.3.22" @@ -707,6 +727,15 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "jobserver" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.65" @@ -728,12 +757,36 @@ version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +[[package]] +name = "libgit2-sys" +version = "0.16.1+1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2a2bb3680b094add03bb3732ec520ece34da31a8cd2d633d1389d0f0fb60d0c" +dependencies = [ + "cc", + "libc", + "libz-sys", + "pkg-config", +] + [[package]] name = "libm" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "libz-sys" +version = "1.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "lock_api" version = "0.4.11" @@ -943,6 +996,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + [[package]] name = "powerfmt" version = "0.2.0" @@ -993,7 +1052,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -1193,6 +1252,7 @@ dependencies = [ "clap", "dotenvy", "futures-util", + "git2", "rnix", "serde", "serde_json", @@ -1524,6 +1584,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index 4259c9f..781bb5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,10 +12,11 @@ bollard = { version = "0.15", features = ["time"] } clap = { version = "4", features = ["env", "derive"] } dotenvy = "0.15" futures-util = "0.3" -rnix = "0.11.0" +git2 = { version = "0.18", default-features = false } +rnix = "0.11" serde = "1" serde_json = "1" -sysinfo = "0.29.10" +sysinfo = "0.29" thiserror = "1" time = { version = "0.3", features = ["serde"] } tokio = { version = "1", features = ["full"] } diff --git a/src/main.rs b/src/main.rs index 545179a..bb477b0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,11 @@ use crate::http::response::response_interceptor; -use anyhow::Result; +use anyhow::{anyhow, Result}; use axum::middleware::from_fn; use bollard::Docker; use clap::Parser; +use node::git::{GitConfig, ThreadSafeRepository}; use std::{net::SocketAddr, path::PathBuf}; +use tokio::fs; mod http; mod nix; @@ -29,6 +31,13 @@ struct Args { 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)] @@ -36,6 +45,8 @@ pub struct AppState { pub stack_dir: PathBuf, pub arion_bin: PathBuf, pub docker: Docker, + pub gitconfig: GitConfig, + pub repository: ThreadSafeRepository, } #[tokio::main] @@ -54,10 +65,22 @@ async fn main() -> Result<()> { let args = Args::parse(); tracing::info!("listening on {}", &args.bind); + let (author_name, author_email) = parse_author(&args.git_author)?; + let gitconfig = GitConfig { + author_name, + author_email, + }; + + // 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, + gitconfig, + repository, }; let app = route::router() @@ -70,3 +93,15 @@ async fn main() -> Result<()> { 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)" + )), + } +} diff --git a/src/node/git.rs b/src/node/git.rs new file mode 100644 index 0000000..e02d1d1 --- /dev/null +++ b/src/node/git.rs @@ -0,0 +1,73 @@ +use anyhow::{anyhow, Result}; +use git2::{ErrorCode, IndexAddOption, Repository, Signature}; +use std::{ + path::Path, + sync::{Arc, Mutex}, +}; +use tracing::info; + +#[derive(Clone)] +pub struct ThreadSafeRepository { + pub inner: Arc>, +} + +#[derive(Clone)] +pub struct GitConfig { + pub author_name: String, + pub author_email: String, +} + +impl ThreadSafeRepository { + pub fn ensure_repository(config: &GitConfig, path: &Path) -> Result { + let res = Repository::open(path); + + match res { + Ok(repository) => Ok(repository.into()), + Err(err) => match err.code() { + ErrorCode::NotFound => ThreadSafeRepository::create_repository(config, path), + _ => Err(anyhow!(err)), + }, + } + } + + fn create_repository(config: &GitConfig, path: &Path) -> Result { + // Create repository + let repo = Repository::init(path)?; + + // Commit all existing files + let mut index = repo.index()?; + index.add_all(["*/*.nix"].iter(), IndexAddOption::DEFAULT, None)?; + let oid = index.write_tree()?; + let tree = repo.find_tree(oid)?; + + // This prevents a nasty condition where the index goes all wack, + // but it's probably a mistake somewhere else + index.write()?; + + let signature = Signature::now(&config.author_name, &config.author_email)?; + let commit_id = repo.commit( + Some("HEAD"), + &signature, + &signature, + "Commit message", + &tree, + &[], + )?; + drop(tree); + + info!( + commit_id = commit_id.to_string(), + "Repository initialized with base commit" + ); + + Ok(repo.into()) + } +} + +impl From for ThreadSafeRepository { + fn from(value: Repository) -> Self { + Self { + inner: Arc::new(Mutex::new(value)), + } + } +} diff --git a/src/node/mod.rs b/src/node/mod.rs index 36ac3e3..7791dbb 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -1,5 +1,6 @@ pub mod container; pub mod error; +pub mod git; pub mod nix; pub mod stack; pub mod system; diff --git a/src/node/stack.rs b/src/node/stack.rs index d2c11f1..c594bb6 100644 --- a/src/node/stack.rs +++ b/src/node/stack.rs @@ -1,8 +1,4 @@ -use super::{ - container::ContainerInfo, - error::StackError, - nix::{parse_arion_compose, StackComposeInfo}, -}; +use super::{container::ContainerInfo, error::StackError, nix::parse_arion_compose}; use crate::http::error::Result; use bollard::{container::ListContainersOptions, service::ContainerSummary, Docker}; use serde::Serialize; diff --git a/templates/base.html b/templates/base.html index f71f025..9017cbb 100644 --- a/templates/base.html +++ b/templates/base.html @@ -25,6 +25,7 @@ background-color: var(--background); color: var(--text); font-family: Inter, sans-serif; + scrollbar-width: thin; } code { @@ -99,6 +100,30 @@ border-color: var(--link-hover); } } + + table.table { + background-color: var(--bg-raised); + border: 3px solid #5958B1; + border-radius: 3px; + + & th, + & td { + padding: 3px 5px; + } + + & th { + font-weight: bold; + } + + & thead { + background-color: #202248; + } + } + + .full-width { + width: 100%; + flex: 1; + } diff --git a/templates/container/get-one.html b/templates/container/get-one.html index 1aa5e29..165f534 100644 --- a/templates/container/get-one.html +++ b/templates/container/get-one.html @@ -19,48 +19,51 @@

Status

-
-
ID
-
{{ info.id }}
-
Status
-
{{ info.state }}
-
Image
-
{{ info.image }}
-
Image ID
-
{{ info.image_id }}
-
Created at
-
{{ info.created_at }}
-
-
- {% match info.volumes %} - {% when Some with (volumes) %} -
Volumes
- {% for volume in volumes %} -
- {{ volume.source.clone().unwrap_or_default() }} → {{ - volume.destination.clone().unwrap_or_default() }} -
- {% endfor %} - {% when None %} - {% endmatch %} - {% match info.env %} - {% when Some with (env) %} -
Environment
- {% for var in env %} -
{{ var }}
- {% endfor %} - {% when None %} - {% endmatch %} - {% match info.labels %} - {% when Some with (labels) %} -
Labels
- {% for label in labels %} -
{{ label.0 }} = {{ label.1 - }}
- {% endfor %} - {% when None %} - {% endmatch %} -
+
+
+
ID
+
{{ info.id }}
+
Status
+
{{ info.state }}
+
Image
+
{{ info.image }}
+
Image ID
+
{{ info.image_id }}
+
Created at
+
{{ info.created_at }}
+
+
+ {% match info.volumes %} + {% when Some with (volumes) %} +
Volumes
+ {% for volume in volumes %} +
+ {{ volume.source.clone().unwrap_or_default() }} → {{ + volume.destination.clone().unwrap_or_default() }} +
+ {% endfor %} + {% when None %} + {% endmatch %} + {% match info.env %} + {% when Some with (env) %} +
Environment
+ {% for var in env %} +
{{ var }}
+ {% endfor %} + {% when None %} + {% endmatch %} + {% match info.labels %} + {% when Some with (labels) %} +
Labels
+ {% for label in labels %} +
{{ label.0 }} = {{ + label.1 + }}
+ {% endfor %} + {% when None %} + {% endmatch %} +
+

Logs

@@ -223,15 +226,23 @@ @media (min-width: 1000px) { main { + overflow: hidden; display: grid; grid-template-areas: - "head head head" - "info log log" - "info log log"; + "head head" + "info log"; grid-template-rows: 120px 1fr; grid-template-columns: 30vw 1fr; } + + + .status-section>article, + #log { + height: calc(100vh - 240px); + max-height: none; + overflow: auto; + } } .actions { diff --git a/templates/home.html b/templates/home.html index 3236bee..1dca3f0 100644 --- a/templates/home.html +++ b/templates/home.html @@ -74,24 +74,43 @@

Stacks

-
    - {% for stack in info.stacks %} -
  • - {{stack.name}} - {% let (running, stopped) = stack.stats() %} - {% if running > 0 %} - {{running}} - {% endif %} - {% if stopped > 0 %} - {{stopped}} - {% endif %} -
  • - {% endfor %} -
+ + + + + + + + + {% for stack in info.stacks %} + + + + + + + + {% endfor %} + +
NameService status
+ + {{stack.name}} + + + {% let (running, stopped) = stack.stats() %} + {% if running > 0 %} + {{running}} + {% endif %} + {% if stopped > 0 %} + {{stopped}} + {% endif %} +

Containers

- +
@@ -115,42 +134,7 @@ diff --git a/templates/stack/get-one.html b/templates/stack/get-one.html index 0df940d..1f6e5a2 100644 --- a/templates/stack/get-one.html +++ b/templates/stack/get-one.html @@ -15,7 +15,7 @@

Containers

-
Name
+
@@ -57,23 +57,6 @@ } .containers { - background-color: var(--bg-raised); - border: 3px solid #5958B1; - border-radius: 3px; - - & th, - & td { - padding: 3px 5px; - } - - & th { - font-weight: bold; - } - - & thead { - background-color: #202248; - } - & .status { text-align: center; font-weight: bold;
Name