Compare commits
No commits in common. "d76474c5240450bd9c4ceae9993941b900d3d23d" and "447ae19b3c37047129590da8e9334257a19ca077" have entirely different histories.
d76474c524
...
447ae19b3c
47 changed files with 722 additions and 1559 deletions
|
|
@ -11,5 +11,5 @@ indent_style = space
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|
||||||
[*.nix]
|
[*.nix]
|
||||||
indent_style = space
|
indent_style = tab
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
|
||||||
80
Cargo.lock
generated
80
Cargo.lock
generated
|
|
@ -69,9 +69,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.11.0"
|
version = "2.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
|
|
@ -87,9 +87,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.58"
|
version = "4.5.54"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806"
|
checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
|
|
@ -97,9 +97,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.58"
|
version = "4.5.54"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2"
|
checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
|
|
@ -109,9 +109,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.5.55"
|
version = "4.5.49"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
|
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|
@ -121,9 +121,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_lex"
|
name = "clap_lex"
|
||||||
version = "1.0.0"
|
version = "0.7.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
|
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorchoice"
|
name = "colorchoice"
|
||||||
|
|
@ -158,8 +158,6 @@ dependencies = [
|
||||||
"fs-err",
|
"fs-err",
|
||||||
"itertools",
|
"itertools",
|
||||||
"libc",
|
"libc",
|
||||||
"regex",
|
|
||||||
"regex-lite",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tap",
|
"tap",
|
||||||
|
|
@ -192,9 +190,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fs-err"
|
name = "fs-err"
|
||||||
version = "3.3.0"
|
version = "3.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "73fde052dbfc920003cfd2c8e2c6e6d4cc7c1091538c3a24226cec0665ab08c0"
|
checksum = "baf68cef89750956493a66a10f512b9e58d9db21f2a573c079c0bdf1207a54a7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
@ -273,9 +271,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.182"
|
version = "0.2.180"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
|
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
|
|
@ -309,9 +307,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.8.0"
|
version = "2.7.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nix"
|
name = "nix"
|
||||||
|
|
@ -387,9 +385,9 @@ checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.106"
|
version = "1.0.105"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
@ -408,9 +406,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.44"
|
version = "1.0.43"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
@ -424,40 +422,22 @@ dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex"
|
|
||||||
version = "1.12.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
|
||||||
dependencies = [
|
|
||||||
"aho-corasick",
|
|
||||||
"memchr",
|
|
||||||
"regex-automata",
|
|
||||||
"regex-syntax",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.4.14"
|
version = "0.4.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-lite"
|
|
||||||
version = "0.1.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.8.9"
|
version = "0.8.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c"
|
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
|
|
@ -575,9 +555,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.116"
|
version = "2.0.114"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb"
|
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -699,9 +679,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.24"
|
version = "1.0.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-linebreak"
|
name = "unicode-linebreak"
|
||||||
|
|
@ -935,6 +915,6 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zmij"
|
name = "zmij"
|
||||||
version = "1.0.21"
|
version = "1.0.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65"
|
||||||
|
|
|
||||||
|
|
@ -11,19 +11,12 @@ path = "src/main.rs"
|
||||||
name = "dynix"
|
name = "dynix"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["regex-full"]
|
|
||||||
regex-full = ["dep:regex"]
|
|
||||||
regex-lite = ["dep:regex-lite"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.5.54", features = ["color", "derive"] }
|
clap = { version = "4.5.54", features = ["color", "derive"] }
|
||||||
command-error = "0.8.0"
|
command-error = "0.8.0"
|
||||||
fs-err = "3.2.2"
|
fs-err = "3.2.2"
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
libc = { version = "0.2.180", features = ["extra_traits"] }
|
libc = { version = "0.2.180", features = ["extra_traits"] }
|
||||||
regex = { version = "1.12.3", optional = true }
|
|
||||||
regex-lite = { version = "0.1.9", optional = true }
|
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
serde_json = "1.0.149"
|
serde_json = "1.0.149"
|
||||||
tap = "1.0.1"
|
tap = "1.0.1"
|
||||||
|
|
|
||||||
36
configuration.nix
Normal file
36
configuration.nix
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
{ pkgs, modulesPath, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./modules/dynamic-overrides.nix
|
||||||
|
./modules/dynamicism
|
||||||
|
"${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";
|
||||||
|
}
|
||||||
65
default.nix
65
default.nix
|
|
@ -5,14 +5,71 @@
|
||||||
in import src { inherit pkgs; },
|
in import src { inherit pkgs; },
|
||||||
}: let
|
}: let
|
||||||
inherit (qpkgs) lib;
|
inherit (qpkgs) lib;
|
||||||
dynix = qpkgs.callPackage ./package.nix { }
|
|
||||||
|> qpkgs.stdlib.mkStdenvPretty;
|
dynix = qpkgs.callPackage ./package.nix { };
|
||||||
|
|
||||||
byStdenv = lib.mapAttrs (stdenvName: stdenv: let
|
byStdenv = lib.mapAttrs (stdenvName: stdenv: let
|
||||||
withStdenv = dynix.override { inherit stdenv; };
|
withStdenv = dynix.override { inherit stdenv; };
|
||||||
dynix' = withStdenv.overrideAttrs (prev: {
|
dynix' = withStdenv.overrideAttrs (prev: {
|
||||||
pname = "${prev.pname}-${stdenvName}";
|
pname = "${prev.pname}-${stdenvName}";
|
||||||
});
|
});
|
||||||
in dynix') qpkgs.validStdenvs;
|
in dynix') qpkgs.validStdenvs;
|
||||||
in dynix.overrideAttrs (prev: lib.recursiveUpdate prev {
|
|
||||||
passthru = { inherit byStdenv; };
|
evalNixos = import (pkgs.path + "/nixos");
|
||||||
|
|
||||||
|
doChange = {
|
||||||
|
option,
|
||||||
|
value,
|
||||||
|
}:
|
||||||
|
assert lib.isList option;
|
||||||
|
assert lib.all lib.isString option;
|
||||||
|
let
|
||||||
|
nixosBefore = evalNixos {
|
||||||
|
configuration = ./configuration.nix;
|
||||||
|
};
|
||||||
|
dynamicBefore = nixosBefore.config.dynamicism.finalSettings;
|
||||||
|
|
||||||
|
nixosAfter = evalNixos {
|
||||||
|
configuration = { ... }: {
|
||||||
|
imports = [
|
||||||
|
./configuration.nix
|
||||||
|
(lib.setAttrByPath option (lib.mkOverride (-999) value))
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
withActivationScripts = evalNixos {
|
||||||
|
configuration = ({ ... }: {
|
||||||
|
imports = [ ./configuration.nix ];
|
||||||
|
config.environment.systemPackages = [ nixosAfter.config.dynamicism.for.gotosocial.activate ];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
inherit nixosBefore nixosAfter withActivationScripts;
|
||||||
|
};
|
||||||
|
|
||||||
|
in dynix.overrideAttrs (final: prev: let
|
||||||
|
self = final.finalPackage;
|
||||||
|
in lib.recursiveUpdate prev {
|
||||||
|
passthru = {
|
||||||
|
ts = let
|
||||||
|
scope = pkgs.callPackage ./modules/tests.nix { };
|
||||||
|
in scope.packages scope;
|
||||||
|
|
||||||
|
dync = self.nixos.config.dynamicism;
|
||||||
|
dyno = self.nixos.options.dynamicism;
|
||||||
|
gotosocial = self.nixos.options.dynamicism.for.valueMeta.attrs.gotosocial.configuration;
|
||||||
|
|
||||||
|
inherit byStdenv;
|
||||||
|
nixos = evalNixos {
|
||||||
|
configuration = ./configuration.nix;
|
||||||
|
};
|
||||||
|
c = self.nixos;
|
||||||
|
nixos-vm = self.nixos.config.system.build.vm;
|
||||||
|
doChange = builtins.seq self.nixos.config.dynamicism doChange;
|
||||||
|
withVox = self.doChange {
|
||||||
|
option = lib.splitString "." "services.gotosocial.settings.application-name";
|
||||||
|
value = "Vox is an asshole";
|
||||||
|
};
|
||||||
|
};
|
||||||
})
|
})
|
||||||
|
|
|
||||||
29
flake.lock
generated
29
flake.lock
generated
|
|
@ -1,21 +1,5 @@
|
||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"fenix": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1771226183,
|
|
||||||
"narHash": "sha256-AbaMtaLbe37l2VI/KSRk63PuBnX/YDDFL0G1eFMbvwI=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "fenix",
|
|
||||||
"rev": "2e3759c5ef51f320eb0aaf83f2a32baae33db237",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "fenix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-utils": {
|
"flake-utils": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
|
|
@ -37,11 +21,11 @@
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1770537093,
|
"lastModified": 1768875095,
|
||||||
"narHash": "sha256-pF1quXG5wsgtyuPOHcLfYg/ft/QMr8NnX0i6tW2187s=",
|
"narHash": "sha256-dYP3DjiL7oIiiq3H65tGIXXIT1Waiadmv93JS0sS+8A=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "fef9403a3e4d31b0a23f0bacebbec52c248fbb51",
|
"rev": "ed142ab1b3a092c4d149245d0c4126a5d7ea00b0",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -54,11 +38,11 @@
|
||||||
"qyriad-nur": {
|
"qyriad-nur": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1770385314,
|
"lastModified": 1767357308,
|
||||||
"narHash": "sha256-zDlvon/yF9STxGi3l38j9EgTFHBHOjCJlP8mMX7zw5M=",
|
"narHash": "sha256-PWDIBupTHASnzwPUuafIhwBCFKxjsSVj4QRAdX5y/z4=",
|
||||||
"owner": "Qyriad",
|
"owner": "Qyriad",
|
||||||
"repo": "nur-packages",
|
"repo": "nur-packages",
|
||||||
"rev": "23716e0347215a721f9489515a0c3dc91122c7d5",
|
"rev": "c08309f918a0528ceb23659c0cc4a3c901fe8afa",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -69,7 +53,6 @@
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"fenix": "fenix",
|
|
||||||
"flake-utils": "flake-utils",
|
"flake-utils": "flake-utils",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
"qyriad-nur": "qyriad-nur"
|
"qyriad-nur": "qyriad-nur"
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,6 @@
|
||||||
flake = false;
|
flake = false;
|
||||||
};
|
};
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
fenix = {
|
|
||||||
url = "github:nix-community/fenix";
|
|
||||||
flake = false;
|
|
||||||
};
|
|
||||||
qyriad-nur = {
|
qyriad-nur = {
|
||||||
url = "github:Qyriad/nur-packages";
|
url = "github:Qyriad/nur-packages";
|
||||||
flake = false;
|
flake = false;
|
||||||
|
|
@ -19,13 +15,11 @@
|
||||||
self,
|
self,
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
flake-utils,
|
flake-utils,
|
||||||
fenix,
|
|
||||||
qyriad-nur,
|
qyriad-nur,
|
||||||
}: flake-utils.lib.eachDefaultSystem (system: let
|
}: flake-utils.lib.eachDefaultSystem (system: let
|
||||||
pkgs = import nixpkgs { inherit system; };
|
pkgs = import nixpkgs { inherit system; };
|
||||||
qpkgs = import qyriad-nur { inherit pkgs; };
|
qpkgs = import qyriad-nur { inherit pkgs; };
|
||||||
inherit (qpkgs) lib;
|
inherit (qpkgs) lib;
|
||||||
fenixLib = import fenix { inherit pkgs; };
|
|
||||||
|
|
||||||
dynix = import ./default.nix { inherit pkgs qpkgs; };
|
dynix = import ./default.nix { inherit pkgs qpkgs; };
|
||||||
extraVersions = lib.mapAttrs' (stdenvName: value: {
|
extraVersions = lib.mapAttrs' (stdenvName: value: {
|
||||||
|
|
@ -33,7 +27,7 @@
|
||||||
inherit value;
|
inherit value;
|
||||||
}) dynix.byStdenv;
|
}) dynix.byStdenv;
|
||||||
|
|
||||||
devShell = import ./shell.nix { inherit pkgs qpkgs dynix fenixLib; };
|
devShell = import ./shell.nix { inherit pkgs qpkgs dynix; };
|
||||||
extraDevShells = lib.mapAttrs' (stdenvName: value: {
|
extraDevShells = lib.mapAttrs' (stdenvName: value: {
|
||||||
name = "${stdenvName}-dynix";
|
name = "${stdenvName}-dynix";
|
||||||
inherit value;
|
inherit value;
|
||||||
|
|
|
||||||
21
modules/dynamic-overrides.nix
Normal file
21
modules/dynamic-overrides.nix
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Managed by dynix.
|
||||||
|
{ lib, ... }:
|
||||||
|
|
||||||
|
lib.mkMerge [
|
||||||
|
{
|
||||||
|
services.gotosocial = {
|
||||||
|
enable = true;
|
||||||
|
setupPostgresqlDB = true;
|
||||||
|
settings = {
|
||||||
|
application-name = "example!";
|
||||||
|
host = "yuki.local";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
services.gotosocial.settings.application-name = lib.mkOverride 99 "removed herobrine";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
services.gotosocial.settings.application-name = lib.mkOverride 98 "reädded herobrine";
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -1,5 +1,138 @@
|
||||||
{ ... }:
|
{ pkgs, lib, config, options, ... }:
|
||||||
|
let
|
||||||
|
inherit (lib.options)
|
||||||
|
mkOption
|
||||||
|
showOption
|
||||||
|
;
|
||||||
|
t = lib.types;
|
||||||
|
|
||||||
|
inherit (import ./lib.nix { inherit lib; })
|
||||||
|
typeCheck
|
||||||
|
convenientAttrPath
|
||||||
|
concatFoldl
|
||||||
|
recUpdateFoldl
|
||||||
|
recUpdateFoldlAttrs
|
||||||
|
;
|
||||||
|
|
||||||
|
evalNixos = import (pkgs.path + "/nixos");
|
||||||
|
|
||||||
|
opts = options.dynamicism;
|
||||||
|
|
||||||
|
subOpts = lib.mapAttrs (_: metaAttr: metaAttr.configuration.options) options.dynamicism.for.valueMeta.attrs;
|
||||||
|
settingsFormat = pkgs.formats.yaml { };
|
||||||
|
|
||||||
|
finalSettingsFor = { ... }@submod: recUpdateFoldl (optPath:
|
||||||
|
lib.setAttrByPath optPath (lib.getAttrFromPath optPath config)
|
||||||
|
) submod.source-options;
|
||||||
|
|
||||||
|
ourAssertions = lib.concatAttrValues {
|
||||||
|
unitsExist = subOpts
|
||||||
|
|> lib.attrValues
|
||||||
|
|> concatFoldl (submod: submod.systemd-services-updated.value
|
||||||
|
|> lib.map (unit: {
|
||||||
|
assertion = config.systemd.units.${unit}.enable or false;
|
||||||
|
message = ''
|
||||||
|
${showOption submod.systemd-services-updated.loc}' specified non-existent unit '${unit}'
|
||||||
|
'';
|
||||||
|
})
|
||||||
|
|> lib.optionals submod.enable.value
|
||||||
|
);
|
||||||
|
|
||||||
|
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
|
||||||
{
|
{
|
||||||
imports = [ ./dynamicism.nix ];
|
#
|
||||||
|
# Interface.
|
||||||
|
#
|
||||||
|
options.dynamicism = {
|
||||||
|
for = mkOption {
|
||||||
|
type = t.attrsOf (t.submoduleWith {
|
||||||
|
modules = [ ./submodule.nix ];
|
||||||
|
shorthandOnlyDefinesConfig = false;
|
||||||
|
specialArgs = { inherit pkgs; };
|
||||||
|
});
|
||||||
|
default = { };
|
||||||
|
};
|
||||||
|
|
||||||
|
finalSettings = mkOption {
|
||||||
|
type = t.attrsOf t.raw;
|
||||||
|
internal = true;
|
||||||
|
readOnly = true;
|
||||||
|
description = ''
|
||||||
|
Attrset of each `source-options` tree to their actual values.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
doChange = mkOption {
|
||||||
|
type = t.functionTo t.pathInStore;
|
||||||
|
readOnly = true;
|
||||||
|
description = ''
|
||||||
|
The function to call to Do The Thing.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Assertions.
|
||||||
|
config.assertions = ourAssertions;
|
||||||
|
|
||||||
|
#
|
||||||
|
# Generic implementation.
|
||||||
|
#
|
||||||
|
config.dynamicism.doChange = {
|
||||||
|
option,
|
||||||
|
value,
|
||||||
|
configuration ? builtins.getEnv "NIXOS_CONFIG",
|
||||||
|
}: let
|
||||||
|
loc = opts.doChange.loc ++ [ "(function argument)" "value" ];
|
||||||
|
option' = typeCheck loc convenientAttrPath option;
|
||||||
|
nixosAfter = evalNixos {
|
||||||
|
configuration = { config, ... }: {
|
||||||
|
imports = [
|
||||||
|
configuration
|
||||||
|
(lib.setAttrByPath option' (lib.mkOverride (-999) value))
|
||||||
|
];
|
||||||
|
|
||||||
|
environment.systemPackages = [
|
||||||
|
config.dynamicism.for.gotosocial.activate
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
allActivations = lib.mapAttrsToList (name: submod: submod.activate) config.dynamicism.for;
|
||||||
|
allActivationScripts = pkgs.writeShellApplication {
|
||||||
|
name = "dynamicism-activate";
|
||||||
|
runtimeInputs = allActivations;
|
||||||
|
text = nixosAfter.config.dynamicism.for
|
||||||
|
|> lib.mapAttrsToList (name: submod: ''
|
||||||
|
echo "Activating dynamicism for ${name}"
|
||||||
|
${lib.getExe submod.activate}
|
||||||
|
'')
|
||||||
|
|> lib.concatStringsSep "\n";
|
||||||
|
};
|
||||||
|
in allActivationScripts;
|
||||||
|
|
||||||
|
config.dynamicism.finalSettings = lib.asserts.checkAssertWarn ourAssertions [ ] (
|
||||||
|
recUpdateFoldlAttrs (name: { ... }@submod: finalSettingsFor submod) config.dynamicism.for
|
||||||
|
);
|
||||||
|
|
||||||
|
# Implementations.
|
||||||
|
config.dynamicism.for.gotosocial = let
|
||||||
|
cfg = config.dynamicism.for.gotosocial;
|
||||||
|
in {
|
||||||
|
source-options = [
|
||||||
|
"services.gotosocial.settings"
|
||||||
|
];
|
||||||
|
|
||||||
|
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
|
||||||
|
'';
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
{ pkgs, lib, config, ... }:
|
|
||||||
let
|
|
||||||
cfg = config.services.distccd;
|
|
||||||
|
|
||||||
cliArgs = lib.cli.toCommandLineShellGNU { explicitBool = false; } {
|
|
||||||
no-detach = true;
|
|
||||||
daemon = true;
|
|
||||||
enable-tcp-insecure = true;
|
|
||||||
port = cfg.port;
|
|
||||||
|
|
||||||
# Nulls are handled automatically.
|
|
||||||
job-lifetime = cfg.jobTimeout;
|
|
||||||
log-level = cfg.logLevel;
|
|
||||||
jobs = cfg.maxJobs;
|
|
||||||
nice = cfg.nice;
|
|
||||||
stats = cfg.stats.enable;
|
|
||||||
stats-port = if cfg.stats.enable then cfg.stats.port else null;
|
|
||||||
zeroconf = cfg.zeroconf;
|
|
||||||
allow = cfg.allowedClients;
|
|
||||||
};
|
|
||||||
|
|
||||||
startDistccd = pkgs.writeShellApplication {
|
|
||||||
name = "start-distccd";
|
|
||||||
runtimeInputs = [ pkgs.distccMasquerade ];
|
|
||||||
|
|
||||||
text = ''
|
|
||||||
${lib.getExe' cfg.package "distccd"} \
|
|
||||||
${cliArgs}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
dynamicism.for.distccd = {
|
|
||||||
source-options = [
|
|
||||||
"services.distccd.jobTimeout"
|
|
||||||
"services.distccd.logLevel"
|
|
||||||
"services.distccd.maxJobs"
|
|
||||||
"services.distccd.nice"
|
|
||||||
];
|
|
||||||
unitDropins."distccd.service" = pkgs.writeTextFile {
|
|
||||||
name = "distccd-override.conf";
|
|
||||||
text = ''
|
|
||||||
[Service]
|
|
||||||
ExecStart=
|
|
||||||
ExecStart=${lib.getExe startDistccd}
|
|
||||||
'';
|
|
||||||
passthru.startScript = startDistccd;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,213 +0,0 @@
|
||||||
{ pkgs, lib, config, options, ... }:
|
|
||||||
let
|
|
||||||
inherit (lib.options)
|
|
||||||
mkOption
|
|
||||||
showOption
|
|
||||||
;
|
|
||||||
inherit (lib.asserts)
|
|
||||||
checkAssertWarn
|
|
||||||
;
|
|
||||||
t = lib.types;
|
|
||||||
|
|
||||||
inherit (import ./lib.nix { inherit lib; })
|
|
||||||
typeCheck
|
|
||||||
convenientAttrPath
|
|
||||||
executablePathInStore
|
|
||||||
concatFoldl
|
|
||||||
recUpdateFoldl
|
|
||||||
recUpdateFoldlAttrs
|
|
||||||
;
|
|
||||||
|
|
||||||
evalNixos = import (pkgs.path + "/nixos");
|
|
||||||
|
|
||||||
opts = options.dynamicism;
|
|
||||||
|
|
||||||
subOpts = lib.mapAttrs (_: metaAttr: metaAttr.configuration.options) options.dynamicism.for.valueMeta.attrs;
|
|
||||||
|
|
||||||
seqTrue = v: lib.seq v true;
|
|
||||||
|
|
||||||
finalSettingsFor = { ... }@submod: recUpdateFoldl (optPath:
|
|
||||||
lib.setAttrByPath optPath (lib.getAttrFromPath optPath config)
|
|
||||||
) submod.source-options;
|
|
||||||
|
|
||||||
ourAssertions = lib.concatAttrValues {
|
|
||||||
unitsExist = subOpts
|
|
||||||
|> lib.attrValues
|
|
||||||
|> concatFoldl (submod: submod.systemd-services-updated.value
|
|
||||||
|> lib.map (unit: {
|
|
||||||
assertion = config.systemd.units.${unit}.enable or false;
|
|
||||||
message = ''
|
|
||||||
${showOption submod.systemd-services-updated.loc}' specified non-existent unit '${unit}'
|
|
||||||
'';
|
|
||||||
})
|
|
||||||
|> lib.optionals submod.enable.value
|
|
||||||
);
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
|
||||||
#
|
|
||||||
# Interface.
|
|
||||||
#
|
|
||||||
options.dynamicism = {
|
|
||||||
for = mkOption {
|
|
||||||
type = t.attrsOf (t.submoduleWith {
|
|
||||||
modules = [ ./submodule.nix ];
|
|
||||||
shorthandOnlyDefinesConfig = false;
|
|
||||||
specialArgs = {
|
|
||||||
host = { inherit pkgs options config; };
|
|
||||||
};
|
|
||||||
});
|
|
||||||
default = { };
|
|
||||||
};
|
|
||||||
|
|
||||||
finalEnabledSubmodules = mkOption {
|
|
||||||
type = options.dynamicism.for.type;
|
|
||||||
internal = true;
|
|
||||||
readOnly = true;
|
|
||||||
default = lib.filterAttrs (lib.const (lib.getAttr "enable")) config.dynamicism.for;
|
|
||||||
};
|
|
||||||
|
|
||||||
finalSettings = mkOption {
|
|
||||||
type = t.attrsOf t.raw;
|
|
||||||
internal = true;
|
|
||||||
readOnly = true;
|
|
||||||
description = ''
|
|
||||||
Attrset of each `source-options` tree to their actual values.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
finalActivationScript = mkOption {
|
|
||||||
type = executablePathInStore;
|
|
||||||
internal = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
applyDynamicConfiguration = mkOption {
|
|
||||||
#type = t.functionTo t.pathInStore;
|
|
||||||
type = t.functionTo t.raw;
|
|
||||||
readOnly = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Assertions.
|
|
||||||
config.assertions = ourAssertions;
|
|
||||||
|
|
||||||
#
|
|
||||||
# Generic implementation.
|
|
||||||
#
|
|
||||||
|
|
||||||
config.system.activationScripts = config.dynamicism.for
|
|
||||||
|> lib.filterAttrs (lib.const (lib.getAttr "enable"))
|
|
||||||
|> lib.mapAttrs' (name: submod: let
|
|
||||||
forUnit = unitName: assert lib.isString unitName; let
|
|
||||||
dropinDir = "/run/systemd/system/${unitName}.d";
|
|
||||||
systemctl = lib.getExe' pkgs.systemd "systemctl";
|
|
||||||
in ''
|
|
||||||
if [[ -d "${dropinDir}" ]]; then
|
|
||||||
echo "Removing files in "${dropinDir}"
|
|
||||||
echo "Removing files in "${dropinDir}" >&2
|
|
||||||
|
|
||||||
rm -rvf "${dropinDir}/"*
|
|
||||||
rmdir "${dropinDir}"
|
|
||||||
|
|
||||||
${systemctl} daemon-reload
|
|
||||||
${systemctl} try-reload-or-restart "${unitName}"
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
|
|
||||||
in {
|
|
||||||
name = "dynix-reset-dynamicism-for-${name}";
|
|
||||||
value.deps = [ "etc" "stdio" "specialfs" "binsh" "usrbinenv" "var" "udevd" ];
|
|
||||||
value.text = ''
|
|
||||||
set -x
|
|
||||||
echo "Removing existing dynamic overrides for ${name}"
|
|
||||||
echo "Removing existing dynamic overrides for ${name}" >&2
|
|
||||||
|
|
||||||
${submod.systemd-services-updated |> lib.map forUnit |> lib.concatStringsSep "\n"}
|
|
||||||
|
|
||||||
set +x
|
|
||||||
'';
|
|
||||||
});
|
|
||||||
|
|
||||||
config.dynamicism = {
|
|
||||||
|
|
||||||
applyDynamicConfiguration = {
|
|
||||||
baseConfiguration ? builtins.getEnv "NIXOS_CONFIG",
|
|
||||||
newConfiguration ? (lib.filesystem.dirOf baseConfiguration) + "/dynamic.nix",
|
|
||||||
}: let
|
|
||||||
locFor = appendage: lib.concatLists [
|
|
||||||
opts.applyDynamicConfiguration.loc
|
|
||||||
[ "(function argument)" ]
|
|
||||||
(lib.toList appendage)
|
|
||||||
];
|
|
||||||
in
|
|
||||||
assert seqTrue (typeCheck (locFor "baseConfiguration") t.deferredModule baseConfiguration);
|
|
||||||
assert seqTrue (typeCheck (locFor "newConfiguration") t.deferredModule newConfiguration);
|
|
||||||
let
|
|
||||||
|
|
||||||
_file = "«inline module in ${showOption opts.applyDynamicConfiguration.loc}»";
|
|
||||||
|
|
||||||
nixosBefore = evalNixos {
|
|
||||||
configuration = { ... }: {
|
|
||||||
inherit _file;
|
|
||||||
imports = [ baseConfiguration ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
nixosAfter = evalNixos {
|
|
||||||
configuration = { ... }: {
|
|
||||||
inherit _file;
|
|
||||||
imports = [ baseConfiguration newConfiguration ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
submodulesChanged = nixosAfter.config.dynamicism.for
|
|
||||||
|> lib.filterAttrs (lib.const (lib.getAttr "enable"))
|
|
||||||
|> lib.filterAttrs (submodName: _:
|
|
||||||
nixosBefore.config.dynamicism.for.${submodName}.finalSettings
|
|
||||||
!=
|
|
||||||
nixosAfter.config.dynamicism.for.${submodName}.finalSettings
|
|
||||||
);
|
|
||||||
|
|
||||||
runForSubmodCalled = name: ''
|
|
||||||
echo "Activating dynamic configuration for ${name}"
|
|
||||||
${lib.getExe nixosAfter.config.dynamicism.for.${name}.activate}
|
|
||||||
'';
|
|
||||||
|
|
||||||
runForChanged = submodulesChanged
|
|
||||||
|> lib.mapAttrsToList (name: _: runForSubmodCalled name)
|
|
||||||
|> lib.concatStringsSep "\n";
|
|
||||||
|
|
||||||
in pkgs.writeShellApplication {
|
|
||||||
name = "dynamicism-activate";
|
|
||||||
text = runForChanged;
|
|
||||||
passthru.configuration = nixosAfter;
|
|
||||||
};
|
|
||||||
|
|
||||||
finalActivationScript = pkgs.writeShellApplication {
|
|
||||||
name = "dynamicism-activate-all";
|
|
||||||
text = config.dynamicism.for
|
|
||||||
|> lib.filterAttrs (lib.const (lib.getAttr "enable"))
|
|
||||||
|> lib.mapAttrsToList (name: submod: ''
|
|
||||||
echo "Activating dynamic configuration for ${name}"
|
|
||||||
${lib.getExe submod.activate}
|
|
||||||
'')
|
|
||||||
|> lib.concatStringsSep "\n";
|
|
||||||
};
|
|
||||||
|
|
||||||
finalSettings = config.dynamicism.for
|
|
||||||
|> recUpdateFoldlAttrs (name: { ... }@submod: finalSettingsFor submod)
|
|
||||||
|> checkAssertWarn ourAssertions [ ];
|
|
||||||
};
|
|
||||||
|
|
||||||
# Implementations.
|
|
||||||
imports = [
|
|
||||||
./gotosocial.nix
|
|
||||||
./harmonia.nix
|
|
||||||
./distccd.nix
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
{ pkgs, lib, config, ... }:
|
|
||||||
let
|
|
||||||
settingsFormat = pkgs.formats.yaml { };
|
|
||||||
configFile = settingsFormat.generate "gotosocial-override.yml" config.services.gotosocial.settings;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
dynamicism.for.gotosocial = {
|
|
||||||
source-options = [ "services.gotosocial.settings" ];
|
|
||||||
|
|
||||||
unitDropins."gotosocial.service" = pkgs.writeTextFile {
|
|
||||||
name = "gotosocial-override.conf";
|
|
||||||
text = ''
|
|
||||||
[Service]
|
|
||||||
ExecStart=
|
|
||||||
ExecStart=${lib.getExe' pkgs.gotosocial "gotosocial"} --config-path ${configFile} server start
|
|
||||||
'';
|
|
||||||
passthru = { inherit configFile; };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
{ pkgs, lib, config, ... }:
|
|
||||||
let
|
|
||||||
cfg = config.dynamicism.for.harmonia;
|
|
||||||
settingsFormat = pkgs.formats.toml { };
|
|
||||||
|
|
||||||
# FIXME: referring to config.dynamicism.finalSettings.* in here
|
|
||||||
# makes lib.checkAssertWarn in the generic module cause infinite recursion.
|
|
||||||
finalSettings = config.services.harmonia.settings;
|
|
||||||
|
|
||||||
configFile = settingsFormat.generate "harmonia-override.toml" finalSettings;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
dynamicism.for.harmonia = {
|
|
||||||
source-options = [ "services.harmonia.settings" ];
|
|
||||||
|
|
||||||
unitDropins."harmonia.service" = pkgs.writeTextFile {
|
|
||||||
name = "harmonia-override.conf";
|
|
||||||
text = ''
|
|
||||||
[Service]
|
|
||||||
Environment=CONFIG_FILE=${configFile}
|
|
||||||
'';
|
|
||||||
|
|
||||||
passthru = { inherit configFile; };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -4,12 +4,12 @@
|
||||||
t = lib.types;
|
t = lib.types;
|
||||||
in lib.fix (self: {
|
in lib.fix (self: {
|
||||||
/** Perform module-system type checking and resolving on a single option value. */
|
/** Perform module-system type checking and resolving on a single option value. */
|
||||||
typeCheck = loc: optionType: value:
|
typeCheck = loc: option: value:
|
||||||
assert lib.isOptionType optionType;
|
assert lib.isOptionType option;
|
||||||
assert lib.isList loc;
|
assert lib.isList loc;
|
||||||
assert lib.all lib.isString loc;
|
assert lib.all lib.isString loc;
|
||||||
let
|
let
|
||||||
merged = lib.modules.mergeDefinitions loc optionType [ {
|
merged = lib.modules.mergeDefinitions loc option [ {
|
||||||
inherit value;
|
inherit value;
|
||||||
file = "«inline»";
|
file = "«inline»";
|
||||||
} ];
|
} ];
|
||||||
|
|
@ -18,19 +18,6 @@ in lib.fix (self: {
|
||||||
/** 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 = lib.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
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
concatFoldl = f: list: lib.foldl' (acc: value: acc ++ (f value)) [ ] list;
|
concatFoldl = f: list: lib.foldl' (acc: value: acc ++ (f value)) [ ] list;
|
||||||
recUpdateFoldl = f: list: lib.foldl' (acc: value: lib.recursiveUpdate acc (f value)) { } list;
|
recUpdateFoldl = f: list: lib.foldl' (acc: value: lib.recursiveUpdate acc (f value)) { } list;
|
||||||
recUpdateFoldlAttrs = f: attrs: lib.foldlAttrs (acc: name: value: lib.recursiveUpdate acc (f name value)) { } attrs;
|
recUpdateFoldlAttrs = f: attrs: lib.foldlAttrs (acc: name: value: lib.recursiveUpdate acc (f name value)) { } attrs;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
name,
|
name,
|
||||||
|
pkgs,
|
||||||
lib,
|
lib,
|
||||||
config,
|
config,
|
||||||
host,
|
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
|
|
@ -14,15 +14,26 @@ let
|
||||||
mkEnableOption
|
mkEnableOption
|
||||||
literalExpression
|
literalExpression
|
||||||
;
|
;
|
||||||
|
inherit (lib.types)
|
||||||
|
mkOptionType
|
||||||
|
;
|
||||||
t = lib.types;
|
t = lib.types;
|
||||||
|
|
||||||
inherit (import ./lib.nix { inherit lib; })
|
/** Either a list of strings, or a dotted string that will be split. */
|
||||||
convenientAttrPath
|
convenientAttrPath = t.coercedTo t.str (lib.splitString ".") (t.listOf t.str);
|
||||||
executablePathInStore
|
|
||||||
recUpdateFoldl
|
|
||||||
;
|
|
||||||
|
|
||||||
pkgs = host.pkgs;
|
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 = {
|
||||||
|
|
@ -48,22 +59,13 @@ in
|
||||||
default = lib.attrNames config.unitDropins;
|
default = lib.attrNames config.unitDropins;
|
||||||
};
|
};
|
||||||
|
|
||||||
finalSettings = mkOption {
|
|
||||||
type = t.attrsOf t.raw;
|
|
||||||
internal = true;
|
|
||||||
default = recUpdateFoldl (optPath:
|
|
||||||
lib.setAttrByPath optPath (lib.getAttrFromPath optPath host.config)
|
|
||||||
) config.source-options;
|
|
||||||
};
|
|
||||||
|
|
||||||
configFile = mkOption {
|
configFile = mkOption {
|
||||||
type = t.nullOr t.pathInStore;
|
type = t.pathInStore;
|
||||||
internal = true;
|
internal = true;
|
||||||
default = null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
unitDropins = mkOption {
|
unitDropins = mkOption {
|
||||||
type = t.attrsOf t.package;
|
type = t.attrsOf t.pathInStore;
|
||||||
internal = true;
|
internal = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -80,19 +82,17 @@ in
|
||||||
text = let
|
text = let
|
||||||
doEdits = config.unitDropins
|
doEdits = config.unitDropins
|
||||||
|> lib.mapAttrsToList (service: dropin: ''
|
|> lib.mapAttrsToList (service: dropin: ''
|
||||||
cat "${dropin}" | systemctl edit "${service}" --runtime --drop=dynix-${dropin.name} --stdin
|
cat "${dropin}" | systemctl edit "${service}" --runtime --stdin
|
||||||
'')
|
'');
|
||||||
|> lib.concatStringsSep "\n";
|
|
||||||
doReloads = config.unitDropins
|
doReloads = config.unitDropins
|
||||||
|> lib.mapAttrsToList (service: _: ''
|
|> lib.mapAttrsToList (service: _: ''
|
||||||
systemctl reload-or-restart "${service}"
|
systemctl reload-or-restart "${service}"
|
||||||
'')
|
'');
|
||||||
|> lib.concatStringsSep "\n";
|
in [
|
||||||
in ''
|
doEdits
|
||||||
${doEdits}
|
doReloads
|
||||||
|
] |> lib.concatLists
|
||||||
${doReloads}
|
|> lib.concatStringsSep "\n";
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
{ pkgs, lib, config, ... }: let
|
|
||||||
cfg = config.dynamicism.for.tzupdate;
|
|
||||||
|
|
||||||
|
|
||||||
# FIXME: referring to config.dynamicism.finalSettings.* in here
|
|
||||||
# makes lib.checkAssertWarn in the generic module cause infinite recursion.
|
|
||||||
#finalSettings = config.dynamicism.finalSettings.tzupdate;
|
|
||||||
finalSettings = config.services.tzupdate.timer;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
dynamicism.for.tzupdate = {
|
|
||||||
source-options = [ "services.tzupdate.timer" ];
|
|
||||||
|
|
||||||
unitDropins."tzupdate.timer" = pkgs.writeText "tzupdate-timer-override.conf" ''
|
|
||||||
[Timer]
|
|
||||||
OnCalendar=
|
|
||||||
OnCalendar=${finalSettings.interval}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
}
|
|
||||||
4
modules/tests-common.nix
Normal file
4
modules/tests-common.nix
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
|
nix.package = pkgs.lixPackageSets.latest.lix;
|
||||||
|
}
|
||||||
76
modules/tests-main.py
Normal file
76
modules/tests-main.py
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
#import re
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import cast, TYPE_CHECKING
|
||||||
|
|
||||||
|
from test_driver.machine import Machine
|
||||||
|
from test_driver.errors import RequestedAssertionFailed
|
||||||
|
|
||||||
|
DEFAULT_NIX = "@DEFAULT_NIX@"
|
||||||
|
CONFIGURATION_NIX = "@CONFIGURATION_NIX@"
|
||||||
|
DYNAMICISM = "@DYNAMICISM@"
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
global machine
|
||||||
|
machine = cast(Machine, ...)
|
||||||
|
|
||||||
|
def run_log(machine: Machine, *commands: str, timeout: int | None = 60) -> str:
|
||||||
|
output = ""
|
||||||
|
for command in commands:
|
||||||
|
with machine.nested(f"must succeed: {command}"):
|
||||||
|
(status, out) = machine.execute(f"{command} | tee /dev/stderr", timeout=timeout)
|
||||||
|
if status != 0:
|
||||||
|
machine.log(f"output: {out}")
|
||||||
|
raise RequestedAssertionFailed(
|
||||||
|
f"command `{command}` failed (exit code {status})",
|
||||||
|
)
|
||||||
|
output += out
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
def get_config_file() -> Path:
|
||||||
|
machine.wait_for_unit("gotosocial.service")
|
||||||
|
gotosocial_pid = int(machine.get_unit_property("gotosocial.service", "MainPID"))
|
||||||
|
print(f"{gotosocial_pid=}")
|
||||||
|
|
||||||
|
cmdline = machine.succeed(f"cat /proc/{gotosocial_pid}/cmdline")
|
||||||
|
cmdline_args = cmdline.split("\0")
|
||||||
|
|
||||||
|
config_file_idx = cmdline_args.index("--config-path") + 1
|
||||||
|
config_file = Path(cmdline_args[config_file_idx])
|
||||||
|
|
||||||
|
machine.log(f"copying from VM: {config_file=}")
|
||||||
|
machine.copy_from_vm(config_file.as_posix())
|
||||||
|
|
||||||
|
return machine.out_dir / config_file.name
|
||||||
|
|
||||||
|
|
||||||
|
machine.wait_for_unit("default.target")
|
||||||
|
assert "lix" in run_log(machine, "nix --version").lower()
|
||||||
|
|
||||||
|
print(f"{CONFIGURATION_NIX=}")
|
||||||
|
machine.succeed("mkdir -vp /etc/nixos")
|
||||||
|
machine.copy_from_host(CONFIGURATION_NIX, "/etc/nixos")
|
||||||
|
machine.copy_from_host(DYNAMICISM, "/etc/nixos")
|
||||||
|
|
||||||
|
run_log(machine, f"nix build --log-format multiline-with-logs --impure -E 'import <nixpkgs/nixos> {{ configuration = {CONFIGURATION_NIX}; }}'")
|
||||||
|
run_log(machine, f"nixos-rebuild switch --file {CONFIGURATION_NIX} --verbose --print-build-logs")
|
||||||
|
|
||||||
|
config_file_local = get_config_file()
|
||||||
|
machine.log(f"opening copied file: {config_file_local=}")
|
||||||
|
with open(config_file_local, "r") as f:
|
||||||
|
text = f.read()
|
||||||
|
lines = text.splitlines()
|
||||||
|
application_name = next(line for line in lines if line.startswith("application-name:"))
|
||||||
|
assert "gotosocial-for-machine" in application_name, f"'gotosocial-for-machine' should be in {application_name=}"
|
||||||
|
|
||||||
|
print(f"{DEFAULT_NIX=}")
|
||||||
|
|
||||||
|
run_log(machine, "eza -lah --color=always --group-directories-first --tree /etc/")
|
||||||
|
|
||||||
|
#exec_start = machine.succeed("systemctl show gotosocial.service --property=ExecStart --value")
|
||||||
|
#exec_start = machine.succeed("systemctl show gotosocial.service --property=ExecStart --value")
|
||||||
|
|
||||||
|
#service_text = machine.succeed("systemctl show gotosocial.service")
|
||||||
|
#service_props = dict(line.split("=", maxsplit=1) for line in service_text.splitlines())
|
||||||
|
#exec_start = service_props['ExecStart']
|
||||||
|
#print(f"{exec_start=}")
|
||||||
52
modules/tests.nix
Normal file
52
modules/tests.nix
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
testers,
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
inherit (testers) runNixOSTest;
|
||||||
|
in lib.makeScope lib.callPackageWith (self: {
|
||||||
|
main = runNixOSTest {
|
||||||
|
name = "nixos-test-dynamicism-main";
|
||||||
|
|
||||||
|
defaults = { pkgs, ... }: {
|
||||||
|
imports = [ ./dynamicism ];
|
||||||
|
|
||||||
|
nix = {
|
||||||
|
package = pkgs.lixPackageSets.latest.lix;
|
||||||
|
settings.experimental-features = [ "nix-command" ];
|
||||||
|
nixPath = [ "nixpkgs=${pkgs.path}" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
environment.shellAliases = {
|
||||||
|
ls = "eza --long --header --group --group-directories-first --classify --binary";
|
||||||
|
};
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
eza
|
||||||
|
fd
|
||||||
|
ripgrep
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
nodes.machine = { name, ... }: {
|
||||||
|
#services.gotosocial = {
|
||||||
|
# enable = true;
|
||||||
|
# setupPostgresqlDB = true;
|
||||||
|
# settings = {
|
||||||
|
# application-name = "gotosocial-for-${name}";
|
||||||
|
# host = "${name}.local";
|
||||||
|
# };
|
||||||
|
#};
|
||||||
|
#
|
||||||
|
#dynamicism.for.gotosocial.enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# What's a little IFD between friends?
|
||||||
|
testScript = pkgs.replaceVars ./tests-main.py {
|
||||||
|
DEFAULT_NIX = ../default.nix;
|
||||||
|
CONFIGURATION_NIX = ./tests-configuration.nix;
|
||||||
|
DYNAMICISM = ./dynamicism;
|
||||||
|
}
|
||||||
|
|> builtins.readFile;
|
||||||
|
};
|
||||||
|
})
|
||||||
164
package.nix
164
package.nix
|
|
@ -1,8 +1,6 @@
|
||||||
{
|
{
|
||||||
lib,
|
lib,
|
||||||
clangStdenv,
|
stdenv,
|
||||||
callPackage,
|
|
||||||
linkFarm,
|
|
||||||
rustHooks,
|
rustHooks,
|
||||||
rustPackages,
|
rustPackages,
|
||||||
versionCheckHook,
|
versionCheckHook,
|
||||||
|
|
@ -10,151 +8,79 @@
|
||||||
rustPlatform,
|
rustPlatform,
|
||||||
cargo,
|
cargo,
|
||||||
}: let
|
}: let
|
||||||
stdenv = clangStdenv;
|
|
||||||
cargoToml = lib.importTOML ./Cargo.toml;
|
cargoToml = lib.importTOML ./Cargo.toml;
|
||||||
cargoPackage = cargoToml.package;
|
cargoPackage = cargoToml.package;
|
||||||
in stdenv.mkDerivation (finalAttrs: let
|
|
||||||
self = finalAttrs.finalPackage;
|
in stdenv.mkDerivation (self: {
|
||||||
in {
|
|
||||||
pname = cargoPackage.name;
|
pname = cargoPackage.name;
|
||||||
version = cargoPackage.version;
|
version = cargoPackage.version;
|
||||||
|
|
||||||
strictDeps = true;
|
strictDeps = true;
|
||||||
__structuredAttrs = true;
|
__structuredAttrs = true;
|
||||||
|
|
||||||
outputs = [ "out" "modules" ];
|
|
||||||
|
|
||||||
doCheck = true;
|
doCheck = true;
|
||||||
doInstallCheck = true;
|
doInstallCheck = true;
|
||||||
|
|
||||||
phases = [ "unpackPhase" "patchPhase" "installPhase" ];
|
src = lib.fileset.toSource {
|
||||||
|
root = ./.;
|
||||||
src = linkFarm "dynix-source" {
|
fileset = lib.fileset.unions [
|
||||||
inherit (self) dynixCommand dynixModules;
|
./Cargo.toml
|
||||||
|
./Cargo.lock
|
||||||
|
./src
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
installPhase = lib.dedent ''
|
cargoDeps = rustPlatform.importCargoLock {
|
||||||
runHook preInstall
|
lockFile = ./Cargo.lock;
|
||||||
|
};
|
||||||
|
|
||||||
mkdir -p "$out"
|
versionCheckProgramArg = "--version";
|
||||||
cp -r --reflink=auto "$dynixCommand/"* "$out/"
|
|
||||||
mkdir -p "$modules"
|
|
||||||
cp -r --reflink=auto "$dynixModules/"* "$modules/"
|
|
||||||
|
|
||||||
runHook postInstall
|
nativeBuildInputs = rustHooks.asList ++ [
|
||||||
'';
|
cargo
|
||||||
|
];
|
||||||
|
|
||||||
#
|
nativeInstallCheckInputs = [
|
||||||
# SUB-DERIVATONS
|
versionCheckHook
|
||||||
#
|
];
|
||||||
|
|
||||||
dynixCommand = stdenv.mkDerivation (finalAttrs: let
|
|
||||||
commandSelf = finalAttrs.finalPackage;
|
|
||||||
in {
|
|
||||||
pname = "${self.pname}-command";
|
|
||||||
inherit (self) version;
|
|
||||||
inherit (self) strictDeps __structuredAttrs;
|
|
||||||
inherit (self) doCheck doInstallCheck;
|
|
||||||
|
|
||||||
src = lib.fileset.toSource {
|
|
||||||
root = ./.;
|
|
||||||
fileset = lib.fileset.unions [
|
|
||||||
./Cargo.toml
|
|
||||||
./Cargo.lock
|
|
||||||
./src
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
cargoDeps = rustPlatform.importCargoLock {
|
|
||||||
lockFile = ./Cargo.lock;
|
|
||||||
};
|
|
||||||
|
|
||||||
nativeBuildInputs = rustHooks.asList ++ [
|
|
||||||
cargo
|
|
||||||
];
|
|
||||||
|
|
||||||
nativeInstallCheckInputs = [
|
|
||||||
versionCheckHook
|
|
||||||
];
|
|
||||||
|
|
||||||
meta = {
|
|
||||||
mainProgram = "dynix";
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
dynixModules = stdenv.mkDerivation (finalAttrs: let
|
|
||||||
modulesSelf = finalAttrs.finalPackage;
|
|
||||||
in {
|
|
||||||
pname = "${self.pname}-modules";
|
|
||||||
inherit (self) version;
|
|
||||||
inherit (self) strictDeps __structuredAttrs;
|
|
||||||
inherit (self) doCheck doInstallCheck;
|
|
||||||
|
|
||||||
src = lib.fileset.toSource {
|
|
||||||
root = ./modules/dynamicism;
|
|
||||||
fileset = lib.fileset.unions [
|
|
||||||
./modules/dynamicism
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
phases = [ "unpackPhase" "patchPhase" "installPhase" ];
|
|
||||||
|
|
||||||
modulesOut = "${placeholder "out"}/share/nixos/modules/dynix";
|
|
||||||
|
|
||||||
installPhase = lib.dedent ''
|
|
||||||
runHook preInstall
|
|
||||||
|
|
||||||
mkdir -p "$modulesOut"
|
|
||||||
cp -r "$src/"* "$modulesOut/"
|
|
||||||
|
|
||||||
runHook postInstall
|
|
||||||
'';
|
|
||||||
});
|
|
||||||
|
|
||||||
#
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
|
|
||||||
passthru.mkDevShell = {
|
passthru.mkDevShell = {
|
||||||
path,
|
|
||||||
mkShell,
|
mkShell,
|
||||||
python3Packages,
|
|
||||||
fenixToolchain,
|
|
||||||
}: let
|
}: let
|
||||||
mkShell' = mkShell.override { inherit stdenv; };
|
mkShell' = mkShell.override { stdenv = stdenv; };
|
||||||
pyEnv = python3Packages.python.withPackages (p: [ p.beartype ]);
|
|
||||||
in mkShell' {
|
in mkShell' {
|
||||||
name = "devshell-for-${self.name}";
|
name = "${self.pname}-devshell-${self.version}";
|
||||||
inputsFrom = [ self ];
|
inputsFrom = [ self.finalPackage ];
|
||||||
packages = [
|
packages = [
|
||||||
pyEnv
|
rustPackages.rustc
|
||||||
stdenv.cc
|
rustPackages.rustfmt
|
||||||
fenixToolchain
|
|
||||||
];
|
];
|
||||||
env.PYTHONPATH = [
|
|
||||||
"${pyEnv}/${pyEnv.sitePackages}"
|
|
||||||
# Cursed.
|
|
||||||
"${path}/nixos/lib/test-driver/src"
|
|
||||||
] |> lib.concatStringsSep ":";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
passthru.modulesPath = self.modules + "/share/nixos/modules";
|
passthru.tests.clippy = self.finalPackage.overrideAttrs (prev: {
|
||||||
passthru.dynix = self.modulesPath + "/dynix";
|
pname = "${self.pname}-clippy";
|
||||||
|
|
||||||
passthru.tests = lib.fix (callPackage ./tests {
|
nativeCheckInputs = prev.nativeCheckInputs or [ ] ++ [
|
||||||
dynix = self;
|
rustPackages.clippy
|
||||||
}).packages;
|
];
|
||||||
|
|
||||||
passthru.allTests = linkFarm "dynix-all-tests" self.tests;
|
dontConfigure = true;
|
||||||
|
dontBuild = true;
|
||||||
|
doCheck = true;
|
||||||
|
dontFixup = true;
|
||||||
|
dontInstallCheck = true;
|
||||||
|
|
||||||
|
checkPhase = lib.trim ''
|
||||||
|
echo "cargoClippyPhase()"
|
||||||
|
cargo clippy --all-targets --profile "$cargoCheckType" -- --deny warnings
|
||||||
|
'';
|
||||||
|
|
||||||
|
installPhase = lib.trim ''
|
||||||
|
touch "$out"
|
||||||
|
'';
|
||||||
|
});
|
||||||
|
|
||||||
meta = {
|
meta = {
|
||||||
longDescription = lib.dedent ''
|
|
||||||
Default output contains the Rust binary.
|
|
||||||
The `modules` output contains the modules prefixed under `/share/nixos/modules/dynix`.
|
|
||||||
The `dynix` passthru attr is a shortcut for the modules output *with* the modules prefix,
|
|
||||||
and thus `dynix.dynix` can be passed directly to `imports = [`.
|
|
||||||
'';
|
|
||||||
mainProgram = "dynix";
|
mainProgram = "dynix";
|
||||||
outputsToInstall = [ "out" "modules" ];
|
|
||||||
};
|
};
|
||||||
}))
|
}))
|
||||||
|
|
|
||||||
21
shell.nix
21
shell.nix
|
|
@ -1,26 +1,23 @@
|
||||||
{
|
{
|
||||||
pkgs ? import <nixpkgs> {
|
pkgs ? import <nixpkgs> { },
|
||||||
config = {
|
|
||||||
checkMeta = true;
|
|
||||||
allowAliases = false;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
qpkgs ? let
|
qpkgs ? let
|
||||||
src = fetchTarball "https://github.com/Qyriad/nur-packages/archive/main.tar.gz";
|
src = fetchTarball "https://github.com/Qyriad/nur-packages/archive/main.tar.gz";
|
||||||
in import src { inherit pkgs; },
|
in import src { inherit pkgs; },
|
||||||
dynix ? import ./default.nix { inherit pkgs qpkgs; },
|
dynix ? import ./default.nix { inherit pkgs qpkgs; },
|
||||||
fenixLib ? let
|
|
||||||
src = fetchTarball "https://github.com/nix-community/fenix/archive/main.tar.gz";
|
|
||||||
in import src { inherit pkgs; },
|
|
||||||
fenixToolchain ? fenixLib.latest.toolchain,
|
|
||||||
}: let
|
}: let
|
||||||
inherit (pkgs) lib;
|
inherit (pkgs) lib;
|
||||||
|
|
||||||
mkDevShell = dynix: qpkgs.callPackage dynix.mkDevShell { inherit fenixToolchain; };
|
mkDevShell = dynix: qpkgs.callPackage dynix.mkDevShell { };
|
||||||
devShell = mkDevShell dynix;
|
devShell = mkDevShell dynix;
|
||||||
|
|
||||||
byStdenv = lib.mapAttrs (lib.const mkDevShell) dynix.byStdenv;
|
byStdenv = lib.mapAttrs (lib.const mkDevShell) dynix.byStdenv;
|
||||||
|
|
||||||
in devShell.overrideAttrs (prev: {
|
in devShell.overrideAttrs (prev: lib.recursiveUpdate prev {
|
||||||
passthru = { inherit byStdenv; };
|
passthru = { inherit byStdenv; };
|
||||||
|
env.PYTHONPATH = [
|
||||||
|
"${pkgs.python3Packages.beartype}/${pkgs.python3.sitePackages}"
|
||||||
|
] |> lib.concatStringsSep ":";
|
||||||
|
packages = prev.packages or [ ] ++ [
|
||||||
|
pkgs.python3Packages.beartype
|
||||||
|
];
|
||||||
})
|
})
|
||||||
|
|
|
||||||
34
src/args.rs
34
src/args.rs
|
|
@ -1,7 +1,4 @@
|
||||||
use std::{
|
use std::sync::Arc;
|
||||||
env,
|
|
||||||
sync::{Arc, LazyLock},
|
|
||||||
};
|
|
||||||
|
|
||||||
use clap::ColorChoice;
|
use clap::ColorChoice;
|
||||||
|
|
||||||
|
|
@ -55,7 +52,6 @@ impl FromStr for NixOsOption {
|
||||||
pub struct AppendCmd {
|
pub struct AppendCmd {
|
||||||
#[arg(required = true)]
|
#[arg(required = true)]
|
||||||
pub name: Arc<str>,
|
pub name: Arc<str>,
|
||||||
|
|
||||||
#[arg(required = true)]
|
#[arg(required = true)]
|
||||||
pub value: Arc<str>,
|
pub value: Arc<str>,
|
||||||
}
|
}
|
||||||
|
|
@ -71,28 +67,6 @@ pub enum Subcommand {
|
||||||
Delta(DeltaCmd),
|
Delta(DeltaCmd),
|
||||||
}
|
}
|
||||||
|
|
||||||
static DEFAULT_PATH: LazyLock<Box<OsStr>> = LazyLock::new(|| {
|
|
||||||
// This has to be in a let binding to keep the storage around.
|
|
||||||
let nixos_config = env::var_os("NIXOS_CONFIG");
|
|
||||||
let nixos_config = nixos_config
|
|
||||||
.as_deref()
|
|
||||||
.map(Path::new)
|
|
||||||
.unwrap_or(Path::new("/etc/nixos/configuration.nix"));
|
|
||||||
|
|
||||||
nixos_config
|
|
||||||
.parent()
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
error!(
|
|
||||||
"Your $NIXOS_CONFIG value doesn't make sense: {}. Ignoring.",
|
|
||||||
nixos_config.display(),
|
|
||||||
);
|
|
||||||
Path::new("/etc/nixos")
|
|
||||||
})
|
|
||||||
.join("dynamic.nix")
|
|
||||||
.into_os_string()
|
|
||||||
.into_boxed_os_str()
|
|
||||||
});
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, clap::Parser)]
|
#[derive(Debug, Clone, PartialEq, clap::Parser)]
|
||||||
#[command(version, about, author)]
|
#[command(version, about, author)]
|
||||||
#[command(arg_required_else_help(true), args_override_self(true))]
|
#[command(arg_required_else_help(true), args_override_self(true))]
|
||||||
|
|
@ -101,10 +75,8 @@ pub struct Args {
|
||||||
#[arg(long, global(true), default_value = "auto")]
|
#[arg(long, global(true), default_value = "auto")]
|
||||||
pub color: ColorChoice,
|
pub color: ColorChoice,
|
||||||
|
|
||||||
/// The .nix file with dynamic overrides to modify.
|
// FIXME: default to /etc/configuration.nix, or something?
|
||||||
/// [default: $(dirname ${NIXOS_CONFIG-/etc/nixos/configuration.nix})/dynamic.nix]
|
#[arg(long, global(true), default_value = "./configuration.nix")]
|
||||||
#[arg(long, global(true), default_value = &**DEFAULT_PATH)]
|
|
||||||
#[arg(hide_default_value(true))]
|
|
||||||
pub file: Arc<OsStr>,
|
pub file: Arc<OsStr>,
|
||||||
|
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
|
|
|
||||||
110
src/lib.rs
110
src/lib.rs
|
|
@ -1,7 +1,4 @@
|
||||||
use std::{
|
use std::{iter, sync::Arc};
|
||||||
iter,
|
|
||||||
sync::{Arc, LazyLock},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub(crate) mod prelude {
|
pub(crate) mod prelude {
|
||||||
#![allow(unused_imports)]
|
#![allow(unused_imports)]
|
||||||
|
|
@ -44,37 +41,13 @@ pub mod line;
|
||||||
mod nixcmd;
|
mod nixcmd;
|
||||||
pub use line::Line;
|
pub use line::Line;
|
||||||
pub mod source;
|
pub mod source;
|
||||||
pub use source::{SourceFile, SourceLine};
|
pub use source::SourceLine;
|
||||||
|
|
||||||
#[cfg(all(not(feature = "regex-full"), not(feature = "regex-lite")))]
|
|
||||||
compile_error!("At least one of features `regex-full` or `regex-lite` must be used");
|
|
||||||
|
|
||||||
#[cfg(feature = "regex-full")]
|
|
||||||
use regex as _regex;
|
|
||||||
|
|
||||||
// Having both `regex-full` and `regex-lite` isn't an error; it's just wasteful.
|
|
||||||
#[cfg(not(feature = "regex-full"))]
|
|
||||||
use regex_lite as _regex;
|
|
||||||
|
|
||||||
use _regex::Regex;
|
|
||||||
|
|
||||||
use itertools::Itertools;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub const ASCII_WHITESPACE: &[char] = &['\t', '\n', '\x0C', '\r', ' '];
|
use crate::source::SourceFile;
|
||||||
|
|
||||||
/// Regex pattern to extract the priority in a `lib.mkOverride` call.
|
pub const ASCII_WHITESPACE: &[char] = &['\t', '\n', '\x0C', '\r', ' '];
|
||||||
static MK_OVERRIDE_RE: LazyLock<Regex> = LazyLock::new(|| {
|
|
||||||
// Named capture group: priority
|
|
||||||
// - Word boundary
|
|
||||||
// - Literal `mkOverride`
|
|
||||||
// - One or more whitespace characters
|
|
||||||
// - Literal open parenthesis
|
|
||||||
// - Named capture group "priority"
|
|
||||||
// - One or more of: digit characters, or literal `-`
|
|
||||||
// - Literal close parenthesis
|
|
||||||
Regex::new(r"(?-u)\bmkOverride\s+\((?<priority>[\d-]+)\)").unwrap()
|
|
||||||
});
|
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug")]
|
#[tracing::instrument(level = "debug")]
|
||||||
pub fn do_delta(args: Arc<Args>, delta_args: DeltaCmd) -> Result<(), BoxDynError> {
|
pub fn do_delta(args: Arc<Args>, delta_args: DeltaCmd) -> Result<(), BoxDynError> {
|
||||||
|
|
@ -92,15 +65,17 @@ pub fn do_append(args: Arc<Args>, append_args: AppendCmd) -> Result<(), BoxDynEr
|
||||||
filepath.to_path_buf()
|
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();
|
let mut opts = File::options();
|
||||||
opts.read(true)
|
opts.read(true)
|
||||||
.write(true)
|
.write(true)
|
||||||
.create(false)
|
.create(false)
|
||||||
.custom_flags(libc::O_CLOEXEC);
|
.custom_flags(libc::O_CLOEXEC);
|
||||||
|
let source_file = SourceFile::open_from(Arc::from(def_path), opts)?;
|
||||||
|
|
||||||
let source_file = SourceFile::open_from(Arc::from(filepath), opts)?;
|
let pri = get_highest_prio(&append_args.name, source_file.clone())?;
|
||||||
let pri = get_where(source_file.clone())?;
|
|
||||||
|
|
||||||
let new_pri = pri - 1;
|
let new_pri = pri - 1;
|
||||||
|
|
||||||
let new_pri_line = get_next_prio_line(
|
let new_pri_line = get_next_prio_line(
|
||||||
|
|
@ -110,7 +85,7 @@ pub fn do_append(args: Arc<Args>, append_args: AppendCmd) -> Result<(), BoxDynEr
|
||||||
append_args.value.into(),
|
append_args.value.into(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
debug!("new_pri_line={new_pri_line}");
|
eprintln!("new_pri_line={new_pri_line}");
|
||||||
|
|
||||||
write_next_prio(source_file, new_pri_line)?;
|
write_next_prio(source_file, new_pri_line)?;
|
||||||
|
|
||||||
|
|
@ -133,30 +108,35 @@ pub fn expr_for_configuration(source_file: &Path) -> OsString {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn maybe_extract_prio_from_line(line: &SourceLine) -> Option<i64> {
|
pub fn get_where(option_name: &str, configuration_nix: &Path) -> Result<Box<Path>, BoxDynError> {
|
||||||
MK_OVERRIDE_RE
|
let expr = expr_for_configuration(configuration_nix);
|
||||||
.captures(line.text_ref())
|
let attrpath = format!("options.{}.definitionsWithLocations", option_name);
|
||||||
.map(|caps| caps.name("priority").unwrap().as_str())
|
|
||||||
.map(|prio_str| {
|
let output = nixcmd::NixEvalExpr { expr, attrpath }
|
||||||
i64::from_str(prio_str).unwrap_or_else(|e| {
|
.into_command()
|
||||||
panic!(
|
.output_checked_utf8()?;
|
||||||
"lib.mkOverride called with non-integer {}: {}. Nix source code is wrong!\n{}",
|
let stdout = output.stdout();
|
||||||
prio_str, e, line,
|
|
||||||
);
|
let definitions: Box<[DefinitionWithLocation]> = serde_json::from_str(&stdout)?;
|
||||||
})
|
let last_location = definitions.into_iter().last().unwrap();
|
||||||
})
|
|
||||||
|
Ok(Box::from(last_location.file))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_where(dynamic_nix: SourceFile) -> Result<i64, BoxDynError> {
|
pub fn get_highest_prio(option_name: &str, source: SourceFile) -> Result<i64, BoxDynError> {
|
||||||
let lines = dynamic_nix.lines()?;
|
// Get the current highest priority.
|
||||||
let prio = lines
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(maybe_extract_prio_from_line)
|
|
||||||
.sorted_unstable()
|
|
||||||
.next() // Priorities with lower integer values are "stronger" priorities.
|
|
||||||
.unwrap_or(0);
|
|
||||||
|
|
||||||
Ok(prio)
|
let expr = expr_for_configuration(&source.path());
|
||||||
|
|
||||||
|
// Get the highest priority, and the file its defined in.
|
||||||
|
let attrpath = format!("options.{}.highestPrio", option_name);
|
||||||
|
let output = nixcmd::NixEvalExpr { expr, attrpath }
|
||||||
|
.into_command()
|
||||||
|
.output_checked_utf8()?;
|
||||||
|
let stdout = output.stdout();
|
||||||
|
let highest_prio = i64::from_str(stdout.trim())?;
|
||||||
|
|
||||||
|
Ok(highest_prio)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_next_prio_line(
|
pub fn get_next_prio_line(
|
||||||
|
|
@ -166,19 +146,15 @@ pub fn get_next_prio_line(
|
||||||
new_value: Arc<str>,
|
new_value: Arc<str>,
|
||||||
) -> Result<SourceLine, BoxDynError> {
|
) -> Result<SourceLine, BoxDynError> {
|
||||||
let source_lines = source.lines()?;
|
let source_lines = source.lines()?;
|
||||||
let penultimate = source_lines.get(source_lines.len() - 2);
|
let last_line = source_lines.last();
|
||||||
// FIXME: don't rely on whitespace lol
|
assert_eq!(last_line.map(SourceLine::text).as_deref(), Some("]"));
|
||||||
debug_assert_eq!(penultimate.map(SourceLine::text).as_deref(), Some(" ];"));
|
let last_line = last_line.unwrap();
|
||||||
let penultimate = penultimate.unwrap();
|
|
||||||
|
|
||||||
let new_generation = 0 - new_prio;
|
|
||||||
|
|
||||||
let new_line = SourceLine {
|
let new_line = SourceLine {
|
||||||
line: penultimate.line,
|
line: last_line.line,
|
||||||
path: source.path(),
|
path: source.path(),
|
||||||
text: Arc::from(format!(
|
text: Arc::from(format!(
|
||||||
" {} = lib.mkOverride ({}) ({}); # DYNIX GENERATION {}",
|
" {option_name} = lib.mkOverride ({new_prio}) ({new_value});",
|
||||||
option_name, new_prio, new_value, new_generation,
|
|
||||||
)),
|
)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -189,12 +165,12 @@ pub fn write_next_prio(mut source: SourceFile, new_line: SourceLine) -> Result<(
|
||||||
let new_mod_start = SourceLine {
|
let new_mod_start = SourceLine {
|
||||||
line: new_line.line.prev(),
|
line: new_line.line.prev(),
|
||||||
path: source.path(),
|
path: source.path(),
|
||||||
text: Arc::from(" {"),
|
text: Arc::from(" {"),
|
||||||
};
|
};
|
||||||
let new_mod_end = SourceLine {
|
let new_mod_end = SourceLine {
|
||||||
line: new_line.line.next(),
|
line: new_line.line.next(),
|
||||||
path: source.path(),
|
path: source.path(),
|
||||||
text: Arc::from(" }"),
|
text: Arc::from(" }"),
|
||||||
};
|
};
|
||||||
|
|
||||||
source.insert_lines(&[new_mod_start, new_line, new_mod_end])?;
|
source.insert_lines(&[new_mod_start, new_line, new_mod_end])?;
|
||||||
|
|
|
||||||
9
tests/basic/configuration-package.nix
Normal file
9
tests/basic/configuration-package.nix
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
runCommand,
|
||||||
|
}: runCommand "tests-basic-configuration-dot-nix" {
|
||||||
|
} ''
|
||||||
|
set -euo pipefail
|
||||||
|
mkdir -vp "$out/share/nixos"
|
||||||
|
cp -rv ${./configuration.nix} "$out/share/nixos/configuration.nix"
|
||||||
|
cp -rv ${../../modules/dynamicism} "$out/share/nixos/dynamicism"
|
||||||
|
''
|
||||||
|
|
@ -1,30 +1,28 @@
|
||||||
{ pkgs, lib, config, modulesPath, ... }:
|
{ pkgs, lib, config, modulesPath, ... }:
|
||||||
let
|
let
|
||||||
name = config.networking.hostName;
|
name = config.networking.hostName;
|
||||||
moduleList = import "${modulesPath}/module-list.nix";
|
nixosLibPath = (modulesPath + "/../lib");
|
||||||
|
moduleList = import (modulesPath + "/module-list.nix");
|
||||||
|
|
||||||
dynixFromSearchPath = let
|
optionalPath = p: lib.optional (builtins.pathExists p) p;
|
||||||
res = builtins.tryEval <dynix>;
|
|
||||||
in lib.optional res.success res.value;
|
|
||||||
in
|
in
|
||||||
|
assert builtins.pathExists nixosLibPath;
|
||||||
|
builtins.seq lib
|
||||||
|
builtins.seq modulesPath
|
||||||
|
builtins.seq moduleList
|
||||||
{
|
{
|
||||||
imports = [
|
imports = moduleList ++ [
|
||||||
"${modulesPath}/testing/test-instrumentation.nix"
|
(modulesPath + "/testing/test-instrumentation.nix")
|
||||||
./hardware-configuration.nix
|
|
||||||
] ++ lib.concatLists [
|
] ++ lib.concatLists [
|
||||||
moduleList
|
(optionalPath ./hardware-configuration.nix)
|
||||||
dynixFromSearchPath
|
(optionalPath ./dynamicism)
|
||||||
|
(optionalPath ../../modules/dynamicism)
|
||||||
];
|
];
|
||||||
|
|
||||||
dynamicism.for.tzupdate.enable = true;
|
|
||||||
services.tzupdate = {
|
|
||||||
enable = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
system.switch.enable = true;
|
system.switch.enable = true;
|
||||||
documentation.enable = false;
|
documentation.enable = false;
|
||||||
|
|
||||||
networking.hostName = "tzupdate-machine";
|
networking.hostName = "machine";
|
||||||
|
|
||||||
boot.loader.grub = {
|
boot.loader.grub = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
@ -34,23 +32,28 @@ in
|
||||||
|
|
||||||
nix = {
|
nix = {
|
||||||
package = pkgs.lixPackageSets.latest.lix;
|
package = pkgs.lixPackageSets.latest.lix;
|
||||||
nixPath = [
|
nixPath = [ "nixpkgs=${pkgs.path}" ];
|
||||||
"nixpkgs=${pkgs.path}"
|
|
||||||
"/nix/var/nix/profiles/per-user/root/profile/share/nixos/modules"
|
|
||||||
];
|
|
||||||
|
|
||||||
settings = {
|
settings = {
|
||||||
experimental-features = [ "nix-command" "pipe-operator" ];
|
experimental-features = [ "nix-command" "pipe-operator" ];
|
||||||
substituters = lib.mkForce [ ];
|
substituters = lib.mkForce [ ];
|
||||||
hashed-mirrors = null;
|
hashed-mirrors = null;
|
||||||
connect-timeout = 1;
|
connect-timeout = 1;
|
||||||
# For my debugging purposes.
|
|
||||||
show-trace = true;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
services.gotosocial = {
|
||||||
|
enable = true;
|
||||||
|
setupPostgresqlDB = true;
|
||||||
|
settings = {
|
||||||
|
application-name = "gotosocial-for-${name}";
|
||||||
|
host = "${name}.local";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
dynamicism.for.gotosocial.enable = true;
|
||||||
|
|
||||||
environment.pathsToLink = [ "/share" ];
|
environment.pathsToLink = [ "/share" ];
|
||||||
environment.extraOutputsToInstall = [ "modules" ];
|
|
||||||
environment.variables = {
|
environment.variables = {
|
||||||
"NIXOS_CONFIG" = "/etc/nixos/configuration.nix";
|
"NIXOS_CONFIG" = "/etc/nixos/configuration.nix";
|
||||||
};
|
};
|
||||||
|
|
@ -58,7 +61,6 @@ in
|
||||||
environment.shellAliases = {
|
environment.shellAliases = {
|
||||||
ls = "eza --long --header --group --group-directories-first --classify --binary";
|
ls = "eza --long --header --group --group-directories-first --classify --binary";
|
||||||
};
|
};
|
||||||
|
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
eza
|
eza
|
||||||
fd
|
fd
|
||||||
84
tests/basic/test-script.py
Normal file
84
tests/basic/test-script.py
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
from pathlib import Path
|
||||||
|
import shlex
|
||||||
|
import textwrap
|
||||||
|
from typing import cast, TYPE_CHECKING
|
||||||
|
|
||||||
|
from beartype import beartype
|
||||||
|
|
||||||
|
from test_driver.machine import Machine
|
||||||
|
from test_driver.errors import RequestedAssertionFailed
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
global machine
|
||||||
|
machine = cast(Machine, ...)
|
||||||
|
assert machine.shell is not None
|
||||||
|
|
||||||
|
ls = "eza -lah --color=always --group-directories-first"
|
||||||
|
|
||||||
|
@beartype
|
||||||
|
def run_log(machine: Machine, *commands: str, timeout: int | None = 60) -> str:
|
||||||
|
output = ""
|
||||||
|
for command in commands:
|
||||||
|
with machine.nested(f"must succeed: {command}"):
|
||||||
|
(status, out) = machine.execute(f"{command} | tee /dev/stderr", timeout=timeout)
|
||||||
|
if status != 0:
|
||||||
|
machine.log(f"output: {out}")
|
||||||
|
raise RequestedAssertionFailed(
|
||||||
|
f"command `{command}` failed (exit code {status})",
|
||||||
|
)
|
||||||
|
output += out
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
@beartype
|
||||||
|
def get_config_file() -> str:
|
||||||
|
machine.wait_for_unit("gotosocial.service")
|
||||||
|
gotosocial_pid = int(machine.get_unit_property("gotosocial.service", "MainPID"))
|
||||||
|
print(f"{gotosocial_pid=}")
|
||||||
|
|
||||||
|
cmdline = machine.succeed(f"cat /proc/{gotosocial_pid}/cmdline")
|
||||||
|
cmdline_args = cmdline.split("\0")
|
||||||
|
|
||||||
|
config_file_idx = cmdline_args.index("--config-path") + 1
|
||||||
|
config_file = Path(cmdline_args[config_file_idx])
|
||||||
|
|
||||||
|
machine.log(f"copying from VM: {config_file=}")
|
||||||
|
machine.copy_from_vm(config_file.as_posix())
|
||||||
|
|
||||||
|
config_file_path = machine.out_dir / config_file.name
|
||||||
|
with open(config_file_path, "r") as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
|
machine.wait_for_unit("default.target")
|
||||||
|
assert "lix" in machine.succeed("nix --version").lower()
|
||||||
|
|
||||||
|
run_log(machine, "nixos-generate-config")
|
||||||
|
machine.succeed("mkdir -vp /etc/nixos")
|
||||||
|
machine.succeed("cp -rv /run/current-system/sw/share/nixos/* /etc/nixos/")
|
||||||
|
machine.succeed("env PAGER= nixos-rebuild switch --log-format raw-with-logs -v --fallback >&2")
|
||||||
|
|
||||||
|
config_text = get_config_file()
|
||||||
|
lines = config_text.splitlines()
|
||||||
|
application_name = next((line for line in lines if line.startswith("application-name:")), None)
|
||||||
|
assert application_name is not None, f"no 'application-name:' found in config file: {textwrap.indent(config_text, "")}"
|
||||||
|
assert "gotosocial-for-machine" in application_name, f"'gotosocial-for-machine' should be in {application_name=}"
|
||||||
|
|
||||||
|
new_app_name = "yay!"
|
||||||
|
expr = textwrap.dedent(f"""
|
||||||
|
let
|
||||||
|
nixos = import <nixpkgs/nixos> {{ }};
|
||||||
|
in nixos.config.dynamicism.doChange {{
|
||||||
|
option = "services.gotosocial.settings.application-name";
|
||||||
|
value = "{new_app_name}";
|
||||||
|
}}
|
||||||
|
""").strip()
|
||||||
|
machine.succeed(rf"""
|
||||||
|
nix run --show-trace --log-format raw-with-logs --impure -E {shlex.quote(expr)}
|
||||||
|
""".strip())
|
||||||
|
|
||||||
|
config_file_new = get_config_file()
|
||||||
|
lines = config_file_new.splitlines()
|
||||||
|
|
||||||
|
application_name = next(line for line in lines if line.startswith("application-name:"))
|
||||||
|
assert new_app_name in application_name, f"'{new_app_name}' should be in {application_name=}"
|
||||||
43
tests/basic/test.nix
Normal file
43
tests/basic/test.nix
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
name = "nixos-test-dynamicism-main";
|
||||||
|
|
||||||
|
defaults = { ... }: { };
|
||||||
|
|
||||||
|
#node.pkgsReadOnly = false;
|
||||||
|
|
||||||
|
extraPythonPackages = p: [
|
||||||
|
p.beartype
|
||||||
|
];
|
||||||
|
|
||||||
|
nodes.machine = { pkgs, config, ... }: {
|
||||||
|
imports = [ ./configuration.nix ];
|
||||||
|
|
||||||
|
system.includeBuildDependencies = true;
|
||||||
|
system.switch.enable = true;
|
||||||
|
|
||||||
|
virtualisation.additionalPaths = [ config.system.build.toplevel ];
|
||||||
|
virtualisation = {
|
||||||
|
memorySize = 4096;
|
||||||
|
cores = 4;
|
||||||
|
writableStore = true;
|
||||||
|
mountHostNixStore = true;
|
||||||
|
installBootLoader = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
environment.systemPackages = let
|
||||||
|
configFileTree = pkgs.callPackage ./configuration-package.nix { };
|
||||||
|
in [
|
||||||
|
configFileTree
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# What's a little IFD between friends?
|
||||||
|
testScript = ./test-script.py
|
||||||
|
|> builtins.readFile;
|
||||||
|
}
|
||||||
|
|
@ -1,79 +1,8 @@
|
||||||
{
|
{
|
||||||
pkgs ? import <nixpkgs> { },
|
pkgs ? import <nixpkgs> { },
|
||||||
qpkgs ? let
|
lib ? pkgs.lib,
|
||||||
src = fetchTree (builtins.parseFlakeRef "github:Qyriad/nur-packages");
|
}: lib.makeScope lib.callPackageWith (self: let
|
||||||
in import src { inherit pkgs; },
|
inherit (pkgs.testers) runNixOSTest;
|
||||||
callPackage ? qpkgs.callPackage,
|
in {
|
||||||
lib ? qpkgs.lib,
|
basic = runNixOSTest ./basic/test.nix;
|
||||||
dynix ? qpkgs.callPackage ../package.nix { },
|
|
||||||
}: let
|
|
||||||
|
|
||||||
mkDynixConfigurationDotNix = callPackage ./mk-test-configuration-dot-nix.nix { };
|
|
||||||
|
|
||||||
runDynixTest = testModule: pkgs.testers.runNixOSTest {
|
|
||||||
imports = [ testModule ];
|
|
||||||
|
|
||||||
# NOTE: these are arguments to each *test module*.
|
|
||||||
# Not the NixOS modules of the test's nodes.
|
|
||||||
_module.args = { inherit mkDynixConfigurationDotNix; };
|
|
||||||
|
|
||||||
# Why is this argument called "extraBaseModule**s**" but take a single module argument...
|
|
||||||
# Also note this is an extra base module for each node of the test,
|
|
||||||
# not an extra test module.
|
|
||||||
extraBaseModules = { name, config, options, modulesPath, ... }: {
|
|
||||||
/**
|
|
||||||
* Everything in this module will disappear once nixos-rebuild switch happens.
|
|
||||||
* So each test will need to use `mkDynixConfigurationDotNix` to get
|
|
||||||
* ./dynix-vm-configuration included in the in-VM configuration.
|
|
||||||
*/
|
|
||||||
|
|
||||||
imports = (import "${modulesPath}/module-list.nix") ++ [
|
|
||||||
# For the VM node, but not the in-VM configuration.nix
|
|
||||||
./module-allow-rebuild-in-vm.nix
|
|
||||||
# For the VM node, and the in-VM configuration.nix
|
|
||||||
./dynix-vm-configuration.nix
|
|
||||||
dynix.dynix
|
|
||||||
];
|
|
||||||
|
|
||||||
systemd.services."install-dynix" = {
|
|
||||||
enable = true;
|
|
||||||
serviceConfig.Type = "oneshot";
|
|
||||||
serviceConfig.RemainAfterExit = true;
|
|
||||||
path = [ config.system.path ];
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
requiredBy = [ "multi-user.target" ];
|
|
||||||
after = [ "default.target" ];
|
|
||||||
script = ''
|
|
||||||
if [[ -e /etc/nixos/hardware-configuration.nix ]]; then
|
|
||||||
echo "install-dynix: configuration already copied; nothing to do"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
nix profile install -vv "${dynix.drvPath}^*" # "
|
|
||||||
|
|
||||||
mkdir -vp /etc/nixos
|
|
||||||
nixos-generate-config
|
|
||||||
cp -rv --dereference /run/current-system/sw/share/nixos/*.nix /etc/nixos/
|
|
||||||
if ! [[ -e /etc/nixos/dynix-vm-configuration.nix ]]; then
|
|
||||||
echo "FAILURE"
|
|
||||||
echo "FAILURE" >&2
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
passthru = { inherit options; };
|
|
||||||
|
|
||||||
environment.systemPackages = [ config.passthru.configurationDotNix ];
|
|
||||||
|
|
||||||
# Just making something in this strict in `name`,
|
|
||||||
# which is only present as an argument for nodes and I don't want to
|
|
||||||
# confuse that with the test modules.
|
|
||||||
warnings = builtins.seq name [ ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
in lib.makeScope lib.callPackageWith (self: {
|
|
||||||
gotosocial = runDynixTest ./gotosocial/test.nix;
|
|
||||||
harmonia = runDynixTest ./harmonia/test.nix;
|
|
||||||
distccd = runDynixTest ./distccd/test.nix;
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
services.distccd = {
|
|
||||||
enable = true;
|
|
||||||
jobTimeout = 900;
|
|
||||||
maxJobs = 12;
|
|
||||||
nice = -10;
|
|
||||||
};
|
|
||||||
|
|
||||||
dynamicism.for.distccd.enable = true;
|
|
||||||
|
|
||||||
networking.hostName = "distccd-machine";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
import argparse
|
|
||||||
import functools
|
|
||||||
#from pathlib import Path
|
|
||||||
#from pprint import pformat
|
|
||||||
import shlex
|
|
||||||
import textwrap
|
|
||||||
from typing import Any, cast, TYPE_CHECKING
|
|
||||||
|
|
||||||
from beartype import beartype
|
|
||||||
|
|
||||||
from test_driver.machine import Machine
|
|
||||||
from test_driver.errors import RequestedAssertionFailed
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
global machine
|
|
||||||
machine = cast(Machine, ...)
|
|
||||||
assert machine.shell is not None
|
|
||||||
|
|
||||||
ls = "eza -lah --color=always --group-directories-first"
|
|
||||||
|
|
||||||
indent = functools.partial(textwrap.indent, prefix=' ')
|
|
||||||
|
|
||||||
@beartype
|
|
||||||
def run_log(machine: Machine, *commands: str, timeout: int | None = 60) -> str:
|
|
||||||
output = ""
|
|
||||||
for command in commands:
|
|
||||||
with machine.nested(f"must succeed: {command}"):
|
|
||||||
(status, out) = machine.execute(f"{command} | tee /dev/stderr", timeout=timeout)
|
|
||||||
if status != 0:
|
|
||||||
machine.log(f"output: {out}")
|
|
||||||
raise RequestedAssertionFailed(
|
|
||||||
f"command `{command}` failed (exit code {status})",
|
|
||||||
)
|
|
||||||
output += out
|
|
||||||
|
|
||||||
return output
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
#parser.add_argument("--no-detach", action="store_true")
|
|
||||||
#parser.add_argument("--daemon", action="store_true")
|
|
||||||
#parser.add_argument("--enable-tcp-insecure", action="store_true")
|
|
||||||
#parser.add_argument("--port", type=int)
|
|
||||||
parser.add_argument("--jobs", type=int)
|
|
||||||
parser.add_argument("--job-lifetime", type=int)
|
|
||||||
parser.add_argument("--log-level", type=str)
|
|
||||||
#parser.add_argument("--nice", type=int)
|
|
||||||
#parser.add_argument("--stats", action="store_true")
|
|
||||||
#parser.add_argument("--stats-port", type=int)
|
|
||||||
|
|
||||||
@beartype
|
|
||||||
def get_cli_args() -> argparse.Namespace:
|
|
||||||
machine.wait_for_unit("distccd.service")
|
|
||||||
mainpid = int(machine.get_unit_property("distccd.service", "MainPID"))
|
|
||||||
machine.log(f"{mainpid=}")
|
|
||||||
pidtext = machine.succeed(f"pgrep -P {mainpid}")
|
|
||||||
machine.log(f"{pidtext=}")
|
|
||||||
pid = int(pidtext.splitlines()[0])
|
|
||||||
machine.log(f"{pid=}")
|
|
||||||
execstart = machine.get_unit_property("distccd.service", "ExecStart")
|
|
||||||
print(f"{execstart=}")
|
|
||||||
|
|
||||||
cmdline = machine.succeed(f"cat /proc/{pid}/cmdline")
|
|
||||||
cmdline_args = cmdline.split("\0")
|
|
||||||
machine.log(f"{cmdline_args=}")
|
|
||||||
print(f"{cmdline_args=}")
|
|
||||||
|
|
||||||
#return shlex.join(cmdline_args[1:])
|
|
||||||
args, rest = parser.parse_known_args(cmdline_args)
|
|
||||||
return args
|
|
||||||
|
|
||||||
@beartype
|
|
||||||
def dynix_append(option: str, value: Any):
|
|
||||||
machine.succeed(f'''
|
|
||||||
dynix append {shlex.quote(option)} {shlex.quote(str(value))}
|
|
||||||
'''.strip())
|
|
||||||
|
|
||||||
@beartype
|
|
||||||
def do_apply():
|
|
||||||
expr = textwrap.dedent("""
|
|
||||||
let
|
|
||||||
nixos = import <nixpkgs/nixos> { };
|
|
||||||
in nixos.config.dynamicism.applyDynamicConfiguration {
|
|
||||||
baseConfiguration = /etc/nixos/configuration.nix;
|
|
||||||
newConfiguration = /etc/nixos/dynamic.nix;
|
|
||||||
}
|
|
||||||
""").strip()
|
|
||||||
|
|
||||||
machine.succeed(rf"""
|
|
||||||
nix run --show-trace --log-format raw-with-logs --impure -E {shlex.quote(expr)}
|
|
||||||
""".strip())
|
|
||||||
|
|
||||||
machine.wait_for_unit("default.target")
|
|
||||||
assert "lix" in machine.succeed("nix --version").lower()
|
|
||||||
machine.log("INIT")
|
|
||||||
|
|
||||||
# Config should have our initial values.
|
|
||||||
args = get_cli_args()
|
|
||||||
#assert '--jobs 12' in args, f'--jobs 12 not in {args=}'
|
|
||||||
assert args.jobs == 12, f'{args.jobs=} != 12'
|
|
||||||
assert args.job_lifetime == 900, f'{args.job_lifetime} != 900'
|
|
||||||
assert args.log_level == 'warning', f'{args.log_level=} != warning'
|
|
||||||
|
|
||||||
with machine.nested("must succeed: initial nixos-rebuild switch"):
|
|
||||||
machine.succeed("env PAGER= nixos-rebuild switch --log-format raw-with-logs --fallback")
|
|
||||||
|
|
||||||
# Config should not have changed.
|
|
||||||
args = get_cli_args()
|
|
||||||
#machine.log(f"config.toml after first rebuild: {indent(pformat(args))}")
|
|
||||||
#assert int(args['workers']) == 4, f"{args['workers']=} != 4"
|
|
||||||
#assert int(args['max_connection_rate']) == 256, f"{args['max_connection_rate']=} != 256"
|
|
||||||
#
|
|
||||||
new_jobs = 4
|
|
||||||
dynix_append("services.distccd.maxJobs", new_jobs)
|
|
||||||
do_apply()
|
|
||||||
|
|
||||||
args = get_cli_args()
|
|
||||||
|
|
||||||
# Only jobs should have changed. The others should still be default.
|
|
||||||
assert args.jobs == new_jobs, f'{args.jobs=} != {new_jobs=}'
|
|
||||||
assert args.job_lifetime == 900, f'{args.job_lifetime} != 900'
|
|
||||||
assert args.log_level == 'warning', f'{args.log_level=} != warning'
|
|
||||||
|
|
||||||
new_log_level = 'error'
|
|
||||||
dynix_append("services.distccd.logLevel", f'"{new_log_level}"')
|
|
||||||
do_apply()
|
|
||||||
|
|
||||||
args = get_cli_args()
|
|
||||||
#assert args.jobs == new_jobs, f'{args.jobs=} != {new_jobs=}'
|
|
||||||
#assert args.job_lifetime == 900, f'{args.job_lifetime} != 900'
|
|
||||||
assert args.log_level == new_log_level, f'{args.log_level=} != {new_log_level=}'
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
{ mkDynixConfigurationDotNix, config, ... }:
|
|
||||||
let
|
|
||||||
testName = config.name;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
name = "nixos-test-dynamicism-distccd";
|
|
||||||
|
|
||||||
defaults = { ... }: { };
|
|
||||||
|
|
||||||
extraPythonPackages = p: [ p.beartype ];
|
|
||||||
|
|
||||||
nodes.machine = { ... }: {
|
|
||||||
imports = [ ./configuration.nix ];
|
|
||||||
|
|
||||||
passthru.configurationDotNix = mkDynixConfigurationDotNix {
|
|
||||||
name = testName;
|
|
||||||
configuration = ./configuration.nix;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
testScript = builtins.readFile ./test-script.py;
|
|
||||||
}
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
{ pkgs, lib, modulesPath, ... }:
|
|
||||||
let
|
|
||||||
moduleList = import "${modulesPath}/module-list.nix";
|
|
||||||
|
|
||||||
dynixFromSearchPath = let
|
|
||||||
res = builtins.tryEval <dynix>;
|
|
||||||
in lib.optional res.success res.value;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
"${modulesPath}/testing/test-instrumentation.nix"
|
|
||||||
] ++ lib.concatLists [
|
|
||||||
dynixFromSearchPath
|
|
||||||
moduleList
|
|
||||||
];
|
|
||||||
|
|
||||||
system.switch.enable = true;
|
|
||||||
system.includeBuildDependencies = true;
|
|
||||||
documentation.enable = false;
|
|
||||||
|
|
||||||
boot.loader.grub = {
|
|
||||||
enable = true;
|
|
||||||
device = "/dev/vda";
|
|
||||||
forceInstall = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
nix = {
|
|
||||||
package = pkgs.lixPackageSets.latest.lix;
|
|
||||||
nixPath = [
|
|
||||||
"nixpkgs=${pkgs.path}"
|
|
||||||
"/nix/var/nix/profiles/per-user/root/profile/share/nixos/modules"
|
|
||||||
];
|
|
||||||
|
|
||||||
settings = {
|
|
||||||
experimental-features = [ "nix-command" "pipe-operator" ];
|
|
||||||
substituters = lib.mkForce [ ];
|
|
||||||
hashed-mirrors = null;
|
|
||||||
connect-timeout = 1;
|
|
||||||
# For my debugging purposes.
|
|
||||||
show-trace = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
environment.pathsToLink = [ "/share" ];
|
|
||||||
environment.extraOutputsToInstall = [ "modules" ];
|
|
||||||
environment.variables = {
|
|
||||||
"NIXOS_CONFIG" = "/etc/nixos/configuration.nix";
|
|
||||||
};
|
|
||||||
|
|
||||||
environment.shellAliases = {
|
|
||||||
ls = "eza --long --header --group --group-directories-first --classify --binary";
|
|
||||||
};
|
|
||||||
|
|
||||||
environment.systemPackages = with pkgs; [
|
|
||||||
eza
|
|
||||||
fd
|
|
||||||
ripgrep
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
runCommand,
|
|
||||||
}: runCommand "tests-gotosocial-configuration-dot-nix" {
|
|
||||||
} ''
|
|
||||||
set -euo pipefail
|
|
||||||
install -Dm a=r ${./configuration.nix} "$out/share/nixos/configuration.nix"
|
|
||||||
''
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
{ pkgs, lib, config, modulesPath, ... }:
|
|
||||||
let
|
|
||||||
name = config.networking.hostName;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
networking.hostName = "gotosocial-machine";
|
|
||||||
services.gotosocial = {
|
|
||||||
enable = true;
|
|
||||||
setupPostgresqlDB = true;
|
|
||||||
settings = {
|
|
||||||
application-name = "gotosocial-for-machine";
|
|
||||||
host = "${name}.local";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
dynamicism.for.gotosocial.enable = true;
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
/** Dummy hardware configuration.
|
|
||||||
* Will be replaced with the real one in the test VM.
|
|
||||||
*/
|
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
from pathlib import Path
|
|
||||||
import shlex
|
|
||||||
import textwrap
|
|
||||||
from typing import cast, TYPE_CHECKING
|
|
||||||
|
|
||||||
from beartype import beartype
|
|
||||||
|
|
||||||
from test_driver.machine import Machine
|
|
||||||
from test_driver.errors import RequestedAssertionFailed
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
global machine
|
|
||||||
machine = cast(Machine, ...)
|
|
||||||
assert machine.shell is not None
|
|
||||||
|
|
||||||
ls = "eza -lah --color=always --group-directories-first"
|
|
||||||
|
|
||||||
@beartype
|
|
||||||
def run_log(machine: Machine, *commands: str, timeout: int | None = 60) -> str:
|
|
||||||
output = ""
|
|
||||||
for command in commands:
|
|
||||||
with machine.nested(f"must succeed: {command}"):
|
|
||||||
(status, out) = machine.execute(f"{command} | tee /dev/stderr", timeout=timeout)
|
|
||||||
if status != 0:
|
|
||||||
machine.log(f"output: {out}")
|
|
||||||
raise RequestedAssertionFailed(
|
|
||||||
f"command `{command}` failed (exit code {status})",
|
|
||||||
)
|
|
||||||
output += out
|
|
||||||
|
|
||||||
return output
|
|
||||||
|
|
||||||
@beartype
|
|
||||||
def get_config_file() -> str:
|
|
||||||
machine.wait_for_unit("gotosocial.service")
|
|
||||||
gotosocial_pid = int(machine.get_unit_property("gotosocial.service", "MainPID"))
|
|
||||||
|
|
||||||
cmdline = machine.succeed(f"cat /proc/{gotosocial_pid}/cmdline")
|
|
||||||
cmdline_args = cmdline.split("\0")
|
|
||||||
|
|
||||||
config_file_idx = cmdline_args.index("--config-path") + 1
|
|
||||||
config_file = Path(cmdline_args[config_file_idx])
|
|
||||||
|
|
||||||
machine.log(f"copying from VM: {config_file=}")
|
|
||||||
machine.copy_from_vm(config_file.as_posix())
|
|
||||||
|
|
||||||
config_file_path = machine.out_dir / config_file.name
|
|
||||||
with open(config_file_path, "r") as f:
|
|
||||||
data = f.read()
|
|
||||||
|
|
||||||
config_file_path.unlink()
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
@beartype
|
|
||||||
def dynix_append(option: str, value: str):
|
|
||||||
machine.succeed(f'''
|
|
||||||
dynix append {shlex.quote(option)} {shlex.quote(value)}
|
|
||||||
'''.strip())
|
|
||||||
|
|
||||||
@beartype
|
|
||||||
def do_apply():
|
|
||||||
expr = textwrap.dedent("""
|
|
||||||
let
|
|
||||||
nixos = import <nixpkgs/nixos> { };
|
|
||||||
in nixos.config.dynamicism.applyDynamicConfiguration {
|
|
||||||
baseConfiguration = /etc/nixos/configuration.nix;
|
|
||||||
newConfiguration = /etc/nixos/dynamic.nix;
|
|
||||||
}
|
|
||||||
""").strip()
|
|
||||||
|
|
||||||
machine.succeed(rf"""
|
|
||||||
nix run --show-trace --log-format raw-with-logs --impure -E {shlex.quote(expr)}
|
|
||||||
""".strip())
|
|
||||||
|
|
||||||
|
|
||||||
machine.wait_for_unit("default.target")
|
|
||||||
assert "lix" in machine.succeed("nix --version").lower()
|
|
||||||
machine.wait_for_unit("install-dynix.service")
|
|
||||||
|
|
||||||
dynix_out = machine.succeed("dynix --version")
|
|
||||||
assert "dynix" in dynix_out, f"dynix not in {dynix_out=}"
|
|
||||||
|
|
||||||
machine.log("REBUILDING configuration inside VM")
|
|
||||||
machine.succeed("env PAGER= nixos-rebuild switch --log-format raw-with-logs --fallback")
|
|
||||||
machine.wait_for_unit("gotosocial.service")
|
|
||||||
|
|
||||||
# Make sure the config before any dynamic changes is what we expect.
|
|
||||||
config_text = get_config_file()
|
|
||||||
lines = config_text.splitlines()
|
|
||||||
try:
|
|
||||||
application_name = next(line for line in lines if line.startswith("application-name:"))
|
|
||||||
except StopIteration:
|
|
||||||
raise AssertionError(f"no 'application-name:' found in config file: {textwrap.indent(config_text, " ")}")
|
|
||||||
assert "gotosocial-for-machine" in application_name, f"'gotosocial-for-machine' should be in {application_name=}"
|
|
||||||
|
|
||||||
new_app_name = "yay!"
|
|
||||||
dynix_append("services.gotosocial.settings.application-name", f'"{new_app_name}"')
|
|
||||||
do_apply()
|
|
||||||
|
|
||||||
config_text = get_config_file()
|
|
||||||
lines = config_text.splitlines()
|
|
||||||
try:
|
|
||||||
application_name = next(line for line in lines if line.startswith("application-name:"))
|
|
||||||
except StopIteration:
|
|
||||||
raise AssertionError(f"no 'application-name:' found in config file: {textwrap.indent(config_text, " ")}")
|
|
||||||
assert new_app_name in application_name, f"'{new_app_name}' should be in {application_name=}"
|
|
||||||
|
|
||||||
machine.log("REBUILDING configuration inside VM")
|
|
||||||
machine.succeed("env PAGER= nixos-rebuild switch --log-format raw-with-logs --fallback")
|
|
||||||
|
|
||||||
machine.wait_for_unit("gotosocial.service")
|
|
||||||
|
|
||||||
config_text = get_config_file()
|
|
||||||
lines = config_text.splitlines()
|
|
||||||
try:
|
|
||||||
application_name = next(line for line in lines if line.startswith("application-name:"))
|
|
||||||
except StopIteration:
|
|
||||||
raise AssertionError(f"no 'application-name:' found in config file: {textwrap.indent(config_text, " ")}")
|
|
||||||
assert "gotosocial-for-machine" in application_name, f"'gotosocial-for-machine' should be in {application_name=}"
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
{ mkDynixConfigurationDotNix, config, ... }:
|
|
||||||
let
|
|
||||||
testName = config.name;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
name = "nixos-test-dynamicism-gotosocial";
|
|
||||||
|
|
||||||
defaults = { ... }: { };
|
|
||||||
|
|
||||||
extraPythonPackages = p: [
|
|
||||||
p.beartype
|
|
||||||
];
|
|
||||||
|
|
||||||
nodes.machine = { ... }: {
|
|
||||||
# NOTE: Anything in this `nodes.machine = ` module will not be included
|
|
||||||
# in the VM's NixOS configuration once it does `nixos-rebuild switch`,
|
|
||||||
# except for `./configuration.nix` which will be copied to `/etc/nixos/`.
|
|
||||||
# dynix will also be statefully installed to root's user profile.
|
|
||||||
imports = [ ./configuration.nix ];
|
|
||||||
|
|
||||||
passthru.configurationDotNix = mkDynixConfigurationDotNix {
|
|
||||||
name = testName;
|
|
||||||
configuration = ./configuration.nix;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
testScript = builtins.readFile ./test-script.py;
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
{ lib, modulesPath, ... }:
|
|
||||||
let
|
|
||||||
moduleList = import "${modulesPath}/module-list.nix";
|
|
||||||
|
|
||||||
dynixFromSearchPath = let
|
|
||||||
res = builtins.tryEval <dynix>;
|
|
||||||
in lib.optional res.success res.value;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
#"${modulesPath}/testing/test-instrumentation.nix"
|
|
||||||
#./hardware-configuration.nix
|
|
||||||
] ++ lib.concatLists [
|
|
||||||
#dynixFromSearchPath
|
|
||||||
moduleList
|
|
||||||
];
|
|
||||||
|
|
||||||
dynamicism.for.harmonia.enable = true;
|
|
||||||
services.harmonia = {
|
|
||||||
enable = true;
|
|
||||||
settings = {
|
|
||||||
# Default.
|
|
||||||
workers = 4;
|
|
||||||
# Default.
|
|
||||||
max_connection_rate = 256;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
networking.hostName = "harmonia-machine";
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,124 +0,0 @@
|
||||||
import functools
|
|
||||||
from pathlib import Path
|
|
||||||
from pprint import pformat
|
|
||||||
import shlex
|
|
||||||
import textwrap
|
|
||||||
import tomllib
|
|
||||||
from typing import Any, cast, TYPE_CHECKING
|
|
||||||
|
|
||||||
from beartype import beartype
|
|
||||||
|
|
||||||
from test_driver.machine import Machine
|
|
||||||
from test_driver.errors import RequestedAssertionFailed
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
global machine
|
|
||||||
machine = cast(Machine, ...)
|
|
||||||
assert machine.shell is not None
|
|
||||||
|
|
||||||
ls = "eza -lah --color=always --group-directories-first"
|
|
||||||
|
|
||||||
indent = functools.partial(textwrap.indent, prefix=' ')
|
|
||||||
|
|
||||||
@beartype
|
|
||||||
def run_log(machine: Machine, *commands: str, timeout: int | None = 60) -> str:
|
|
||||||
output = ""
|
|
||||||
for command in commands:
|
|
||||||
with machine.nested(f"must succeed: {command}"):
|
|
||||||
(status, out) = machine.execute(f"{command} | tee /dev/stderr", timeout=timeout)
|
|
||||||
if status != 0:
|
|
||||||
machine.log(f"output: {out}")
|
|
||||||
raise RequestedAssertionFailed(
|
|
||||||
f"command `{command}` failed (exit code {status})",
|
|
||||||
)
|
|
||||||
output += out
|
|
||||||
|
|
||||||
return output
|
|
||||||
|
|
||||||
@beartype
|
|
||||||
def get_config_file() -> dict[str, Any]:
|
|
||||||
machine.wait_for_unit("harmonia.service")
|
|
||||||
pid = int(machine.get_unit_property("harmonia.service", "MainPID"))
|
|
||||||
env_lines: list[str] = machine.succeed(f"cat /proc/{pid}/environ").replace("\0", "\n").splitlines()
|
|
||||||
pairs: list[list[str]] = [line.split("=", maxsplit=1) for line in env_lines]
|
|
||||||
env = dict(pairs)
|
|
||||||
|
|
||||||
config_file = Path(env["CONFIG_FILE"])
|
|
||||||
|
|
||||||
machine.log(f"copying from VM: {config_file=}")
|
|
||||||
machine.copy_from_vm(config_file.as_posix())
|
|
||||||
|
|
||||||
config_file_path = machine.out_dir / config_file.name
|
|
||||||
with open(config_file_path, "rb") as f:
|
|
||||||
config_data = tomllib.load(f)
|
|
||||||
|
|
||||||
config_file_path.unlink()
|
|
||||||
return config_data
|
|
||||||
|
|
||||||
@beartype
|
|
||||||
def dynix_append(option: str, value: Any):
|
|
||||||
machine.succeed(f'''
|
|
||||||
dynix append {shlex.quote(option)} {shlex.quote(str(value))}
|
|
||||||
'''.strip())
|
|
||||||
|
|
||||||
@beartype
|
|
||||||
def do_apply():
|
|
||||||
expr = textwrap.dedent("""
|
|
||||||
let
|
|
||||||
nixos = import <nixpkgs/nixos> { };
|
|
||||||
in nixos.config.dynamicism.applyDynamicConfiguration {
|
|
||||||
baseConfiguration = /etc/nixos/configuration.nix;
|
|
||||||
newConfiguration = /etc/nixos/dynamic.nix;
|
|
||||||
}
|
|
||||||
""").strip()
|
|
||||||
|
|
||||||
machine.succeed(rf"""
|
|
||||||
nix run --show-trace --log-format raw-with-logs --impure -E {shlex.quote(expr)}
|
|
||||||
""".strip())
|
|
||||||
|
|
||||||
machine.wait_for_unit("default.target")
|
|
||||||
assert "lix" in machine.succeed("nix --version").lower()
|
|
||||||
machine.log("INIT")
|
|
||||||
|
|
||||||
# Config should have our initial values.
|
|
||||||
config_toml = get_config_file()
|
|
||||||
machine.log(f"config.toml BEFORE first rebuild (initial): {indent(pformat(config_toml))}")
|
|
||||||
assert int(config_toml['workers']) == 4, f"{config_toml['workers']=} != 4"
|
|
||||||
assert int(config_toml['max_connection_rate']) == 256, f"{config_toml['max_connection_rate']=} != 256"
|
|
||||||
|
|
||||||
with machine.nested("must succeed: initial nixos-rebuild switch"):
|
|
||||||
machine.succeed("env PAGER= nixos-rebuild switch --log-format raw-with-logs --fallback")
|
|
||||||
|
|
||||||
# Config should not have changed.
|
|
||||||
config_toml = get_config_file()
|
|
||||||
machine.log(f"config.toml after first rebuild: {indent(pformat(config_toml))}")
|
|
||||||
assert int(config_toml['workers']) == 4, f"{config_toml['workers']=} != 4"
|
|
||||||
assert int(config_toml['max_connection_rate']) == 256, f"{config_toml['max_connection_rate']=} != 256"
|
|
||||||
|
|
||||||
new_workers = 20
|
|
||||||
dynix_append("services.harmonia.settings.workers", new_workers)
|
|
||||||
do_apply()
|
|
||||||
|
|
||||||
# Workers, but not max connection rate, should have changed.
|
|
||||||
config_toml = get_config_file()
|
|
||||||
machine.log(f"config.toml after DYNAMIC ACTIVATION: {indent(pformat(config_toml))}")
|
|
||||||
assert int(config_toml['workers']) == new_workers, f"{config_toml['workers']=} != {new_workers}"
|
|
||||||
assert int(config_toml['max_connection_rate']) == 256, f"{config_toml['max_connection_rate']=} != 256"
|
|
||||||
|
|
||||||
new_max_connection_rate = 100
|
|
||||||
dynix_append("services.harmonia.settings.max_connection_rate", new_max_connection_rate)
|
|
||||||
do_apply()
|
|
||||||
|
|
||||||
# Max connection rate should have changed, but workers should have reverted.
|
|
||||||
config_toml = get_config_file()
|
|
||||||
machine.log(f"config_toml after SECOND dynamic activation: {indent(pformat(config_toml))}")
|
|
||||||
assert int(config_toml['max_connection_rate']) == new_max_connection_rate, f"{config_toml['max_connection_rate']=} != {new_max_connection_rate}"
|
|
||||||
|
|
||||||
# And this should set everything back.
|
|
||||||
machine.succeed("env PAGER= nixos-rebuild switch --log-format raw-with-logs --fallback")
|
|
||||||
machine.systemctl("restart harmonia.service")
|
|
||||||
machine.wait_for_unit("harmonia.service")
|
|
||||||
config_toml = get_config_file()
|
|
||||||
machine.log(f"config_toml after NORMAL activation: {indent(pformat(config_toml))}")
|
|
||||||
assert int(config_toml['max_connection_rate']) == 256, f'{config_toml["max_connection_rate"]=} != 256'
|
|
||||||
assert int(config_toml['workers']) == 4, f'{config_toml["workers"]=} != 4'
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
{ mkDynixConfigurationDotNix, config, ... }:
|
|
||||||
let
|
|
||||||
testName = config.name;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
name = "nixos-test-dynamicism-harmonia";
|
|
||||||
|
|
||||||
defaults = { ... }: { };
|
|
||||||
|
|
||||||
extraPythonPackages = p: [ p.beartype ];
|
|
||||||
|
|
||||||
nodes.machine = { ... }: {
|
|
||||||
imports = [ ./configuration.nix ];
|
|
||||||
|
|
||||||
passthru.configurationDotNix = mkDynixConfigurationDotNix {
|
|
||||||
name = testName;
|
|
||||||
configuration = ./configuration.nix;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
testScript = builtins.readFile ./test-script.py;
|
|
||||||
}
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
{
|
|
||||||
lib,
|
|
||||||
stdenvNoCC,
|
|
||||||
}: let
|
|
||||||
stdenv = stdenvNoCC;
|
|
||||||
|
|
||||||
mkDynixConfigurationDotNix = finalAttrs: {
|
|
||||||
name,
|
|
||||||
/** A *path* to a `configuration.nix`-like NixOS module.
|
|
||||||
* NOT a NixOS module *value*.
|
|
||||||
*/
|
|
||||||
configuration,
|
|
||||||
}: assert lib.isStringLike configuration; let
|
|
||||||
self = finalAttrs.finalPackage;
|
|
||||||
in {
|
|
||||||
name = "configuration-dot-nix-for-${name}";
|
|
||||||
strictDeps = true;
|
|
||||||
__structuredAttrs = true;
|
|
||||||
preferLocalBuild = true;
|
|
||||||
|
|
||||||
phases = [ "installPhase" ];
|
|
||||||
|
|
||||||
outputs = [ "out" ];
|
|
||||||
modulesOut = "${placeholder "out"}/share/nixos";
|
|
||||||
|
|
||||||
baseConfiguration = configuration;
|
|
||||||
dynixVmConfiguration = ./dynix-vm-configuration.nix;
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
runHook preInstall
|
|
||||||
|
|
||||||
install -Dm a=r "$baseConfiguration" "$modulesOut/test-configuration.nix"
|
|
||||||
install -Dm a=r "$dynixVmConfiguration" "$modulesOut/dynix-vm-configuration.nix"
|
|
||||||
|
|
||||||
echo "/** GENERATED BY mk-test-configuration-dot-nix! */" >> "$modulesOut/configuration.nix"
|
|
||||||
echo "{ ... }:" >> "$modulesOut/configuration.nix"
|
|
||||||
echo >> "$modulesOut/configuration.nix"
|
|
||||||
echo >> "$modulesOut/configuration.nix"
|
|
||||||
echo "{" >> "$modulesOut/configuration.nix"
|
|
||||||
echo " imports = [" >> "$modulesOut/configuration.nix"
|
|
||||||
echo " ./test-configuration.nix" >> "$modulesOut/configuration.nix"
|
|
||||||
echo " ./dynix-vm-configuration.nix" >> "$modulesOut/configuration.nix"
|
|
||||||
echo " ./hardware-configuration.nix" >> "$modulesOut/configuration.nix"
|
|
||||||
echo " ];" >> "$modulesOut/configuration.nix"
|
|
||||||
echo "}" >> "$modulesOut/configuration.nix"
|
|
||||||
|
|
||||||
echo "/** GENERATED BY mk-test-configuration-dot-nix! */" >> "$modulesOut/dynamic.nix"
|
|
||||||
echo "{ lib, ... }:" >> "$modulesOut/dynamic.nix"
|
|
||||||
echo >> "$modulesOut/dynamic.nix"
|
|
||||||
echo >> "$modulesOut/dynamic.nix"
|
|
||||||
echo "{" >> "$modulesOut/dynamic.nix"
|
|
||||||
echo " imports = [ ./configuration.nix ];" >> "$modulesOut/dynamic.nix"
|
|
||||||
echo " config = lib.mkMerge [" >> "$modulesOut/dynamic.nix"
|
|
||||||
echo " ];" >> "$modulesOut/dynamic.nix"
|
|
||||||
echo "}" >> "$modulesOut/dynamic.nix"
|
|
||||||
runHook postInstall
|
|
||||||
'';
|
|
||||||
|
|
||||||
passthru = {
|
|
||||||
modulesPath = self.out + "/share/nixos";
|
|
||||||
configuration = self.out + "/share/nixos/configuration.nix";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in lib.extendMkDerivation {
|
|
||||||
constructDrv = stdenv.mkDerivation;
|
|
||||||
extendDrvArgs = mkDynixConfigurationDotNix;
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
{ name, config, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
system.includeBuildDependencies = true;
|
|
||||||
system.switch.enable = true;
|
|
||||||
|
|
||||||
documentation.enable = false;
|
|
||||||
|
|
||||||
virtualisation = {
|
|
||||||
additionalPaths = [ config.system.build.toplevel ];
|
|
||||||
writableStore = true;
|
|
||||||
mountHostNixStore = true;
|
|
||||||
installBootLoader = true;
|
|
||||||
|
|
||||||
# With how much memory Nix eval uses, this is essentially required.
|
|
||||||
memorySize = 8192;
|
|
||||||
cores = 8;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
boot.loader.grub = {
|
|
||||||
enable = true;
|
|
||||||
device = "/dev/vda";
|
|
||||||
forceInstall = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
# Just making something in this strict in `name`,
|
|
||||||
# which is only present as an argument for nodes and I don't want to
|
|
||||||
# confuse that with the test modules.
|
|
||||||
warnings = builtins.seq name [ ];
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
#from pathlib import Path
|
|
||||||
#import shlex
|
|
||||||
#import textwrap
|
|
||||||
from typing import cast, TYPE_CHECKING
|
|
||||||
|
|
||||||
from beartype import beartype
|
|
||||||
|
|
||||||
from test_driver.machine import Machine
|
|
||||||
from test_driver.errors import RequestedAssertionFailed
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
global machine
|
|
||||||
machine = cast(Machine, ...)
|
|
||||||
assert machine.shell is not None
|
|
||||||
|
|
||||||
ls = "eza -lah --color=always --group-directories-first"
|
|
||||||
|
|
||||||
@beartype
|
|
||||||
def run_log(machine: Machine, *commands: str, timeout: int | None = 60) -> str:
|
|
||||||
output = ""
|
|
||||||
for command in commands:
|
|
||||||
with machine.nested(f"must succeed: {command}"):
|
|
||||||
(status, out) = machine.execute(f"{command} | tee /dev/stderr", timeout=timeout)
|
|
||||||
if status != 0:
|
|
||||||
machine.log(f"output: {out}")
|
|
||||||
raise RequestedAssertionFailed(
|
|
||||||
f"command `{command}` failed (exit code {status})",
|
|
||||||
)
|
|
||||||
output += out
|
|
||||||
|
|
||||||
return output
|
|
||||||
|
|
||||||
machine.wait_for_unit("default.target")
|
|
||||||
assert "lix" in machine.succeed("nix --version").lower()
|
|
||||||
machine.log("INIT")
|
|
||||||
|
|
||||||
|
|
||||||
machine.succeed("nixos-generate-config")
|
|
||||||
machine.succeed("mkdir -vp /etc/nixos")
|
|
||||||
# Dereference is required since that configuration.nix is probably a symlink to the store.
|
|
||||||
machine.succeed("cp -rv --dereference /run/current-system/sw/share/nixos/configuration.nix /etc/nixos/")
|
|
||||||
|
|
||||||
machine.succeed("env PAGER= nixos-rebuild switch --log-format raw-with-logs --fallback")
|
|
||||||
|
|
||||||
@beartype
|
|
||||||
def get_interval() -> str:
|
|
||||||
prop = machine.get_unit_property("tzupdate.timer", "OnCalendar")
|
|
||||||
print(f"{prop=}")
|
|
||||||
|
|
||||||
return prop
|
|
||||||
|
|
||||||
get_interval()
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
{ ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
name = "nixos-test-dynamicism-tzupdate";
|
|
||||||
|
|
||||||
extraPythonPackages = p: [
|
|
||||||
p.beartype
|
|
||||||
];
|
|
||||||
|
|
||||||
nodes.machine = { name, pkgs, ... }: {
|
|
||||||
imports = [ ./configuration.nix ];
|
|
||||||
|
|
||||||
environment.systemPackages = let
|
|
||||||
configFileTree = pkgs.runCommand "${name}-configuration-dot-nix" { } ''
|
|
||||||
set -euo pipefail
|
|
||||||
install -Dm a=r ${./configuration.nix} "$out/share/nixos/configuration.nix"
|
|
||||||
'';
|
|
||||||
in [
|
|
||||||
configFileTree
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
testScript = builtins.readFile ./test-script.py;
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue