PoC part 2

This commit is contained in:
Qyriad 2026-02-03 22:31:32 +01:00
parent 0580ad02bd
commit 15ed56d8ad
3 changed files with 117 additions and 40 deletions

View file

@ -30,15 +30,22 @@
dynamicBefore = nixosBefore.config.dynamicism.finalSettings; dynamicBefore = nixosBefore.config.dynamicism.finalSettings;
nixosAfter = evalNixos { nixosAfter = evalNixos {
configuration = lib.mkMerge [ configuration = { ... }: {
nixosBefore imports = [
(lib.setAttrByPath option (lib.mkOverride (-999) value)) ./configuration.nix
]; (lib.setAttrByPath option (lib.mkOverride (-999) value))
];
};
}; };
dynamicAfter = nixosAfter.config.dynamicism.finalSettings; withActivationScripts = evalNixos {
configuration = ({ ... }: {
imports = [ ./configuration.nix ];
config.environment.systemPackages = [ nixosAfter.config.dynamicism.for.gotosocial.activate ];
});
};
in { in {
inherit nixosBefore nixosAfter withActivationScripts;
}; };
in dynix.overrideAttrs (final: prev: let in dynix.overrideAttrs (final: prev: let
@ -56,5 +63,9 @@ in lib.recursiveUpdate prev {
c = self.nixos; c = self.nixos;
nixos-vm = self.nixos.config.system.build.vm; nixos-vm = self.nixos.config.system.build.vm;
doChange = builtins.seq self.nixos.config.dynamicism doChange; doChange = builtins.seq self.nixos.config.dynamicism doChange;
withVox = self.doChange {
option = lib.splitString "." "services.gotosocial.settings.application-name";
value = "Vox is an asshole";
};
}; };
}) })

View file

@ -13,19 +13,43 @@ let
cfg = config.dynamicism; cfg = config.dynamicism;
opts = options.dynamicism; opts = options.dynamicism;
subOpts = lib.mapAttrs (_: metaAttr: metaAttr.configuration.options) options.dynamicism.for.valueMeta.attrs; subOpts = lib.mapAttrs (_: metaAttr: metaAttr.configuration.options) options.dynamicism.for.valueMeta.attrs;
settingsFormat = pkgs.formats.yaml { };
concatFoldl = f: list: lib.foldl' (acc: value: acc ++ (f value)) [ ] list;
recUpdateFoldlAttrs = f: attrs: lib.foldlAttrs (acc: name: value: lib.recursiveUpdate acc (f name value)) { } attrs;
finalSettingsFor = { ... }@submod: lib.foldl (acc: optPath: let finalSettingsFor = { ... }@submod: lib.foldl (acc: optPath: let
next = next =
assert lib.isList optPath; assert lib.isList optPath;
lib.setAttrByPath optPath (lib.getAttrFromPath optPath config); lib.setAttrByPath optPath (lib.getAttrFromPath optPath config);
in lib.recursiveUpdate acc next) { } submod.source-options; in lib.recursiveUpdate acc next) { } submod.source-options;
ourAssertions = lib.concatAttrValues {
unitsExist = concatFoldl (submod: let
next = lib.map (unit: assert lib.isString unit; {
assertion = config.systemd.units.${unit}.enable or false;
message = ''
'${showOption submod.systemd-services-updated.loc}' specified non-existent unit '${unit}'
'';
}) submod.systemd-services-updated.value;
in lib.optionals submod.enable.value next) (lib.attrValues subOpts);
optsExist = concatFoldl (submod: lib.optionals submod.enable.value (lib.map (optPath: {
assertion = lib.hasAttrByPath optPath options;
message = "'${showOption submod.source-options.loc}' specified non-existent option '${showOption optPath}'";
}) submod.source-options.value)) (lib.attrValues subOpts);
};
in in
{ {
#
# Interface.
#
options.dynamicism = { options.dynamicism = {
for = mkOption { for = mkOption {
type = t.attrsOf (t.submoduleWith { type = t.attrsOf (t.submoduleWith {
modules = [ ./submodule.nix ]; modules = [ ./submodule.nix ];
shorthandOnlyDefinesConfig = false; shorthandOnlyDefinesConfig = false;
specialArgs = { inherit pkgs; };
}); });
default = { }; default = { };
}; };
@ -40,39 +64,30 @@ in
}; };
}; };
config.assertions = let # Assertions.
unitsExist = lib.foldl' (acc: submod: let config.assertions = ourAssertions;
next = lib.map (unit: assert lib.isString unit; {
assertion = config.systemd.units.${unit}.enable or false;
message = ''
'${showOption submod.systemd-services-updated.loc}' specified non-existent unit '${unit}'
'';
}) submod.systemd-services-updated.value;
in acc ++ lib.optionals submod.enable.value next) [ ] (lib.attrValues subOpts);
optsExist = lib.foldl' (acc: submod: let #
next = lib.map (optPath: { # Generic implementation.
assertion = lib.hasAttrByPath optPath options; #
message = "'${showOption submod.source-options.loc}' specified non-existent option '${showOption optPath}'"; config.dynamicism.finalSettings = lib.asserts.checkAssertWarn ourAssertions [ ] (
}) submod.source-options.value; recUpdateFoldlAttrs (name: { ... }@submod: finalSettingsFor submod) config.dynamicism.for
in acc ++ lib.optionals submod.enable.value next) [ ] (lib.attrValues subOpts); );
in lib.concatLists [
unitsExist
optsExist
];
config.dynamicism.for = { # Implementations.
gotosocial = { config.dynamicism.for.gotosocial = let
source-options = [ cfg = config.dynamicism.for.gotosocial;
"services.gotosocial.setting" in {
]; source-options = [
systemd-services-updated = [ "services.gotosocial.settings"
"gotosocial.service" ];
];
}; configFile = settingsFormat.generate "gotosocial-override.yml" config.services.gotosocial.settings;
unitDropins."gotosocial.service" = pkgs.writeText "gotosocial-override.conf" ''
[Service]
ExecStart=
ExecStart=${lib.getExe' pkgs.gotosocial "gotosocial"} --config-path ${cfg.configFile} start
'';
}; };
config.dynamicism.finalSettings = lib.foldlAttrs (acc: name: { ... }@submod: let
next = finalSettingsFor submod;
in lib.recursiveUpdate acc next) { } config.dynamicism.for;
} }

View file

@ -1,5 +1,6 @@
{ {
name, name,
pkgs,
lib, lib,
config, config,
... ...
@ -13,10 +14,26 @@ let
mkEnableOption mkEnableOption
literalExpression literalExpression
; ;
inherit (lib.types)
mkOptionType
;
t = lib.types; t = lib.types;
/** Either a list of strings, or a dotted string that will be split. */ /** Either a list of strings, or a dotted string that will be split. */
convenientAttrPath = t.coercedTo t.str (lib.splitString ".") (t.listOf t.str); convenientAttrPath = t.coercedTo t.str (lib.splitString ".") (t.listOf t.str);
executablePathInStore = mkOptionType {
name = "exepath";
description = "executable path in the Nix store";
descriptionClass = "noun";
merge = lib.mergeEqualOption;
functor = lib.defaultFunctor "exepath";
check = x: if lib.isDerivation x then (
x.meta.mainProgram or null != null
) else (
lib.pathInStore.check x
);
};
in in
{ {
options = { options = {
@ -36,12 +53,46 @@ in
A list of systemd unit names (including the suffix, e.g. `.service`) that need to be updated. A list of systemd unit names (including the suffix, e.g. `.service`) that need to be updated.
''; '';
example = literalExpression '' example = literalExpression ''
"gotosocial.service" [ "gotosocial.service" ]
''; '';
default = lib.attrNames config.unitDropins;
};
configFile = mkOption {
type = t.pathInStore;
internal = true;
};
unitDropins = mkOption {
type = t.attrsOf t.pathInStore;
internal = true;
};
activate = mkOption {
type = executablePathInStore;
internal = true;
}; };
}; };
config = mkIf config.enable { config = mkIf config.enable {
activate = pkgs.writeShellApplication {
name = "dynamicism-for-${name}-activate";
runtimeInputs = [ pkgs.systemd ];
text = let
doEdits = config.unitDropins
|> lib.mapAttrsToList (service: dropin: ''
cat "${dropin}" | systemctl edit "${service}" --runtime --stdin
'');
doReloads = config.unitDropins
|> lib.mapAttrsToList (service: _: ''
systemctl reload-or-restart "${service}"
'');
in [
doEdits
doReloads
] |> lib.concatLists
|> lib.concatStringsSep "\n";
};
}; };
} }