Write generation state into a state file and restructure the services.json file produced by nix.
This commit is contained in:
parent
8f31818a27
commit
c3be9ceb19
6 changed files with 143 additions and 80 deletions
10
nix/lib.nix
10
nix/lib.nix
|
|
@ -18,16 +18,14 @@ in
|
|||
};
|
||||
|
||||
services =
|
||||
map
|
||||
lib.listToAttrs
|
||||
(map
|
||||
(name:
|
||||
let
|
||||
serviceName = "${name}.service";
|
||||
in
|
||||
{
|
||||
name = serviceName;
|
||||
service = ''${nixosConfig.config.systemd.units."${serviceName}".unit}/${serviceName}'';
|
||||
})
|
||||
nixosConfig.config.service-manager.services;
|
||||
lib.nameValuePair serviceName { storePath = ''${nixosConfig.config.systemd.units."${serviceName}".unit}/${serviceName}''; })
|
||||
nixosConfig.config.service-manager.services);
|
||||
|
||||
servicesPath = pkgs.writeTextFile {
|
||||
name = "services";
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@
|
|||
}:
|
||||
let
|
||||
services =
|
||||
lib.listToAttrs (lib.flip lib.genList 30 (ix: {
|
||||
name = "service-${toString ix}";
|
||||
value = {
|
||||
lib.listToAttrs
|
||||
(lib.flip lib.genList 10 (ix:
|
||||
lib.nameValuePair "service-${toString ix}"
|
||||
{
|
||||
enable = true;
|
||||
description = "service-${toString ix}";
|
||||
wants = [ "network-online.target" ];
|
||||
|
|
@ -25,10 +26,10 @@ let
|
|||
};
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
script = ''
|
||||
sleep ${if ix > 20 then "3" else "1"}
|
||||
sleep ${if ix > 5 then "3" else "1"}
|
||||
'';
|
||||
};
|
||||
}));
|
||||
})
|
||||
);
|
||||
in
|
||||
{
|
||||
options = {
|
||||
|
|
|
|||
124
src/activate.rs
124
src/activate.rs
|
|
@ -1,73 +1,135 @@
|
|||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs::DirBuilder;
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
use std::{fs, io, iter, str};
|
||||
use std::{fs, io, str};
|
||||
|
||||
use super::{create_store_link, systemd, StorePath};
|
||||
use super::{create_store_link, systemd, StorePath, SERVICE_MANAGER_STATE_DIR, SYSTEMD_UNIT_DIR};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ServiceConfig {
|
||||
name: String,
|
||||
service: String,
|
||||
#[serde(flatten)]
|
||||
store_path: StorePath,
|
||||
}
|
||||
|
||||
impl ServiceConfig {
|
||||
fn store_path(&self) -> StorePath {
|
||||
StorePath::from(self.service.to_owned())
|
||||
}
|
||||
type Services = HashMap<String, ServiceConfig>;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct LinkedServiceConfig {
|
||||
#[serde(flatten)]
|
||||
service_config: ServiceConfig,
|
||||
linked_path: String,
|
||||
}
|
||||
|
||||
type LinkedServices = HashMap<String, LinkedServiceConfig>;
|
||||
|
||||
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")?;
|
||||
log::debug!("{:?}", read_linked_services()?);
|
||||
|
||||
log::info!("Reading service definitions...");
|
||||
let file = fs::File::open(store_path.store_path + "/services/services.json")?;
|
||||
let reader = io::BufReader::new(file);
|
||||
let services: Services = serde_json::from_reader(reader)?;
|
||||
|
||||
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 linked_services = link_services(services);
|
||||
serialise_linked_services(&linked_services)?;
|
||||
|
||||
let service_manager = systemd::ServiceManager::new_session()?;
|
||||
start_services(&service_manager, &services, &Some(Duration::from_secs(30)))?;
|
||||
|
||||
start_services(
|
||||
&service_manager,
|
||||
linked_services,
|
||||
&Some(Duration::from_secs(30)),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn link_services(services: Services) -> LinkedServices {
|
||||
services.iter().fold(
|
||||
HashMap::with_capacity(services.len()),
|
||||
|mut linked_services, (name, service_config)| {
|
||||
let linked_path = format!("{}/{}", SYSTEMD_UNIT_DIR, name);
|
||||
match create_store_link(&service_config.store_path, Path::new(&linked_path)) {
|
||||
Ok(_) => {
|
||||
linked_services.insert(
|
||||
name.to_owned(),
|
||||
LinkedServiceConfig {
|
||||
service_config: service_config.to_owned(),
|
||||
linked_path,
|
||||
},
|
||||
);
|
||||
linked_services
|
||||
}
|
||||
e @ Err(_) => {
|
||||
log::error!("Error linking service {}, skipping.", name);
|
||||
log::error!("{:?}", e);
|
||||
linked_services
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// FIXME: we should probably lock this file to avoid concurrent writes
|
||||
fn serialise_linked_services(linked_services: &LinkedServices) -> Result<()> {
|
||||
let state_file = format!("{}/services.json", SERVICE_MANAGER_STATE_DIR);
|
||||
DirBuilder::new()
|
||||
.recursive(true)
|
||||
.create(SERVICE_MANAGER_STATE_DIR)?;
|
||||
|
||||
log::info!("Writing state info into file: {}", state_file);
|
||||
let writer = io::BufWriter::new(fs::File::create(state_file)?);
|
||||
serde_json::to_writer(writer, linked_services)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_linked_services() -> Result<LinkedServices> {
|
||||
let state_file = format!("{}/services.json", SERVICE_MANAGER_STATE_DIR);
|
||||
DirBuilder::new()
|
||||
.recursive(true)
|
||||
.create(SERVICE_MANAGER_STATE_DIR)?;
|
||||
|
||||
if Path::new(&state_file).is_file() {
|
||||
log::info!("Reading state info from {}", state_file);
|
||||
let reader = io::BufReader::new(fs::File::open(state_file)?);
|
||||
let linked_services = serde_json::from_reader(reader)?;
|
||||
return Ok(linked_services);
|
||||
}
|
||||
Ok(HashMap::default())
|
||||
}
|
||||
|
||||
fn start_services(
|
||||
service_manager: &systemd::ServiceManager,
|
||||
services: &[ServiceConfig],
|
||||
services: LinkedServices,
|
||||
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) {
|
||||
let successful_services = services.keys().fold(
|
||||
HashSet::with_capacity(services.len()),
|
||||
|mut set, service| match service_manager.restart_unit(service) {
|
||||
Ok(_) => {
|
||||
log::info!("Restarting service {}...", service.name);
|
||||
set.into_iter()
|
||||
.chain(iter::once(Box::new(service.name.to_owned())))
|
||||
.collect()
|
||||
log::info!("Restarting service {}...", service);
|
||||
set.insert(Box::new(service.to_owned()));
|
||||
set
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!(
|
||||
"Error restarting unit, please consult the logs: {}",
|
||||
service.name
|
||||
service
|
||||
);
|
||||
log::error!("{}", e);
|
||||
set
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
if !service_manager.monitor_jobs_finish(job_monitor, timeout, successful_services)? {
|
||||
anyhow::bail!("Timeout waiting for systemd jobs");
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use std::collections::HashMap;
|
|||
use std::path::Path;
|
||||
use std::{fs, process, str};
|
||||
|
||||
use super::{create_store_link, StorePath, FLAKE_ATTR, PROFILE_NAME};
|
||||
use super::{create_store_link, StorePath, FLAKE_ATTR, GCROOT_PATH, PROFILE_PATH};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
|
@ -14,9 +14,6 @@ struct NixBuildOutput {
|
|||
}
|
||||
|
||||
pub fn generate(flake_uri: &str) -> Result<()> {
|
||||
let profile_path = format!("/nix/var/nix/profiles/{}", PROFILE_NAME);
|
||||
let gcroot_path = format!("/nix/var/nix/gcroots/{}-current", PROFILE_NAME);
|
||||
|
||||
// FIXME: we should not hard-code the system here
|
||||
let flake_attr = format!("{}.x86_64-linux", FLAKE_ATTR);
|
||||
|
||||
|
|
@ -24,10 +21,10 @@ pub fn generate(flake_uri: &str) -> Result<()> {
|
|||
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)?;
|
||||
install_nix_profile(&store_path, PROFILE_PATH).map(print_out_and_err)?;
|
||||
|
||||
log::info!("Registering GC root...");
|
||||
create_gcroot(&gcroot_path, &profile_path)?;
|
||||
create_gcroot(GCROOT_PATH, PROFILE_PATH)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -36,7 +33,7 @@ fn install_nix_profile(store_path: &StorePath, profile_path: &str) -> Result<pro
|
|||
.arg("--profile")
|
||||
.arg(profile_path)
|
||||
.arg("--set")
|
||||
.arg(&store_path.path)
|
||||
.arg(&store_path.store_path)
|
||||
.output()
|
||||
.map_err(anyhow::Error::from)
|
||||
}
|
||||
|
|
|
|||
17
src/lib.rs
17
src/lib.rs
|
|
@ -3,29 +3,34 @@ pub mod generate;
|
|||
mod systemd;
|
||||
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::os::unix;
|
||||
use std::path::Path;
|
||||
use std::{fs, str};
|
||||
|
||||
const FLAKE_ATTR: &str = "serviceConfig";
|
||||
const PROFILE_NAME: &str = "service-manager";
|
||||
const PROFILE_PATH: &str = "/nix/var/nix/profiles/service-manager";
|
||||
const GCROOT_PATH: &str = "/nix/var/nix/gcroots/service-manager-current";
|
||||
const SYSTEMD_UNIT_DIR: &str = "/run/systemd/system";
|
||||
const SERVICE_MANAGER_STATE_DIR: &str = "/var/lib/service-manager/state";
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StorePath {
|
||||
pub path: String,
|
||||
pub store_path: String,
|
||||
}
|
||||
|
||||
impl From<String> for StorePath {
|
||||
fn from(path: String) -> Self {
|
||||
StorePath {
|
||||
path: path.trim().into(),
|
||||
store_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)
|
||||
write!(f, "{}", self.store_path)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -34,7 +39,7 @@ fn create_store_link(store_path: &StorePath, from: &Path) -> Result<()> {
|
|||
if from.is_symlink() {
|
||||
fs::remove_file(from)?;
|
||||
}
|
||||
unix::fs::symlink(&store_path.path, from).map_err(anyhow::Error::from)
|
||||
unix::fs::symlink(&store_path.store_path, from).map_err(anyhow::Error::from)
|
||||
}
|
||||
|
||||
pub fn compose<A, B, C, G, F>(f: F, g: G) -> impl Fn(A) -> C
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ fn main() -> ExitCode {
|
|||
let args = Args::parse();
|
||||
|
||||
// FIXME: set default level to info
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug")).init();
|
||||
handle_toplevel_error(go(args.action))
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue