Continue implementing basic features.
This commit is contained in:
parent
fbe9f2eabb
commit
f784f06107
6 changed files with 244 additions and 27 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -7,3 +7,4 @@
|
|||
|
||||
result
|
||||
.pre-commit-config.yaml
|
||||
.direnv
|
||||
|
|
|
|||
93
Cargo.lock
generated
93
Cargo.lock
generated
|
|
@ -2,6 +2,12 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
|
|
@ -14,6 +20,12 @@ version = "1.0.79"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.1.4"
|
||||
|
|
@ -109,6 +121,12 @@ dependencies = [
|
|||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.139"
|
||||
|
|
@ -121,6 +139,29 @@ version = "0.1.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset",
|
||||
"pin-utils",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.17.0"
|
||||
|
|
@ -133,6 +174,12 @@ version = "6.4.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
|
|
@ -189,13 +236,59 @@ dependencies = [
|
|||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.91"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "service-manager"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"nix",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
|
|
|
|||
|
|
@ -7,3 +7,6 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
clap = { version = "4.1.4", features = ["derive"] }
|
||||
nix = "0.26.2"
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_json = "1.0.91"
|
||||
|
|
|
|||
|
|
@ -58,9 +58,10 @@
|
|||
modules = [
|
||||
./nix/modules
|
||||
];
|
||||
inherit (self.packages.${system}) service-manager;
|
||||
};
|
||||
|
||||
packages = rec {
|
||||
packages = {
|
||||
service-manager =
|
||||
pkgs.rustPlatform.buildRustPackage
|
||||
{
|
||||
|
|
@ -74,7 +75,7 @@
|
|||
|
||||
cargoLock.lockFile = ./Cargo.lock;
|
||||
};
|
||||
default = service-manager;
|
||||
default = self.packages.${system}.service-manager;
|
||||
};
|
||||
devShells.default = pkgs.devshell.mkShell {
|
||||
packages = with pkgs; [
|
||||
|
|
|
|||
14
nix/lib.nix
14
nix/lib.nix
|
|
@ -6,6 +6,7 @@ in
|
|||
makeServiceConfig =
|
||||
{ system
|
||||
, modules
|
||||
, service-manager
|
||||
,
|
||||
}:
|
||||
let
|
||||
|
|
@ -15,10 +16,12 @@ in
|
|||
inherit system modules;
|
||||
specialArgs = { };
|
||||
};
|
||||
services =
|
||||
lib.flip lib.genAttrs
|
||||
(serviceName:
|
||||
nixosConfig.config.systemd.units."${serviceName}.service".unit)
|
||||
|
||||
services = map
|
||||
(name: {
|
||||
inherit name;
|
||||
service = ''${nixosConfig.config.systemd.units."${name}.service".unit}/${name}.service'';
|
||||
})
|
||||
nixosConfig.config.service-manager.services;
|
||||
|
||||
servicesPath = pkgs.writeTextFile {
|
||||
|
|
@ -27,7 +30,8 @@ in
|
|||
text = lib.generators.toJSON { } services;
|
||||
};
|
||||
activationScript = pkgs.writeShellScript "activate" ''
|
||||
echo "${servicesPath}"
|
||||
${service-manager}/bin/service-manager activate \
|
||||
--store-path "$(realpath $(dirname ''${0}))"
|
||||
'';
|
||||
in
|
||||
pkgs.linkFarmFromDrvs "service-manager" [
|
||||
|
|
|
|||
151
src/main.rs
151
src/main.rs
|
|
@ -1,10 +1,15 @@
|
|||
use clap::Parser;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
use std::os::unix;
|
||||
use std::path::Path;
|
||||
use std::{env, fs, process, str};
|
||||
use std::{env, fs, io, process, str};
|
||||
|
||||
#[derive(Debug)]
|
||||
const FLAKE_ATTR: &str = "serviceConfig";
|
||||
const PROFILE_NAME: &str = "service-manager";
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct StorePath {
|
||||
path: String,
|
||||
}
|
||||
|
|
@ -17,33 +22,114 @@ impl From<String> for StorePath {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
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)]
|
||||
#[command(author, version, about, long_about=None)]
|
||||
struct Args {
|
||||
#[arg(short, long)]
|
||||
#[command(subcommand)]
|
||||
action: Action,
|
||||
}
|
||||
|
||||
#[derive(clap::Subcommand, Debug)]
|
||||
enum Action {
|
||||
Activate {
|
||||
#[arg(long)]
|
||||
store_path: StorePath,
|
||||
},
|
||||
Generate {
|
||||
#[arg(long)]
|
||||
flake_uri: String,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let args = Args::parse();
|
||||
|
||||
let profile_name = "service-manager";
|
||||
match args.action {
|
||||
Action::Activate { store_path } => activate(store_path),
|
||||
Action::Generate { flake_uri } => generate(&flake_uri),
|
||||
}
|
||||
}
|
||||
|
||||
#[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())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: error message if not euid 0
|
||||
fn activate(store_path: StorePath) -> Result<(), Box<dyn Error>> {
|
||||
if nix::unistd::Uid::is_root(nix::unistd::getuid()) {
|
||||
return Err("We need root permissions.".into());
|
||||
}
|
||||
println!("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)?;
|
||||
println!("{:?}", services);
|
||||
|
||||
services.iter().try_for_each(|service| {
|
||||
create_store_link(
|
||||
&service.store_path(),
|
||||
Path::new(&format!("/run/systemd/system/{}.service", service.name)),
|
||||
)
|
||||
})?;
|
||||
|
||||
if process::Command::new("systemctl")
|
||||
.arg("daemon-reload")
|
||||
.output()
|
||||
.expect("Unable to run systemctl.")
|
||||
.status
|
||||
.success()
|
||||
{
|
||||
services.iter().for_each(|service| {
|
||||
print_out_and_err(
|
||||
process::Command::new("systemctl")
|
||||
.arg("start")
|
||||
.arg(&service.name)
|
||||
.output()
|
||||
.expect("Unable to run systemctl"),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate(flake_uri: &str) -> Result<(), Box<dyn Error>> {
|
||||
// TODO: we temporarily put this under per-user to avoid needing root access
|
||||
// we will move this to /nix/var/nix/profiles/system later on.
|
||||
let user = env::var("USER").expect("USER env var undefined");
|
||||
let profile_path = format!("/nix/var/nix/profiles/per-user/{}/{}", user, profile_name);
|
||||
let profile_path = format!("/nix/var/nix/profiles/per-user/{}/{}", user, PROFILE_NAME);
|
||||
let gcroot_path = format!(
|
||||
"/nix/var/nix/gcroots/per-user/{}/{}-current",
|
||||
user, profile_name
|
||||
user, PROFILE_NAME
|
||||
);
|
||||
|
||||
let flake_attr = "serviceConfig.x86_64-linux";
|
||||
// FIXME: we should not hard-code the system here
|
||||
let flake_attr = format!("{}.x86_64-linux", FLAKE_ATTR);
|
||||
|
||||
let nix_build_output = run_nix_build(&args.flake_uri, flake_attr);
|
||||
println!("Running nix build...");
|
||||
let nix_build_output = run_nix_build(flake_uri, &flake_attr);
|
||||
|
||||
let store_path = get_store_path(nix_build_output)?;
|
||||
println!("Found store path: {:?}", store_path);
|
||||
println!("Generating new generation from {}", store_path);
|
||||
print_out_and_err(install_nix_profile(&store_path, &profile_path));
|
||||
println!("Registering GC root...");
|
||||
create_gcroot(&gcroot_path, &store_path).expect("Failed to create GC root.");
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -60,28 +146,57 @@ fn install_nix_profile(store_path: &StorePath, profile_path: &str) -> process::O
|
|||
}
|
||||
|
||||
fn create_gcroot(gcroot_path: &str, store_path: &StorePath) -> Result<(), Box<dyn Error>> {
|
||||
let path = Path::new(gcroot_path);
|
||||
if path.is_symlink() {
|
||||
fs::remove_file(path).expect("Error removing old GC root.");
|
||||
create_store_link(store_path, Path::new(gcroot_path))
|
||||
}
|
||||
unix::fs::symlink(&store_path.path, gcroot_path).map_err(Box::from)
|
||||
|
||||
fn create_store_link(store_path: &StorePath, from: &Path) -> Result<(), Box<dyn Error>> {
|
||||
println!("Creating symlink: {} -> {}", from.display(), store_path);
|
||||
if from.is_symlink() {
|
||||
fs::remove_file(from)
|
||||
.unwrap_or_else(|_| panic!("Error removing old symlink: {}.", from.display()));
|
||||
}
|
||||
unix::fs::symlink(&store_path.path, from).map_err(Box::from)
|
||||
}
|
||||
|
||||
fn get_store_path(nix_build_result: process::Output) -> Result<StorePath, Box<dyn Error>> {
|
||||
if nix_build_result.status.success() {
|
||||
String::from_utf8(nix_build_result.stdout)
|
||||
.map_err(Box::from)
|
||||
.map(StorePath::from)
|
||||
.and_then(parse_nix_build_output)
|
||||
} else {
|
||||
String::from_utf8(nix_build_result.stderr).map_or_else(boxed_error(), boxed_error())
|
||||
}
|
||||
}
|
||||
|
||||
#[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, Box<dyn Error>> {
|
||||
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(format!(
|
||||
"No output '{}' found in nix build result.",
|
||||
expected_output_name
|
||||
)
|
||||
.into());
|
||||
}
|
||||
Err("Multiple build results were returned, we cannot handle that yet.No output '{}' found in nix build result.".into())
|
||||
}
|
||||
|
||||
fn run_nix_build(flake_uri: &str, flake_attr: &str) -> process::Output {
|
||||
process::Command::new("nix")
|
||||
.arg("build")
|
||||
.arg(format!("{}#{}", flake_uri, flake_attr))
|
||||
.arg("--print-out-paths")
|
||||
.arg("--json")
|
||||
.output()
|
||||
.expect("Failed to execute nix, is it on your path?")
|
||||
}
|
||||
|
|
@ -94,8 +209,8 @@ fn print_out_and_err(output: process::Output) -> process::Output {
|
|||
|
||||
fn print_u8(bytes: &[u8]) {
|
||||
str::from_utf8(bytes).map_or((), |s| {
|
||||
if !s.is_empty() {
|
||||
println!("{}", s)
|
||||
if !s.trim().is_empty() {
|
||||
println!("{}", s.trim())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue