Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion flake.nix
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
description = "Manage system config using nix on any distro";
description = "Manage system configurations using Nix on any Linux distribution.";

inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";

Expand Down Expand Up @@ -85,5 +85,21 @@
});
}
);

nixosModules = rec {
system-manager = ./nix/modules;
default = system-manager;
};

templates = {
standalone = {
path = ./templates/standalone;
description = "System Manager standalone setup";
};
nixos = {
path = ./templates/nixos;
description = "System Manager as a NixOS module";
};
};
};
}
1 change: 1 addition & 0 deletions packages.nix
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ in
./Cargo.toml
./Cargo.lock
./src
./templates
];
};

Expand Down
1 change: 1 addition & 0 deletions shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@ pkgs.mkShellNoCC {
nixfmt-rfc-style
rustfmt
clippy
rust-analyzer
];
}
134 changes: 133 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
use anyhow::Result;
use anyhow::{anyhow, bail, Result};
use clap::Parser;
use std::ffi::OsString;
use std::fs::{create_dir_all, OpenOptions};
use std::io::Write;
use std::mem;
use std::path::{Path, PathBuf};
use std::process::{self, ExitCode};

use system_manager::{NixOptions, StorePath};

/// The bytes for the NixOS flake template is included in the binary to avoid unnecessary
/// network calls when initializing a system-manager configuration from the command line.
pub const NIXOS_FLAKE_TEMPLATE: &[u8; 683] = include_bytes!("../templates/nixos/flake.nix");

/// The bytes for the standalone flake template is included in the binary to avoid unnecessary
/// network calls when initializing a system-manager configuration from the command line.
pub const STANDALONE_FLAKE_TEMPLATE: &[u8; 739] =
include_bytes!("../templates/standalone/flake.nix");

/// The bytes for the standalone flake template is included in the binary to avoid unnecessary
/// network calls when initializing a system-manager configuration from the command line.
pub const SYSTEM_MODULE_TEMPLATE: &[u8; 1153] = include_bytes!("../templates/system.nix");

#[derive(clap::Parser, Debug)]
#[command(
author,
Expand All @@ -31,6 +46,34 @@ struct Args {
nix_options: Option<Vec<String>>,
}

#[derive(clap::Args, Debug)]
struct InitArgs {
/// The path to initialize the configuration at.
#[arg(
// The default_value is not resolved at this point so we must
// parse it ourselves with a value_parser closure.
default_value = "~/.config/system-manager",
value_parser = |src: &str| -> Result<PathBuf> {
if src.starts_with("~") {
if let Some(home) = std::env::home_dir() {
let expanded = src.replace("~", &home.to_string_lossy());
return Ok(PathBuf::from(expanded));
}
bail!("Failed to determine a home directory to initialize the configuration in.")
}
Ok(PathBuf::from(src))
},
)]
path: PathBuf,
/// Whether or not to include a 'flake.nix' as part of the new configuration.
/// By default, if the host has the 'flakes' and 'nix-command' experimental features
/// enabled, a 'flake.nix' will be included. A flake template is automatically selected
/// by checking the host system's features. Flake templates are available on the system-manager
/// flake attribute 'outputs.templates'.
#[arg(long, default_value = "false")]
no_flake: bool,
}

#[derive(clap::Args, Debug)]
struct BuildArgs {
#[arg(long = "flake", name = "FLAKE_URI")]
Expand Down Expand Up @@ -83,6 +126,13 @@ struct StoreOrFlakeArgs {

#[derive(clap::Subcommand, Debug)]
enum Action {
/// Initializes a configuration in the given directory. If the directory
/// does not exist, then it will be created. The default directory is
/// '~/.config/system-manager'.
Init {
#[command(flatten)]
init_args: InitArgs,
},
/// Build a new system-manager generation, register it as the active profile, and activate it
Switch {
#[command(flatten)]
Expand Down Expand Up @@ -177,6 +227,60 @@ fn go(args: Args) -> Result<()> {
&nix_options,
)
.and_then(print_store_path),
Action::Init {
init_args: InitArgs { mut path, no_flake },
} => {
create_dir_all(&path).map_err(|err| {
anyhow!(
"encountered an error while creating configuration directory '{}': {err:?}",
path.display()
)
})?;
path = path.canonicalize().map_err(|err| {
anyhow!(
"failed to resolve '{}' into an absolute path: {err:?}",
path.display()
)
})?;
log::info!(
"Initializing new system-manager configuration at '{}'",
path.display()
);

let system_config_filepath = {
let mut buf = path.clone();
buf.push("system.nix");
buf
};
init_config_file(&system_config_filepath, SYSTEM_MODULE_TEMPLATE)?;

let has_flake_support = process::Command::new("nix")
.arg("show-config")
.output()
.is_ok_and(|output| {
let out_str = String::from_utf8_lossy(&output.stdout);
out_str.contains("experimental-features")
&& out_str.contains("flakes")
&& out_str.contains("nix-command")
});
if !no_flake && has_flake_support {
let flake_config_filepath = {
let mut buf = path.clone();
buf.push("flake.nix");
buf
};
let is_nixos = process::Command::new("nixos-version")
.output()
.is_ok_and(|output| !output.stdout.is_empty());
if is_nixos {
init_config_file(&flake_config_filepath, NIXOS_FLAKE_TEMPLATE)?
} else {
init_config_file(&flake_config_filepath, STANDALONE_FLAKE_TEMPLATE)?
}
}
log::info!("Configuration '{}' ready for activation!", path.display());
Ok(())
}
Action::Switch {
build_args: BuildArgs { flake_uri },
activation_args: ActivationArgs { ephemeral },
Expand All @@ -196,6 +300,34 @@ fn go(args: Args) -> Result<()> {
}
}

/// Create and write all bytes from a buffer into a new config file if it doesn't already exist.
fn init_config_file(filepath: &PathBuf, buf: &[u8]) -> Result<()> {
match OpenOptions::new()
.create_new(true)
.write(true)
.truncate(false)
.open(filepath)
{
Ok(mut file) => {
file.write_all(buf)?;
log::info!("{}B written to '{}'", buf.len(), filepath.display())
}
Err(err) if matches!(err.kind(), std::io::ErrorKind::AlreadyExists) => {
log::warn!(
"'{}' already exists, leaving it unchanged...",
filepath.display()
)
}
Err(err) => {
bail!(
"failed to initialize system configuration at '{}': {err:?}",
filepath.display()
)
}
}
Ok(())
}

fn print_store_path<SP: AsRef<StorePath>>(store_path: SP) -> Result<()> {
// Print the raw store path to stdout
println!("{}", store_path.as_ref());
Expand Down
33 changes: 33 additions & 0 deletions templates/nixos/flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
description = "NixOS System Manager configuration";

inputs = {
# Specify the source of System Manager and Nixpkgs.
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
system-manager = {
url = "github:numtide/system-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
};

outputs =
{
self,
nixpkgs,
system-manager,
...
}:
let
system = "x86_64-linux";
in
{
nixosConfigurations.default = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
./configuration.nix
./system.nix
system-manager.nixosModules.system-manager
];
};
};
}
32 changes: 32 additions & 0 deletions templates/standalone/flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
description = "Standalone System Manager configuration";

inputs = {
# Specify the source of System Manager and Nixpkgs.
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
system-manager = {
url = "github:numtide/system-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
};

outputs =
{
self,
nixpkgs,
system-manager,
...
}:
let
system = "x86_64-linux";
in
{
systemConfigs.default = system-manager.lib.makeSystemConfig {
# Specify your system configuration modules here, for example,
# the path to your system.nix.
modules = [ ./system.nix ];

# Optionally specify extraSpecialArgs and overlays
};
};
}
55 changes: 55 additions & 0 deletions templates/system.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{ lib, pkgs, ... }:
{
config = {
nixpkgs.hostPlatform = "x86_64-linux";

# Enable and configure services
services = {
# nginx.enable = true;
};

environment = {
# Packages that should be installed on a system
systemPackages = [
# pkgs.hello
];

# Add directories and files to `/etc` and set their permissions
etc = {
# with_ownership = {
# text = ''
# This is just a test!
# '';
# mode = "0755";
# uid = 5;
# gid = 6;
# };
#
# with_ownership2 = {
# text = ''
# This is just a test!
# '';
# mode = "0755";
# user = "nobody";
# group = "users";
# };
};
};

# Enable and configure systemd services
systemd.services = { };

# Configure systemd tmpfile settings
systemd.tmpfiles = {
# rules = [
# "D /var/tmp/system-manager 0755 root root -"
# ];
#
# settings.sample = {
# "/var/tmp/sample".d = {
# mode = "0755";
# };
# };
};
};
}