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
|
result
|
||||||
.pre-commit-config.yaml
|
.pre-commit-config.yaml
|
||||||
|
.direnv
|
||||||
|
|
|
||||||
93
Cargo.lock
generated
93
Cargo.lock
generated
|
|
@ -2,6 +2,12 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
|
|
@ -14,6 +20,12 @@ version = "1.0.79"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.1.4"
|
version = "4.1.4"
|
||||||
|
|
@ -109,6 +121,12 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.139"
|
version = "0.2.139"
|
||||||
|
|
@ -121,6 +139,29 @@ version = "0.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
|
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]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.17.0"
|
version = "1.17.0"
|
||||||
|
|
@ -133,6 +174,12 @@ version = "6.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
|
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-error"
|
name = "proc-macro-error"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
|
@ -189,13 +236,59 @@ dependencies = [
|
||||||
"windows-sys",
|
"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]]
|
[[package]]
|
||||||
name = "service-manager"
|
name = "service-manager"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"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]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
|
|
|
||||||
|
|
@ -7,3 +7,6 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.1.4", features = ["derive"] }
|
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 = [
|
modules = [
|
||||||
./nix/modules
|
./nix/modules
|
||||||
];
|
];
|
||||||
|
inherit (self.packages.${system}) service-manager;
|
||||||
};
|
};
|
||||||
|
|
||||||
packages = rec {
|
packages = {
|
||||||
service-manager =
|
service-manager =
|
||||||
pkgs.rustPlatform.buildRustPackage
|
pkgs.rustPlatform.buildRustPackage
|
||||||
{
|
{
|
||||||
|
|
@ -74,7 +75,7 @@
|
||||||
|
|
||||||
cargoLock.lockFile = ./Cargo.lock;
|
cargoLock.lockFile = ./Cargo.lock;
|
||||||
};
|
};
|
||||||
default = service-manager;
|
default = self.packages.${system}.service-manager;
|
||||||
};
|
};
|
||||||
devShells.default = pkgs.devshell.mkShell {
|
devShells.default = pkgs.devshell.mkShell {
|
||||||
packages = with pkgs; [
|
packages = with pkgs; [
|
||||||
|
|
|
||||||
14
nix/lib.nix
14
nix/lib.nix
|
|
@ -6,6 +6,7 @@ in
|
||||||
makeServiceConfig =
|
makeServiceConfig =
|
||||||
{ system
|
{ system
|
||||||
, modules
|
, modules
|
||||||
|
, service-manager
|
||||||
,
|
,
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
|
|
@ -15,10 +16,12 @@ in
|
||||||
inherit system modules;
|
inherit system modules;
|
||||||
specialArgs = { };
|
specialArgs = { };
|
||||||
};
|
};
|
||||||
services =
|
|
||||||
lib.flip lib.genAttrs
|
services = map
|
||||||
(serviceName:
|
(name: {
|
||||||
nixosConfig.config.systemd.units."${serviceName}.service".unit)
|
inherit name;
|
||||||
|
service = ''${nixosConfig.config.systemd.units."${name}.service".unit}/${name}.service'';
|
||||||
|
})
|
||||||
nixosConfig.config.service-manager.services;
|
nixosConfig.config.service-manager.services;
|
||||||
|
|
||||||
servicesPath = pkgs.writeTextFile {
|
servicesPath = pkgs.writeTextFile {
|
||||||
|
|
@ -27,7 +30,8 @@ in
|
||||||
text = lib.generators.toJSON { } services;
|
text = lib.generators.toJSON { } services;
|
||||||
};
|
};
|
||||||
activationScript = pkgs.writeShellScript "activate" ''
|
activationScript = pkgs.writeShellScript "activate" ''
|
||||||
echo "${servicesPath}"
|
${service-manager}/bin/service-manager activate \
|
||||||
|
--store-path "$(realpath $(dirname ''${0}))"
|
||||||
'';
|
'';
|
||||||
in
|
in
|
||||||
pkgs.linkFarmFromDrvs "service-manager" [
|
pkgs.linkFarmFromDrvs "service-manager" [
|
||||||
|
|
|
||||||
151
src/main.rs
151
src/main.rs
|
|
@ -1,10 +1,15 @@
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::os::unix;
|
use std::os::unix;
|
||||||
use std::path::Path;
|
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 {
|
struct StorePath {
|
||||||
path: String,
|
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)]
|
#[command(author, version, about, long_about=None)]
|
||||||
struct Args {
|
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,
|
flake_uri: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
let args = Args::parse();
|
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
|
// 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.
|
// we will move this to /nix/var/nix/profiles/system later on.
|
||||||
let user = env::var("USER").expect("USER env var undefined");
|
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!(
|
let gcroot_path = format!(
|
||||||
"/nix/var/nix/gcroots/per-user/{}/{}-current",
|
"/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)?;
|
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));
|
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.");
|
create_gcroot(&gcroot_path, &store_path).expect("Failed to create GC root.");
|
||||||
Ok(())
|
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>> {
|
fn create_gcroot(gcroot_path: &str, store_path: &StorePath) -> Result<(), Box<dyn Error>> {
|
||||||
let path = Path::new(gcroot_path);
|
create_store_link(store_path, Path::new(gcroot_path))
|
||||||
if path.is_symlink() {
|
|
||||||
fs::remove_file(path).expect("Error removing old GC root.");
|
|
||||||
}
|
}
|
||||||
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>> {
|
fn get_store_path(nix_build_result: process::Output) -> Result<StorePath, Box<dyn Error>> {
|
||||||
if nix_build_result.status.success() {
|
if nix_build_result.status.success() {
|
||||||
String::from_utf8(nix_build_result.stdout)
|
String::from_utf8(nix_build_result.stdout)
|
||||||
.map_err(Box::from)
|
.map_err(Box::from)
|
||||||
.map(StorePath::from)
|
.and_then(parse_nix_build_output)
|
||||||
} else {
|
} else {
|
||||||
String::from_utf8(nix_build_result.stderr).map_or_else(boxed_error(), boxed_error())
|
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 {
|
fn run_nix_build(flake_uri: &str, flake_attr: &str) -> process::Output {
|
||||||
process::Command::new("nix")
|
process::Command::new("nix")
|
||||||
.arg("build")
|
.arg("build")
|
||||||
.arg(format!("{}#{}", flake_uri, flake_attr))
|
.arg(format!("{}#{}", flake_uri, flake_attr))
|
||||||
.arg("--print-out-paths")
|
.arg("--json")
|
||||||
.output()
|
.output()
|
||||||
.expect("Failed to execute nix, is it on your path?")
|
.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]) {
|
fn print_u8(bytes: &[u8]) {
|
||||||
str::from_utf8(bytes).map_or((), |s| {
|
str::from_utf8(bytes).map_or((), |s| {
|
||||||
if !s.is_empty() {
|
if !s.trim().is_empty() {
|
||||||
println!("{}", s)
|
println!("{}", s.trim())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue