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 =
|
services =
|
||||||
map
|
lib.listToAttrs
|
||||||
|
(map
|
||||||
(name:
|
(name:
|
||||||
let
|
let
|
||||||
serviceName = "${name}.service";
|
serviceName = "${name}.service";
|
||||||
in
|
in
|
||||||
{
|
lib.nameValuePair serviceName { storePath = ''${nixosConfig.config.systemd.units."${serviceName}".unit}/${serviceName}''; })
|
||||||
name = serviceName;
|
nixosConfig.config.service-manager.services);
|
||||||
service = ''${nixosConfig.config.systemd.units."${serviceName}".unit}/${serviceName}'';
|
|
||||||
})
|
|
||||||
nixosConfig.config.service-manager.services;
|
|
||||||
|
|
||||||
servicesPath = pkgs.writeTextFile {
|
servicesPath = pkgs.writeTextFile {
|
||||||
name = "services";
|
name = "services";
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,10 @@
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
services =
|
services =
|
||||||
lib.listToAttrs (lib.flip lib.genList 30 (ix: {
|
lib.listToAttrs
|
||||||
name = "service-${toString ix}";
|
(lib.flip lib.genList 10 (ix:
|
||||||
value = {
|
lib.nameValuePair "service-${toString ix}"
|
||||||
|
{
|
||||||
enable = true;
|
enable = true;
|
||||||
description = "service-${toString ix}";
|
description = "service-${toString ix}";
|
||||||
wants = [ "network-online.target" ];
|
wants = [ "network-online.target" ];
|
||||||
|
|
@ -25,10 +26,10 @@ let
|
||||||
};
|
};
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
script = ''
|
script = ''
|
||||||
sleep ${if ix > 20 then "3" else "1"}
|
sleep ${if ix > 5 then "3" else "1"}
|
||||||
'';
|
'';
|
||||||
};
|
})
|
||||||
}));
|
);
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options = {
|
options = {
|
||||||
|
|
|
||||||
124
src/activate.rs
124
src/activate.rs
|
|
@ -1,73 +1,135 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashSet;
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::fs::DirBuilder;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::time::Duration;
|
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")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct ServiceConfig {
|
struct ServiceConfig {
|
||||||
name: String,
|
#[serde(flatten)]
|
||||||
service: String,
|
store_path: StorePath,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServiceConfig {
|
type Services = HashMap<String, ServiceConfig>;
|
||||||
fn store_path(&self) -> StorePath {
|
|
||||||
StorePath::from(self.service.to_owned())
|
#[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<()> {
|
pub fn activate(store_path: StorePath) -> Result<()> {
|
||||||
log::info!("Activating service-manager profile: {}", store_path);
|
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 reader = io::BufReader::new(file);
|
||||||
|
let services: Services = serde_json::from_reader(reader)?;
|
||||||
|
|
||||||
let services: Vec<ServiceConfig> = serde_json::from_reader(reader)?;
|
let linked_services = link_services(services);
|
||||||
|
serialise_linked_services(&linked_services)?;
|
||||||
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()?;
|
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(())
|
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(
|
fn start_services(
|
||||||
service_manager: &systemd::ServiceManager,
|
service_manager: &systemd::ServiceManager,
|
||||||
services: &[ServiceConfig],
|
services: LinkedServices,
|
||||||
timeout: &Option<Duration>,
|
timeout: &Option<Duration>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
service_manager.daemon_reload()?;
|
service_manager.daemon_reload()?;
|
||||||
|
|
||||||
let job_monitor = service_manager.monitor_jobs_init()?;
|
let job_monitor = service_manager.monitor_jobs_init()?;
|
||||||
|
|
||||||
let successful_services = services.iter().fold(HashSet::new(), |set, service| {
|
let successful_services = services.keys().fold(
|
||||||
match service_manager.restart_unit(&service.name) {
|
HashSet::with_capacity(services.len()),
|
||||||
|
|mut set, service| match service_manager.restart_unit(service) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
log::info!("Restarting service {}...", service.name);
|
log::info!("Restarting service {}...", service);
|
||||||
set.into_iter()
|
set.insert(Box::new(service.to_owned()));
|
||||||
.chain(iter::once(Box::new(service.name.to_owned())))
|
set
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!(
|
log::error!(
|
||||||
"Error restarting unit, please consult the logs: {}",
|
"Error restarting unit, please consult the logs: {}",
|
||||||
service.name
|
service
|
||||||
);
|
);
|
||||||
log::error!("{}", e);
|
log::error!("{}", e);
|
||||||
set
|
set
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
if !service_manager.monitor_jobs_finish(job_monitor, timeout, successful_services)? {
|
if !service_manager.monitor_jobs_finish(job_monitor, timeout, successful_services)? {
|
||||||
anyhow::bail!("Timeout waiting for systemd jobs");
|
anyhow::bail!("Timeout waiting for systemd jobs");
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use std::collections::HashMap;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::{fs, process, str};
|
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)]
|
#[derive(Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
|
@ -14,9 +14,6 @@ struct NixBuildOutput {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate(flake_uri: &str) -> Result<()> {
|
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
|
// FIXME: we should not hard-code the system here
|
||||||
let flake_attr = format!("{}.x86_64-linux", FLAKE_ATTR);
|
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)?;
|
let store_path = run_nix_build(flake_uri, &flake_attr).and_then(get_store_path)?;
|
||||||
|
|
||||||
log::info!("Generating new generation from {}", 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...");
|
log::info!("Registering GC root...");
|
||||||
create_gcroot(&gcroot_path, &profile_path)?;
|
create_gcroot(GCROOT_PATH, PROFILE_PATH)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,7 +33,7 @@ fn install_nix_profile(store_path: &StorePath, profile_path: &str) -> Result<pro
|
||||||
.arg("--profile")
|
.arg("--profile")
|
||||||
.arg(profile_path)
|
.arg(profile_path)
|
||||||
.arg("--set")
|
.arg("--set")
|
||||||
.arg(&store_path.path)
|
.arg(&store_path.store_path)
|
||||||
.output()
|
.output()
|
||||||
.map_err(anyhow::Error::from)
|
.map_err(anyhow::Error::from)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
17
src/lib.rs
17
src/lib.rs
|
|
@ -3,29 +3,34 @@ pub mod generate;
|
||||||
mod systemd;
|
mod systemd;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::os::unix;
|
use std::os::unix;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::{fs, str};
|
use std::{fs, str};
|
||||||
|
|
||||||
const FLAKE_ATTR: &str = "serviceConfig";
|
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 struct StorePath {
|
||||||
pub path: String,
|
pub store_path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<String> for StorePath {
|
impl From<String> for StorePath {
|
||||||
fn from(path: String) -> Self {
|
fn from(path: String) -> Self {
|
||||||
StorePath {
|
StorePath {
|
||||||
path: path.trim().into(),
|
store_path: path.trim().into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for StorePath {
|
impl std::fmt::Display for StorePath {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
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() {
|
if from.is_symlink() {
|
||||||
fs::remove_file(from)?;
|
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
|
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();
|
let args = Args::parse();
|
||||||
|
|
||||||
// FIXME: set default level to info
|
// 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))
|
handle_toplevel_error(go(args.action))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue