From 9ae0630db46bbbf7fdc1b8871b0fcc44782d5558 Mon Sep 17 00:00:00 2001 From: Qyriad Date: Mon, 2 Feb 2026 13:42:07 +0100 Subject: [PATCH] factor out do_append in prep for delta subcommands --- configuration.nix | 36 +++++++++++++++++++++++++ default.nix | 12 +++++++-- dynamic-options.nix | 61 +++++++++++++++++++++++++++++++++++++++++++ dynamic-submodule.nix | 45 +++++++++++++++++++++++++++++++ dynamic.nix | 15 +++++++++++ src/args.rs | 40 +++++++++++++++++++--------- src/lib.rs | 41 +++++++++++++++++++++++++++-- src/main.rs | 43 +++--------------------------- 8 files changed, 237 insertions(+), 56 deletions(-) create mode 100644 configuration.nix create mode 100644 dynamic-options.nix create mode 100644 dynamic-submodule.nix create mode 100644 dynamic.nix diff --git a/configuration.nix b/configuration.nix new file mode 100644 index 0000000..8ad95b0 --- /dev/null +++ b/configuration.nix @@ -0,0 +1,36 @@ +{ pkgs, modulesPath, ... }: + +{ + imports = [ + ./dynamic.nix + ./dynamic-options.nix + "${modulesPath}/profiles/qemu-guest.nix" + ]; + + dynamicism.for.gotosocial.enable = true; + + # Just an example system. + users.mutableUsers = false; + users.users.root = { + password = "root"; + }; + + services.openssh = { + enable = true; + settings = { + PermitRootLogin = "yes"; + }; + }; + + environment.shellAliases = { + ls = "eza --long --header --group --group-directories-first --classify --binary"; + }; + + environment.systemPackages = with pkgs; [ + eza + fd + ripgrep + ]; + + system.stateVersion = "25.11"; +} diff --git a/default.nix b/default.nix index 3c3e64f..ece52f0 100644 --- a/default.nix +++ b/default.nix @@ -15,6 +15,14 @@ }); in dynix') qpkgs.validStdenvs; -in dynix.overrideAttrs (prev: lib.recursiveUpdate prev { - passthru = { inherit byStdenv; }; +in dynix.overrideAttrs (final: prev: let + self = final.finalPackage; +in lib.recursiveUpdate prev { + passthru = { + inherit byStdenv; + nixos = import (pkgs.path + "/nixos") { + configuration = ./configuration.nix; + }; + nixos-vm = self.nixos.config.system.build.vm; + }; }) diff --git a/dynamic-options.nix b/dynamic-options.nix new file mode 100644 index 0000000..da3de76 --- /dev/null +++ b/dynamic-options.nix @@ -0,0 +1,61 @@ +{ pkgs, lib, config, ... }: +let + inherit (lib.modules) + mkIf + ; + inherit (lib.options) + mkOption + mkEnableOption + literalExpression + showOption + ; + t = lib.types; + cfg = config.dynamicism; + + settingsFormat = pkgs.formats.yaml { }; + + assertionFor = submodName: unitName: let + optName = [ "dynamicism" "for" submodName "systemd-services-updated" ]; + in { + assertion = config.systemd.units.${unitName}.enable or false; + message = "'${showOption optName}' specified non-existentant unit '${unitName}'"; + }; + + assertionsFor = + submodName: + submod: + lib.map (assertionFor submodName) submod.systemd-services-updated; +in +{ + options.dynamicism.for = mkOption { + type = t.attrsOf (t.submoduleWith { + modules = [ ./dynamic-submodule.nix ]; + shorthandOnlyDefinesConfig = false; + }); + default = { }; + }; + + options.dynamicism.finalSettings = mkOption { + type = t.attrsOf t.raw; + internal = true; + readOnly = true; + }; + + config.assertions = lib.foldlAttrs (acc: name: submod: let + next = lib.optionals submod.enable (assertionsFor name submod); + in acc ++ next) [ ] cfg.for; + + config.dynamicism.for = { + gotosocial = { + source-parameters = [ "services" "gotosocial" "settings" ]; + systemd-services-updated = [ "gotosocial.service" ]; + }; + }; + + # FIXME: this should be a fold. + config.dynamicism.finalSettings = lib.mapAttrs (name: submod: let + #optValue = lib.getAttrFromPath submod. + # FIXME: this should be a fold. + optValue = lib.getAttrFromPath submod.source-parameters config; + in optValue) cfg.for; +} diff --git a/dynamic-submodule.nix b/dynamic-submodule.nix new file mode 100644 index 0000000..d00b4d3 --- /dev/null +++ b/dynamic-submodule.nix @@ -0,0 +1,45 @@ +{ + name, + pkgs, + lib, + config, + ... +}: +let + inherit (lib.modules) + mkIf + ; + inherit (lib.options) + mkOption + mkEnableOption + literalExpression + ; + t = lib.types; +in +{ + options = { + enable = mkEnableOption "Dynamicism for ${name}"; + + source-parameters = mkOption { + type = t.listOf t.str; + description = "An attrpath of the NixOS option this dynamicism uses"; + example = literalExpression '' + [ "services" "gotosocial" "settings" ] + ''; + }; + + systemd-services-updated = mkOption { + type = t.listOf t.str; + description = '' + A list of systemd unit names (including the suffix, e.g. `.service`) that need to be updated. + ''; + example = literalExpression '' + "gotosocial.service" + ''; + }; + }; + + config = mkIf config.enable { + + }; +} diff --git a/dynamic.nix b/dynamic.nix new file mode 100644 index 0000000..b37d8da --- /dev/null +++ b/dynamic.nix @@ -0,0 +1,15 @@ +# Managed by dynix. +{ lib, ... }: + +lib.mkMerge [ + { + services.gotosocial = { + enable = true; + setupPostgresqlDB = true; + settings = { + application-name = "example!"; + host = "yuki.local"; + }; + }; + } +] diff --git a/src/args.rs b/src/args.rs index 8de2c39..9de35b1 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use clap::ColorChoice; use crate::prelude::*; @@ -46,27 +48,39 @@ impl FromStr for NixOsOption { } } +#[derive(Debug, Clone, PartialEq, clap::Parser)] +pub struct AppendCmd { + #[arg(required = true)] + pub name: Arc, + #[arg(required = true)] + pub value: Arc, +} + +#[derive(Debug, Clone, PartialEq, clap::Subcommand)] +pub enum Subcommand { + Append(AppendCmd), +} + #[derive(Debug, Clone, PartialEq, clap::Parser)] #[command(version, about, author, arg_required_else_help(true))] -pub struct Parser { +#[command(propagate_version = true)] +pub struct Args { #[arg(long, default_value = "auto")] pub color: ColorChoice, #[arg(long)] - pub file: Box, + pub file: Arc, - #[arg(required = true)] - pub name: Box, - #[arg(required = true)] - pub value: Box, - ///// Flakeref to a base configuration to modify. - //#[arg(group = "config", long, default_value("."))] - //#[arg(long, default_value(Some(".")))] - //flake: Option>>, - // - //#[arg(group = "config", long)] - //expr: Option, + #[command(subcommand)] + pub subcommand: Subcommand, } +///// Flakeref to a base configuration to modify. +//#[arg(group = "config", long, default_value("."))] +//#[arg(long, default_value(Some(".")))] +//flake: Option>>, +// +//#[arg(group = "config", long)] +//expr: Option, //impl Parser { // fn eval_cmd(&self) { diff --git a/src/lib.rs b/src/lib.rs index ce762d7..55dfc48 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{iter, sync::Arc}; pub(crate) mod prelude { #![allow(unused_imports)] @@ -32,7 +32,7 @@ pub(crate) mod prelude { use prelude::*; pub mod args; -pub use args::Parser; +pub use args::{AppendCmd, Args}; mod color; pub use color::{CLI_ENABLE_COLOR, SHOULD_COLOR}; pub mod line; @@ -47,6 +47,43 @@ use crate::source::SourceFile; pub const ASCII_WHITESPACE: &[char] = &['\t', '\n', '\x0C', '\r', ' ']; +pub fn do_append(args: Arc, append_args: AppendCmd) -> Result<(), BoxDynError> { + let filepath = Path::new(&args.file); + let filepath: PathBuf = if filepath.is_relative() && !filepath.starts_with("./") { + iter::once(OsStr::new("./")) + .chain(filepath.iter()) + .collect() + } else { + filepath.to_path_buf() + }; + + // Get what file that thing is defined in. + let def_path = get_where(&append_args.name, &filepath)?; + + let mut opts = File::options(); + opts.read(true) + .write(true) + .create(false) + .custom_flags(libc::O_CLOEXEC); + let source_file = SourceFile::open_from(Arc::from(def_path), opts)?; + + let pri = get_highest_prio(&append_args.name, source_file.clone())?; + let new_pri = pri - 1; + + let new_pri_line = get_next_prio_line( + source_file.clone(), + append_args.name.into(), + new_pri, + append_args.value.into(), + )?; + + eprintln!("new_pri_line={new_pri_line}"); + + write_next_prio(source_file, new_pri_line)?; + + Ok(()) +} + #[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize)] pub struct DefinitionWithLocation { pub file: Box, diff --git a/src/main.rs b/src/main.rs index b944e9e..f45b144 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,11 @@ -use std::ffi::OsStr; use std::io::{self, IsTerminal}; -use std::iter; -use std::path::{Path, PathBuf}; use std::process::ExitCode; use std::{error::Error as StdError, sync::Arc}; -use dynix::source::SourceFile; use clap::{ColorChoice, Parser as _}; -use fs_err::File; -use fs_err::os::unix::fs::OpenOptionsExt; fn main_wrapped() -> Result<(), Box> { - let args = dynix::Parser::parse(); + let args = Arc::new(dynix::Args::parse()); dbg!(&args); @@ -24,40 +18,11 @@ fn main_wrapped() -> Result<(), Box> { success.expect("logic error in CLI_ENABLE_COLOR"); } - let filepath = Path::new(&args.file); - let filepath: PathBuf = if filepath.is_relative() && !filepath.starts_with("./") { - iter::once(OsStr::new("./")) - .chain(filepath.iter()) - .collect() - } else { - filepath.to_path_buf() + use dynix::args::Subcommand::*; + match &args.subcommand { + Append(append_args) => dynix::do_append(args.clone(), append_args.clone())?, }; - // Get what file that thing is defined in. - let def_path = dynix::get_where(&args.name, &filepath)?; - dbg!(&def_path); - let def_path = Arc::from(def_path); - let mut opts = File::options(); - opts.read(true) - .write(true) - .create(false) - .custom_flags(libc::O_CLOEXEC); - let source_file = SourceFile::open_from(Arc::clone(&def_path), opts)?; - - let pri = dynix::get_highest_prio(&args.name, source_file.clone())?; - let new_pri = pri - 1; - - let new_pri_line = dynix::get_next_prio_line( - source_file.clone(), - args.name.into(), - new_pri, - args.value.into(), - )?; - - eprintln!("new_pri_line={new_pri_line}"); - - dynix::write_next_prio(source_file, new_pri_line)?; - Ok(()) }