Split code in modules.
This commit is contained in:
parent
c7a481976d
commit
1620a81d15
7 changed files with 277 additions and 244 deletions
54
Cargo.lock
generated
54
Cargo.lock
generated
|
|
@ -13,9 +13,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.68"
|
version = "1.0.69"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
|
checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
|
|
@ -125,18 +125,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.4.0"
|
version = "0.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
|
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.2.6"
|
version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
|
checksum = "856b5cb0902c2b6d65d5fd97dfa30f9b70c7538e770b98eab5ed52d8db923e01"
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "humantime"
|
name = "humantime"
|
||||||
|
|
@ -146,9 +143,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "io-lifetimes"
|
name = "io-lifetimes"
|
||||||
version = "1.0.4"
|
version = "1.0.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e"
|
checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
|
|
@ -156,9 +153,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is-terminal"
|
name = "is-terminal"
|
||||||
version = "0.4.2"
|
version = "0.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189"
|
checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"hermit-abi",
|
||||||
"io-lifetimes",
|
"io-lifetimes",
|
||||||
|
|
@ -180,9 +177,9 @@ checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libdbus-sys"
|
name = "libdbus-sys"
|
||||||
version = "0.2.3"
|
version = "0.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2264f9d90a9b4e60a2dc722ad899ea0374f03c2e96e755fe22a8f551d4d5fb3c"
|
checksum = "9f8d7ae751e1cb825c840ae5e682f59b098cdfd213c350ac268b61449a5f58a0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|
@ -281,9 +278,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.50"
|
version = "1.0.51"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
|
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
@ -316,9 +313,9 @@ checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.36.7"
|
version = "0.36.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03"
|
checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"errno",
|
"errno",
|
||||||
|
|
@ -356,9 +353,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.91"
|
version = "1.0.92"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
|
checksum = "7434af0dc1cbd59268aa98b4c22c131c0584d2232f6fb166efb993e2832e896a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
|
|
@ -366,7 +363,7 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "service-manager"
|
name = "service_manager"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|
@ -456,9 +453,18 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.42.0"
|
version = "0.45.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
|
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.42.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows_aarch64_gnullvm",
|
"windows_aarch64_gnullvm",
|
||||||
"windows_aarch64_msvc",
|
"windows_aarch64_msvc",
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
[package]
|
[package]
|
||||||
name = "service-manager"
|
name = "service_manager"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "service-manager"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.68"
|
anyhow = "1.0.68"
|
||||||
clap = { version = "4.1.4", features = ["derive"] }
|
clap = { version = "4.1.4", features = ["derive"] }
|
||||||
|
|
|
||||||
78
src/activate.rs
Normal file
78
src/activate.rs
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::{fs, io, iter, str};
|
||||||
|
|
||||||
|
use super::{create_store_link, systemd, StorePath};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct ServiceConfig {
|
||||||
|
name: String,
|
||||||
|
service: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServiceConfig {
|
||||||
|
fn store_path(&self) -> StorePath {
|
||||||
|
StorePath::from(self.service.to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn activate(store_path: StorePath) -> Result<()> {
|
||||||
|
log::info!("Activating service-manager profile: {}", store_path);
|
||||||
|
|
||||||
|
let file = fs::File::open(store_path.path + "/services/services.json")?;
|
||||||
|
let reader = io::BufReader::new(file);
|
||||||
|
|
||||||
|
let services: Vec<ServiceConfig> = serde_json::from_reader(reader)?;
|
||||||
|
|
||||||
|
services.iter().try_for_each(|service| {
|
||||||
|
create_store_link(
|
||||||
|
&service.store_path(),
|
||||||
|
Path::new(&format!("/run/systemd/system/{}", service.name)),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let service_manager = systemd::ServiceManager::new_session()?;
|
||||||
|
start_services(&service_manager, &services, &Some(Duration::from_secs(30)))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_services(
|
||||||
|
service_manager: &systemd::ServiceManager,
|
||||||
|
services: &[ServiceConfig],
|
||||||
|
timeout: &Option<Duration>,
|
||||||
|
) -> Result<()> {
|
||||||
|
service_manager.daemon_reload()?;
|
||||||
|
|
||||||
|
let job_monitor = service_manager.monitor_jobs_init()?;
|
||||||
|
|
||||||
|
let successful_services = services.iter().fold(HashSet::new(), |set, service| {
|
||||||
|
match service_manager.restart_unit(&service.name) {
|
||||||
|
Ok(_) => {
|
||||||
|
log::info!("Restarting service {}...", service.name);
|
||||||
|
set.into_iter()
|
||||||
|
.chain(iter::once(Box::new(service.name.to_owned())))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!(
|
||||||
|
"Error restarting unit, please consult the logs: {}",
|
||||||
|
service.name
|
||||||
|
);
|
||||||
|
log::error!("{}", e);
|
||||||
|
set
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if !service_manager.monitor_jobs_finish(job_monitor, timeout, successful_services)? {
|
||||||
|
anyhow::bail!("Timeout waiting for systemd jobs");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: do we want to propagate unit failures here in some way?
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
109
src/generate.rs
Normal file
109
src/generate.rs
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::{env, fs, process, str};
|
||||||
|
|
||||||
|
use super::{create_store_link, StorePath, FLAKE_ATTR, PROFILE_NAME};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct NixBuildOutput {
|
||||||
|
drv_path: String,
|
||||||
|
outputs: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate(flake_uri: &str) -> Result<()> {
|
||||||
|
let user = env::var("USER")?;
|
||||||
|
// TODO: we temporarily put this under per-user to avoid needing root access
|
||||||
|
// we will move this to /nix/var/nix/profiles/ later on.
|
||||||
|
let profiles_dir = format!("profiles/per-user/{}", user);
|
||||||
|
let gcroots_dir = format!("gcroots/per-user/{}", user);
|
||||||
|
let profile_path = format!("/nix/var/nix/{}/{}", profiles_dir, PROFILE_NAME);
|
||||||
|
let gcroot_path = format!("/nix/var/nix/{}/{}-current", gcroots_dir, PROFILE_NAME);
|
||||||
|
|
||||||
|
// FIXME: we should not hard-code the system here
|
||||||
|
let flake_attr = format!("{}.x86_64-linux", FLAKE_ATTR);
|
||||||
|
|
||||||
|
log::info!("Running nix build...");
|
||||||
|
let store_path = run_nix_build(flake_uri, &flake_attr).and_then(get_store_path)?;
|
||||||
|
|
||||||
|
log::info!("Generating new generation from {}", store_path);
|
||||||
|
install_nix_profile(&store_path, &profile_path).map(print_out_and_err)?;
|
||||||
|
|
||||||
|
log::info!("Registering GC root...");
|
||||||
|
create_gcroot(&gcroot_path, &profile_path)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn install_nix_profile(store_path: &StorePath, profile_path: &str) -> Result<process::Output> {
|
||||||
|
process::Command::new("nix-env")
|
||||||
|
.arg("--profile")
|
||||||
|
.arg(profile_path)
|
||||||
|
.arg("--set")
|
||||||
|
.arg(&store_path.path)
|
||||||
|
.output()
|
||||||
|
.map_err(anyhow::Error::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_gcroot(gcroot_path: &str, profile_path: &str) -> Result<()> {
|
||||||
|
let profile_store_path = fs::canonicalize(profile_path)?;
|
||||||
|
let store_path = StorePath::from(String::from(profile_store_path.to_string_lossy()));
|
||||||
|
create_store_link(&store_path, Path::new(gcroot_path))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_store_path(nix_build_result: process::Output) -> Result<StorePath> {
|
||||||
|
if nix_build_result.status.success() {
|
||||||
|
String::from_utf8(nix_build_result.stdout)
|
||||||
|
.map_err(anyhow::Error::from)
|
||||||
|
.and_then(parse_nix_build_output)
|
||||||
|
} else {
|
||||||
|
String::from_utf8(nix_build_result.stderr)
|
||||||
|
.map_err(anyhow::Error::from)
|
||||||
|
.and_then(|e| {
|
||||||
|
log::error!("{}", e);
|
||||||
|
Err(anyhow!("Nix build failed."))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_nix_build_output(output: String) -> Result<StorePath> {
|
||||||
|
let expected_output_name = "out";
|
||||||
|
let results: Vec<NixBuildOutput> = serde_json::from_str(&output)?;
|
||||||
|
|
||||||
|
if let [result] = results.as_slice() {
|
||||||
|
if let Some(store_path) = result.outputs.get(expected_output_name) {
|
||||||
|
return Ok(StorePath::from(store_path.to_owned()));
|
||||||
|
}
|
||||||
|
return Err(anyhow!(
|
||||||
|
"No output '{}' found in nix build result.",
|
||||||
|
expected_output_name
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Err(anyhow!(
|
||||||
|
"Multiple build results were returned, we cannot handle that yet."
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_nix_build(flake_uri: &str, flake_attr: &str) -> Result<process::Output> {
|
||||||
|
process::Command::new("nix")
|
||||||
|
.arg("build")
|
||||||
|
.arg(format!("{}#{}", flake_uri, flake_attr))
|
||||||
|
.arg("--json")
|
||||||
|
.output()
|
||||||
|
.map_err(anyhow::Error::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_out_and_err(output: process::Output) -> process::Output {
|
||||||
|
print_u8(&output.stdout);
|
||||||
|
print_u8(&output.stderr);
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_u8(bytes: &[u8]) {
|
||||||
|
str::from_utf8(bytes).map_or((), |s| {
|
||||||
|
if !s.trim().is_empty() {
|
||||||
|
log::info!("{}", s.trim())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
46
src/lib.rs
Normal file
46
src/lib.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
pub mod activate;
|
||||||
|
pub mod generate;
|
||||||
|
mod systemd;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use std::os::unix;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::{fs, str};
|
||||||
|
|
||||||
|
const FLAKE_ATTR: &str = "serviceConfig";
|
||||||
|
const PROFILE_NAME: &str = "service-manager";
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct StorePath {
|
||||||
|
pub path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for StorePath {
|
||||||
|
fn from(path: String) -> Self {
|
||||||
|
StorePath {
|
||||||
|
path: path.trim().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for StorePath {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_store_link(store_path: &StorePath, from: &Path) -> Result<()> {
|
||||||
|
log::info!("Creating symlink: {} -> {}", from.display(), store_path);
|
||||||
|
if from.is_symlink() {
|
||||||
|
fs::remove_file(from)?;
|
||||||
|
}
|
||||||
|
unix::fs::symlink(&store_path.path, from).map_err(anyhow::Error::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compose<A, B, C, G, F>(f: F, g: G) -> impl Fn(A) -> C
|
||||||
|
where
|
||||||
|
F: Fn(B) -> C,
|
||||||
|
G: Fn(A) -> B,
|
||||||
|
{
|
||||||
|
move |x| f(g(x))
|
||||||
|
}
|
||||||
224
src/main.rs
224
src/main.rs
|
|
@ -1,35 +1,7 @@
|
||||||
mod systemd;
|
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
use std::os::unix;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::time::Duration;
|
|
||||||
use std::{env, fs, io, iter, process, str};
|
|
||||||
|
|
||||||
const FLAKE_ATTR: &str = "serviceConfig";
|
use service_manager::StorePath;
|
||||||
const PROFILE_NAME: &str = "service-manager";
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct StorePath {
|
|
||||||
path: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<String> for StorePath {
|
|
||||||
fn from(path: String) -> Self {
|
|
||||||
StorePath {
|
|
||||||
path: path.trim().into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for StorePath {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(clap::Parser, Debug)]
|
#[derive(clap::Parser, Debug)]
|
||||||
#[command(author, version, about, long_about=None)]
|
#[command(author, version, about, long_about=None)]
|
||||||
|
|
@ -58,203 +30,21 @@ fn main() {
|
||||||
|
|
||||||
match args.action {
|
match args.action {
|
||||||
Action::Activate { store_path } => handle_toplevel_error(activate(store_path)),
|
Action::Activate { store_path } => handle_toplevel_error(activate(store_path)),
|
||||||
Action::Generate { flake_uri } => handle_toplevel_error(generate(&flake_uri)),
|
Action::Generate { flake_uri } => {
|
||||||
|
handle_toplevel_error(service_manager::generate::generate(&flake_uri))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_toplevel_error<T>(r: Result<T>) {
|
|
||||||
if let Err(e) = r {
|
|
||||||
log::error!("{}", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct ServiceConfig {
|
|
||||||
name: String,
|
|
||||||
service: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ServiceConfig {
|
|
||||||
fn store_path(&self) -> StorePath {
|
|
||||||
StorePath::from(self.service.to_owned())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn activate(store_path: StorePath) -> Result<()> {
|
fn activate(store_path: StorePath) -> Result<()> {
|
||||||
if !nix::unistd::Uid::is_root(nix::unistd::getuid()) {
|
if !nix::unistd::Uid::is_root(nix::unistd::getuid()) {
|
||||||
return Err(anyhow!("We need root permissions."));
|
return Err(anyhow!("We need root permissions."));
|
||||||
}
|
}
|
||||||
log::info!("Activating service-manager profile: {}", store_path);
|
service_manager::activate::activate(store_path)
|
||||||
|
|
||||||
let file = fs::File::open(store_path.path + "/services/services.json")?;
|
|
||||||
let reader = io::BufReader::new(file);
|
|
||||||
|
|
||||||
let services: Vec<ServiceConfig> = serde_json::from_reader(reader)?;
|
|
||||||
|
|
||||||
services.iter().try_for_each(|service| {
|
|
||||||
create_store_link(
|
|
||||||
&service.store_path(),
|
|
||||||
Path::new(&format!("/run/systemd/system/{}", service.name)),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let service_manager = systemd::ServiceManager::new_session()?;
|
|
||||||
start_services(&service_manager, &services, &Some(Duration::from_secs(30)))?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_services(
|
fn handle_toplevel_error<T>(r: Result<T>) {
|
||||||
service_manager: &systemd::ServiceManager,
|
if let Err(e) = r {
|
||||||
services: &[ServiceConfig],
|
log::error!("{}", e)
|
||||||
timeout: &Option<Duration>,
|
|
||||||
) -> Result<()> {
|
|
||||||
service_manager.daemon_reload()?;
|
|
||||||
|
|
||||||
let job_monitor = service_manager.monitor_jobs_init()?;
|
|
||||||
|
|
||||||
let successful_services = services.iter().fold(HashSet::new(), |set, service| {
|
|
||||||
match service_manager.restart_unit(&service.name) {
|
|
||||||
Ok(_) => {
|
|
||||||
log::info!("Restarting service {}...", service.name);
|
|
||||||
set.into_iter()
|
|
||||||
.chain(iter::once(Box::new(service.name.to_owned())))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::error!(
|
|
||||||
"Error restarting unit, please consult the logs: {}",
|
|
||||||
service.name
|
|
||||||
);
|
|
||||||
log::error!("{}", e);
|
|
||||||
set
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if !service_manager.monitor_jobs_finish(job_monitor, timeout, successful_services)? {
|
|
||||||
anyhow::bail!("Timeout waiting for systemd jobs");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: do we want to propagate unit failures here in some way?
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate(flake_uri: &str) -> Result<()> {
|
|
||||||
let user = env::var("USER")?;
|
|
||||||
// TODO: we temporarily put this under per-user to avoid needing root access
|
|
||||||
// we will move this to /nix/var/nix/profiles/ later on.
|
|
||||||
let profiles_dir = format!("profiles/per-user/{}", user);
|
|
||||||
let gcroots_dir = format!("gcroots/per-user/{}", user);
|
|
||||||
let profile_path = format!("/nix/var/nix/{}/{}", profiles_dir, PROFILE_NAME);
|
|
||||||
let gcroot_path = format!("/nix/var/nix/{}/{}-current", gcroots_dir, PROFILE_NAME);
|
|
||||||
|
|
||||||
// FIXME: we should not hard-code the system here
|
|
||||||
let flake_attr = format!("{}.x86_64-linux", FLAKE_ATTR);
|
|
||||||
|
|
||||||
log::info!("Running nix build...");
|
|
||||||
let store_path = run_nix_build(flake_uri, &flake_attr).and_then(get_store_path)?;
|
|
||||||
|
|
||||||
log::info!("Generating new generation from {}", store_path);
|
|
||||||
install_nix_profile(&store_path, &profile_path).map(print_out_and_err)?;
|
|
||||||
|
|
||||||
log::info!("Registering GC root...");
|
|
||||||
create_gcroot(&gcroot_path, &profile_path)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn install_nix_profile(store_path: &StorePath, profile_path: &str) -> Result<process::Output> {
|
|
||||||
process::Command::new("nix-env")
|
|
||||||
.arg("--profile")
|
|
||||||
.arg(profile_path)
|
|
||||||
.arg("--set")
|
|
||||||
.arg(&store_path.path)
|
|
||||||
.output()
|
|
||||||
.map_err(anyhow::Error::from)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_gcroot(gcroot_path: &str, profile_path: &str) -> Result<()> {
|
|
||||||
let profile_store_path = fs::canonicalize(profile_path)?;
|
|
||||||
let store_path = StorePath::from(String::from(profile_store_path.to_string_lossy()));
|
|
||||||
create_store_link(&store_path, Path::new(gcroot_path))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_store_link(store_path: &StorePath, from: &Path) -> Result<()> {
|
|
||||||
log::info!("Creating symlink: {} -> {}", from.display(), store_path);
|
|
||||||
if from.is_symlink() {
|
|
||||||
fs::remove_file(from)?;
|
|
||||||
}
|
|
||||||
unix::fs::symlink(&store_path.path, from).map_err(anyhow::Error::from)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_store_path(nix_build_result: process::Output) -> Result<StorePath> {
|
|
||||||
if nix_build_result.status.success() {
|
|
||||||
String::from_utf8(nix_build_result.stdout)
|
|
||||||
.map_err(anyhow::Error::from)
|
|
||||||
.and_then(parse_nix_build_output)
|
|
||||||
} else {
|
|
||||||
String::from_utf8(nix_build_result.stderr)
|
|
||||||
.map_err(anyhow::Error::from)
|
|
||||||
.and_then(|e| {
|
|
||||||
log::error!("{}", e);
|
|
||||||
Err(anyhow!("Nix build failed."))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct NixBuildOutput {
|
|
||||||
drv_path: String,
|
|
||||||
outputs: HashMap<String, String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_nix_build_output(output: String) -> Result<StorePath> {
|
|
||||||
let expected_output_name = "out";
|
|
||||||
let results: Vec<NixBuildOutput> = serde_json::from_str(&output)?;
|
|
||||||
|
|
||||||
if let [result] = results.as_slice() {
|
|
||||||
if let Some(store_path) = result.outputs.get(expected_output_name) {
|
|
||||||
return Ok(StorePath::from(store_path.to_owned()));
|
|
||||||
}
|
|
||||||
return Err(anyhow!(
|
|
||||||
"No output '{}' found in nix build result.",
|
|
||||||
expected_output_name
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Err(anyhow!(
|
|
||||||
"Multiple build results were returned, we cannot handle that yet."
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_nix_build(flake_uri: &str, flake_attr: &str) -> Result<process::Output> {
|
|
||||||
process::Command::new("nix")
|
|
||||||
.arg("build")
|
|
||||||
.arg(format!("{}#{}", flake_uri, flake_attr))
|
|
||||||
.arg("--json")
|
|
||||||
.output()
|
|
||||||
.map_err(anyhow::Error::from)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print_out_and_err(output: process::Output) -> process::Output {
|
|
||||||
print_u8(&output.stdout);
|
|
||||||
print_u8(&output.stderr);
|
|
||||||
output
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print_u8(bytes: &[u8]) {
|
|
||||||
str::from_utf8(bytes).map_or((), |s| {
|
|
||||||
if !s.trim().is_empty() {
|
|
||||||
log::info!("{}", s.trim())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn compose<A, B, C, G, F>(f: F, g: G) -> impl Fn(A) -> C
|
|
||||||
where
|
|
||||||
F: Fn(B) -> C,
|
|
||||||
G: Fn(A) -> B,
|
|
||||||
{
|
|
||||||
move |x| f(g(x))
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -155,8 +155,8 @@ impl ServiceManager {
|
||||||
let job_names_clone = Arc::clone(&job_names);
|
let job_names_clone = Arc::clone(&job_names);
|
||||||
let token = self.proxy.match_signal(
|
let token = self.proxy.match_signal(
|
||||||
move |h: OrgFreedesktopSystemd1ManagerJobRemoved, _: &Connection, _: &Message| {
|
move |h: OrgFreedesktopSystemd1ManagerJobRemoved, _: &Connection, _: &Message| {
|
||||||
log::debug!("{} added", h.unit);
|
|
||||||
log_thread("Signal handling");
|
log_thread("Signal handling");
|
||||||
|
log::info!("Job for {} done", h.unit);
|
||||||
{
|
{
|
||||||
// Insert a new name, and let the lock go out of scope immediately
|
// Insert a new name, and let the lock go out of scope immediately
|
||||||
job_names_clone.lock().unwrap().insert(h.unit);
|
job_names_clone.lock().unwrap().insert(h.unit);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue