mirror of
https://github.com/NixOS/nixpkgs.git
synced 2026-09-13 10:49:56 +00:00
Compare commits
434 Commits
master
...
haskell-up
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a01c66acaf | ||
|
|
875a3a5dbe | ||
|
|
40b76b77e7 | ||
|
|
dcbf4fd862 | ||
|
|
af25c7891d | ||
|
|
ecc90485e0 | ||
|
|
582a47d164 | ||
|
|
35bbabcc81 | ||
|
|
02f183acd1 | ||
|
|
3fb2ae8756 | ||
|
|
c022230402 | ||
|
|
0d161d6bf7 | ||
|
|
401a7b2ea1 | ||
|
|
24d928ae66 | ||
|
|
6e8192fc92 | ||
|
|
2e8fa702e3 | ||
|
|
b228fd3bc9 | ||
|
|
14d9e11267 | ||
|
|
c745615671 | ||
|
|
1c75264348 | ||
|
|
ebb10ffb74 | ||
|
|
abb4be61e6 | ||
|
|
4479c174f3 | ||
|
|
f3c6c12566 | ||
|
|
f3f3cd0559 | ||
|
|
d174047339 | ||
|
|
257e6594e1 | ||
|
|
653400d170 | ||
|
|
65bb1f6dc7 | ||
|
|
e7bb4f9d44 | ||
|
|
8b35ab5a46 | ||
|
|
b91a28c831 | ||
|
|
135ddd3065 | ||
|
|
dfe7bd1c25 | ||
|
|
af7c2eea09 | ||
|
|
8c4ee9bc8c | ||
|
|
a9a8567628 | ||
|
|
e021218908 | ||
|
|
8175001f58 | ||
|
|
d6d0eb9bcd | ||
|
|
4111be5b73 | ||
|
|
37ecebb9e4 | ||
|
|
8597ae6ab7 | ||
|
|
8bf9cb3ab6 | ||
|
|
1027cca0b0 | ||
|
|
e665ed3e2d | ||
|
|
2d1fe98d85 | ||
|
|
41d990226a | ||
|
|
a007df4e16 | ||
|
|
2b2a0aac02 | ||
|
|
7554ac32f6 | ||
|
|
854435d497 | ||
|
|
667abdf0a6 | ||
|
|
5cbd2e121e | ||
|
|
2bfd7ae693 | ||
|
|
c7c781f9ad | ||
|
|
3ad3df3213 | ||
|
|
76ae1ad467 | ||
|
|
72169cece3 | ||
|
|
cfda1c7ca7 | ||
|
|
f6a92561fc | ||
|
|
9745c05cf8 | ||
|
|
c94f146ea5 | ||
|
|
6c41d523f9 | ||
|
|
944495f675 | ||
|
|
3314fcd1d2 | ||
|
|
d78e1df3ac | ||
|
|
0c42aa2b8b | ||
|
|
b9f877b238 | ||
|
|
11e0f4c678 | ||
|
|
179eff5553 | ||
|
|
e7f659cb57 | ||
|
|
edee302be2 | ||
|
|
677378b435 | ||
|
|
51f3af4a42 | ||
|
|
de6e972b4f | ||
|
|
375f55b9dc | ||
|
|
b21c1bfba8 | ||
|
|
64cad13777 | ||
|
|
cea0d6d290 | ||
|
|
cbc29ba88a | ||
|
|
329a84bda4 | ||
|
|
039e7d4321 | ||
|
|
895aa19bba | ||
|
|
0e4dda44be | ||
|
|
3ba16f9b5e | ||
|
|
3df67b93de | ||
|
|
8ed8d7cd2f | ||
|
|
2f153b164f | ||
|
|
f4290a712d | ||
|
|
8125c8f952 | ||
|
|
8fb5f5f8a0 | ||
|
|
fc0af5e28b | ||
|
|
a9d59c8d18 | ||
|
|
13e05d0bc2 | ||
|
|
8f7c86887d | ||
|
|
b29c6b8f9c | ||
|
|
55bbb3b7c9 | ||
|
|
b04bb80014 | ||
|
|
93cb8fb139 | ||
|
|
0100807fdc | ||
|
|
89e7632843 | ||
|
|
2c5eb85631 | ||
|
|
6d4cf664be | ||
|
|
8b057c40b5 | ||
|
|
754e7391d2 | ||
|
|
1f46410e29 | ||
|
|
ffd5c0f686 | ||
|
|
b3d151a51a | ||
|
|
2f89f5073d | ||
|
|
c4be39b04d | ||
|
|
c3f4056d8d | ||
|
|
b455c9d332 | ||
|
|
e1647a36da | ||
|
|
3f450dd19b | ||
|
|
841b8f5979 | ||
|
|
4cd1cda986 | ||
|
|
ad3c53a521 | ||
|
|
5bf4154b18 | ||
|
|
b519de25a3 | ||
|
|
eaa79b902b | ||
|
|
79f8691e65 | ||
|
|
3aa4d2d68f | ||
|
|
c3d77c8517 | ||
|
|
5d1e564405 | ||
|
|
e58f85ec8d | ||
|
|
877e5290de | ||
|
|
4be3abfe1f | ||
|
|
5a6a8cded2 | ||
|
|
f25caecf7a | ||
|
|
dbb43d1da6 | ||
|
|
86b532d04c | ||
|
|
08f8496ff9 | ||
|
|
ab222dd942 | ||
|
|
bb0a89d397 | ||
|
|
b6af5b43ea | ||
|
|
14328fc8f6 | ||
|
|
7ad02aa7c0 | ||
|
|
311763be2d | ||
|
|
36a316ea88 | ||
|
|
2a38509e0f | ||
|
|
8707585398 | ||
|
|
790c31e061 | ||
|
|
1b8e493a4c | ||
|
|
58673d30d4 | ||
|
|
beccc5847a | ||
|
|
3adf419aa4 | ||
|
|
b52f4adde7 | ||
|
|
bd16497ad4 | ||
|
|
ffc266319e | ||
|
|
fa192c1915 | ||
|
|
78d4679404 | ||
|
|
a05b62f756 | ||
|
|
2e37254a0d | ||
|
|
e1f0c80964 | ||
|
|
3ae4f100fa | ||
|
|
b92b966ae0 | ||
|
|
3f0da4e5f2 | ||
|
|
338603c0d4 | ||
|
|
3c1773c843 | ||
|
|
826465b345 | ||
|
|
ad05226f3d | ||
|
|
9da5406197 | ||
|
|
21dcddba97 | ||
|
|
f8b6781bea | ||
|
|
e83add3066 | ||
|
|
d4fadf1842 | ||
|
|
48ea35d0b8 | ||
|
|
5903a7f72d | ||
|
|
a428929e72 | ||
|
|
d6499c4e3c | ||
|
|
9728ac8f06 | ||
|
|
1ae532eb77 | ||
|
|
5df69cc621 | ||
|
|
6046acb728 | ||
|
|
87283c7045 | ||
|
|
ea31f133da | ||
|
|
5a5cd26432 | ||
|
|
d47687c04d | ||
|
|
08f0950a50 | ||
|
|
f4c6e009f9 | ||
|
|
a1beaf2803 | ||
|
|
3594aefa35 | ||
|
|
3b19ff96a1 | ||
|
|
19573606e8 | ||
|
|
7632f2c418 | ||
|
|
b393efc343 | ||
|
|
eef2380c20 | ||
|
|
e3c24ee8db | ||
|
|
116208d9cf | ||
|
|
a332844436 | ||
|
|
86ed8342de | ||
|
|
31f495afe9 | ||
|
|
da941382a6 | ||
|
|
920b50911d | ||
|
|
43159787fd | ||
|
|
a603053a30 | ||
|
|
5865c4be2c | ||
|
|
b72ec6bdee | ||
|
|
c818fb7e53 | ||
|
|
88daa4b688 | ||
|
|
d82c0d269e | ||
|
|
0b6b59708c | ||
|
|
9220258efb | ||
|
|
a8560ee995 | ||
|
|
cfdc80cdd7 | ||
|
|
523ffbe971 | ||
|
|
3aed88a715 | ||
|
|
1c3465fcb7 | ||
|
|
54112f9a6b | ||
|
|
92cbc9ca81 | ||
|
|
6a3c990a99 | ||
|
|
39ee207b1d | ||
|
|
d64edb78f9 | ||
|
|
54726cb9e3 | ||
|
|
b7659cdb62 | ||
|
|
bcf85400c4 | ||
|
|
e3927c07f7 | ||
|
|
8486e8a7b3 | ||
|
|
4f0970401a | ||
|
|
9959ce2f44 | ||
|
|
3f085fe61c | ||
|
|
f61423d87c | ||
|
|
4aeebf57da | ||
|
|
4c7647a6d3 | ||
|
|
afda14f1d4 | ||
|
|
e9af2ed4be | ||
|
|
2735e861f0 | ||
|
|
92ca07b6a5 | ||
|
|
e82d6b924f | ||
|
|
94aa8f8960 | ||
|
|
75496eb67a | ||
|
|
9c55dbeccf | ||
|
|
cef090d1ab | ||
|
|
4132fb9736 | ||
|
|
98c655c09a | ||
|
|
3f49232af6 | ||
|
|
7100632254 | ||
|
|
b078be0041 | ||
|
|
92779d4478 | ||
|
|
070e7a021f | ||
|
|
71b5964ed6 | ||
|
|
00a35c51c7 | ||
|
|
dbd129b241 | ||
|
|
26066e1e2b | ||
|
|
60f1d07046 | ||
|
|
a5e06de0c4 | ||
|
|
8833922b3e | ||
|
|
d0e8fdd1bd | ||
|
|
9122c84035 | ||
|
|
f479cbacab | ||
|
|
f87fabdc91 | ||
|
|
f94cecf640 | ||
|
|
fc0932307c | ||
|
|
ffbec19b05 | ||
|
|
435e7264b6 | ||
|
|
480f0726e7 | ||
|
|
4d0ae93287 | ||
|
|
81ef0ce73b | ||
|
|
24211dd381 | ||
|
|
8b99224c3e | ||
|
|
d56383f426 | ||
|
|
2836c4b1f4 | ||
|
|
16a3b0c7d9 | ||
|
|
7db16b7a40 | ||
|
|
85b5c86354 | ||
|
|
3f6c25ad80 | ||
|
|
a8988fede0 | ||
|
|
68f69e8b05 | ||
|
|
9bbb567bb9 | ||
|
|
5f8a3af9eb | ||
|
|
b25e3e8489 | ||
|
|
a220c38f46 | ||
|
|
fdbcfd8b47 | ||
|
|
eff6c0e60e | ||
|
|
5a03008b47 | ||
|
|
6dd4da168b | ||
|
|
4cbc2251c7 | ||
|
|
a8233a24e1 | ||
|
|
da4f3989f0 | ||
|
|
e0497b3d10 | ||
|
|
a49c815e3d | ||
|
|
f213d6af36 | ||
|
|
aba749b3bb | ||
|
|
e64658550c | ||
|
|
07d2edc929 | ||
|
|
b52ea84a40 | ||
|
|
f394a62a65 | ||
|
|
ad66699b02 | ||
|
|
084d450fa7 | ||
|
|
ec138222f7 | ||
|
|
5a13f175d1 | ||
|
|
117625921f | ||
|
|
2c44031fe0 | ||
|
|
d34ba4d817 | ||
|
|
e34ce374a1 | ||
|
|
a00be59a94 | ||
|
|
ab20ff9b2f | ||
|
|
995d34a153 | ||
|
|
9208b19946 | ||
|
|
9029909ba3 | ||
|
|
b82be3afe6 | ||
|
|
248b70dc3b | ||
|
|
0baca65faa | ||
|
|
b1528a1baf | ||
|
|
200316c491 | ||
|
|
3c4f10205c | ||
|
|
827c840fb1 | ||
|
|
095f6729ca | ||
|
|
1352e67378 | ||
|
|
0814616eaf | ||
|
|
f70431ea67 | ||
|
|
8383c3f904 | ||
|
|
cac8c10692 | ||
|
|
739281cf7a | ||
|
|
ef5a2561e3 | ||
|
|
a5f1afdf0f | ||
|
|
cbe86ed815 | ||
|
|
5617c0ba47 | ||
|
|
caf8327196 | ||
|
|
89b9cdcbba | ||
|
|
3c3354afa3 | ||
|
|
8cc9c34bcb | ||
|
|
8c3d964e79 | ||
|
|
cadaf97e4e | ||
|
|
593c21a56a | ||
|
|
6306c15610 | ||
|
|
884892c2c1 | ||
|
|
627dd785ad | ||
|
|
180157dfa4 | ||
|
|
0d1190abf7 | ||
|
|
c24d964453 | ||
|
|
9ec4c8e1b6 | ||
|
|
b32a192bcf | ||
|
|
338f88c94a | ||
|
|
a262ae9474 | ||
|
|
9f367b9af0 | ||
|
|
9227423f57 | ||
|
|
79b85a6061 | ||
|
|
07837352f5 | ||
|
|
422b020e60 | ||
|
|
d58b43bad8 | ||
|
|
e4e45c45d7 | ||
|
|
a0811c1859 | ||
|
|
41dec8b399 | ||
|
|
4b9529d490 | ||
|
|
eca559367a | ||
|
|
d217b14de6 | ||
|
|
5aafdde3a0 | ||
|
|
9eb68607ef | ||
|
|
430963bdf1 | ||
|
|
4ca20a7582 | ||
|
|
39ad1cb021 | ||
|
|
6ad984ce3d | ||
|
|
8b0587a6d1 | ||
|
|
668847dd15 | ||
|
|
40f0c70593 | ||
|
|
265fda252f | ||
|
|
e1eeed9ea6 | ||
|
|
d3928686c3 | ||
|
|
fb629a8712 | ||
|
|
1fa9545db7 | ||
|
|
7b203a8cdf | ||
|
|
3080e495c5 | ||
|
|
41a68a1180 | ||
|
|
2c84961290 | ||
|
|
eb378a86bf | ||
|
|
b8880df8ac | ||
|
|
79a87d6750 | ||
|
|
61b9e0b5f4 | ||
|
|
2cb1137ad8 | ||
|
|
dc341cc322 | ||
|
|
7ef227eef6 | ||
|
|
df2259de34 | ||
|
|
fbfe792c92 | ||
|
|
18c743b5c9 | ||
|
|
1912d24658 | ||
|
|
8b3bed966a | ||
|
|
8ca5572327 | ||
|
|
f1cd3a542b | ||
|
|
28ea7d3e94 | ||
|
|
015d2be85f | ||
|
|
dcd5f66215 | ||
|
|
c990defdc1 | ||
|
|
ef80ea96c0 | ||
|
|
70b3bb6148 | ||
|
|
9f87b41bbc | ||
|
|
9eab5d0982 | ||
|
|
31b3654bd0 | ||
|
|
fd7118d37f | ||
|
|
3f0c2996b0 | ||
|
|
58545c6507 | ||
|
|
bd20e4d544 | ||
|
|
40fdae7a02 | ||
|
|
4dfddc5e2d | ||
|
|
a42cf3de47 | ||
|
|
98472212df | ||
|
|
1166f6d5fe | ||
|
|
3100f81d11 | ||
|
|
cfae19b0e2 | ||
|
|
4c79be7954 | ||
|
|
a5d168008d | ||
|
|
8960ba3747 | ||
|
|
4bac4c007b | ||
|
|
449e33902d | ||
|
|
cfb525e8c7 | ||
|
|
90e108c717 | ||
|
|
a9c7731b36 | ||
|
|
0e972a63ef | ||
|
|
db6cdf8674 | ||
|
|
83742b85ed | ||
|
|
7fe7f911c5 | ||
|
|
379b9966b6 | ||
|
|
8e147a7423 | ||
|
|
e78b52efb2 | ||
|
|
9db26c0d78 | ||
|
|
44c90f8b11 | ||
|
|
6ae08b4b8c | ||
|
|
e71086f1ad | ||
|
|
a9a7b4af3b | ||
|
|
6e6ee420cc | ||
|
|
4f5821131d | ||
|
|
7c90da01b9 | ||
|
|
87258e4b4c | ||
|
|
07855b2421 | ||
|
|
0ec79f0885 | ||
|
|
9adad48243 | ||
|
|
2e0690adfb | ||
|
|
e9724bc92f | ||
|
|
3b2f54505e | ||
|
|
5498d6a4fe | ||
|
|
b0f9043b9f | ||
|
|
fcd62a690f | ||
|
|
fe8e3efa4f |
@@ -3,35 +3,6 @@
|
||||
Nixpkgs provides a variety of wrapper functions that help build commonly useful derivations.
|
||||
Like [`stdenv.mkDerivation`](#sec-using-stdenv), each of these build helpers creates a derivation, but the arguments passed are different (usually simpler) from those required by `stdenv.mkDerivation`.
|
||||
|
||||
## Arguments with finalAttrs {#trivial-builder-finalAttrs}
|
||||
|
||||
In parameters that reference this section, you may either pass the value itself,
|
||||
or a function that produces it.
|
||||
When it's a function the argument value is [`finalAttrs`] from [`mkDerivation`].
|
||||
|
||||
Typically both the *attributes* and *script* arguments support this, simultaneously if needed.
|
||||
|
||||
::: {.example #ex-trivial-builder-finalAttrs}
|
||||
# Using `finalAttrs` in a build helper
|
||||
|
||||
```nix
|
||||
runCommand "hi" (finalAttrs: { passthru.exe = "${finalAttrs.finalPackage}/bin/hi"; }) ''
|
||||
mkdir -p $out/bin
|
||||
substitute ${./hi.foo} $out/bin/hi --replace-fail "@foo@" ${lib.getExe foo}
|
||||
''
|
||||
```
|
||||
|
||||
This creates a package with an executable script that's in the standard `bin/` directory,
|
||||
but also convenient to interpolate without reliance on `$PATH`, e.g assuming the result of the above is in binding `hi`:
|
||||
```nix
|
||||
''
|
||||
echo START_GREETING
|
||||
${hi.exe} --rude
|
||||
echo END_GREETING
|
||||
''
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## `runCommandWith` {#trivial-builder-runCommandWith}
|
||||
|
||||
@@ -52,10 +23,8 @@ runCommandWith :: {
|
||||
name :: name;
|
||||
stdenv? :: Derivation;
|
||||
runLocal? :: Bool;
|
||||
derivationArgs? :: { ... } | finalAttrs@{ finalPackage :: Derivation, ... } -> { ... };
|
||||
}
|
||||
-> (String | finalAttrs@{ finalPackage :: Derivation, ... } -> String)
|
||||
-> Derivation
|
||||
derivationArgs? :: { ... };
|
||||
} -> String -> Derivation
|
||||
```
|
||||
|
||||
### Inputs {#trivial-builder-runCommandWith-Inputs}
|
||||
@@ -78,10 +47,10 @@ runCommandWith :: {
|
||||
`stdenv` (Derivation)
|
||||
: The [standard environment](#chap-stdenv) to use, defaulting to `pkgs.stdenv`.
|
||||
|
||||
`derivationArgs` (Attribute set *or* [function from `finalAttrs`](#trivial-builder-finalAttrs))
|
||||
`derivationArgs` (Attribute set)
|
||||
: Additional arguments for [`mkDerivation`](#sec-using-stdenv).
|
||||
|
||||
`buildCommand` (String *or* [function from `finalAttrs`](#trivial-builder-finalAttrs))
|
||||
`buildCommand` (String)
|
||||
: Shell commands to run in the derivation builder.
|
||||
|
||||
::: {.note}
|
||||
@@ -140,10 +109,10 @@ While the type signature(s) differ from [`runCommandWith`], individual arguments
|
||||
`name` (String)
|
||||
: The derivation's name
|
||||
|
||||
`derivationArgs` (Attribute set *or* [function from `finalAttrs`](#trivial-builder-finalAttrs))
|
||||
`derivationArgs` (Attribute set)
|
||||
: Additional parameters passed to [`mkDerivation`]
|
||||
|
||||
`buildCommand` (String *or* [function from `finalAttrs`](#trivial-builder-finalAttrs))
|
||||
`buildCommand` (String)
|
||||
: The command(s) run to build the derivation.
|
||||
|
||||
|
||||
@@ -933,6 +902,3 @@ produces an output path `/nix/store/<hash>-runtime-references` containing
|
||||
|
||||
but none of `hello`'s dependencies because those are not referenced directly
|
||||
by `hi`'s output.
|
||||
|
||||
[`finalAttrs`]: #mkderivation-recursive-attributes
|
||||
[`mkDerivation`]: #sec-using-stdenv
|
||||
|
||||
@@ -176,7 +176,5 @@ stdenvNoCC.mkDerivation (
|
||||
manpage-urls = callPackage ../tests/manpage-urls.nix { };
|
||||
};
|
||||
};
|
||||
|
||||
meta.license = lib.licenses.mit;
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
# `installAgentSkills` {#installAgentSkills}
|
||||
|
||||
This hook automatically installs LLM agent skills into the proper location in `$out/share/skills/($pname|$base)/$skill/`.
|
||||
|
||||
Agents do not scan package outputs themselves. Expose skills via `environment.pathsToLink = [ "/share/skills" ];` and symlink the wanted `share/skills/<pname>/<skill>` directories into the agent's skill directory (e.g. `~/.claude/skills/`).
|
||||
|
||||
The automatic behavior of the hook can be disabled by setting the `dontInstallAgentSkills` variable to true.
|
||||
|
||||
Additionally, it exposes the `installSkill` function that can be used from `postInstall`
|
||||
|
||||
## `installSkill` {#installAgentSkills-installSkill}
|
||||
|
||||
The `installSkill` function takes one or two arguments: a directory to copy to the install location, and an optional base directory.
|
||||
|
||||
NB: passing a SKILL.md file directly as the first argument will fail as skills often contain other examples and tooling within the same directory.
|
||||
|
||||
### Example Usage {#installAgentSkills-installSkill-exampleusage}
|
||||
|
||||
```nix
|
||||
{
|
||||
nativeBuildInputs = [ installAgentSkills ];
|
||||
|
||||
postInstall = ''
|
||||
installSkill skills/skill-xyz
|
||||
'';
|
||||
# installs to $out/share/skills/$pname/skill-xyz
|
||||
|
||||
# OR
|
||||
|
||||
postInstall = ''
|
||||
installSkill skills/skill-xyz random-base
|
||||
'';
|
||||
# installs to $out/share/skills/random-base/skill-xyz
|
||||
}
|
||||
```
|
||||
|
||||
Where `skills/skill-xyz` may look like:
|
||||
|
||||
```
|
||||
skills/skill-xyz:
|
||||
- SKILL.md
|
||||
- scripts/
|
||||
- references/
|
||||
- assets/
|
||||
- ...
|
||||
```
|
||||
|
||||
@@ -247,9 +247,6 @@
|
||||
{
|
||||
"file": "hooks/haredo.section.md"
|
||||
},
|
||||
{
|
||||
"file": "hooks/installAgentSkills.section.md"
|
||||
},
|
||||
{
|
||||
"file": "hooks/installShellFiles.section.md"
|
||||
},
|
||||
|
||||
@@ -139,7 +139,7 @@ running NixOS integration tests on the builder (macOS 15+, M3 or newer).
|
||||
```nix
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "https://channels.nixos.org/nixpkgs-26.05-darwin/nixexprs.tar.zst";
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-22.11-darwin";
|
||||
darwin.url = "github:nix-darwin/nix-darwin/master";
|
||||
darwin.inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
@@ -140,9 +140,6 @@
|
||||
"ex-testEqualArrayOrMap-test-function-add-cowbell": [
|
||||
"index.html#ex-testEqualArrayOrMap-test-function-add-cowbell"
|
||||
],
|
||||
"ex-trivial-builder-finalAttrs": [
|
||||
"index.html#ex-trivial-builder-finalAttrs"
|
||||
],
|
||||
"ex-writeShellApplication": [
|
||||
"index.html#ex-writeShellApplication"
|
||||
],
|
||||
@@ -170,15 +167,6 @@
|
||||
"inkscape-plugins": [
|
||||
"index.html#inkscape-plugins"
|
||||
],
|
||||
"installAgentSkills": [
|
||||
"index.html#installAgentSkills"
|
||||
],
|
||||
"installAgentSkills-installSkill": [
|
||||
"index.html#installAgentSkills-installSkill"
|
||||
],
|
||||
"installAgentSkills-installSkill-exampleusage": [
|
||||
"index.html#installAgentSkills-installSkill-exampleusage"
|
||||
],
|
||||
"installfonts": [
|
||||
"index.html#installfonts"
|
||||
],
|
||||
@@ -1011,9 +999,6 @@
|
||||
"treefmt": [
|
||||
"index.html#treefmt"
|
||||
],
|
||||
"trivial-builder-finalAttrs": [
|
||||
"index.html#trivial-builder-finalAttrs"
|
||||
],
|
||||
"typst": [
|
||||
"index.html#typst",
|
||||
"doc/languages-frameworks/typst.section.md#typst"
|
||||
|
||||
@@ -47,8 +47,6 @@
|
||||
- `bundlerApp` now sets `__structuredAttrs = true` for its result package.
|
||||
Out-of-tree packages passing `postBuild` to `bundlerApp` should examine if their `postBuild` commands are compatible with structured attributes.
|
||||
|
||||
- `praat` has been updated to `7.x.x`, which changes the location of the settings file, buttons file, and plug-ins to standardized locations for each platform. This is a breaking change for plug-in writers and users of the sendpraat subroutine.
|
||||
|
||||
- `databricks-cli` has been updated from `0.290.2` to `1.x.x`, the first major release. OAuth tokens for interactive logins (`auth_type = databricks-cli`) are now stored in the OS-native secure store by default (Secret Service on Linux) instead of `~/.databricks/token-cache.json`; cached tokens from older versions are not migrated, so run `databricks auth login` once per profile after upgrading. To keep the previous file-backed storage, set `DATABRICKS_AUTH_STORAGE=plaintext` or add `auth_storage = plaintext` under `[__settings__]` in `~/.databrickscfg`. Additionally, the `vector_search_endpoints` DABs resource renamed `min_qps` to `target_qps` (and the `vector-search-endpoints` command renamed `--min-qps` to `--target-qps`). See the [upstream changelog](https://github.com/databricks/cli/blob/main/CHANGELOG.md) for details.
|
||||
|
||||
- Gradle 7 has been removed because it is end-of-life. Please [upgrade to a newer version of Gradle](https://docs.gradle.org/current/userguide/upgrading_version_7.html).
|
||||
@@ -62,13 +60,8 @@
|
||||
- `gotosocial` has been updated to 0.22.0. This release contains a very long database migration, which should not be cancelled or interrupted under any circumstances.
|
||||
- Postgres users: Following the migration, if you encounter slowdown on Postgres specifically (ie., timing out while loading timelines) you may need to run some manual database maintenance steps. Please check https://docs.gotosocial.org/en/stable/admin/database_maintenance/#postgres.
|
||||
|
||||
- `lego` has been updated to 5.x, which has a changed CLI and state layout.
|
||||
See [upstream's v4 to v5 CLI migration guide](https://go-acme.github.io/lego/migration/cli/) for details.
|
||||
|
||||
- `xsecurelock` no longer supports authentication via htaccess files (`~/.xsecurelock.pw`) or via the `pamtester` program by default. Only the recommended PAM module is supported unless rebuilt with `withHtaccess` or `withPamtester`.
|
||||
|
||||
- `keychain` has been updated to 3.x.x, which is a major and breaking rewrite of the program, including its CLI arguments. See [v3](https://github.com/danielrobbins/keychain/releases/tag/3.0.0) release notes for details and migration.
|
||||
|
||||
- `python3Packages.django-health-check` has been updated to major version 4. See its [migration guide](https://codingjoe.dev/django-health-check/migrate-to-v4/) and [changelog](https://github.com/codingjoe/django-health-check/releases/tag/4.0.0) for breaking changes.
|
||||
|
||||
- `jmtpfs` has been removed due to lack of maintenance and fuse3 support.
|
||||
@@ -104,8 +97,6 @@
|
||||
|
||||
- `tengine` has been removed as it has seen seriously delayed responses to security vulnerabilities.
|
||||
|
||||
- `jellyfin` has been upgraded to major version 12, which contains breaking changes. See the [upstream blog post](https://jellyfin.org/posts/jellyfin-release-12.0) for more information on how to safely upgrade.
|
||||
|
||||
- `writeTextFile` now uses structured attributes and `passAsFile` is no longer used or supported.
|
||||
|
||||
- `nix-serve-ng` (and `haskellPackages.nix-serve-ng`) is now built against Lix instead of CppNix, following upstream which has switched to Lix as its supported Nix implementation.
|
||||
@@ -164,8 +155,6 @@
|
||||
|
||||
- `pdfium` is now built from source instead of packaging prebuilt binaries. `pdfium-binaries` has been renamed to `pdfium`, and `pdfium-binaries-v8` has been removed.
|
||||
|
||||
- `iocaine` has been updated to `3.5.1`.
|
||||
|
||||
- `librest` providing 0.7 ABI was removed. `librest_1_0` providing 1.0 ABI was renamed to `librest` and `librest_1_0` was kept as an alias.
|
||||
|
||||
- `luaPackages.lrexlib-pcre` has been removed as part of the process to fully migrate from the end-of-life PRCE library to PCRE2. `luaPackages.lrexlib-pcre2` and multiple other versions of lrexlib can be used instead.
|
||||
@@ -204,10 +193,6 @@
|
||||
|
||||
- netbox plugins have been moved from the python3Packages to the netboxPlugins package set.
|
||||
|
||||
- The `services.freenet` module has been renamed to `services.hyphanet`.
|
||||
For more information, [see the project's website](https://www.hyphanet.org/freenet-renamed-to-hyphanet.html).
|
||||
The `freenet` user, group and `/var/lib/freenet` data directory are renamed to `hyphanet` on systems with `system.stateVersion` at `26.11` or newer.
|
||||
|
||||
## Other Notable Changes {#sec-nixpkgs-release-26.11-notable-changes}
|
||||
|
||||
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
|
||||
|
||||
289
lib/modules.nix
289
lib/modules.nix
@@ -31,6 +31,7 @@ let
|
||||
length
|
||||
mapAttrs
|
||||
mapAttrsToList
|
||||
mapAttrsRecursiveCond
|
||||
min
|
||||
optional
|
||||
optionalAttrs
|
||||
@@ -253,7 +254,7 @@ let
|
||||
|
||||
# This function takes an empty attrset as an argument.
|
||||
# It could theoretically be replaced with its body,
|
||||
# but such a binding is avoided to allow for earlier garbage collection.
|
||||
# but such a binding is avoided to allow for earlier grabage collection.
|
||||
doCollect =
|
||||
{ }:
|
||||
collectModules class (specialArgs.modulesPath or "") (regularModules ++ [ internalModule ]) (
|
||||
@@ -278,11 +279,7 @@ let
|
||||
let
|
||||
|
||||
# For definitions that have an associated option
|
||||
declaredConfig =
|
||||
let
|
||||
recurse = mapAttrs (n: v: if isOption v then v.value else recurse v);
|
||||
in
|
||||
recurse options;
|
||||
declaredConfig = mapAttrsRecursiveCond (v: !isOption v) (_: v: v.value) options;
|
||||
|
||||
# If freeformType is set, this is for definitions that don't have an associated option
|
||||
freeformConfig =
|
||||
@@ -567,23 +564,12 @@ let
|
||||
modulesPath:
|
||||
{ disabled, modules }:
|
||||
let
|
||||
isDisabledModule = isDisabled modulesPath disabled;
|
||||
keyFilter = filter (attrs: !isDisabledModule attrs);
|
||||
keyFilter = filter (attrs: !isDisabled modulesPath disabled attrs);
|
||||
in
|
||||
catAttrs "module" (
|
||||
genericClosure (
|
||||
if disabled == [ ] then
|
||||
{
|
||||
startSet = modules;
|
||||
operator = attrs: attrs.modules;
|
||||
}
|
||||
else
|
||||
{
|
||||
startSet = keyFilter modules;
|
||||
operator = attrs: keyFilter attrs.modules;
|
||||
}
|
||||
)
|
||||
);
|
||||
catAttrs "module" (genericClosure {
|
||||
startSet = keyFilter modules;
|
||||
operator = attrs: keyFilter attrs.modules;
|
||||
});
|
||||
|
||||
toGraph =
|
||||
modulesPath:
|
||||
@@ -645,28 +631,6 @@ let
|
||||
: 3\. Function argument
|
||||
*/
|
||||
unifyModuleSyntax =
|
||||
let
|
||||
attrsToRemove = [
|
||||
"_class"
|
||||
"_file"
|
||||
"key"
|
||||
"disabledModules"
|
||||
"imports"
|
||||
"options"
|
||||
"config"
|
||||
"meta"
|
||||
"freeformType"
|
||||
];
|
||||
shorthandAttrsToRemove = [
|
||||
"_class"
|
||||
"_file"
|
||||
"key"
|
||||
"disabledModules"
|
||||
"require"
|
||||
"imports"
|
||||
"freeformType"
|
||||
];
|
||||
in
|
||||
file: key: m:
|
||||
let
|
||||
addMeta =
|
||||
@@ -690,7 +654,17 @@ let
|
||||
in
|
||||
if m ? config || m ? options then
|
||||
let
|
||||
badAttrs = removeAttrs m attrsToRemove;
|
||||
badAttrs = removeAttrs m [
|
||||
"_class"
|
||||
"_file"
|
||||
"key"
|
||||
"disabledModules"
|
||||
"imports"
|
||||
"options"
|
||||
"config"
|
||||
"meta"
|
||||
"freeformType"
|
||||
];
|
||||
in
|
||||
if badAttrs != { } then
|
||||
throw "Module `${key}' has an unsupported attribute `${head (attrNames badAttrs)}'. This is caused by introducing a top-level `config' or `options' attribute. Add configuration attributes immediately on the top level instead, or move all of them (namely: ${toString (attrNames badAttrs)}) into the explicit `config' attribute."
|
||||
@@ -716,7 +690,17 @@ let
|
||||
disabledModules = m.disabledModules or [ ];
|
||||
imports = m.require or [ ] ++ m.imports or [ ];
|
||||
options = { };
|
||||
config = addFreeformType (removeAttrs m shorthandAttrsToRemove);
|
||||
config = addFreeformType (
|
||||
removeAttrs m [
|
||||
"_class"
|
||||
"_file"
|
||||
"key"
|
||||
"disabledModules"
|
||||
"require"
|
||||
"imports"
|
||||
"freeformType"
|
||||
]
|
||||
);
|
||||
};
|
||||
|
||||
applyModuleArgsIfFunction =
|
||||
@@ -892,13 +876,15 @@ let
|
||||
# We're descending into attribute ‘name’.
|
||||
let
|
||||
loc = prefix ++ [ name ];
|
||||
defns = pushedDownDefinitionsByName.${name} or [ ];
|
||||
defns' = rawDefinitionsByName.${name} or [ ];
|
||||
isOptionDecl =
|
||||
optionDecls = filter (
|
||||
m:
|
||||
m.options ? _type
|
||||
&& (m.options._type == "option" || throwDeclarationTypeError loc m.options._type m._file);
|
||||
&& (m.options._type == "option" || throwDeclarationTypeError loc m.options._type m._file)
|
||||
) decls;
|
||||
in
|
||||
if all isOptionDecl decls then
|
||||
if length optionDecls == length decls then
|
||||
let
|
||||
opt = fixupOptionType loc (mergeOptionDecls loc decls);
|
||||
in
|
||||
@@ -906,35 +892,7 @@ let
|
||||
matchedOptions = evalOptionValue loc opt defns';
|
||||
unmatchedDefns = [ ];
|
||||
}
|
||||
# this may look like duplicate computations are performed, but testing
|
||||
# with a minimal NixOS config (defined below):
|
||||
#
|
||||
# 1. in 87% of cases, the above `all isOptionDecl decls` passes and we
|
||||
# short-circuit
|
||||
# 2. In 12% of cases, there's only one decl and we short-circuit
|
||||
# 3. in another 1% of cases, we loop again and short-circuit when
|
||||
# `any isOptionDecl decls` fails
|
||||
# 4. In the final case, we pass the `any` and have to refilter. This
|
||||
# only triggers in 1 of the 48000 calls to this function.
|
||||
#
|
||||
# The minimal config was defined with this expression:
|
||||
#
|
||||
# let nixos = import ./nixos/lib/eval-config.nix {
|
||||
# modules = [
|
||||
# ./nixos/modules/profiles/minimal.nix
|
||||
# {
|
||||
# fileSystems."/" = {
|
||||
# device = "/dev/sda1";
|
||||
# fsType = "ext4";
|
||||
# };
|
||||
# boot.loader.grub.devices = [ "/dev/sda" ];
|
||||
# }
|
||||
# ];
|
||||
# }; in nixos.config.system.build.toplevel
|
||||
else if length decls != 1 && any isOptionDecl decls then
|
||||
let
|
||||
optionDecls = filter isOptionDecl decls;
|
||||
in
|
||||
else if optionDecls != [ ] then
|
||||
if
|
||||
all (x: x.options.type.name or null == "submodule") optionDecls
|
||||
# Raw options can only be merged into submodules. Merging into
|
||||
@@ -962,7 +920,7 @@ let
|
||||
(head optionDecls).options.type.description or "<no description>"
|
||||
}' does not support nested options.\n${showRawDecls loc nonOptions}"
|
||||
else
|
||||
mergeModules' loc decls (pushedDownDefinitionsByName.${name} or [ ])
|
||||
mergeModules' loc decls defns
|
||||
) declsByName;
|
||||
|
||||
matchedOptions = mapAttrs (n: v: v.matchedOptions) resultsByName;
|
||||
@@ -1051,17 +1009,19 @@ let
|
||||
: 2\. Function argument
|
||||
*/
|
||||
mergeOptionDecls =
|
||||
loc:
|
||||
loc: opts:
|
||||
foldl'
|
||||
(
|
||||
res: opt:
|
||||
let
|
||||
t = res.type;
|
||||
t' = opt.options.type;
|
||||
mergedType = t.typeMerge t'.functor;
|
||||
typesMergeable = mergedType != null;
|
||||
|
||||
typeSet =
|
||||
if res ? type && opt.options ? type then
|
||||
let
|
||||
mergedType = res.type.typeMerge opt.options.type.functor;
|
||||
in
|
||||
if mergedType != null then
|
||||
if opt.options ? type && res ? type then
|
||||
if typesMergeable then
|
||||
{
|
||||
type = mergedType;
|
||||
}
|
||||
@@ -1072,7 +1032,7 @@ let
|
||||
else
|
||||
{ };
|
||||
|
||||
bothHave = k: res ? ${k} && opt.options ? ${k};
|
||||
bothHave = k: opt.options ? ${k} && res ? ${k};
|
||||
in
|
||||
if bothHave "default" || bothHave "example" || bothHave "description" || bothHave "apply" then
|
||||
# Keep in sync with the same error above!
|
||||
@@ -1117,7 +1077,8 @@ let
|
||||
declarations = [ ];
|
||||
declarationPositions = [ ];
|
||||
options = [ ];
|
||||
};
|
||||
}
|
||||
opts;
|
||||
|
||||
/**
|
||||
Merge all the definitions of an option to produce the final
|
||||
@@ -1170,19 +1131,21 @@ let
|
||||
throw "The option `${showOption loc}' is read-only, but it's set multiple times. Definition values:${showDefs separateDefs}"
|
||||
else
|
||||
mergeDefinitions loc opt.type defs';
|
||||
|
||||
# Apply the 'apply' function to the merged value. This allows options to
|
||||
# yield a value computed from the definitions
|
||||
value = if opt ? apply then opt.apply res.mergedValue else res.mergedValue;
|
||||
|
||||
warnDeprecation =
|
||||
if (opt.type.deprecationMessage != null) then
|
||||
warn "The type `types.${opt.type.name}' of option `${showOption loc}' defined in ${showFiles opt.declarations} is deprecated. ${opt.type.deprecationMessage}"
|
||||
else
|
||||
x: x;
|
||||
|
||||
in
|
||||
(
|
||||
if opt.type.deprecationMessage != null then
|
||||
warn "The type `types.${opt.type.name}' of option `${showOption loc}' defined in ${showFiles opt.declarations} is deprecated. ${opt.type.deprecationMessage}" opt
|
||||
else
|
||||
opt
|
||||
)
|
||||
warnDeprecation opt
|
||||
// {
|
||||
value = addErrorContext "while evaluating the option `${showOption loc}':" (
|
||||
# Apply the 'apply' function to the merged value. This allows options to
|
||||
# yield a value computed from the definitions
|
||||
if opt ? apply then opt.apply res.mergedValue else res.mergedValue
|
||||
);
|
||||
value = addErrorContext "while evaluating the option `${showOption loc}':" value;
|
||||
inherit (res.defsFinal') highestPrio;
|
||||
definitions = catAttrs "value" res.defsFinal;
|
||||
files = catAttrs "file" res.defsFinal;
|
||||
@@ -1222,16 +1185,49 @@ let
|
||||
# Merge definitions of a value of a given type.
|
||||
mergeDefinitions = loc: type: defs: rec {
|
||||
defsFinal' =
|
||||
let
|
||||
# Process mkMerge and mkIf properties.
|
||||
defsNormalized = concatMap (
|
||||
m:
|
||||
map (
|
||||
value:
|
||||
if value._type or null == "definition" then
|
||||
value
|
||||
else
|
||||
{
|
||||
inherit (m) file;
|
||||
inherit value;
|
||||
}
|
||||
) (addErrorContext "while evaluating definitions from `${m.file}':" (dischargeProperties m.value))
|
||||
) defs;
|
||||
|
||||
# Process mkOverride properties.
|
||||
defsFiltered = filterOverrides' defsNormalized;
|
||||
|
||||
# Sort mkOrder properties.
|
||||
defsSorted =
|
||||
# Avoid sorting if we don't have to.
|
||||
if any (def: def.value._type or "" == "order") defsFiltered.values then
|
||||
sortProperties defsFiltered.values
|
||||
else
|
||||
defsFiltered.values;
|
||||
in
|
||||
# Fast path: the overwhelming majority of options have exactly one
|
||||
# definition whose value carries no property wrapper
|
||||
# (mkIf/mkMerge/mkOverride/mkOrder/definition). In that case the
|
||||
# discharge/filter/sort pipeline below is a no-op but still allocates
|
||||
# several intermediate lists and closures. Detect it up front and hand the
|
||||
# original singleton straight to the type merge.
|
||||
# discharge/filter/sort pipeline above is a no-op but still allocates
|
||||
# several intermediate lists and closures. Detect it up front and hand
|
||||
# the original singleton straight to the type merge. The let-bindings
|
||||
# above are lazy and thus never forced on this branch.
|
||||
if
|
||||
length defs == 1
|
||||
&& addErrorContext "while evaluating definitions from `${(head defs).file}':" (
|
||||
!(head defs).value ? _type
|
||||
&& (
|
||||
let
|
||||
d = head defs;
|
||||
in
|
||||
addErrorContext "while evaluating definitions from `${d.file}':" (
|
||||
!(isAttrs d.value && d.value ? _type)
|
||||
)
|
||||
)
|
||||
then
|
||||
{
|
||||
@@ -1239,33 +1235,6 @@ let
|
||||
highestPrio = defaultOverridePriority;
|
||||
}
|
||||
else
|
||||
let
|
||||
# Process mkMerge and mkIf properties.
|
||||
defsNormalized = concatMap (
|
||||
m:
|
||||
map (
|
||||
value:
|
||||
if value._type or null == "definition" then
|
||||
value
|
||||
else
|
||||
{
|
||||
inherit (m) file;
|
||||
inherit value;
|
||||
}
|
||||
) (addErrorContext "while evaluating definitions from `${m.file}':" (dischargeProperties m.value))
|
||||
) defs;
|
||||
|
||||
# Process mkOverride properties.
|
||||
defsFiltered = filterOverrides' defsNormalized;
|
||||
|
||||
# Sort mkOrder properties.
|
||||
defsSorted =
|
||||
# Avoid sorting if we don't have to.
|
||||
if any (def: def.value._type or "" == "order") defsFiltered.values then
|
||||
sortProperties defsFiltered.values
|
||||
else
|
||||
defsFiltered.values;
|
||||
in
|
||||
{
|
||||
values = defsSorted;
|
||||
inherit (defsFiltered) highestPrio;
|
||||
@@ -1377,16 +1346,13 @@ let
|
||||
val;
|
||||
in
|
||||
cfg:
|
||||
if cfg ? _type then
|
||||
if cfg._type == "if" then
|
||||
map (mapAttrsIfAttrs (n: v: mkIf cfg.condition v)) (pushDownProperties cfg.content)
|
||||
else if cfg._type == "merge" then
|
||||
concatMap pushDownProperties cfg.contents
|
||||
else if cfg._type == "override" then
|
||||
map (mapAttrsIfAttrs (n: v: mkOverride cfg.priority v)) (pushDownProperties cfg.content)
|
||||
# FIXME: handle mkOrder?
|
||||
else
|
||||
[ cfg ]
|
||||
if cfg._type or "" == "merge" then
|
||||
concatMap pushDownProperties cfg.contents
|
||||
else if cfg._type or "" == "if" then
|
||||
map (mapAttrsIfAttrs (n: v: mkIf cfg.condition v)) (pushDownProperties cfg.content)
|
||||
else if cfg._type or "" == "override" then
|
||||
map (mapAttrsIfAttrs (n: v: mkOverride cfg.priority v)) (pushDownProperties cfg.content)
|
||||
# FIXME: handle mkOrder?
|
||||
else
|
||||
[ cfg ];
|
||||
|
||||
@@ -1409,16 +1375,13 @@ let
|
||||
*/
|
||||
dischargeProperties =
|
||||
def:
|
||||
if def ? _type then
|
||||
if def._type == "if" then
|
||||
if isBool def.condition then
|
||||
if def.condition then dischargeProperties def.content else [ ]
|
||||
else
|
||||
throw "‘mkIf’ called with a non-Boolean condition"
|
||||
else if def._type == "merge" then
|
||||
concatMap dischargeProperties def.contents
|
||||
if def._type or "" == "merge" then
|
||||
concatMap dischargeProperties def.contents
|
||||
else if def._type or "" == "if" then
|
||||
if isBool def.condition then
|
||||
if def.condition then dischargeProperties def.content else [ ]
|
||||
else
|
||||
[ def ]
|
||||
throw "‘mkIf’ called with a non-Boolean condition"
|
||||
else
|
||||
[ def ];
|
||||
|
||||
@@ -1508,7 +1471,7 @@ let
|
||||
fixupOptionType =
|
||||
loc: opt:
|
||||
if opt.type.getSubModules or null == null then
|
||||
if opt ? type then opt else opt // { type = types.unspecified; }
|
||||
opt // { type = opt.type or types.unspecified; }
|
||||
else
|
||||
opt
|
||||
// {
|
||||
@@ -1537,21 +1500,23 @@ let
|
||||
mergeAttrDefinitionsWithPrio =
|
||||
opt:
|
||||
let
|
||||
defsByAttr = concatLists (
|
||||
concatMap (
|
||||
{ value, ... }@def:
|
||||
map (mapAttrsToList (
|
||||
k: value: {
|
||||
${k} = def // {
|
||||
inherit value;
|
||||
};
|
||||
}
|
||||
)) (pushDownProperties value)
|
||||
) opt.definitionsWithLocations
|
||||
defsByAttr = zipAttrs (
|
||||
concatLists (
|
||||
concatMap (
|
||||
{ value, ... }@def:
|
||||
map (mapAttrsToList (
|
||||
k: value: {
|
||||
${k} = def // {
|
||||
inherit value;
|
||||
};
|
||||
}
|
||||
)) (pushDownProperties value)
|
||||
) opt.definitionsWithLocations
|
||||
)
|
||||
);
|
||||
in
|
||||
assert opt.type.name == "attrsOf" || opt.type.name == "lazyAttrsOf";
|
||||
zipAttrsWith (
|
||||
mapAttrs (
|
||||
k: v:
|
||||
let
|
||||
merging = mergeDefinitions (opt.loc ++ [ k ]) opt.type.nestedTypes.elemType v;
|
||||
|
||||
@@ -226,9 +226,6 @@ checkConfigError 'In attrTag, each tag value must be an option, but tag int was
|
||||
# types
|
||||
checkConfigOutput '"ok"' config.assertions ./types.nix
|
||||
|
||||
# types.nullOr
|
||||
checkConfigError 'A definition for option .nullableValue\.mixed. is not of type .*\. TypeError: The option .nullableValue\.mixed. is defined both null and not null, in .* and .*\.' config.nullableValue.mixed ./types.nix
|
||||
|
||||
# types.pathInStore
|
||||
checkConfigOutput '".*/store/0lz9p8xhf89kb1c1kk6jxrzskaiygnlh-bash-5.2-p15.drv"' config.pathInStore.ok1 ./types.nix
|
||||
checkConfigOutput '".*/store/0fb3ykw9r5hpayd05sr0cizwadzq1d8q-bash-5.2-p15"' config.pathInStore.ok2 ./types.nix
|
||||
|
||||
@@ -60,10 +60,6 @@ in
|
||||
};
|
||||
nullableValue.list = [ { bar = [ 1 ]; } ]; # list
|
||||
nullableValue.lambda = x: x; # Error
|
||||
nullableValue.mixed = lib.mkMerge [
|
||||
null
|
||||
"foo"
|
||||
]; # Error
|
||||
|
||||
# serializableValueWith { nullable = false; }
|
||||
structuredValue.null = null; # Error
|
||||
|
||||
@@ -1242,44 +1242,18 @@ rec {
|
||||
optionDescriptionPhrase (class: class == "noun" || class == "conjunction") elemType
|
||||
}";
|
||||
descriptionClass = "conjunction";
|
||||
check = {
|
||||
__functor = _self: x: x == null || elemType.check x;
|
||||
isV2MergeCoherent = true;
|
||||
};
|
||||
merge = {
|
||||
__functor =
|
||||
self: loc: defs:
|
||||
let
|
||||
inherit (self.v2 { inherit loc defs; }) headError value;
|
||||
in
|
||||
if headError.causedByMixedNulls or false then throw headError.message else value;
|
||||
v2 =
|
||||
{ loc, defs }:
|
||||
if all (def: def.value != null) defs then
|
||||
# There are no null values
|
||||
if elemType.merge ? v2 then
|
||||
checkV2MergeCoherence loc elemType (elemType.merge.v2 { inherit loc defs; })
|
||||
else
|
||||
{
|
||||
value = elemType.merge loc defs;
|
||||
headError = checkDefsForError elemType.check loc defs;
|
||||
valueMeta = { };
|
||||
}
|
||||
else
|
||||
# There are some null values
|
||||
{
|
||||
headError =
|
||||
if length defs == 1 || all (def: def.value == null) defs then
|
||||
null
|
||||
else
|
||||
{
|
||||
message = "The option `${showOption loc}` is defined both null and not null, in ${showFiles (getFiles defs)}.";
|
||||
causedByMixedNulls = true;
|
||||
};
|
||||
value = null;
|
||||
valueMeta = { };
|
||||
};
|
||||
};
|
||||
check = x: x == null || elemType.check x;
|
||||
merge =
|
||||
loc: defs:
|
||||
let
|
||||
nulls = filter (def: def.value == null) defs;
|
||||
in
|
||||
if nulls == [ ] then
|
||||
elemType.merge loc defs
|
||||
else if length nulls == length defs then
|
||||
null
|
||||
else
|
||||
throw "The option `${showOption loc}` is defined both null and not null, in ${showFiles (getFiles defs)}.";
|
||||
emptyValue = {
|
||||
value = null;
|
||||
};
|
||||
|
||||
@@ -1250,11 +1250,6 @@
|
||||
githubId = 2545644;
|
||||
name = "Alessandro Di Federico";
|
||||
};
|
||||
alejandroqh = {
|
||||
github = "alejandroqh";
|
||||
githubId = 40313108;
|
||||
name = "Alejandro Quintanar";
|
||||
};
|
||||
alejandrosame = {
|
||||
email = "alejandrosanchzmedina@gmail.com";
|
||||
matrix = "@alejandrosame:matrix.org";
|
||||
@@ -6234,12 +6229,6 @@
|
||||
github = "dadada";
|
||||
githubId = 7216772;
|
||||
};
|
||||
daimond113 = {
|
||||
name = "daimond113";
|
||||
github = "daimond113";
|
||||
githubId = 72147841;
|
||||
email = "contact@daimond113.com";
|
||||
};
|
||||
dalance = {
|
||||
email = "dalance@gmail.com";
|
||||
github = "dalance";
|
||||
@@ -6777,6 +6766,12 @@
|
||||
githubId = 25856103;
|
||||
name = "DDoSolitary";
|
||||
};
|
||||
dduan = {
|
||||
email = "daniel@duan.ca";
|
||||
github = "dduan";
|
||||
githubId = 75067;
|
||||
name = "Daniel Duan";
|
||||
};
|
||||
de11n = {
|
||||
email = "nixpkgs-commits@deshaw.com";
|
||||
github = "de11n";
|
||||
@@ -7442,12 +7437,6 @@
|
||||
githubId = 93157285;
|
||||
name = "Dmitrii Stepanidenko";
|
||||
};
|
||||
DmitrySkibitsky = {
|
||||
email = "dmitryskibitsky@gmail.com";
|
||||
github = "DmitrySkibitsky";
|
||||
githubId = 20288556;
|
||||
name = "Dmitry Skibitsky";
|
||||
};
|
||||
DmitryTsygankov = {
|
||||
email = "dmitry.tsygankov@gmail.com";
|
||||
github = "DmitryTsygankov";
|
||||
@@ -8821,11 +8810,6 @@
|
||||
githubId = 965612;
|
||||
name = "York Wong";
|
||||
};
|
||||
etiennelescot = {
|
||||
github = "EtienneLescot";
|
||||
githubId = 215859519;
|
||||
name = "Etienne Lescot";
|
||||
};
|
||||
Etjean = {
|
||||
email = "et.jean@outlook.fr";
|
||||
github = "Etjean";
|
||||
@@ -10411,7 +10395,7 @@
|
||||
};
|
||||
gibbert = {
|
||||
email = "gbjgms@gmail.com";
|
||||
github = "2gibbert";
|
||||
github = "zgibberish";
|
||||
githubId = 67570424;
|
||||
name = "gibbert";
|
||||
};
|
||||
@@ -14636,11 +14620,6 @@
|
||||
githubId = 6544084;
|
||||
name = "Kai Harries";
|
||||
};
|
||||
kaistarkk = {
|
||||
github = "KaiStarkk";
|
||||
githubId = 1722064;
|
||||
name = "KaiStarkk";
|
||||
};
|
||||
kalbasit = {
|
||||
email = "wael.nasreddine@gmail.com";
|
||||
matrix = "@kalbasit:matrix.org";
|
||||
@@ -25336,14 +25315,6 @@
|
||||
github = "Ruixi-rebirth";
|
||||
githubId = 75824585;
|
||||
};
|
||||
RumBugen = {
|
||||
name = "Vladislav Wanner";
|
||||
email = "vladislavwanner@gmail.com";
|
||||
github = "RumBugen";
|
||||
githubId = 43646118;
|
||||
matrix = "@rumbugen:matrix.org";
|
||||
keys = [ { fingerprint = "305D 2271 3C43 2E52 FB86 7C64 6107 6951 3194 1931"; } ];
|
||||
};
|
||||
rumpelsepp = {
|
||||
name = "Stefan Tatschner";
|
||||
email = "stefan@rumpelsepp.org";
|
||||
@@ -31439,11 +31410,6 @@
|
||||
githubId = 28888242;
|
||||
name = "WORLDofPEACE";
|
||||
};
|
||||
wormt = {
|
||||
github = "wormt";
|
||||
githubId = 209373679;
|
||||
name = "wormt";
|
||||
};
|
||||
WoutSwinkels = {
|
||||
name = "Wout Swinkels";
|
||||
email = "nixpkgs@woutswinkels.com";
|
||||
|
||||
39
maintainers/scripts/haskell/eval-pkg-sets.sh
Executable file
39
maintainers/scripts/haskell/eval-pkg-sets.sh
Executable file
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i bash
|
||||
#!nix-shell -p jq git
|
||||
# shellcheck shell=bash
|
||||
#
|
||||
# Usage: eval-pkg-sets.sh [extra flags for nix-* commands ...]
|
||||
#
|
||||
# Must be executed in a git checkout of Nixpkgs.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
NIXPKGS="$(git rev-parse --show-toplevel)"
|
||||
PKGSETS="$(nix-env --readonly-mode --json --drv-path -f "$NIXPKGS" -qaP -A haskell.compiler "$@" \
|
||||
| jq -r 'to_entries | unique_by(.value.drvPath) .[] .key | sub("^haskell.compiler";"haskell.packages")')"
|
||||
|
||||
trap 'exit 1' SIGINT SIGTERM
|
||||
|
||||
set +e
|
||||
|
||||
badsets=""
|
||||
for set in $PKGSETS; do
|
||||
# Confirm an equivalent package set to haskell.compiler.$entry exists and is usable
|
||||
if ! nix-instantiate --readonly-mode -A "$set.ghc" "$@" > /dev/null 2>&1; then
|
||||
echo "Skipping $set... ($set.ghc does not evaluate)"
|
||||
else
|
||||
echo "Evaluating $set..."
|
||||
|
||||
if ! nix-env --readonly-mode -f "$NIXPKGS" -qaP --drv-path -A "$set" "$@" > /dev/null; then
|
||||
badsets+="$set "
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -n "$badsets" ]; then
|
||||
echo "Found potential eval issues in the following sets:" >&2
|
||||
# shellcheck disable=SC2086
|
||||
printf '%s\n' $badsets
|
||||
exit 1
|
||||
fi
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Attention: For unknown reasons, the script can't be easily cancelled and needs to be killed manually if it shouldn't run to completion.
|
||||
|
||||
use std log
|
||||
use std/log
|
||||
|
||||
let broken_config = "pkgs/development/haskell-modules/configuration-hackage2nix/broken.yaml"
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ fi
|
||||
|
||||
# Stackage solver to use, LTS or Nightly
|
||||
# (should be capitalized like the display name)
|
||||
SOLVER=LTS
|
||||
SOLVER=Nightly
|
||||
# Stackage solver version, if any. Use latest if empty
|
||||
VERSION=
|
||||
TMP_TEMPLATE=update-stackage.XXXXXXX
|
||||
@@ -105,6 +105,7 @@ sed -r \
|
||||
-e '/ hledger-ui /d' \
|
||||
-e '/ hledger-web /d' \
|
||||
-e '/ spacecookie /d' \
|
||||
-e '/ hnix-store-core /d' \
|
||||
< "${tmpfile_new}" >> $stackage_config
|
||||
# Explanations:
|
||||
# cabal2nix, distribution-nixpkgs, jailbreak-cabal, language-nix: These are our packages and we know what we are doing.
|
||||
|
||||
@@ -32,6 +32,5 @@ stdenv.mkDerivation {
|
||||
description = "Utility to generate a Nix expression for a Perl package from CPAN";
|
||||
mainProgram = "nix-generate-from-cpan";
|
||||
platforms = lib.platforms.unix;
|
||||
license = lib.licenses.mit;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -39,6 +39,5 @@ stdenv.mkDerivation {
|
||||
description = "Utility for Nixpkgs contributors to check Nixpkgs for common errors";
|
||||
mainProgram = "nixpkgs-lint";
|
||||
platforms = lib.platforms.unix;
|
||||
license = lib.licenses.mit;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -751,6 +751,7 @@ with lib.maintainers;
|
||||
|
||||
swift = {
|
||||
members = [
|
||||
dduan
|
||||
samasaur
|
||||
stephank
|
||||
];
|
||||
|
||||
@@ -15,7 +15,6 @@ An example of how to build an image:
|
||||
imports = [ "${modulesPath}/image/repart.nix" ];
|
||||
|
||||
image.repart = {
|
||||
enable = true;
|
||||
name = "image";
|
||||
partitions = {
|
||||
"esp" = {
|
||||
@@ -149,7 +148,6 @@ in
|
||||
fileSystems."/".device = "/dev/disk/by-label/nixos";
|
||||
|
||||
image.repart = {
|
||||
enable = true;
|
||||
name = "image";
|
||||
partitions = {
|
||||
"esp" = {
|
||||
|
||||
@@ -68,18 +68,6 @@
|
||||
"module-boot-plymouth-tpm2-totp-quick-start-enable": [
|
||||
"index.html#module-boot-plymouth-tpm2-totp-quick-start-enable"
|
||||
],
|
||||
"module-services-cliproxyapi": [
|
||||
"index.html#module-services-cliproxyapi"
|
||||
],
|
||||
"module-services-cliproxyapi-authentication": [
|
||||
"index.html#module-services-cliproxyapi-authentication"
|
||||
],
|
||||
"module-services-cliproxyapi-authentication-cli": [
|
||||
"index.html#module-services-cliproxyapi-authentication-cli"
|
||||
],
|
||||
"module-services-cliproxyapi-authentication-management-api": [
|
||||
"index.html#module-services-cliproxyapi-authentication-management-api"
|
||||
],
|
||||
"module-services-keycloak-unix-socket": [
|
||||
"index.html#module-services-keycloak-unix-socket"
|
||||
],
|
||||
|
||||
@@ -161,8 +161,6 @@ Alongside many enhancements to NixOS modules and general system improvements, th
|
||||
|
||||
- [immich-public-proxy](https://github.com/alangrainger/immich-public-proxy), a proxy for sharing Immich albums without exposing the Immich API. Available as [services.immich-public-proxy](#opt-services.immich-public-proxy.enable).
|
||||
|
||||
- [Omada Software Controller](https://www.tp-link.com/us/business-networking/omada-sdn-controller/omada-software-controller), a professional and free on-premises controller for the Omada Software Defined Networking (SDN) platform, provides centralized management of Omada access points, switches and routers. Available as [services.omada](#opt-services.omada.enable).
|
||||
|
||||
- [Zipline](https://zipline.diced.sh/), a ShareX/file upload server that is easy to use, packed with features, and with an easy setup. Available as [services.zipline](#opt-services.zipline.enable).
|
||||
|
||||
- [GlitchTip](https://glitchtip.com/), an open source Sentry API compatible error tracking platform. Available as [services.glitchtip](#opt-services.glitchtip.enable).
|
||||
|
||||
@@ -34,8 +34,6 @@
|
||||
|
||||
- [tranquil](https://tangled.org/tranquil.farm/tranquil-pds) is an ATProto PDS (personal data server) implementation in Rust. A featureful, spec conscious and community driven alternative to the Bluesky reference implementation PDS. Available as [services.tranquil-pds](#opt-services.tranquil-pds.enable).
|
||||
|
||||
- [Zerobyte](https://github.com/nicotsx/zerobyte), backup automation for self-hosters built on top of restic, with a web interface to schedule, manage and monitor encrypted backups. Available as [services.zerobyte](#opt-services.zerobyte.enable).
|
||||
|
||||
- [Cardwire](https://github.com/OpenGamingCollective/cardwire), a GPU manager for Linux that uses eBPF+LSM hooks to control GPUs. Available as [services.cardwired](#opt-services.cardwired.enable).
|
||||
|
||||
- [Moonlight Qt](https://moonlight-stream.org/), a client for playing your PC games on almost any device. Available as [programs.moonlight-qt](#opt-programs.moonlight-qt.enable).
|
||||
@@ -62,8 +60,6 @@
|
||||
|
||||
- [feishin](https://github.com/jeffvli/feishin), a modern self-hosted music player. Available as [services.feishin](#opt-services.feishin.enable).
|
||||
|
||||
- [Aurral](https://aurral.org), a Lidarr companion for self-hosted music discovery. Available as [services.aurral](#opt-services.aurral.enable).
|
||||
|
||||
- [CastSponsorSkip](https://github.com/gabe565/CastSponsorSkip/), skips YouTube sponsorships (and sometimes ads) on all local Google Cast devices.
|
||||
|
||||
- [Stump](https://www.stumpapp.dev/), a free and open source comics, manga and digital book server with OPDS support. Available as [services.stump](#opt-services.stump.enable).
|
||||
@@ -74,8 +70,6 @@
|
||||
|
||||
- [Freescout](https://freescout.net/), a free, open source Helpdesk and shared mailbox. Available as [services.freescout](#opt-services.freescout.enable).
|
||||
|
||||
- [CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI), a proxy that provides OpenAI/Gemini/Claude/Codex/Grok compatible API interfaces from OAuth-based AI CLI subscriptions. Available as [services.cliproxyapi](#opt-services.cliproxyapi.enable).
|
||||
|
||||
- [Lix TOML remote builders](https://docs.lix.systems/manual/lix/stable/advanced-topics/distributed-builds.html#using-a-toml-configuration), remote builder configuration using lix's TOML format. Available as [lix.buildMachines](#opt-lix.buildMachines). Note: incompatible with `nix.buildMachines`.
|
||||
|
||||
- [Forgejo Runner](https://forgejo.org/docs/latest/admin/actions/), a daemon for Forgejo Actions. Available as [services.forgejo-runner](#opt-services.forgejo-runner.instances).
|
||||
@@ -102,8 +96,6 @@
|
||||
|
||||
- [vellum](https://github.com/greyxp1/vellum) is a live screen annotation overlay for Wayland. Available as [programs.vellum](#opt-programs.vellum.enable).
|
||||
|
||||
- [iocaine](https://git.madhouse-project.org/iocaine/iocaine) is a defense mechanism against unwanted scrapers. Available as [services.iocaine](#opt-services.iocaine.enable).
|
||||
|
||||
- [stash-clipboard](https://github.com/NotAShelf/stash), a Wayland clipboard "manager" with fast persistent history and multi-media support. Available as [services.stash-clipboard](#opt-services.stash-clipboard.enable).
|
||||
|
||||
- [OO7](https://github.com/linux-credentials/oo7) is a desktop-agnostic Secret Service provider. Available as [services.oo7](#opt-services.oo7.enable)
|
||||
@@ -158,12 +150,6 @@
|
||||
|
||||
- `boot.vesa` has been removed. It was deprecated in 2020 because Xorg now works better with kernel modesetting. If you still need the legacy VESA 800x600 fallback, set `boot.kernelParams = [ "vga=0x317" "nomodeset" ];` directly.
|
||||
|
||||
- `security.acme` now uses `lego` 5.x.
|
||||
Existing ACME account state is migrated automatically.
|
||||
If you pass raw `lego` flags through `security.acme.*.extraLegoFlags`, `security.acme.*.extraLegoRunFlags`, or `security.acme.*.extraLegoRenewFlags`, update them for the `lego` 5.x CLI.
|
||||
In particular, `lego renew` has been replaced by the renewal mode of `lego run`.
|
||||
The certificate identifiers formed by `domain` and `extraDomainNames` must be unique and canonical to avoid repeated renewals. Use IDNA A-labels for internationalized domain names and RFC 5952 notation for IPv6 addresses.
|
||||
|
||||
- `authentik` has been updated to 2026.5.3, which changes the default listen address from `0.0.0.0` to `[::]`.
|
||||
IPv4-only deployments might need to adjust their listen settings.
|
||||
Deployments running the server and worker in the same network namespace must also set at least the worker
|
||||
@@ -183,8 +169,6 @@
|
||||
|
||||
- `services.pid-fan-controller` no longer provides deep configuration rewriting and adheres now fully to RFC42.
|
||||
|
||||
- `strongSwan` has been updated to 6.1.0, which defaults to only accepts IKEv2 connections as responder.
|
||||
|
||||
- The `extraArgs` and `check` arguments to `nixos/lib/eval-config.nix` (and therefore to `lib.nixosSystem`) have been removed after being deprecated with a warning since 2021. Passing them is now an evaluation error. Instead of `extraArgs`, set `config._module.args`; instead of `check = false`, set `config._module.check = false`. The `extraArgs` attribute on the resulting configuration has been removed as well.
|
||||
|
||||
- Rustical migrates from `settings.http.host` and `settings.http.port` to `settings.http.bind` to support UNIX domain sockets as well as TCP sockets in one setting.
|
||||
@@ -258,8 +242,6 @@
|
||||
|
||||
- The papra NixOS module is now hardening the systemd unit by default. If this breaks any of the configured directories, please reconfigure them through `services.papra.environment` to enable sandbox passthrough.
|
||||
|
||||
- The `image/repart.nix` module now requires users to explicitly enable its use via `image.repart.enable`.
|
||||
|
||||
- `services.selfoss.extraConfig` and `services.selfoss.database` have been removed in favor of the structured [](#opt-services.selfoss.settings) option. When moving the `database` options to `settings`, you should also switch to the upstream naming:
|
||||
|
||||
- `type` → [`db_type`](#opt-services.selfoss.settings.db_type)
|
||||
@@ -336,9 +318,6 @@
|
||||
Migrating sites to Grav 2 is a [manual process](https://learn.getgrav.org/20/migration/manual-migration) with this package since the migration plugin cannot modify the Nix store.
|
||||
The [`services.grav.package`](#opt-services.grav.package) option defaults to `pkgs.grav_2` if [`system.stateVersion`](#opt-system.stateVersion) >=26.11.
|
||||
|
||||
- The implementation of [`services.btrfs.autoScrub`](#opt-services.btrfs.autoScrub.enable) has been refactored to use systemd templates.
|
||||
As part of this change, mountpoints to be scrubbed are now automatically mounted (if not already mounted) when the unit is started.
|
||||
|
||||
- `services.plausible` can now again seed an initial admin user declaratively via [`services.plausible.adminUser.email`](#opt-services.plausible.adminUser.email).
|
||||
This makes fully declarative deployments safer: Otherwise the user needed to either accept Plausible's unauthenticated "first launch" setup wizard, which lets anyone reaching the instance create the first admin account, or do more work (deploying with NixOS's default binding to `localhost` without exposing it publicly, going through the wizard, and then deploying Plausible exposed to the Internet).
|
||||
This option was previously removed with NixOS 25.05 due to an upstream Plausible change making declarative admin creation more difficult, but this change re-implements the admin creation directly.
|
||||
|
||||
@@ -52,7 +52,6 @@ rec {
|
||||
# Pin virt-11.0 to avoid gic-version=3 that works on MacOS 15+ only.
|
||||
# FIXME: Revert to `virt` after minimal supported macos is 15+.
|
||||
aarch64-linux = "${qemuPkg}/bin/qemu-system-aarch64 -machine virt-11.0,accel=${accel "hvf"} -cpu max";
|
||||
x86_64-linux = "${qemuPkg}/bin/qemu-system-x86_64 -machine type=q35,accel=${accel "hvf"} -cpu max";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
|
||||
inherit (lib)
|
||||
mkDefault
|
||||
mkEnableOption
|
||||
mkIf
|
||||
mkOption
|
||||
optional
|
||||
types
|
||||
;
|
||||
|
||||
cfg = config.hardware.ipu7;
|
||||
|
||||
in
|
||||
{
|
||||
|
||||
options.hardware.ipu7 = {
|
||||
|
||||
enable = mkEnableOption "support for Intel IPU7/MIPI cameras";
|
||||
|
||||
platform = mkOption {
|
||||
type = types.enum [
|
||||
"ipu7x"
|
||||
"ipu75xa"
|
||||
];
|
||||
description = ''
|
||||
Choose the version for your hardware platform. The IPU reports which one
|
||||
it is through its PCI ID, visible as the Multimedia controller in lspci.
|
||||
|
||||
- ipu7x (Lunar Lake, PCI 8086:645d)
|
||||
Sensor list: https://github.com/intel/ipu7-camera-hal/tree/main/config/linux/ipu7x/sensors
|
||||
- ipu75xa (Panther Lake, PCI 8086:b05d)
|
||||
Sensor list: https://github.com/intel/ipu7-camera-hal/tree/main/config/linux/ipu75xa/sensors
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
# Kernels >= 6.17 ship an IPU7 core and ISys in drivers/staging/media/ipu7,
|
||||
# but no PSys, so they cannot drive the hardware ISP that the camera HAL
|
||||
# needs. ipu7-drivers supplies just the PSys module (intel-ipu7-psys), which
|
||||
# has no in-tree counterpart, and links it against the in-tree core and ISys
|
||||
# that already enumerate the sensor.
|
||||
boot.extraModulePackages = with config.boot.kernelPackages; [
|
||||
ipu7-drivers
|
||||
];
|
||||
|
||||
hardware.firmware = with pkgs; [
|
||||
ipu7-camera-bins
|
||||
ivsc-firmware
|
||||
];
|
||||
|
||||
services.udev.extraRules = ''
|
||||
SUBSYSTEM=="intel-ipu7-psys", MODE="0660", GROUP="video"
|
||||
'';
|
||||
|
||||
services.v4l2-relayd.instances.ipu7 = {
|
||||
enable = mkDefault true;
|
||||
|
||||
cardLabel = mkDefault "Intel MIPI Camera";
|
||||
|
||||
extraPackages =
|
||||
with pkgs.gst_all_1;
|
||||
optional (cfg.platform == "ipu7x") icamerasrc-ipu7x
|
||||
++ optional (cfg.platform == "ipu75xa") icamerasrc-ipu75xa;
|
||||
|
||||
input = {
|
||||
pipeline = "icamerasrc";
|
||||
# REVIEW from https://edc.intel.com/content/www/us/en/secure/design/confidential/products/platforms/details/lunar-lake-mx/core-ultra-200v-series-processors-datasheet-volume-1-of-2/camera-integrated-isp/
|
||||
# Output Formats - NV12, NV16, I420, M420, YUY2, YUYV, P010, P016
|
||||
format = "NV12";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -131,7 +131,6 @@ in
|
||||
];
|
||||
|
||||
options.image.repart = {
|
||||
enable = lib.mkEnableOption "systemd-repart boot image";
|
||||
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
@@ -314,7 +313,7 @@ in
|
||||
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
config = {
|
||||
image.baseName =
|
||||
let
|
||||
version = config.image.repart.version;
|
||||
|
||||
@@ -124,7 +124,7 @@ in
|
||||
datadog = 76;
|
||||
lighttpd = 77;
|
||||
lightdm = 78;
|
||||
# hyphanet = 79; # was freenet; dynamically allocated as of 2026-09-02
|
||||
freenet = 79;
|
||||
ircd = 80;
|
||||
bacula = 81;
|
||||
#almir = 82; # removed 2018-03-25, the almir package was removed in 30291227f2411abaca097773eedb49b8f259e297 during 2017-08
|
||||
@@ -466,7 +466,7 @@ in
|
||||
datadog = 76;
|
||||
lighttpd = 77;
|
||||
lightdm = 78;
|
||||
# hyphanet = 79; # was freenet; dynamically allocated as of 2026-09-02
|
||||
freenet = 79;
|
||||
ircd = 80;
|
||||
bacula = 81;
|
||||
#almir = 82; # removed 2018-03-25, the almir package was removed in 30291227f2411abaca097773eedb49b8f259e297 during 2017-08
|
||||
|
||||
@@ -130,7 +130,6 @@
|
||||
./hardware/video/virtualbox.nix
|
||||
./hardware/video/webcam/facetimehd.nix
|
||||
./hardware/video/webcam/ipu6.nix
|
||||
./hardware/video/webcam/ipu7.nix
|
||||
./hardware/wooting.nix
|
||||
./hardware/xone.nix
|
||||
./hardware/xpad-noone.nix
|
||||
@@ -855,7 +854,6 @@
|
||||
./services/misc/anki-sync-server.nix
|
||||
./services/misc/apache-kafka.nix
|
||||
./services/misc/atuin.nix
|
||||
./services/misc/aurral.nix
|
||||
./services/misc/autobrr.nix
|
||||
./services/misc/autofs.nix
|
||||
./services/misc/autorandr.nix
|
||||
@@ -873,7 +871,6 @@
|
||||
./services/misc/cgminer.nix
|
||||
./services/misc/clipcat.nix
|
||||
./services/misc/clipmenu.nix
|
||||
./services/misc/cliproxyapi.nix
|
||||
./services/misc/comfyui.nix
|
||||
./services/misc/confd.nix
|
||||
./services/misc/conman.nix
|
||||
@@ -1246,6 +1243,7 @@
|
||||
./services/networking/firezone/relay.nix
|
||||
./services/networking/firezone/server.nix
|
||||
./services/networking/flannel.nix
|
||||
./services/networking/freenet.nix
|
||||
./services/networking/freeradius.nix
|
||||
./services/networking/frp.nix
|
||||
./services/networking/frr.nix
|
||||
@@ -1274,7 +1272,6 @@
|
||||
./services/networking/htpdate.nix
|
||||
./services/networking/https-dns-proxy.nix
|
||||
./services/networking/hylafax/default.nix
|
||||
./services/networking/hyphanet.nix
|
||||
./services/networking/i2p.nix
|
||||
./services/networking/i2pd.nix
|
||||
./services/networking/icecream/daemon.nix
|
||||
@@ -1283,7 +1280,6 @@
|
||||
./services/networking/imaginary.nix
|
||||
./services/networking/inadyn.nix
|
||||
./services/networking/inspircd.nix
|
||||
./services/networking/iocaine.nix
|
||||
./services/networking/iodine.nix
|
||||
./services/networking/iperf3.nix
|
||||
./services/networking/ircd-hybrid/default.nix
|
||||
@@ -1387,7 +1383,6 @@
|
||||
./services/networking/ofono.nix
|
||||
./services/networking/oidentd.nix
|
||||
./services/networking/oink.nix
|
||||
./services/networking/omada.nix
|
||||
./services/networking/onedrive.nix
|
||||
./services/networking/openconnect.nix
|
||||
./services/networking/openvpn.nix
|
||||
@@ -1712,7 +1707,6 @@
|
||||
./services/web-apps/filebrowser.nix
|
||||
./services/web-apps/firefly-iii-data-importer.nix
|
||||
./services/web-apps/firefly-iii.nix
|
||||
./services/web-apps/flame.nix
|
||||
./services/web-apps/flarum.nix
|
||||
./services/web-apps/fluidd.nix
|
||||
./services/web-apps/freescout.nix
|
||||
@@ -1882,7 +1876,6 @@
|
||||
./services/web-apps/your_spotify.nix
|
||||
./services/web-apps/youtrack.nix
|
||||
./services/web-apps/zabbix.nix
|
||||
./services/web-apps/zerobyte.nix
|
||||
./services/web-apps/zipline.nix
|
||||
./services/web-apps/zitadel.nix
|
||||
./services/web-servers/agate.nix
|
||||
|
||||
@@ -225,7 +225,6 @@ in
|
||||
mainProgram = "create-builder";
|
||||
description = "Create a Linux builder VM for macOS";
|
||||
platforms = lib.platforms.darwin;
|
||||
maintainers = with lib.maintainers; [ booxter ];
|
||||
};
|
||||
passthru = {
|
||||
# Let users in the repl inspect the config
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
|
||||
Additionally to installing `k3b` enabling this will
|
||||
add `setuid` wrappers in `/run/wrappers/bin`
|
||||
for both `cdrdao` and `cdrecord`.
|
||||
for both `cdrdao` and `cdrecord`. On first
|
||||
run you must manually configure the path of `cdrdae` and
|
||||
`cdrecord` to correspond to the appropriate paths under
|
||||
`/run/wrappers/bin` in the "Setup External Programs" menu.
|
||||
'';
|
||||
};
|
||||
};
|
||||
@@ -45,5 +48,6 @@
|
||||
source = "${pkgs.cdrtools}/bin/cdrecord";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@@ -29,13 +29,15 @@ let
|
||||
builtInRemovedMsg = "This is now built-in in DMS and doesn't need additional dependencies.";
|
||||
|
||||
optionalPackages =
|
||||
optionals cfg.enableVPN [
|
||||
optionals cfg.enableSystemMonitoring [ pkgs.dgop ]
|
||||
++ optionals cfg.enableVPN [
|
||||
pkgs.glib
|
||||
pkgs.networkmanager
|
||||
]
|
||||
++ optional cfg.enableDynamicTheming pkgs.matugen
|
||||
++ optional cfg.enableAudioWavelength pkgs.cava
|
||||
++ optional cfg.enableCalendarEvents pkgs.khal;
|
||||
++ optional cfg.enableCalendarEvents pkgs.khal
|
||||
++ optional cfg.enableClipboardPaste pkgs.wtype;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
@@ -45,8 +47,6 @@ in
|
||||
path ++ [ "enableSystemSound" ]
|
||||
) "qtmultimedia is now included on dms-shell package.")
|
||||
(lib.mkRemovedOptionModule (path ++ [ "enableClipboard" ]) builtInRemovedMsg)
|
||||
(lib.mkRemovedOptionModule (path ++ [ "enableSystemMonitoring" ]) builtInRemovedMsg)
|
||||
(lib.mkRemovedOptionModule (path ++ [ "enableClipboardPaste" ]) builtInRemovedMsg)
|
||||
];
|
||||
|
||||
options.programs.dms-shell = {
|
||||
@@ -86,6 +86,17 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
enableSystemMonitoring = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to install dependencies required for system monitoring widgets.
|
||||
This includes process list viewers and system resource monitors.
|
||||
|
||||
Requires: dgop
|
||||
'';
|
||||
};
|
||||
|
||||
enableVPN = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
@@ -130,6 +141,17 @@ in
|
||||
'';
|
||||
};
|
||||
|
||||
enableClipboardPaste = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to install dependencies required for pasting directly from the clipboard history support.
|
||||
This enables pressing Shift+Return for pasting entries from the clipboard history.
|
||||
|
||||
Requires: wtype
|
||||
'';
|
||||
};
|
||||
|
||||
quickshell = {
|
||||
package = mkPackageOption pkgs "quickshell" { };
|
||||
};
|
||||
|
||||
@@ -278,7 +278,7 @@ in
|
||||
''}
|
||||
|
||||
# Determine current fqdn hostname
|
||||
HOST=$(${lib.getExe pkgs.unixtools.hostname} --fqdn)
|
||||
HOST=$(hostname --fqdn)
|
||||
|
||||
# Setup command line history.
|
||||
# Don't export these, otherwise other shells (bash) will try to use same HISTFILE.
|
||||
|
||||
@@ -242,6 +242,8 @@ let
|
||||
+ lib.optionalString (data.csr != null) " - ${data.csr}"
|
||||
+ lib.optionalString (data.profile != null) " - ${data.profile}";
|
||||
certDir = mkHash hashData;
|
||||
# TODO remove domainHash usage entirely. Waiting on go-acme/lego#1532
|
||||
domainHash = mkHash "${lib.concatStringsSep " " extraDomains} ${data.domain}";
|
||||
accountHash = (mkAccountHash acmeServer data);
|
||||
accountDir = accountDirRoot + accountHash;
|
||||
|
||||
@@ -252,10 +254,7 @@ let
|
||||
"--dns"
|
||||
data.dnsProvider
|
||||
]
|
||||
++ lib.optionals (!data.dnsPropagationCheck) [
|
||||
"--dns.propagation.disable-ans"
|
||||
"--dns.propagation.disable-rns"
|
||||
]
|
||||
++ lib.optionals (!data.dnsPropagationCheck) [ "--dns.propagation-disable-ans" ]
|
||||
++ lib.optionals (data.dnsResolver != null) [
|
||||
"--dns.resolvers"
|
||||
data.dnsResolver
|
||||
@@ -270,7 +269,7 @@ let
|
||||
else if data.listenHTTP != null then
|
||||
[
|
||||
"--http"
|
||||
"--http.address"
|
||||
"--http.port"
|
||||
data.listenHTTP
|
||||
]
|
||||
else
|
||||
@@ -310,27 +309,21 @@ let
|
||||
]) extraDomains
|
||||
++ data.extraLegoFlags;
|
||||
|
||||
# `lego run` renews when its state still contains a certificate resource. The full
|
||||
# path is also a recovery path, so force renewal without an ARI `replaces`
|
||||
# identifier that may refer to a stale certificate.
|
||||
# Although --must-staple is common to both modes, it is not declared as a
|
||||
# mode-agnostic argument in lego and thus must come after the mode.
|
||||
runOpts = lib.escapeShellArgs (
|
||||
[ "run" ]
|
||||
++ commonOpts
|
||||
++ [
|
||||
"--no-random-sleep"
|
||||
"--renew-force"
|
||||
"--ari-disable"
|
||||
]
|
||||
++ lib.optionals (data.csr == null) [ "--force-cert-domains" ]
|
||||
commonOpts
|
||||
++ [ "run" ]
|
||||
++ lib.optionals data.ocspMustStaple [ "--must-staple" ]
|
||||
++ lib.optionals (data.profile != null) [ "--profile=${data.profile}" ]
|
||||
++ data.extraLegoRunFlags
|
||||
);
|
||||
renewOpts = lib.escapeShellArgs (
|
||||
[ "run" ]
|
||||
++ commonOpts
|
||||
++ [ "--no-random-sleep" ]
|
||||
++ lib.optionals (data.csr == null) [ "--force-cert-domains" ]
|
||||
commonOpts
|
||||
++ [
|
||||
"renew"
|
||||
"--no-random-sleep"
|
||||
]
|
||||
++ lib.optionals data.ocspMustStaple [ "--must-staple" ]
|
||||
++ lib.optionals (data.profile != null) [ "--profile=${data.profile}" ]
|
||||
++ data.extraLegoRenewFlags
|
||||
@@ -582,18 +575,13 @@ let
|
||||
}
|
||||
}
|
||||
|
||||
# Multiple certificates can share an account. Serialize the check and
|
||||
# migration because lego v5 moves the shared key out of keys/.
|
||||
exec {MIGRATION_LOCK_FD}> "${lockdir}migration-${accountHash}.lock"
|
||||
${pkgs.flock}/bin/flock "$MIGRATION_LOCK_FD"
|
||||
if [ -n "$(find accounts -path '*/keys/*.key' -print -quit)" ]; then
|
||||
printf 'Y\n' | lego migrate --account-only --path .
|
||||
fi
|
||||
exec {MIGRATION_LOCK_FD}>&-
|
||||
echo '${domainHash}' > domainhash.txt
|
||||
|
||||
# Check if the existing certificate can be renewed.
|
||||
# Check if a new order is needed
|
||||
# We can only renew if the list of domains has not changed.
|
||||
# We also need an account key. Avoids #190493
|
||||
if [ -e '${certificateKey}' ] && [ -e 'certificates/${keyName}.crt' ] && \
|
||||
if cmp -s domainhash.txt certificates/domainhash.txt && [ -e '${certificateKey}' ] && \
|
||||
[ -e 'certificates/${keyName}.crt' ] && \
|
||||
[ -n "$(find accounts -name '${
|
||||
if (data.email != null) then data.email else placeholderEmail
|
||||
}.key')" ];
|
||||
@@ -602,7 +590,7 @@ let
|
||||
# Try to renew, and silently fail if the cert is not expired.
|
||||
# Avoids #85794 and resolves #129838
|
||||
if ! lego ${renewOpts} ${
|
||||
if data.validMinDays != null then "--renew-days ${toString data.validMinDays}" else ""
|
||||
if data.validMinDays != null then "--days ${toString data.validMinDays}" else "--dynamic"
|
||||
}; then
|
||||
if is_expiration_skippable out/full.pem; then
|
||||
echo 1>&2 "nixos-acme: Ignoring failed renewal because expiration isn't due yet"
|
||||
@@ -622,6 +610,8 @@ let
|
||||
exit 10
|
||||
fi
|
||||
|
||||
mv domainhash.txt certificates/
|
||||
|
||||
touch out/acme-success
|
||||
|
||||
# Copy all certs to the "real" certs directory
|
||||
@@ -882,7 +872,7 @@ let
|
||||
type = lib.types.listOf lib.types.str;
|
||||
inherit (defaultAndText "extraLegoFlags" [ ]) default defaultText;
|
||||
description = ''
|
||||
Additional flags to pass to both `lego run` invocations.
|
||||
Additional global flags to pass to all lego commands.
|
||||
'';
|
||||
};
|
||||
|
||||
@@ -890,7 +880,7 @@ let
|
||||
type = lib.types.listOf lib.types.str;
|
||||
inherit (defaultAndText "extraLegoRenewFlags" [ ]) default defaultText;
|
||||
description = ''
|
||||
Additional flags to pass to the non-forced renewal `lego run` invocation.
|
||||
Additional flags to pass to lego renew.
|
||||
'';
|
||||
};
|
||||
|
||||
@@ -898,7 +888,7 @@ let
|
||||
type = lib.types.listOf lib.types.str;
|
||||
inherit (defaultAndText "extraLegoRunFlags" [ ]) default defaultText;
|
||||
description = ''
|
||||
Additional flags to pass to the forced obtain/reissue `lego run` invocation.
|
||||
Additional flags to pass to lego run.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
@@ -236,8 +236,7 @@ in
|
||||
|
||||
# If DynamicUser= is enabled, PrivateTmp=true is implied (and cannot be turned off).
|
||||
# so disable them unless PrivateTmp=true is explicitely set.
|
||||
# We're explicitely comparing to `false` here, because PrivateTmp can also be "disconnected".
|
||||
${lib.optionalString (cfg.serviceConfig.PrivateTmp == false) ''
|
||||
${lib.optionalString (!cfg.serviceConfig.PrivateTmp) ''
|
||||
echo "InaccessiblePaths=-+/tmp" >> "$serviceFile"
|
||||
echo "InaccessiblePaths=-+/var/tmp" >> "$serviceFile"
|
||||
''}
|
||||
|
||||
@@ -560,16 +560,6 @@ in
|
||||
++ lib.optional (cfg.dockerHost == null) "docker.service";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
# Periphery shells out to `docker`, `docker compose` and `git` through `sh -c`.
|
||||
path = [
|
||||
pkgs.git
|
||||
config.virtualisation.docker.package
|
||||
]
|
||||
++ lib.optionals (!cfg.disableTerminals) [
|
||||
"/run/current-system/sw"
|
||||
"/run/wrappers"
|
||||
];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = cfg.user;
|
||||
@@ -614,6 +604,11 @@ in
|
||||
// cfg.environment
|
||||
);
|
||||
|
||||
ExecSearchPath = lib.mkIf (!cfg.disableTerminals) [
|
||||
"/run/current-system/sw/bin"
|
||||
"/run/wrappers/bin"
|
||||
];
|
||||
|
||||
EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
|
||||
|
||||
StateDirectory = "komodo-periphery";
|
||||
|
||||
@@ -69,8 +69,7 @@ let
|
||||
cassandraEnvPkg = "${cfg.package}/conf/cassandra-env.sh";
|
||||
cassandraLogbackConfig = pkgs.writeText "logback.xml" cfg.logbackConfig;
|
||||
|
||||
strictDeps = true;
|
||||
__structuredAttrs = true;
|
||||
passAsFile = [ "extraEnvSh" ];
|
||||
inherit (cfg) extraEnvSh package;
|
||||
|
||||
buildCommand = ''
|
||||
@@ -81,7 +80,7 @@ let
|
||||
|
||||
( cat "$cassandraEnvPkg"
|
||||
echo "# lines from services.cassandra.extraEnvSh: "
|
||||
printf "%s" "$extraEnvSh"
|
||||
cat "$extraEnvShPath"
|
||||
) > "$out/cassandra-env.sh"
|
||||
|
||||
# Delete default JMX Port, otherwise we can't set it using env variable
|
||||
|
||||
@@ -44,17 +44,19 @@ let
|
||||
makeBinPath [
|
||||
cfg.quickshell.package
|
||||
compositorPkg
|
||||
pkgs.glib # provides gdbus, used by the fprintd hardware probe and portal reads
|
||||
]
|
||||
}
|
||||
${
|
||||
escapeShellArgs (
|
||||
[
|
||||
"${cfg.package}/bin/dms-greeter"
|
||||
"sh"
|
||||
"${cfg.package}/share/quickshell/dms/Modules/Greetd/assets/dms-greeter"
|
||||
"--cache-dir"
|
||||
cacheDir
|
||||
"--command"
|
||||
cfg.compositor.name
|
||||
"-p"
|
||||
"${cfg.package}/share/quickshell/dms"
|
||||
]
|
||||
++ lib.optionals (cfg.compositor.customConfig != "") [
|
||||
"-C"
|
||||
@@ -112,7 +114,21 @@ in
|
||||
options.services.displayManager.dms-greeter = {
|
||||
enable = mkEnableOption "DankMaterialShell greeter";
|
||||
|
||||
package = lib.mkPackageOption pkgs "dms-greeter" { };
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = if cfgDms.enable then cfgDms.package else pkgs.dms-shell;
|
||||
defaultText = literalExpression ''
|
||||
if config.programs.dms-shell.enable
|
||||
then config.programs.dms-shell.package
|
||||
else pkgs.dms-shell;
|
||||
'';
|
||||
description = ''
|
||||
The DankMaterialShell package to use for the greeter.
|
||||
|
||||
Defaults to the package from `programs.dms-shell` if it is enabled,
|
||||
otherwise defaults to `pkgs.dms-shell`.
|
||||
'';
|
||||
};
|
||||
|
||||
compositor = {
|
||||
name = mkOption {
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.services.aurral;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.aurral = {
|
||||
enable = lib.mkEnableOption "Aurral is the Lidarr companion for self-hosted music discovery";
|
||||
|
||||
package = lib.mkPackageOption pkgs "aurral" { };
|
||||
|
||||
directories = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.externalPath;
|
||||
default = [ ];
|
||||
description = ''
|
||||
Directories that Aurral needs access to. Other directories won't be visible by the app.
|
||||
Environment variable `DOWNLOAD_FOLDER` is added automatically.
|
||||
See BindPaths in {manpage}`systemd.exec(5)`.
|
||||
'';
|
||||
};
|
||||
|
||||
dataDir = lib.mkOption {
|
||||
type = lib.types.externalPath;
|
||||
default = "/var/lib/aurral";
|
||||
description = ''
|
||||
The directory where Aurral stores its stateful data.
|
||||
'';
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 3001;
|
||||
description = "Port number";
|
||||
};
|
||||
|
||||
openFirewall = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Open ports in the firewall for Aurral.
|
||||
'';
|
||||
};
|
||||
|
||||
user = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "aurral";
|
||||
description = ''
|
||||
User account under which Aurral runs.
|
||||
'';
|
||||
};
|
||||
|
||||
group = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "aurral";
|
||||
description = ''
|
||||
Group under which Aurral runs.
|
||||
'';
|
||||
};
|
||||
|
||||
environment = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.str;
|
||||
default = { };
|
||||
example = {
|
||||
DOWNLOAD_FOLDER = "/media/downloads";
|
||||
TRUST_PROXY = "true";
|
||||
};
|
||||
description = ''
|
||||
Environment variables passed to the service.
|
||||
'';
|
||||
};
|
||||
|
||||
environmentFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
Environment file as defined in {manpage}`systemd.exec(5)` passed to the service.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
systemd.services.aurral = {
|
||||
description = "Aurral";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
environment = cfg.environment // {
|
||||
AURRAL_DATA_DIR = cfg.dataDir;
|
||||
PORT = toString cfg.port;
|
||||
};
|
||||
|
||||
path = [ cfg.package ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStart = lib.getExe cfg.package;
|
||||
Restart = "on-failure";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
|
||||
StateDirectory = lib.mkIf (cfg.dataDir == "/var/lib/aurral") "aurral";
|
||||
WorkingDirectory = cfg.dataDir;
|
||||
ReadWritePaths = "";
|
||||
ProtectSystem = "strict";
|
||||
BindPaths = [
|
||||
cfg.dataDir
|
||||
]
|
||||
++ (lib.map (x: "-" + x) cfg.directories)
|
||||
++ lib.optional (cfg.environment ? DOWNLOAD_FOLDER) cfg.environment.DOWNLOAD_FOLDER;
|
||||
BindReadOnlyPaths = [
|
||||
builtins.storeDir
|
||||
"${config.security.pki.caBundle}:/etc/ssl/certs/ca-certificates.crt"
|
||||
"-/etc/resolv.conf"
|
||||
]
|
||||
++ lib.optionals config.services.resolved.enable [
|
||||
"/run/systemd/resolve/stub-resolv.conf"
|
||||
"/run/systemd/resolve/resolv.conf"
|
||||
];
|
||||
RestrictSUIDSGID = true;
|
||||
CapabilityBoundingSet = "";
|
||||
RestrictAddressFamilies = [
|
||||
"AF_UNIX"
|
||||
"AF_INET"
|
||||
"AF_INET6"
|
||||
];
|
||||
SocketBindDeny = "any";
|
||||
SocketBindAllow = toString cfg.port;
|
||||
SystemCallErrorNumber = "EPERM";
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
"~@privileged"
|
||||
"~@resources"
|
||||
];
|
||||
UMask = "0007";
|
||||
SystemCallArchitectures = "native";
|
||||
ProtectProc = "invisible";
|
||||
ProcSubset = "pid";
|
||||
LockPersonality = true;
|
||||
NoNewPrivileges = true;
|
||||
DevicePolicy = "closed";
|
||||
PrivateIPC = true;
|
||||
PrivatePIDs = true;
|
||||
ProtectClock = true;
|
||||
ProtectHome = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectHostname = true;
|
||||
RemoveIPC = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictNamespaces = true;
|
||||
MemoryDenyWriteExecute = false;
|
||||
};
|
||||
|
||||
confinement.enable = true;
|
||||
};
|
||||
|
||||
systemd.tmpfiles.settings."10-aurral" = lib.mkIf (cfg.environment ? DOWNLOAD_FOLDER) {
|
||||
${cfg.environment.DOWNLOAD_FOLDER}.d = {
|
||||
inherit (cfg) user group;
|
||||
mode = "0770";
|
||||
};
|
||||
};
|
||||
|
||||
users.users = lib.mkIf (cfg.user == "aurral") {
|
||||
aurral = {
|
||||
isSystemUser = true;
|
||||
home = cfg.dataDir;
|
||||
group = cfg.group;
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = lib.mkIf (cfg.group == "aurral") {
|
||||
aurral = { };
|
||||
};
|
||||
|
||||
networking.firewall = lib.mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ cfg.port ];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
# CLIProxyAPI {#module-services-cliproxyapi}
|
||||
|
||||
[CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI) exposes OAuth-based subscription CLIs (Claude Code, Codex, Gemini, Qwen, Grok, Antigravity) behind OpenAI/Gemini/Anthropic-compatible HTTP APIs.
|
||||
|
||||
Enable it with:
|
||||
|
||||
```nix
|
||||
{
|
||||
services.cliproxyapi.enable = true;
|
||||
}
|
||||
```
|
||||
|
||||
The service runs as a dedicated `cliproxyapi` user and keeps its configuration and OAuth tokens under `/var/lib/cliproxyapi`.
|
||||
|
||||
## Authentication {#module-services-cliproxyapi-authentication}
|
||||
|
||||
Provider logins use OAuth and must land in the service's `auth-dir` (`/var/lib/cliproxyapi`), which is owned by the `cliproxyapi` user. Either of the approaches below writes the token with the correct ownership, and the running service picks it up without a restart.
|
||||
|
||||
### Management API {#module-services-cliproxyapi-authentication-management-api}
|
||||
|
||||
Set a management key in [](#opt-services.cliproxyapi.settings):
|
||||
|
||||
```nix
|
||||
{
|
||||
services.cliproxyapi.settings.remote-management.secret-key._secret =
|
||||
"/run/secrets/cliproxyapi-mgmt-key";
|
||||
}
|
||||
```
|
||||
|
||||
Then request an authentication URL for the desired provider and open it in a browser:
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer <management-key>" \
|
||||
http://127.0.0.1:8317/v0/management/anthropic-auth-url
|
||||
```
|
||||
|
||||
The daemon completes the OAuth flow itself and stores the token in its `auth-dir`. Authentication endpoints are available for the `anthropic`, `codex`, `xai`, `antigravity`, and `kimi` providers.
|
||||
|
||||
### Command-line login {#module-services-cliproxyapi-authentication-cli}
|
||||
|
||||
Add the package so the `cliproxyapi` binary is on `PATH`:
|
||||
|
||||
```nix
|
||||
{
|
||||
environment.systemPackages = [ config.services.cliproxyapi.package ];
|
||||
}
|
||||
```
|
||||
|
||||
Then run the login as the service user, pointing at the managed configuration:
|
||||
|
||||
```bash
|
||||
sudo -u cliproxyapi cliproxyapi -config /var/lib/cliproxyapi/config.yaml --claude-login
|
||||
```
|
||||
|
||||
Other providers use their matching flags, for example `--codex-login` or `--xai-login`. On a headless host, pass `-no-browser` to print the OAuth URL instead of launching a browser.
|
||||
@@ -1,153 +0,0 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
utils,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.services.cliproxyapi;
|
||||
format = pkgs.formats.yaml { };
|
||||
stateDir = "/var/lib/cliproxyapi";
|
||||
configPath = "${stateDir}/config.yaml";
|
||||
settings = {
|
||||
auth-dir = stateDir;
|
||||
}
|
||||
// cfg.settings;
|
||||
secretsReplacement = utils.genJqSecretsReplacement {
|
||||
loadCredential = true;
|
||||
} settings configPath;
|
||||
port = cfg.settings.port or 8317;
|
||||
in
|
||||
{
|
||||
options.services.cliproxyapi = {
|
||||
enable = lib.mkEnableOption "CLIProxyAPI";
|
||||
|
||||
package = lib.mkPackageOption pkgs "cliproxyapi" { };
|
||||
|
||||
settings = lib.mkOption {
|
||||
type = format.type;
|
||||
default = { };
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
host = "127.0.0.1";
|
||||
port = 8317;
|
||||
api-keys = [ { _secret = "/run/secrets/cliproxyapi-api-key"; } ];
|
||||
remote-management.secret-key._secret = "/run/secrets/cliproxyapi-management-key";
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Configuration for CLIProxyAPI. See the
|
||||
[example configuration](https://github.com/router-for-me/CLIProxyAPI/blob/main/config.example.yaml)
|
||||
for available options. Secret values can be loaded from files using
|
||||
`._secret = "/path/to/secret";`.
|
||||
'';
|
||||
};
|
||||
|
||||
environmentFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
example = "/run/secrets/cliproxyapi.env";
|
||||
description = "Environment file as defined in {manpage}`systemd.exec(5)`.";
|
||||
};
|
||||
|
||||
openFirewall = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Whether to open the firewall for the specified port.";
|
||||
};
|
||||
|
||||
user = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "cliproxyapi";
|
||||
description = "User account under which CLIProxyAPI runs.";
|
||||
};
|
||||
|
||||
group = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "cliproxyapi";
|
||||
description = "Group under which CLIProxyAPI runs.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
users.users = lib.mkIf (cfg.user == "cliproxyapi") {
|
||||
cliproxyapi = {
|
||||
isSystemUser = true;
|
||||
group = cfg.group;
|
||||
home = stateDir;
|
||||
description = "CLIProxyAPI service user";
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = lib.mkIf (cfg.group == "cliproxyapi") {
|
||||
cliproxyapi = { };
|
||||
};
|
||||
|
||||
systemd.services.cliproxyapi = {
|
||||
description = "Proxy that provides OpenAI/Gemini/Claude/Codex/Grok compatible API interfaces";
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
preStart = secretsReplacement.script;
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
StateDirectory = "cliproxyapi";
|
||||
StateDirectoryMode = "0700";
|
||||
WorkingDirectory = stateDir;
|
||||
ExecStart = "${lib.getExe cfg.package} -config ${configPath}";
|
||||
Restart = "on-failure";
|
||||
RestartSec = 5;
|
||||
LoadCredential = secretsReplacement.credentials;
|
||||
EnvironmentFile = lib.mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
|
||||
|
||||
# Hardening
|
||||
CapabilityBoundingSet = "";
|
||||
NoNewPrivileges = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
PrivateDevices = true;
|
||||
PrivateUsers = true;
|
||||
ProtectHostname = true;
|
||||
ProtectClock = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectProc = "invisible";
|
||||
ProcSubset = "pid";
|
||||
RestrictAddressFamilies = [
|
||||
"AF_INET"
|
||||
"AF_INET6"
|
||||
];
|
||||
RestrictNamespaces = true;
|
||||
RestrictSUIDSGID = true;
|
||||
RestrictRealtime = true;
|
||||
RemoveIPC = true;
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
"~@privileged"
|
||||
"~@resources"
|
||||
];
|
||||
UMask = "0077";
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = lib.mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ port ];
|
||||
};
|
||||
};
|
||||
|
||||
meta = {
|
||||
doc = ./cliproxyapi.md;
|
||||
maintainers = [ lib.maintainers.anish ];
|
||||
};
|
||||
}
|
||||
@@ -111,13 +111,7 @@ in
|
||||
ExecStart = toString [
|
||||
(lib.getExe' cfg.package "llama-server")
|
||||
(lib.cli.toCommandLine (optionName: {
|
||||
option =
|
||||
if lib.hasPrefix "-" optionName then
|
||||
optionName
|
||||
else if builtins.stringLength optionName > 1 then
|
||||
"--${optionName}"
|
||||
else
|
||||
"-${optionName}";
|
||||
option = if builtins.stringLength optionName > 1 then "--${optionName}" else "-${optionName}";
|
||||
sep = " ";
|
||||
explicitBool = false;
|
||||
formatArg = lib.generators.mkValueStringDefault { };
|
||||
|
||||
51
nixos/modules/services/networking/freenet.nix
Normal file
51
nixos/modules/services/networking/freenet.nix
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.freenet;
|
||||
varDir = "/var/lib/freenet";
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.freenet = {
|
||||
enable = lib.mkEnableOption "Freenet daemon";
|
||||
|
||||
nice = lib.mkOption {
|
||||
type = lib.types.ints.between (-20) 19;
|
||||
default = 10;
|
||||
description = "Set the nice level for the Freenet daemon";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
systemd.services.freenet = {
|
||||
description = "Freenet daemon";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = lib.getExe pkgs.freenet;
|
||||
User = "freenet";
|
||||
UMask = "0007";
|
||||
WorkingDirectory = varDir;
|
||||
Nice = cfg.nice;
|
||||
};
|
||||
};
|
||||
|
||||
users.users.freenet = {
|
||||
group = "freenet";
|
||||
description = "Freenet daemon user";
|
||||
home = varDir;
|
||||
createHome = true;
|
||||
uid = config.ids.uids.freenet;
|
||||
};
|
||||
|
||||
users.groups.freenet.gid = config.ids.gids.freenet;
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ nagy ];
|
||||
}
|
||||
@@ -120,93 +120,6 @@ in
|
||||
example = "/var/lib/headplane";
|
||||
};
|
||||
|
||||
proxy_auth = mkOption {
|
||||
type = types.nullOr (
|
||||
types.submodule {
|
||||
options = {
|
||||
enabled = mkEnableOption ''
|
||||
delegating Headplane authentication to a trusted reverse proxy
|
||||
instead of logging in through Headplane directly. Identity
|
||||
headers are only trusted on requests whose client IP matches
|
||||
`allowed_cidrs`; all Headscale API calls then use
|
||||
`headscale.api_key_path`
|
||||
'';
|
||||
|
||||
allowed_cidrs = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [
|
||||
"127.0.0.1/32"
|
||||
"::1/128"
|
||||
];
|
||||
description = ''
|
||||
Client CIDRs allowed to authenticate via the configured proxy
|
||||
headers.
|
||||
'';
|
||||
example = [ "10.0.0.0/8" ];
|
||||
};
|
||||
|
||||
trusted_proxy_cidrs = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [
|
||||
"127.0.0.1/32"
|
||||
"::1/128"
|
||||
];
|
||||
description = ''
|
||||
Direct proxy CIDRs trusted to supply `ip_header`.
|
||||
'';
|
||||
example = [ "127.0.0.1/32" ];
|
||||
};
|
||||
|
||||
ip_header = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Header containing the original client IP, such as
|
||||
`X-Forwarded-For` or `X-Real-IP`. Only read when the direct
|
||||
socket peer matches `trusted_proxy_cidrs`.
|
||||
'';
|
||||
example = "X-Forwarded-For";
|
||||
};
|
||||
|
||||
user_header = mkOption {
|
||||
type = types.str;
|
||||
default = "Remote-User";
|
||||
description = ''
|
||||
Header containing the stable authenticated user identity.
|
||||
'';
|
||||
example = "Remote-User";
|
||||
};
|
||||
|
||||
email_header = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Optional header containing the authenticated user's email address.";
|
||||
example = "Remote-Email";
|
||||
};
|
||||
|
||||
name_header = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Optional header containing the authenticated user's display name.";
|
||||
example = "Remote-Name";
|
||||
};
|
||||
|
||||
picture_header = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Optional header containing the authenticated user's profile picture URL.";
|
||||
example = "Remote-Picture";
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
default = null;
|
||||
description = ''
|
||||
Delegate Headplane authentication to a trusted reverse proxy. See the
|
||||
upstream [Proxy Authentication docs](https://github.com/tale/headplane/blob/main/docs/features/proxy-auth.md).
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
default = { };
|
||||
@@ -532,18 +445,6 @@ in
|
||||
services.headplane.settings.headscale.api_key_path must be set when the agent is enabled.
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion =
|
||||
cfg.settings.server.proxy_auth == null
|
||||
|| !cfg.settings.server.proxy_auth.enabled
|
||||
|| cfg.settings.headscale.api_key_path != null;
|
||||
message = ''
|
||||
services.headplane.settings.headscale.api_key_path must be set
|
||||
when services.headplane.settings.server.proxy_auth.enabled is true.
|
||||
Proxy authentication requires a Headscale API key to make Headscale
|
||||
API calls on behalf of proxy-authenticated users.
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
environment = {
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.hyphanet;
|
||||
useNewNames = lib.versionAtLeast config.system.stateVersion "26.11";
|
||||
userName = if useNewNames then "hyphanet" else "freenet";
|
||||
varDir = "/var/lib/${userName}";
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.hyphanet = {
|
||||
enable = lib.mkEnableOption "Hyphanet daemon";
|
||||
|
||||
nice = lib.mkOption {
|
||||
type = lib.types.ints.between (-20) 19;
|
||||
default = 10;
|
||||
description = "Set the nice level for the Hyphanet daemon";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
imports = [
|
||||
(lib.mkRenamedOptionModule [ "services" "freenet" ] [ "services" "hyphanet" ])
|
||||
];
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
systemd.services.hyphanet = {
|
||||
description = "Hyphanet daemon";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = lib.getExe pkgs.hyphanet;
|
||||
User = userName;
|
||||
UMask = "0007";
|
||||
WorkingDirectory = varDir;
|
||||
Nice = cfg.nice;
|
||||
};
|
||||
};
|
||||
|
||||
users.users.${userName} = {
|
||||
group = userName;
|
||||
description = "Hyphanet daemon user";
|
||||
home = varDir;
|
||||
createHome = true;
|
||||
};
|
||||
|
||||
users.groups.${userName} = { };
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ nagy ];
|
||||
}
|
||||
@@ -263,14 +263,6 @@ in
|
||||
types.submodule {
|
||||
inherit freeformType;
|
||||
options = {
|
||||
type = lib.mkOption {
|
||||
type = types.str;
|
||||
default = "server";
|
||||
description = ''
|
||||
Type of server tunnel.
|
||||
See <https://docs.i2pd.website/en/latest/user-guide/tunnels/#tunnel-types>.
|
||||
'';
|
||||
};
|
||||
host = lib.mkOption {
|
||||
type = types.either types.str credType;
|
||||
description = "IP address of server (on this address i2pd will send data from I2P)";
|
||||
@@ -297,14 +289,6 @@ in
|
||||
types.submodule {
|
||||
inherit freeformType;
|
||||
options = {
|
||||
type = lib.mkOption {
|
||||
type = types.str;
|
||||
default = "client";
|
||||
description = ''
|
||||
Type of client tunnel.
|
||||
See <https://docs.i2pd.website/en/latest/user-guide/tunnels/#tunnel-types>.
|
||||
'';
|
||||
};
|
||||
port = lib.mkOption {
|
||||
type = types.port;
|
||||
description = "Port of client tunnel (on this port i2pd will receive data)";
|
||||
@@ -417,8 +401,10 @@ in
|
||||
gen = attr: settings: {
|
||||
conf = genConfig "i2pd.conf" (credSubstituteRec attr settings);
|
||||
tunconf = genTunnels "i2pd-tunnels.conf" (
|
||||
lib.mapAttrs' (k: v: lib.nameValuePair "client-${k}" v) (credSubstituteRec attr cfg.clientTunnels)
|
||||
// lib.mapAttrs' (k: v: lib.nameValuePair "server-${k}" v) (
|
||||
lib.mapAttrs' (k: v: lib.nameValuePair "client-${k}" (v // { "type" = "client"; })) (
|
||||
credSubstituteRec attr cfg.clientTunnels
|
||||
)
|
||||
// lib.mapAttrs' (k: v: lib.nameValuePair "server-${k}" (v // { "type" = "server"; })) (
|
||||
credSubstituteRec attr cfg.serverTunnels
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1,226 +0,0 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (lib)
|
||||
any
|
||||
getExe
|
||||
hasPrefix
|
||||
mapAttrsToList
|
||||
mkIf
|
||||
literalExpression
|
||||
mkEnableOption
|
||||
mkMerge
|
||||
mkOption
|
||||
mkPackageOption
|
||||
optional
|
||||
optionals
|
||||
;
|
||||
|
||||
inherit (lib.types)
|
||||
attrsOf
|
||||
bool
|
||||
listOf
|
||||
nullOr
|
||||
path
|
||||
str
|
||||
submodule
|
||||
;
|
||||
|
||||
cfg = config.services.iocaine;
|
||||
|
||||
jsonFormat = pkgs.formats.json { };
|
||||
|
||||
hasUDSbind = any (hasPrefix "/") (
|
||||
mapAttrsToList (_server: cfg: cfg.bind) (cfg.settings.server or { })
|
||||
);
|
||||
|
||||
ifHasSettings = optional (cfg.settings != null);
|
||||
|
||||
hasFirewall = cfg.settings.firewall.enable;
|
||||
|
||||
description = "iocaine, the deadliest poison known to AI";
|
||||
in
|
||||
{
|
||||
options.services.iocaine = {
|
||||
enable = mkEnableOption description;
|
||||
|
||||
package = mkPackageOption pkgs "iocaine" { };
|
||||
|
||||
environment = mkOption {
|
||||
default = { };
|
||||
type = attrsOf str;
|
||||
description = "Environment variables for iocaine.";
|
||||
example = literalExpression ''
|
||||
{
|
||||
RUST_LOG = "info";
|
||||
RUST_BACKTRACE = "1";
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = nullOr (submodule {
|
||||
freeformType = jsonFormat.type;
|
||||
|
||||
options = {
|
||||
firewall.enable = mkOption {
|
||||
default = false;
|
||||
type = bool;
|
||||
description = "Enables the firewall";
|
||||
example = true;
|
||||
};
|
||||
};
|
||||
});
|
||||
default = null;
|
||||
description = ''
|
||||
The configuration for iocaine.
|
||||
See [the configuration reference](https://iocaine.madhouse-project.org/documentation/3/configuration/)
|
||||
for full documentation on the fields.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
server.default = {
|
||||
bind = "localhost:2137";
|
||||
mode = "http";
|
||||
use.handler-from = "default";
|
||||
};
|
||||
|
||||
handler.default = {
|
||||
settings = {
|
||||
"ai-robots-txt-path" = "/etc/iocaine/data/ai.robots.txt-robots.json";
|
||||
sources = {
|
||||
training-corpus = [
|
||||
"/data/corpus/1984.txt"
|
||||
"/data/corpus/brave-new-world.txt"
|
||||
];
|
||||
wordlists = [ "/data/corpus/words.txt" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
extraSettingsPaths = mkOption {
|
||||
type = listOf path;
|
||||
default = [ ];
|
||||
description = "Configuration paths to run iocaine with. Useful for secrets";
|
||||
example = literalExpression ''
|
||||
[
|
||||
"/etc/iocaine/iocaine.json"
|
||||
./iocaine.json
|
||||
]
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
environment.etc."iocaine/iocaine.json" = mkIf (cfg.settings != null) {
|
||||
source = jsonFormat.generate "iocaine.json" cfg.settings;
|
||||
};
|
||||
|
||||
systemd.services = mkMerge [
|
||||
{
|
||||
iocaine = {
|
||||
inherit description;
|
||||
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
|
||||
environment = {
|
||||
HOME = "%S/home";
|
||||
}
|
||||
// cfg.environment;
|
||||
|
||||
restartTriggers =
|
||||
(ifHasSettings config.environment.etc."iocaine/iocaine.json".source) ++ cfg.extraSettingsPaths;
|
||||
|
||||
stopIfChanged = false;
|
||||
|
||||
serviceConfig = {
|
||||
Type = "notify";
|
||||
ExecStart = toString (
|
||||
[
|
||||
(getExe cfg.package)
|
||||
]
|
||||
++ (map (path: "--config-path=${path}") (
|
||||
(ifHasSettings "/etc/iocaine/iocaine.json") ++ cfg.extraSettingsPaths
|
||||
))
|
||||
++ [ "start" ]
|
||||
);
|
||||
|
||||
Restart = "on-failure";
|
||||
DynamicUser = true;
|
||||
UMask = "0077";
|
||||
LimitNOFILE = 524288;
|
||||
|
||||
StateDirectory = "iocaine";
|
||||
WorkingDirectory = "%S/iocaine";
|
||||
RuntimeDirectory = "iocaine";
|
||||
|
||||
ProtectSystem = "strict";
|
||||
ProtectClock = true;
|
||||
ProtectHostname = true;
|
||||
ProtectProc = "invisible";
|
||||
ProtectControlGroups = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectHome = true;
|
||||
|
||||
PrivateTmp = true;
|
||||
PrivateDevices = true;
|
||||
PrivateUsers = !hasFirewall;
|
||||
|
||||
SystemCallArchitectures = "native";
|
||||
DevicePolicy = "closed";
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = false;
|
||||
NoNewPrivileges = true;
|
||||
|
||||
RestrictAddressFamilies =
|
||||
(optionals hasUDSbind [
|
||||
"AF_INET"
|
||||
"AF_INET6"
|
||||
"AF_UNIX"
|
||||
])
|
||||
++ (optionals hasFirewall [ "AF_NETLINK" ]);
|
||||
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
"~@privileged"
|
||||
"~@resources"
|
||||
];
|
||||
|
||||
CapabilityBoundingSet = mkIf hasFirewall [ "CAP_NET_ADMIN" ];
|
||||
AmbientCapabilities = mkIf hasFirewall [ "CAP_NET_ADMIN" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
(
|
||||
let
|
||||
iocaineDep = {
|
||||
requires = [ "iocaine.service" ];
|
||||
after = [ "iocaine.service" ];
|
||||
serviceConfig.SupplementaryGroups = [ "iocaine" ];
|
||||
};
|
||||
in
|
||||
{
|
||||
nginx = mkIf (config.services.nginx.enable && hasUDSbind) iocaineDep;
|
||||
caddy = mkIf (config.services.caddy.enable && hasUDSbind) iocaineDep;
|
||||
}
|
||||
)
|
||||
];
|
||||
};
|
||||
|
||||
meta = {
|
||||
maintainers = with lib.maintainers; [ poz ];
|
||||
};
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.omada;
|
||||
in
|
||||
|
||||
{
|
||||
options = {
|
||||
services.omada = {
|
||||
enable = lib.mkEnableOption "Enable the Omada Software Controller service.";
|
||||
|
||||
package = lib.mkPackageOption pkgs "omada-software-controller" { };
|
||||
|
||||
user = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "omada";
|
||||
description = ''
|
||||
User under which the Omada Software Controller service runs.
|
||||
'';
|
||||
};
|
||||
|
||||
group = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "omada";
|
||||
description = ''
|
||||
Group under which the Omada Software Controller service runs.
|
||||
'';
|
||||
};
|
||||
|
||||
dataDir = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = "/var/lib/omada";
|
||||
description = ''
|
||||
The path where the Omada Software Controller stores all data. This path must
|
||||
be in sync with the omada-software-controller package (where it is hardcoded
|
||||
during the build in accordance with its own `dataDir` argument).
|
||||
'';
|
||||
};
|
||||
|
||||
openFirewallDevicePorts = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to open the firewall ports required for Omada devices to communicate
|
||||
with the Omada Software Controller (discovery, adoption, management, etc.).
|
||||
'';
|
||||
};
|
||||
|
||||
openFirewallWebPorts = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to open the firewall ports of the web interface (8043, 8088).
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
users.groups.${cfg.group} = { };
|
||||
|
||||
users.users.${cfg.user} = {
|
||||
description = "Omada Software Controller user";
|
||||
group = cfg.group;
|
||||
home = cfg.dataDir;
|
||||
createHome = true;
|
||||
isSystemUser = true;
|
||||
};
|
||||
|
||||
systemd.tmpfiles.settings."10-omada" =
|
||||
lib.genAttrs
|
||||
(map (dir: "${cfg.dataDir}/${dir}") [
|
||||
"data"
|
||||
"logs"
|
||||
"properties"
|
||||
"work"
|
||||
])
|
||||
(_: {
|
||||
d = {
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
};
|
||||
});
|
||||
|
||||
systemd.services.omada = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
description = "Omada Software Controller";
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${lib.getExe cfg.package} start";
|
||||
ExecStop = "${lib.getExe cfg.package} stop";
|
||||
# The control script asks for confirmation (y/n) before starting when
|
||||
# it detects that MongoDB was upgraded across major versions. Answer it,
|
||||
# otherwise its read loop would spin forever on an EOF stdin.
|
||||
StandardInput = "data";
|
||||
StandardInputText = "y";
|
||||
Type = "forking";
|
||||
TimeoutSec = 300;
|
||||
RuntimeDirectory = "omada";
|
||||
RuntimeDirectoryMode = "0755";
|
||||
PIDFile = "/run/omada/omada.pid";
|
||||
WorkingDirectory = cfg.dataDir;
|
||||
StateDirectory = baseNameOf cfg.dataDir;
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
Environment = [
|
||||
"OMADA_USER=${cfg.user}"
|
||||
];
|
||||
Restart = "on-failure";
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = {
|
||||
allowedUDPPorts = lib.optionals cfg.openFirewallDevicePorts [
|
||||
19810 # discovery port
|
||||
29810 # discovery port
|
||||
];
|
||||
allowedTCPPorts =
|
||||
lib.optionals cfg.openFirewallDevicePorts [
|
||||
29811 # management port
|
||||
29812 # adoption port
|
||||
29813 # upgrade port
|
||||
29814 # management port
|
||||
29815 # transfer port
|
||||
29816 # rtty port
|
||||
29817 # device management port
|
||||
]
|
||||
++ lib.optionals cfg.openFirewallWebPorts [
|
||||
8043 # web port (HTTPS)
|
||||
8088 # web port (HTTP)
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -95,8 +95,8 @@ in
|
||||
wantedBy = [ "network.target" ];
|
||||
serviceConfig = {
|
||||
Type = "forking";
|
||||
ExecStart = "${cfg.dataDir}/vpnserver/vpnserver start";
|
||||
ExecStop = "${cfg.dataDir}/vpnserver/vpnserver stop";
|
||||
ExecStart = "${package}/bin/vpnserver start";
|
||||
ExecStop = "${package}/bin/vpnserver stop";
|
||||
};
|
||||
preStart = ''
|
||||
rm -rf ${cfg.dataDir}/vpnserver/vpnserver
|
||||
@@ -116,8 +116,8 @@ in
|
||||
wantedBy = [ "network.target" ];
|
||||
serviceConfig = {
|
||||
Type = "forking";
|
||||
ExecStart = "${cfg.dataDir}/vpnbridge/vpnbridge start";
|
||||
ExecStop = "${cfg.dataDir}/vpnbridge/vpnbridge stop";
|
||||
ExecStart = "${package}/bin/vpnbridge start";
|
||||
ExecStop = "${package}/bin/vpnbridge stop";
|
||||
};
|
||||
preStart = ''
|
||||
rm -rf ${cfg.dataDir}/vpnbridge/vpnbridge
|
||||
@@ -137,8 +137,8 @@ in
|
||||
wantedBy = [ "network.target" ];
|
||||
serviceConfig = {
|
||||
Type = "forking";
|
||||
ExecStart = "${cfg.dataDir}/vpnclient/vpnclient start";
|
||||
ExecStop = "${cfg.dataDir}/vpnclient/vpnclient stop";
|
||||
ExecStart = "${package}/bin/vpnclient start";
|
||||
ExecStop = "${package}/bin/vpnclient stop";
|
||||
};
|
||||
preStart = ''
|
||||
rm -rf ${cfg.dataDir}/vpnclient/vpnclient
|
||||
|
||||
@@ -79,13 +79,13 @@ in
|
||||
mkAttrsOfParams
|
||||
{
|
||||
|
||||
version = mkIntParam 2 ''
|
||||
version = mkIntParam 0 ''
|
||||
IKE major version to use for connection.
|
||||
|
||||
- 1 uses IKEv1 aka ISAKMP,
|
||||
- 2 uses IKEv2 (the default).
|
||||
- A connection using 0 accepts both IKEv1 and IKEv2 as responder,
|
||||
and initiates the connection actively with IKEv2.
|
||||
- 2 uses IKEv2.
|
||||
- A connection using the default of 0 accepts both IKEv1 and IKEv2 as
|
||||
responder, and initiates the connection actively with IKEv2.
|
||||
'';
|
||||
|
||||
local_addrs = mkCommaSepListParam [ ] ''
|
||||
|
||||
@@ -149,28 +149,7 @@ in
|
||||
"2270007"
|
||||
];
|
||||
description = ''
|
||||
List of matchers specifying which rules should be disabled.
|
||||
These can be raw SID numbers or something like "group:emerging-coinminer.rules".
|
||||
'';
|
||||
};
|
||||
|
||||
dropRules = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = literalExpression ''
|
||||
[ "2274852" "4327876" "902244405" ]
|
||||
'';
|
||||
description = ''
|
||||
List of matchers specifying which rules should be converted to drop rules.
|
||||
These can be raw SID numbers or something like "group:emerging-coinminer.rules".
|
||||
'';
|
||||
};
|
||||
reloadOnRulesetUpdate = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to reload Suricata if it is running after an automated ruleset update.
|
||||
This is a blocking reload, and may take some time depending on the number of rules and computational power of the host.
|
||||
List of rules that should be disabled.
|
||||
'';
|
||||
};
|
||||
};
|
||||
@@ -234,20 +213,11 @@ in
|
||||
};
|
||||
|
||||
systemd.services = {
|
||||
suricata-blocking-reload = lib.mkIf cfg.reloadOnRulesetUpdate {
|
||||
description = "Refresh Runtime Suricata Ruleset";
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecCondition = "systemctl is-active --quiet suricata.service";
|
||||
ExecStart = "${pkg}/bin/suricatasc -c reload-rules";
|
||||
};
|
||||
};
|
||||
suricata-update = {
|
||||
description = "Update Suricata Rules";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
onSuccess = lib.mkIf cfg.reloadOnRulesetUpdate [ "suricata-blocking-reload.service" ];
|
||||
|
||||
script =
|
||||
let
|
||||
@@ -260,8 +230,7 @@ in
|
||||
${concatStringsSep "\n" enabledSourcesCmds}
|
||||
${python.interpreter} ${pkg}/bin/suricata-update update-sources
|
||||
${python.interpreter} ${pkg}/bin/suricata-update update --suricata-conf ${cfg.configFile} --no-test \
|
||||
--disable-conf ${pkgs.writeText "suricata-disable-conf" "${concatStringsSep "\n" cfg.disabledRules}"} \
|
||||
--drop-conf ${pkgs.writeText "suricata-drop.conf" "${concatStringsSep "\n" cfg.dropRules}"}
|
||||
--disable-conf ${pkgs.writeText "suricata-disable-conf" "${concatStringsSep "\n" cfg.disabledRules}"}
|
||||
'';
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
|
||||
@@ -18,47 +18,6 @@ let
|
||||
port
|
||||
;
|
||||
|
||||
# `host` is a bind address, and 0.0.0.0 means "every interface" to bind(2)
|
||||
# but nothing at all to connect(2). Workers run beside the server, so they
|
||||
# dial it directly rather than going back out through `baseUrl` - a reverse
|
||||
# proxy that may terminate TLS, require auth, or simply not be up yet.
|
||||
localHost = if cfg.host == "0.0.0.0" then "127.0.0.1" else cfg.host;
|
||||
|
||||
# Prefect builds the SQLAlchemy URL itself from these discrete settings, so
|
||||
# the password never has to be interpolated into a string that would land in
|
||||
# the store. It arrives separately as PREFECT_SERVER_DATABASE_PASSWORD, from
|
||||
# `databasePasswordFile`.
|
||||
postgresEnvironment = [
|
||||
"PREFECT_SERVER_DATABASE_DRIVER=postgresql+asyncpg"
|
||||
"PREFECT_SERVER_DATABASE_HOST=${cfg.databaseHost}"
|
||||
"PREFECT_SERVER_DATABASE_PORT=${cfg.databasePort}"
|
||||
"PREFECT_SERVER_DATABASE_NAME=${cfg.databaseName}"
|
||||
"PREFECT_SERVER_DATABASE_USER=${cfg.databaseUser}"
|
||||
];
|
||||
|
||||
# Identical for the server and every worker, so it is written once.
|
||||
hardening = {
|
||||
DynamicUser = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
NoNewPrivileges = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
LockPersonality = true;
|
||||
CapabilityBoundingSet = [ ];
|
||||
AmbientCapabilities = [ ];
|
||||
RestrictSUIDSGID = true;
|
||||
RestrictAddressFamilies = [
|
||||
"AF_INET"
|
||||
"AF_INET6"
|
||||
"AF_UNIX"
|
||||
];
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectControlGroups = true;
|
||||
MemoryAccounting = true;
|
||||
};
|
||||
in
|
||||
{
|
||||
options.services.prefect = {
|
||||
@@ -87,9 +46,7 @@ in
|
||||
type = path;
|
||||
default = "/var/lib/prefect-server";
|
||||
description = ''
|
||||
Working directory for the server. Note that Prefect's own state -
|
||||
including the SQLite database - lives in the unit's `StateDirectory`
|
||||
rather than here, since the service runs as a `DynamicUser`.
|
||||
Specify the directory for Prefect.
|
||||
'';
|
||||
};
|
||||
|
||||
@@ -129,19 +86,11 @@ in
|
||||
databasePasswordFile = lib.mkOption {
|
||||
type = nullOr str;
|
||||
default = null;
|
||||
example = "/run/secrets/prefect-database-password";
|
||||
description = ''
|
||||
Path to a file containing the postgres password as an environment
|
||||
variable assignment:
|
||||
path to a file containing e.g.:
|
||||
DBPASSWORD=supersecret
|
||||
|
||||
```
|
||||
PREFECT_SERVER_DATABASE_PASSWORD=supersecret
|
||||
```
|
||||
|
||||
Stored outside the nix store, read by systemd as an `EnvironmentFile`.
|
||||
|
||||
Leave `null` when postgres authenticates the server some other way,
|
||||
such as peer authentication over a unix socket.
|
||||
stored outside the nix store, read by systemd as EnvironmentFile.
|
||||
'';
|
||||
};
|
||||
|
||||
@@ -172,15 +121,9 @@ in
|
||||
};
|
||||
|
||||
baseUrl = lib.mkOption {
|
||||
type = str;
|
||||
default = "http://${localHost}:${toString cfg.port}";
|
||||
defaultText = lib.literalExpression ''"http://''${host}:''${toString port}"'';
|
||||
example = "https://example.com/prefect";
|
||||
description = ''
|
||||
External url the UI is reached at, when served by a reverse proxy.
|
||||
Defaults to the address the server itself binds, which is what you
|
||||
want when there is no proxy in front of it.
|
||||
'';
|
||||
type = nullOr str;
|
||||
default = null;
|
||||
description = "external url when served by a reverse proxy, e.g. `https://example.com/prefect`";
|
||||
};
|
||||
};
|
||||
|
||||
@@ -192,20 +135,44 @@ in
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
|
||||
serviceConfig = hardening // {
|
||||
serviceConfig = {
|
||||
DynamicUser = true;
|
||||
StateDirectory = "prefect-server";
|
||||
# TODO all my efforts to setup the database url
|
||||
# have failed with some unable to open file
|
||||
Environment = [
|
||||
"PREFECT_HOME=%S/prefect-server"
|
||||
"PREFECT_UI_STATIC_DIRECTORY=%S/prefect-server"
|
||||
"PREFECT_SERVER_ANALYTICS_ENABLED=off"
|
||||
"PREFECT_UI_API_URL=${cfg.baseUrl}/api"
|
||||
"PREFECT_UI_URL=${cfg.baseUrl}"
|
||||
]
|
||||
++ lib.optionals (cfg.database == "postgres") postgresEnvironment;
|
||||
];
|
||||
EnvironmentFile =
|
||||
if cfg.database == "postgres" && cfg.databasePasswordFile != null then
|
||||
[ cfg.databasePasswordFile ]
|
||||
else
|
||||
[ ];
|
||||
|
||||
EnvironmentFile = lib.optional (
|
||||
cfg.database == "postgres" && cfg.databasePasswordFile != null
|
||||
) cfg.databasePasswordFile;
|
||||
# ReadWritePaths = [ cfg.dataDir ];
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
NoNewPrivileges = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
LockPersonality = true;
|
||||
CapabilityBoundingSet = [ ];
|
||||
AmbientCapabilities = [ ];
|
||||
RestrictSUIDSGID = true;
|
||||
RestrictAddressFamilies = [
|
||||
"AF_INET"
|
||||
"AF_INET6"
|
||||
"AF_UNIX"
|
||||
];
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectControlGroups = true;
|
||||
MemoryAccounting = true;
|
||||
|
||||
ExecStart = "${lib.getExe cfg.package} server start --host ${cfg.host} --port ${toString cfg.port}";
|
||||
Restart = "always";
|
||||
@@ -218,21 +185,36 @@ in
|
||||
"prefect-worker-${poolName}" = {
|
||||
description = "prefect worker for pool '${poolName}'";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [
|
||||
"network.target"
|
||||
"prefect-server.service"
|
||||
];
|
||||
after = [ "network.target" ];
|
||||
|
||||
# A process worker shells out to prefect for every flow run it picks
|
||||
# up, so the package has to be on its PATH and not merely in ExecStart.
|
||||
path = [ cfg.package ];
|
||||
environment.systemPackages = cfg.package;
|
||||
|
||||
serviceConfig = hardening // {
|
||||
serviceConfig = {
|
||||
DynamicUser = true;
|
||||
StateDirectory = "prefect-worker-${poolName}";
|
||||
Environment = [
|
||||
"PREFECT_HOME=%S/prefect-worker-${poolName}"
|
||||
"PREFECT_API_URL=http://${localHost}:${toString cfg.port}/api"
|
||||
"PREFECT_API_URL=${cfg.baseUrl}/api"
|
||||
];
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
NoNewPrivileges = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
LockPersonality = true;
|
||||
CapabilityBoundingSet = [ ];
|
||||
AmbientCapabilities = [ ];
|
||||
RestrictSUIDSGID = true;
|
||||
RestrictAddressFamilies = [
|
||||
"AF_INET"
|
||||
"AF_INET6"
|
||||
"AF_UNIX"
|
||||
];
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectControlGroups = true;
|
||||
MemoryAccounting = true;
|
||||
ExecStart = ''
|
||||
${lib.getExe cfg.package} worker start \
|
||||
--pool ${poolName} \
|
||||
@@ -244,6 +226,4 @@ in
|
||||
};
|
||||
}) cfg.workerPools;
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ happysalada ];
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ with lib;
|
||||
let
|
||||
cfg = config.services.coder;
|
||||
name = "coder";
|
||||
configRefUrl = "https://coder.com/docs/@main/admin/setup/configuration-reference";
|
||||
in
|
||||
{
|
||||
options = {
|
||||
@@ -83,10 +82,7 @@ in
|
||||
environment = {
|
||||
extra = mkOption {
|
||||
type = types.attrs;
|
||||
description = ''
|
||||
Extra environment variables to pass run Coder's server with.
|
||||
See [Coder configuration reference](${configRefUrl}).
|
||||
'';
|
||||
description = "Extra environment variables to pass run Coder's server with. See Coder documentation.";
|
||||
default = { };
|
||||
example = {
|
||||
CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS = true;
|
||||
@@ -95,10 +91,7 @@ in
|
||||
};
|
||||
file = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
description = ''
|
||||
Systemd environment file to add to Coder.
|
||||
See [Coder configuration reference](${configRefUrl}).
|
||||
'';
|
||||
description = "Systemd environment file to add to Coder.";
|
||||
default = null;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,371 +0,0 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.services.flame;
|
||||
|
||||
settingsFormat = pkgs.formats.json { };
|
||||
|
||||
# Accepts either a list of strings or a raw semicolon-separated string.
|
||||
schemaToStr = x: if builtins.isList x then lib.concatStringsSep ";" x else x;
|
||||
|
||||
# Needed to prepopulate DB
|
||||
sqlQuote = s: "'" + builtins.replaceStrings [ "'" ] [ "''" ] s + "'";
|
||||
|
||||
seedSql = pkgs.writeText "flame-seed.sql" ''
|
||||
${lib.optionalString (cfg.apps != [ ] || cfg.categories != [ ]) ''
|
||||
DELETE FROM bookmarks;
|
||||
DELETE FROM categories;
|
||||
DELETE FROM apps;
|
||||
''}
|
||||
${lib.concatMapStringsSep "\n" (app: ''
|
||||
INSERT INTO apps (name, url, icon, description, isPinned, createdAt, updatedAt)
|
||||
VALUES (${sqlQuote app.name}, ${sqlQuote app.url}, ${sqlQuote app.icon}, ${sqlQuote app.description}, ${
|
||||
if app.isPinned then "1" else "0"
|
||||
}, datetime('now'), datetime('now'));
|
||||
'') cfg.apps}
|
||||
${lib.concatMapStringsSep "\n" (cat: ''
|
||||
INSERT INTO categories (name, isPinned, createdAt, updatedAt)
|
||||
VALUES (${sqlQuote cat.name}, ${
|
||||
if cat.isPinned then "1" else "0"
|
||||
}, datetime('now'), datetime('now'));
|
||||
${lib.concatMapStringsSep "\n" (bm: ''
|
||||
INSERT INTO bookmarks (name, url, icon, categoryId, createdAt, updatedAt)
|
||||
VALUES (${sqlQuote bm.name}, ${sqlQuote bm.url}, ${sqlQuote bm.icon}, (SELECT id FROM categories WHERE name = ${sqlQuote cat.name} ORDER BY id DESC LIMIT 1), datetime('now'), datetime('now'));
|
||||
'') cat.bookmarks}
|
||||
'') cfg.categories}
|
||||
'';
|
||||
|
||||
cssFile = pkgs.writeText "flame-custom.css" cfg.customCSS;
|
||||
|
||||
# Build-time symlink farm of everything Flame ships except data/ and
|
||||
# public/, which are left as empty placeholders here and populated at
|
||||
# runtime (data/ is real state; public/ is refreshed from cfg.package
|
||||
# on every start, since it holds built client assets).
|
||||
appTree = pkgs.runCommand "flame-app-tree" { } ''
|
||||
mkdir -p $out
|
||||
for entry in ${cfg.package}/lib/flame/*; do
|
||||
name=$(basename "$entry")
|
||||
if [ "$name" != data ] && [ "$name" != public ]; then
|
||||
ln -s "$entry" "$out/$name"
|
||||
fi
|
||||
done
|
||||
mkdir -p $out/data $out/public
|
||||
'';
|
||||
|
||||
# WEATHER_API_KEY is deliberately excluded here; it's injected at
|
||||
# runtime from `weatherApiKeyFile` so it never touches the Nix store.
|
||||
settingsFile = settingsFormat.generate "flame-settings.json" (
|
||||
lib.filterAttrs (n: _: n != "weatherApiKeyFile") cfg.settings
|
||||
// lib.optionalAttrs (cfg.settings ? greetingsSchema) {
|
||||
greetingsSchema = schemaToStr cfg.settings.greetingsSchema;
|
||||
}
|
||||
// lib.optionalAttrs (cfg.settings ? daySchema) {
|
||||
daySchema = schemaToStr cfg.settings.daySchema;
|
||||
}
|
||||
// lib.optionalAttrs (cfg.settings ? monthSchema) {
|
||||
monthSchema = schemaToStr cfg.settings.monthSchema;
|
||||
}
|
||||
);
|
||||
in
|
||||
{
|
||||
meta.maintainers = with lib.maintainers; [ DerGrumpf ];
|
||||
|
||||
options.services.flame = {
|
||||
enable = lib.mkEnableOption "Flame, a self-hosted startpage for your server";
|
||||
package = lib.mkPackageOption pkgs "flame" { };
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 5005;
|
||||
description = "Port on which to serve the Flame web interface.";
|
||||
};
|
||||
|
||||
passwordFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
description = ''
|
||||
Path to a file containing the password to log in to Flame's settings panel.
|
||||
This is the recommended option as it avoids storing the password in the Nix store.
|
||||
Compatible with sops-nix and agenix.
|
||||
'';
|
||||
example = "/run/secrets/flame-password";
|
||||
};
|
||||
|
||||
openFirewall = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Whether to open the firewall for the port used by Flame.";
|
||||
};
|
||||
|
||||
customCSS = lib.mkOption {
|
||||
type = lib.types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
Custom CSS injected into Flame's UI, written to
|
||||
{file}`public/flame.css` on every service start. Can also be used
|
||||
to define a fully custom theme via CSS custom properties — see
|
||||
[Flame's Custom CSS wiki page](https://github.com/pawelmalak/flame/wiki/Custom-CSS).
|
||||
'';
|
||||
example = ''
|
||||
.Home_SettingsButton__Qvn8C {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
categories = lib.mkOption {
|
||||
type = lib.types.listOf (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Name of the bookmark category.";
|
||||
};
|
||||
isPinned = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Whether the category is pinned by default.";
|
||||
};
|
||||
bookmarks = lib.mkOption {
|
||||
default = [ ];
|
||||
description = "Bookmarks belonging to this category.";
|
||||
type = lib.types.listOf (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Name of the bookmark.";
|
||||
};
|
||||
url = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "URL of the bookmark.";
|
||||
};
|
||||
icon = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "";
|
||||
description = "Icon name or URL for the bookmark.";
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
default = [ ];
|
||||
description = ''
|
||||
Bookmark categories and their bookmarks. When non-empty, this
|
||||
fully replaces the contents of Flame's `categories` and
|
||||
`bookmarks` tables on every service start — any bookmarks added
|
||||
through the web UI will not persist across restarts.
|
||||
'';
|
||||
example = [
|
||||
{
|
||||
name = "Dev";
|
||||
bookmarks = [
|
||||
{
|
||||
name = "GitHub";
|
||||
url = "https://github.com";
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
apps = lib.mkOption {
|
||||
type = lib.types.listOf (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Name of the app.";
|
||||
};
|
||||
url = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "URL of the app.";
|
||||
};
|
||||
icon = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "cancel";
|
||||
description = "Icon name or URL for the app.";
|
||||
};
|
||||
description = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "";
|
||||
description = "Short description shown for the app.";
|
||||
};
|
||||
isPinned = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Whether the app is pinned by default.";
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
default = [ ];
|
||||
description = ''
|
||||
Applications shown on the dashboard. When non-empty, this fully
|
||||
replaces the contents of Flame's `apps` table on every service
|
||||
start — any apps added through the web UI will not persist
|
||||
across restarts.
|
||||
'';
|
||||
example = [
|
||||
{
|
||||
name = "Router";
|
||||
url = "http://192.168.1.1";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
settings = lib.mkOption {
|
||||
type = lib.types.submodule {
|
||||
freeformType = settingsFormat.type;
|
||||
|
||||
options = {
|
||||
weatherApiKeyFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
Path to a file containing the API key obtained from https://www.weatherapi.com used for
|
||||
Flame's weather widget.
|
||||
Compatible with sops-nix and agenix.
|
||||
'';
|
||||
example = "/run/secrets/flame-weather-api-key";
|
||||
};
|
||||
};
|
||||
};
|
||||
default = { };
|
||||
description = ''
|
||||
Flame settings, written to Flame's settings JSON on every service
|
||||
start. Accepts any key Flame's settings API supports; see
|
||||
[Flame's source](https://github.com/pawelmalak/flame/blob/master/client/src/context/context.js)
|
||||
for the current schema, since Flame does not publish separate
|
||||
settings documentation.
|
||||
|
||||
`greetingsSchema`, `daySchema`, and `monthSchema` accept either a
|
||||
list of strings or a single semicolon-separated string.
|
||||
'';
|
||||
example = {
|
||||
lat = 52.52;
|
||||
long = 13.405;
|
||||
customTitle = "My Dashboard";
|
||||
hideHeader = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
systemd.services = {
|
||||
flame-seed = lib.mkIf (cfg.apps != [ ] || cfg.categories != [ ]) {
|
||||
description = "Seed Flame apps and bookmarks";
|
||||
after = [ "flame.service" ];
|
||||
requires = [ "flame.service" ];
|
||||
wantedBy = [ "flame.service" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
DynamicUser = true;
|
||||
StateDirectory = "flame";
|
||||
};
|
||||
|
||||
script = ''
|
||||
for i in $(seq 1 30); do
|
||||
if ${lib.getExe pkgs.sqlite} /var/lib/flame/app/data/db.sqlite \
|
||||
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='apps';" | grep -q 1; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
${lib.getExe pkgs.sqlite} /var/lib/flame/app/data/db.sqlite < ${seedSql}
|
||||
'';
|
||||
};
|
||||
|
||||
flame = {
|
||||
description = "Flame, a self-hosted startpage for your server";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
|
||||
preStart = ''
|
||||
for entry in ${appTree}/*; do
|
||||
name=$(basename "$entry")
|
||||
if [ "$name" != data ] && [ "$name" != public ]; then
|
||||
ln -sfn "$entry" /var/lib/flame/app/"$name"
|
||||
fi
|
||||
done
|
||||
|
||||
for entry in /var/lib/flame/app/data /var/lib/flame/app/public; do
|
||||
if [ -L "$entry" ]; then
|
||||
rm -f "$entry"
|
||||
fi
|
||||
done
|
||||
mkdir -p /var/lib/flame/app/data/uploads /var/lib/flame/app/public
|
||||
|
||||
if [ ! -f /var/lib/flame/app/data/.secret ]; then
|
||||
${lib.getExe pkgs.openssl} rand -hex 32 > /var/lib/flame/app/data/.secret
|
||||
fi
|
||||
chmod 644 /var/lib/flame/app/data/.secret
|
||||
|
||||
cp -r ${cfg.package}/lib/flame/public/. /var/lib/flame/app/public/
|
||||
chmod -R u+w /var/lib/flame/app/public
|
||||
|
||||
install -m644 ${cssFile} /var/lib/flame/app/data/flame.css
|
||||
|
||||
${lib.getExe pkgs.jq} -n --slurpfile base ${cfg.package}/lib/flame/utils/init/initialConfig.json \
|
||||
'$base[0]' > /var/lib/flame/app/data/config.json.tmp
|
||||
|
||||
${lib.optionalString (cfg.settings.weatherApiKeyFile != null) ''
|
||||
weatherApiKey=$(cat ${cfg.settings.weatherApiKeyFile})
|
||||
${lib.getExe pkgs.jq} --arg key "$weatherApiKey" '.WEATHER_API_KEY = $key' \
|
||||
${settingsFile} > /var/lib/flame/app/data/settings-with-key.json
|
||||
''}
|
||||
|
||||
${lib.getExe pkgs.jq} -s '.[0] * .[1]' \
|
||||
/var/lib/flame/app/data/config.json.tmp \
|
||||
${
|
||||
if cfg.settings.weatherApiKeyFile != null then
|
||||
"/var/lib/flame/app/data/settings-with-key.json"
|
||||
else
|
||||
settingsFile
|
||||
} \
|
||||
> /var/lib/flame/app/data/config.json
|
||||
rm -f /var/lib/flame/app/data/config.json.tmp
|
||||
chmod u+w /var/lib/flame/app/data/config.json
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
DynamicUser = true;
|
||||
StateDirectory = [
|
||||
"flame"
|
||||
"flame/app"
|
||||
];
|
||||
WorkingDirectory = "/var/lib/flame/app";
|
||||
Environment = [
|
||||
"PORT=${toString cfg.port}"
|
||||
"NODE_ENV=production"
|
||||
"VERSION=${cfg.package.version}"
|
||||
];
|
||||
LoadCredential = [ "flame-password:${cfg.passwordFile}" ];
|
||||
Restart = "always";
|
||||
NoNewPrivileges = true;
|
||||
PrivateTmp = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
CapabilityBoundingSet = "";
|
||||
};
|
||||
|
||||
script = ''
|
||||
export PASSWORD="$(cat "$CREDENTIALS_DIRECTORY/flame-password")"
|
||||
exec ${lib.getExe pkgs.nodejs} --preserve-symlinks --preserve-symlinks-main server.js
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = lib.mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ cfg.port ];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -50,6 +50,9 @@ let
|
||||
mkOption
|
||||
mkEnableOption
|
||||
;
|
||||
|
||||
postgresqlPackage =
|
||||
if cfg.database.enable then config.services.postgresql.package else pkgs.postgresql;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
@@ -256,29 +259,6 @@ in
|
||||
default = "immich";
|
||||
description = "The database user for immich.";
|
||||
};
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default =
|
||||
if config.services.postgresql.enable then config.services.postgresql.package else pkgs.postgresql;
|
||||
defaultText = lib.literalExpression ''
|
||||
if config.services.postgresql.enable then
|
||||
config.services.postgresql.package
|
||||
else
|
||||
pkgs.postgresql
|
||||
'';
|
||||
example = lib.literalExpression "pkgs.postgresql_18";
|
||||
description = ''
|
||||
The postgresql package providing the client programs that immich uses,
|
||||
most notably the `pg_dumpall` of its database backup job.
|
||||
|
||||
These programs refuse to talk to a server that is newer than
|
||||
themselves, so this must not be older than the server reachable at
|
||||
{option}`services.immich.database.host`. It is derived from
|
||||
{option}`services.postgresql.package` whenever the postgresql module
|
||||
is enabled on this host; set it explicitly when the database lives on
|
||||
another machine.
|
||||
'';
|
||||
};
|
||||
};
|
||||
redis = {
|
||||
enable = mkEnableOption "a redis cache for use with immich" // {
|
||||
@@ -357,7 +337,7 @@ in
|
||||
in
|
||||
[
|
||||
''
|
||||
${lib.getExe' cfg.database.package "psql"} -d "${cfg.database.name}" -f "${sqlFile}"
|
||||
${lib.getExe' postgresqlPackage "psql"} -d "${cfg.database.name}" -f "${sqlFile}"
|
||||
''
|
||||
];
|
||||
|
||||
@@ -427,7 +407,7 @@ in
|
||||
path = [
|
||||
# gzip and pg_dumpall are used by the backup service
|
||||
pkgs.gzip
|
||||
cfg.database.package
|
||||
postgresqlPackage
|
||||
];
|
||||
|
||||
preStart = mkIf (cfg.settings != null) secretsReplacement.script;
|
||||
|
||||
@@ -332,18 +332,6 @@ in
|
||||
assertion = cfg.cron.enable -> cfg.cron.key != null;
|
||||
message = ''services.invoiceplane.sites."${hostName}".cron.key must be set in order to use cron service.'';
|
||||
}
|
||||
{
|
||||
assertion =
|
||||
(lib.versionAtLeast (pkg hostName cfg).version "1.7.2" && cfg.invoiceTemplates != [ ])
|
||||
-> cfg.settings ? CUSTOM_INVOICE_TEMPLATES_PDF;
|
||||
message = ''services.invoiceplane.sites."${hostName}".invoiceTemplates is set but settings.CUSTOM_INVOICE_TEMPLATES_PDF is not. Since InvoicePlane >= 1.7.2 (current: ${cfg.package.version}), the filename of the custom invoice template PHP file must be explicitly whitelisted via settings.CUSTOM_INVOICE_TEMPLATES_PDF, otherwise it will not be picked up.'';
|
||||
}
|
||||
{
|
||||
assertion =
|
||||
(lib.versionAtLeast (pkg hostName cfg).version "1.7.2" && cfg.quoteTemplates != [ ])
|
||||
-> cfg.settings ? CUSTOM_QUOTE_TEMPLATES_PDF;
|
||||
message = ''services.invoiceplane.sites."${hostName}".quoteTemplates is set but settings.CUSTOM_QUOTE_TEMPLATES_PDF is not. Since InvoicePlane >= 1.7.2 (current: ${cfg.package.version}), the filename of the custom quote template PHP file must be explicitly whitelisted via settings.CUSTOM_QUOTE_TEMPLATES_PDF, otherwise it will not be picked up.'';
|
||||
}
|
||||
]) eachSite
|
||||
);
|
||||
|
||||
|
||||
@@ -28,12 +28,12 @@ let
|
||||
]}
|
||||
}
|
||||
'';
|
||||
__structuredAttrs = true;
|
||||
passAsFile = [ "serversList" ];
|
||||
}
|
||||
''
|
||||
cp -r ${cfg.package}/assets $out
|
||||
chmod +w "$out/servers_list.js"
|
||||
printf "%s" "$serversList" > "$out/servers_list.js"
|
||||
cp "$serversListPath" "$out/servers_list.js"
|
||||
substitute ${cfg.package}/assets/index.html $out/index.html \
|
||||
--replace-fail "s.setParameter(\"telemetry_level\",\"basic\"); //enable telemetry" "override_settings();" \
|
||||
--replace-fail "LibreSpeed Example" ${lib.escapeShellArg (lib.escapeXML cfg.frontend.pageTitle)} \
|
||||
|
||||
@@ -50,28 +50,10 @@ in
|
||||
default = false;
|
||||
description = ''
|
||||
Disable HTTP token authentication with requests.
|
||||
WARNING: This will make your instance vulnerable!
|
||||
Only turn this on if nothing but trusted local clients can reach the API.
|
||||
Note that web pages open in a browser on this machine also count as local
|
||||
callers; restrict allowed_origins if you disable auth.
|
||||
WARNING: This will make your instance vulnerable! Only turn this on if you are ONLY connecting from localhost.
|
||||
'';
|
||||
};
|
||||
|
||||
allowed_origins = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ "*" ];
|
||||
description = ''
|
||||
Origins allowed to call the API from a browser.
|
||||
This is a CORS allowlist, not an auth mechanism: it only governs which
|
||||
web pages a browser will let read this API's responses.
|
||||
The default "*" means any site open in your browser can send requests to
|
||||
this instance, which matters most when disable_auth is on. Restrict this to
|
||||
your own frontends to close that off, or use an empty list to block all
|
||||
browser (cross-origin) callers.
|
||||
'';
|
||||
example = [ "http://localhost:8000" ];
|
||||
};
|
||||
|
||||
disable_fetch_requests = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
@@ -107,16 +89,6 @@ in
|
||||
'';
|
||||
example = 0;
|
||||
};
|
||||
|
||||
access_log = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Log every HTTP request with client address, method, path and status.
|
||||
Generation requests are already logged in detail; this adds the rest,
|
||||
such as model list and health polls.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
logging = {
|
||||
@@ -138,15 +110,6 @@ in
|
||||
description = "Enable request logging. NOTE: Only use this for debugging!";
|
||||
};
|
||||
|
||||
log_timestamps = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Prefix console log lines with the time of day.
|
||||
The log files under logs/ always carry full timestamps.
|
||||
'';
|
||||
};
|
||||
|
||||
log_chat_completion_requests = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
@@ -312,7 +275,7 @@ in
|
||||
default = [ ];
|
||||
description = ''
|
||||
List of VRAM sizes to split between GPUs, in GB.
|
||||
Used both with and without tensor parallelism.
|
||||
Used with tensor parallelism.
|
||||
'';
|
||||
example = [
|
||||
16
|
||||
@@ -326,49 +289,10 @@ in
|
||||
description = ''
|
||||
Number of mixture-of-expert layers to offload to CPU inference.
|
||||
Only affects MoE models. Set a large value such as 999 to offload all layers.
|
||||
Mutually exclusive with cpu_moe_split_experts.
|
||||
'';
|
||||
example = 999;
|
||||
};
|
||||
|
||||
cpu_moe_split_experts = lib.mkOption {
|
||||
type = lib.types.ints.unsigned;
|
||||
default = 0;
|
||||
description = ''
|
||||
Number of routed experts per MoE layer to offload to CPU inference.
|
||||
Unlike cpu_moe_offload_layers, this splits every MoE layer instead of
|
||||
offloading whole layers: the coldest experts are kept in system RAM and
|
||||
computed on the CPU, overlapping each layer's own GPU compute, with dynamic
|
||||
placement keeping hot experts in VRAM.
|
||||
Mutually exclusive with cpu_moe_offload_layers; not supported with tensor
|
||||
parallelism.
|
||||
'';
|
||||
example = 4;
|
||||
};
|
||||
|
||||
cpu_moe_threads = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.ints.unsigned;
|
||||
default = null;
|
||||
description = ''
|
||||
Worker thread count for CPU MoE inference.
|
||||
Applies to both cpu_moe_offload_layers and cpu_moe_split_experts. When null,
|
||||
defers to the EXL3_MOE_CPU_THREADS environment variable, then half the CPU core
|
||||
count.
|
||||
'';
|
||||
example = 8;
|
||||
};
|
||||
|
||||
ngram_ram = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Load a model's n-gram embedding table fully into system RAM.
|
||||
Only affects PLE models with n-gram embeddings (e.g. Qwen3.8-Flash-Next).
|
||||
By default the table is streamed from disk during inference; loading it into
|
||||
RAM avoids per-token disk reads at the cost of tens of GB of system memory.
|
||||
'';
|
||||
};
|
||||
|
||||
rope_scale = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.number;
|
||||
default = 1.0;
|
||||
@@ -457,16 +381,6 @@ in
|
||||
description = "Enables vision support if the model supports it.";
|
||||
};
|
||||
|
||||
vision_offload = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Keep the vision model's weights in system RAM instead of VRAM.
|
||||
Weights are stored in pinned host memory and streamed to the GPU during
|
||||
inference, trading vision speed for VRAM. Only applies when vision is enabled.
|
||||
'';
|
||||
};
|
||||
|
||||
template_vars_default = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.anything;
|
||||
default = { };
|
||||
@@ -541,33 +455,6 @@ in
|
||||
'';
|
||||
};
|
||||
|
||||
reasoning_budget_tokens = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.int;
|
||||
default = null;
|
||||
description = ''
|
||||
Default reasoning token budget.
|
||||
When a request's reasoning content exceeds the budget, the server forces the
|
||||
end of the reasoning phase by injecting reasoning_budget_message followed by
|
||||
the model's end-of-reasoning tokens. 0 ends reasoning as soon as it starts;
|
||||
null or a negative value disables the budget. Overridable per request via
|
||||
reasoning_budget_tokens (aliases: reasoning_budget, thinking_budget,
|
||||
thinking_token_budget) or reasoning.max_tokens.
|
||||
Requires a reasoning format: reasoning tags, Harmony or Muse Glimmer.
|
||||
'';
|
||||
example = 1024;
|
||||
};
|
||||
|
||||
reasoning_budget_message = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Text injected before the end-of-reasoning tokens when the reasoning budget is
|
||||
exhausted. When null, only the end-of-reasoning tokens are forced.
|
||||
Overridable per request via reasoning_budget_message.
|
||||
'';
|
||||
example = "Time to answer.";
|
||||
};
|
||||
|
||||
tool_format = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
@@ -588,17 +475,6 @@ in
|
||||
true. When active, supersedes the reasoning and tool format settings.
|
||||
'';
|
||||
};
|
||||
|
||||
muse_glimmer = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.bool;
|
||||
default = null;
|
||||
description = ''
|
||||
Parse responses in the Muse Glimmer message format.
|
||||
Auto-detected from the model's special tokens when null; set to true or false
|
||||
to override. Setting tool_format to "muse_glimmer" is equivalent to setting
|
||||
this to true. When active, supersedes the reasoning and tool format settings.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
draft_model = {
|
||||
@@ -719,13 +595,10 @@ in
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Select a sampler override preset, found in the sampler_overrides folder of the
|
||||
package.
|
||||
Select a sampler override preset, found in the sampler-overrides folder.
|
||||
This overrides default fallbacks for sampler values that are passed to the API.
|
||||
NOTE: "safe_defaults" provides llama.cpp-style fallbacks (temperature 0.8,
|
||||
top_k 40, top_p 0.95, min_p 0.05) for frontends that don't send sampling
|
||||
parameters. Leaving this null means no fallbacks at all.
|
||||
A preset that cannot be found makes TabbyAPI exit at startup.
|
||||
NOTE: "safe_defaults" is noob friendly and provides fallbacks for frontends that
|
||||
don't send sampling parameters. Leave this null for any advanced usage.
|
||||
'';
|
||||
example = "safe_defaults";
|
||||
};
|
||||
@@ -781,28 +654,14 @@ in
|
||||
example = 4096;
|
||||
};
|
||||
|
||||
sysmem_multimodal_cache = lib.mkOption {
|
||||
type = lib.types.ints.unsigned;
|
||||
default = 1024;
|
||||
description = ''
|
||||
Size of the image embedding cache in system memory, in MB.
|
||||
Encoded images are kept so repeated turns of a conversation don't re-run the
|
||||
vision model. Images already in use by a request are never evicted; a context
|
||||
whose images exceed the budget is cached only partially, with a warning.
|
||||
Only applies when vision is enabled.
|
||||
'';
|
||||
example = 4096;
|
||||
};
|
||||
|
||||
cuda_malloc_async = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
default = true;
|
||||
description = ''
|
||||
Use the cudaMallocAsync allocator backend in Torch.
|
||||
When false, the allocator is left to the environment: unless
|
||||
PYTORCH_CUDA_ALLOC_CONF is set, ExLlamaV3 enables expandable segments in
|
||||
Torch's native allocator, which performs better than cudaMallocAsync.
|
||||
Enable this to force the cudaMallocAsync backend instead.
|
||||
Use the cudaMallocAsync backend in Torch.
|
||||
Enabling this is generally preferable, but it may cause issues with certain
|
||||
workloads. Try disabling it if you experience intermittent OoM errors. If false,
|
||||
Torch will use the allocator defined by the system environment.
|
||||
'';
|
||||
};
|
||||
};
|
||||
@@ -830,14 +689,6 @@ in
|
||||
services.tabbyapi.settings.model.template_vars_force.enable_thinking = true;
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion =
|
||||
!(cfg.settings.model.cpu_moe_offload_layers > 0 && cfg.settings.model.cpu_moe_split_experts > 0);
|
||||
message = ''
|
||||
services.tabbyapi.settings.model.cpu_moe_offload_layers and
|
||||
services.tabbyapi.settings.model.cpu_moe_split_experts are mutually exclusive.
|
||||
'';
|
||||
}
|
||||
];
|
||||
networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [
|
||||
cfg.settings.network.port
|
||||
@@ -855,10 +706,6 @@ in
|
||||
TRITON_CACHE_DIR = "/tmp/triton";
|
||||
};
|
||||
|
||||
preStart = ''
|
||||
ln -sfn ${cfg.package}/share/tabbyapi/sampler_overrides sampler_overrides
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${lib.getExe cfg.package} --config=${configFile}";
|
||||
Restart = "on-failure";
|
||||
|
||||
@@ -1,239 +0,0 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.zerobyte;
|
||||
in
|
||||
{
|
||||
meta.maintainers = with lib.maintainers; [ pbek ];
|
||||
|
||||
options.services.zerobyte = {
|
||||
enable = lib.mkEnableOption "Zerobyte, backup automation for self-hosters built on top of restic";
|
||||
|
||||
package = lib.mkPackageOption pkgs "zerobyte" { };
|
||||
|
||||
user = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "zerobyte";
|
||||
description = "User account under which Zerobyte runs.";
|
||||
};
|
||||
|
||||
group = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "zerobyte";
|
||||
description = "Group under which Zerobyte runs.";
|
||||
};
|
||||
|
||||
appSecretFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
example = "/run/secrets/zerobyte-app-secret";
|
||||
description = ''
|
||||
Path to a file containing the application secret (32–256 characters),
|
||||
used to encrypt sensitive data in the database. Generate one with
|
||||
`openssl rand -hex 32`.
|
||||
|
||||
This should not be a path in the Nix store. The file is passed to the
|
||||
service via systemd credentials.
|
||||
'';
|
||||
};
|
||||
|
||||
dataDir = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/var/lib/zerobyte";
|
||||
description = ''
|
||||
Directory used to store the database, encryption keys, local
|
||||
repositories, volume mounts and the restic cache.
|
||||
|
||||
Do not point this to a network share, this will cause permission
|
||||
issues and strong performance degradation.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = lib.mkOption {
|
||||
type = lib.types.submodule {
|
||||
freeformType = lib.types.attrsOf (
|
||||
lib.types.oneOf [
|
||||
lib.types.bool
|
||||
lib.types.int
|
||||
lib.types.str
|
||||
]
|
||||
);
|
||||
};
|
||||
default = { };
|
||||
example = {
|
||||
BASE_URL = "https://zerobyte.example.com";
|
||||
LOG_LEVEL = "debug";
|
||||
GOMAXPROCS = 2;
|
||||
TRUST_PROXY = true;
|
||||
};
|
||||
description = ''
|
||||
Zerobyte configuration passed as environment variables. See
|
||||
<https://github.com/nicotsx/zerobyte#configuration> for the available
|
||||
settings.
|
||||
|
||||
`BASE_URL` is required. It is highly discouraged to expose Zerobyte
|
||||
directly to the internet; bind `HOST` to localhost and use a secure
|
||||
tunnel or an authenticating reverse proxy instead.
|
||||
|
||||
Do not put secrets here; use
|
||||
[](#opt-services.zerobyte.environmentFile) instead.
|
||||
'';
|
||||
};
|
||||
|
||||
environmentFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
example = "/run/secrets/zerobyte.env";
|
||||
description = ''
|
||||
Environment file loaded by systemd, which may be used to pass secrets
|
||||
such as `APP_SECRET` to Zerobyte without putting them into the Nix
|
||||
store.
|
||||
'';
|
||||
};
|
||||
|
||||
provisioningFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
Path to a JSON file with operator-managed repositories and volumes to
|
||||
sync at startup. See
|
||||
<https://zerobyte.app/docs/guides/provisioning> for the format.
|
||||
|
||||
This may contain secrets, so it should not be a path in the Nix store.
|
||||
The file is passed to the service via systemd credentials.
|
||||
'';
|
||||
};
|
||||
|
||||
extraPackages = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.package;
|
||||
default = with pkgs; [
|
||||
cifs-utils
|
||||
davfs2
|
||||
fuse3
|
||||
nfs-utils
|
||||
openssh
|
||||
sshfs
|
||||
util-linux
|
||||
];
|
||||
defaultText = lib.literalExpression "with pkgs; [ cifs-utils davfs2 fuse3 nfs-utils openssh sshfs util-linux ]";
|
||||
description = ''
|
||||
Extra packages added to the `PATH` of the Zerobyte service. The
|
||||
default contains the tools needed to mount NFS, SMB, WebDAV and SFTP
|
||||
volumes. Add `shoutrrr` here if you want notifications to be
|
||||
delivered.
|
||||
'';
|
||||
};
|
||||
|
||||
openFirewall = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Whether to open the firewall for the Zerobyte web interface.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
services.zerobyte.settings = {
|
||||
NODE_ENV = lib.mkDefault "production";
|
||||
# The generated web server binds to all interfaces and port 3000 when
|
||||
# these are unset. Bind to 127.0.0.1 and use port 4096 like upstream:
|
||||
# https://github.com/nicotsx/zerobyte/blob/main/Dockerfile
|
||||
HOST = lib.mkDefault "127.0.0.1";
|
||||
PORT = lib.mkDefault 4096;
|
||||
RESTIC_HOSTNAME = lib.mkDefault config.networking.hostName;
|
||||
ZEROBYTE_DATABASE_URL = "${cfg.dataDir}/data/zerobyte.db";
|
||||
RESTIC_PASS_FILE = "${cfg.dataDir}/data/restic.pass";
|
||||
ZEROBYTE_REPOSITORIES_DIR = "${cfg.dataDir}/repositories";
|
||||
ZEROBYTE_VOLUMES_DIR = "${cfg.dataDir}/volumes";
|
||||
RESTIC_CACHE_DIR = "${cfg.dataDir}/restic/cache";
|
||||
RCLONE_CONFIG_DIR = "${cfg.dataDir}/rclone";
|
||||
ENABLE_LOCAL_AGENT = lib.mkDefault true;
|
||||
}
|
||||
// lib.optionalAttrs (cfg.provisioningFile != null) {
|
||||
PROVISIONING_PATH = "%d/provisioning.json";
|
||||
}
|
||||
// lib.optionalAttrs (cfg.appSecretFile != null) {
|
||||
APP_SECRET_FILE = "%d/app-secret";
|
||||
};
|
||||
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.settings ? BASE_URL;
|
||||
message = "services.zerobyte.settings.BASE_URL must be set.";
|
||||
}
|
||||
{
|
||||
assertion = cfg.appSecretFile != null || cfg.environmentFile != null || cfg.settings ? APP_SECRET;
|
||||
message = ''
|
||||
services.zerobyte: A secret is required to encrypt sensitive data in
|
||||
the database. Set `services.zerobyte.appSecretFile` or provide
|
||||
`APP_SECRET` via `services.zerobyte.environmentFile`.
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = !(cfg.settings ? APP_SECRET);
|
||||
message = ''
|
||||
services.zerobyte.settings.APP_SECRET would expose the secret in the
|
||||
Nix store. Use `services.zerobyte.appSecretFile` or
|
||||
`services.zerobyte.environmentFile` instead.
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
systemd.services.zerobyte = {
|
||||
description = "Zerobyte backup automation";
|
||||
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
|
||||
environment = lib.mapAttrs (
|
||||
_: value: if lib.isBool value then lib.boolToString value else toString value
|
||||
) cfg.settings;
|
||||
|
||||
path = [ cfg.package ] ++ cfg.extraPackages;
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
ExecStart = lib.getExe cfg.package;
|
||||
Restart = "on-failure";
|
||||
StateDirectory = lib.mkIf (lib.hasPrefix "/var/lib/" cfg.dataDir) (
|
||||
lib.removePrefix "/var/lib/" cfg.dataDir
|
||||
);
|
||||
EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
|
||||
LoadCredential =
|
||||
lib.optional (cfg.appSecretFile != null) "app-secret:${cfg.appSecretFile}"
|
||||
++ lib.optional (cfg.provisioningFile != null) "provisioning.json:${cfg.provisioningFile}";
|
||||
|
||||
# The local agent and the volume mount backends need these to perform
|
||||
# NFS, SMB, WebDAV and SFTP mounts.
|
||||
AmbientCapabilities = [ "CAP_SYS_ADMIN" ];
|
||||
CapabilityBoundingSet = [ "CAP_SYS_ADMIN" ];
|
||||
NoNewPrivileges = true;
|
||||
PrivateTmp = true;
|
||||
ProtectHome = lib.mkDefault false;
|
||||
ProtectSystem = lib.mkDefault "full";
|
||||
RestartSec = "10s";
|
||||
UMask = "0077";
|
||||
};
|
||||
};
|
||||
|
||||
users.users = lib.mkIf (cfg.user == "zerobyte") {
|
||||
zerobyte = {
|
||||
isSystemUser = true;
|
||||
group = cfg.group;
|
||||
home = cfg.dataDir;
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = lib.mkIf (cfg.group == "zerobyte") { zerobyte = { }; };
|
||||
|
||||
networking.firewall = lib.mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ (lib.toInt (toString cfg.settings.PORT)) ];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -7,8 +7,6 @@
|
||||
|
||||
let
|
||||
cfg = config.boot.initrd.nix-store-veritysetup;
|
||||
|
||||
json = pkgs.formats.json { };
|
||||
in
|
||||
{
|
||||
meta.maintainers = with lib.maintainers; [ nikstur ];
|
||||
@@ -29,15 +27,6 @@ in
|
||||
contents = {
|
||||
"/etc/systemd/system-generators/nix-store-veritysetup-generator".source =
|
||||
"${lib.getExe pkgs.nix-store-veritysetup-generator}";
|
||||
|
||||
"/etc/systemd/generator-environment.json".source =
|
||||
json.generate "systemd-generator-environment.json"
|
||||
{
|
||||
SYSTEMD_VERITYSETUP_PATH = "${config.boot.initrd.systemd.package}/lib/systemd/systemd-veritysetup";
|
||||
};
|
||||
|
||||
"/etc/systemd/system-environment-generators/env-generator".source =
|
||||
"${config.system.nixos-init.package}/bin/env-generator";
|
||||
};
|
||||
|
||||
storePaths = [
|
||||
|
||||
@@ -63,7 +63,6 @@ in
|
||||
nixStoreMountOpts = mkOption {
|
||||
type = types.listOf types.nonEmptyStr;
|
||||
default = [
|
||||
"x-initrd.mount"
|
||||
"ro"
|
||||
"nodev"
|
||||
"nosuid"
|
||||
|
||||
@@ -772,10 +772,7 @@ in
|
||||
];
|
||||
|
||||
services.initrd-nixos-activation = lib.mkIf (!config.system.nixos-init.enable) {
|
||||
after = [
|
||||
"initrd-find-nixos-closure.service"
|
||||
"initrd-switch-root.target"
|
||||
];
|
||||
after = [ "initrd-switch-root.target" ];
|
||||
requiredBy = [ "initrd-switch-root.service" ];
|
||||
before = [ "initrd-switch-root.service" ];
|
||||
unitConfig.DefaultDependencies = false;
|
||||
|
||||
@@ -15,12 +15,11 @@ let
|
||||
mkIf
|
||||
optionals
|
||||
mkDefault
|
||||
nameValuePair
|
||||
listToAttrs
|
||||
filterAttrs
|
||||
mapAttrsToList
|
||||
foldl'
|
||||
getExe
|
||||
escape
|
||||
versionOlder
|
||||
;
|
||||
|
||||
inInitrd = config.boot.initrd.supportedFilesystems.btrfs or false;
|
||||
@@ -150,116 +149,66 @@ in
|
||||
)
|
||||
);
|
||||
|
||||
systemd.services."btrfs-scrub@" = {
|
||||
description = "btrfs scrub on %f";
|
||||
documentation = [ "man:btrfs-scrub(8)" ];
|
||||
# scrub prevents suspend2ram or proper shutdown on linux < 6.19
|
||||
conflicts = optionals (versionOlder config.boot.kernelPackages.kernel.version "6.19") [
|
||||
"shutdown.target"
|
||||
"sleep.target"
|
||||
];
|
||||
before = optionals (versionOlder config.boot.kernelPackages.kernel.version "6.19") [
|
||||
"shutdown.target"
|
||||
"sleep.target"
|
||||
];
|
||||
# TODO: Did not manage to do it via the usual btrfs-scrub@.timer/.service
|
||||
# template units due to problems enabling the parameterized units,
|
||||
# so settled with many units and templating via nix for now.
|
||||
# https://github.com/NixOS/nixpkgs/pull/32496#discussion_r156527544
|
||||
systemd.timers =
|
||||
let
|
||||
scrubTimer =
|
||||
fs:
|
||||
let
|
||||
fs' = utils.escapeSystemdPath fs;
|
||||
in
|
||||
nameValuePair "btrfs-scrub-${fs'}" {
|
||||
description = "regular btrfs scrub timer on ${fs}";
|
||||
|
||||
# prevent problems with MemoryDenyWriteExecute
|
||||
environment.PYTHON_JIT = "0";
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnCalendar = cfgScrub.interval;
|
||||
AccuracySec = "1d";
|
||||
Persistent = true;
|
||||
};
|
||||
};
|
||||
in
|
||||
listToAttrs (map scrubTimer cfgScrub.fileSystems);
|
||||
|
||||
unitConfig.RequiresMountsFor = "%f";
|
||||
systemd.services =
|
||||
let
|
||||
scrubService =
|
||||
fs:
|
||||
let
|
||||
fs' = utils.escapeSystemdPath fs;
|
||||
in
|
||||
nameValuePair "btrfs-scrub-${fs'}" {
|
||||
description = "btrfs scrub on ${fs}";
|
||||
documentation = [ "man:btrfs-scrub(8)" ];
|
||||
# scrub prevents suspend2ram or proper shutdown on linux < 6.19
|
||||
conflicts = lib.optionals (lib.versionOlder config.boot.kernelPackages.kernel.version "6.19") [
|
||||
"shutdown.target"
|
||||
"sleep.target"
|
||||
];
|
||||
before = lib.optionals (lib.versionOlder config.boot.kernelPackages.kernel.version "6.19") [
|
||||
"shutdown.target"
|
||||
"sleep.target"
|
||||
];
|
||||
|
||||
serviceConfig =
|
||||
let
|
||||
btrfsCmd = getExe pkgs.btrfs-progs;
|
||||
btrfsCancelCmd = pkgs.writers.writePython3 "btrfs-scrub-maybe-cancel" { } ''
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
btrfs = "${escape [ "\"" "\\" ] btrfsCmd}"
|
||||
result = subprocess.run(
|
||||
[btrfs, "scrub", "cancel"] + sys.argv[1:],
|
||||
stderr=subprocess.PIPE,
|
||||
check=False,
|
||||
shell=False
|
||||
)
|
||||
|
||||
# ignore errors if there was no running scrub to cancel
|
||||
if result.returncode == 2:
|
||||
sys.exit(0)
|
||||
|
||||
sys.stderr.buffer.write(result.stderr)
|
||||
sys.exit(result.returncode)
|
||||
'';
|
||||
additionalScrubArgs = optionals (cfgScrub.limit != null) [
|
||||
"--limit"
|
||||
cfgScrub.limit
|
||||
];
|
||||
in
|
||||
{
|
||||
# simple and not oneshot, otherwise ExecStop is not used
|
||||
Type = "simple";
|
||||
Nice = 19;
|
||||
CPUSchedulingPolicy = "idle";
|
||||
IOSchedulingClass = "idle";
|
||||
ExecStart = "${
|
||||
utils.escapeSystemdExecArgs (
|
||||
[
|
||||
btrfsCmd
|
||||
"scrub"
|
||||
"start"
|
||||
"-B"
|
||||
]
|
||||
++ additionalScrubArgs
|
||||
)
|
||||
} %f";
|
||||
# if the service is stopped before scrub end, cancel it
|
||||
ExecStop = "${utils.escapeSystemdExecArg btrfsCancelCmd} %f";
|
||||
# hardening
|
||||
# required for starting/cancelling the scrub operation
|
||||
CapabilityBoundingSet = [
|
||||
"CAP_SYS_ADMIN"
|
||||
"CAP_DAC_READ_SEARCH"
|
||||
];
|
||||
NoNewPrivileges = true;
|
||||
# no ProtectSystem/ProtectHome since the path to be scrubbed can refer to a device,
|
||||
# which in turn might be mounted there and mounting it read-only prevents scrubbing
|
||||
StateDirectory = "btrfs"; # contains progress information
|
||||
PrivateNetwork = true;
|
||||
ProtectHostname = true;
|
||||
ProtectClock = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectControlGroups = true;
|
||||
RestrictAddressFamilies = [ "AF_UNIX" ]; # used internally for communication
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
PrivateMounts = true;
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
"~@mount"
|
||||
];
|
||||
SystemCallArchitectures = "native";
|
||||
# no ProtectKernelTunables since /sys/fs/btrfs access is required
|
||||
# no User= since written files have to be accessible by scrub commands run manually
|
||||
};
|
||||
};
|
||||
|
||||
systemd.timers."btrfs-scrub@" = {
|
||||
description = "Regular btrfs scrub on %f";
|
||||
documentation = [ "man:btrfs-scrub(8)" ];
|
||||
|
||||
timerConfig = {
|
||||
OnCalendar = cfgScrub.interval;
|
||||
AccuracySec = "1d";
|
||||
Persistent = true;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.targets.timers.wants = map (
|
||||
fs: "btrfs-scrub@${utils.escapeSystemdPath fs}.timer"
|
||||
) cfgScrub.fileSystems;
|
||||
serviceConfig = {
|
||||
# simple and not oneshot, otherwise ExecStop is not used
|
||||
Type = "simple";
|
||||
Nice = 19;
|
||||
IOSchedulingClass = "idle";
|
||||
ExecStart = "${pkgs.btrfs-progs}/bin/btrfs scrub start -B ${
|
||||
lib.optionalString (cfgScrub.limit != null) "--limit ${cfgScrub.limit}"
|
||||
} ${fs}";
|
||||
# if the service is stopped before scrub end, cancel it
|
||||
ExecStop = pkgs.writeShellScript "btrfs-scrub-maybe-cancel" ''
|
||||
(${pkgs.btrfs-progs}/bin/btrfs scrub status ${fs} | ${pkgs.gnugrep}/bin/grep finished) || ${pkgs.btrfs-progs}/bin/btrfs scrub cancel ${fs}
|
||||
'';
|
||||
};
|
||||
};
|
||||
in
|
||||
listToAttrs (map scrubService cfgScrub.fileSystems);
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
|
||||
let
|
||||
serialDevice = if pkgs.stdenv.hostPlatform.isx86 then "ttyS0" else "ttyAMA0";
|
||||
|
||||
efiArch = pkgs.stdenv.hostPlatform.efiArch;
|
||||
in
|
||||
{
|
||||
meta = {
|
||||
@@ -16,7 +14,6 @@ in
|
||||
};
|
||||
|
||||
imports = [
|
||||
../image/repart.nix
|
||||
./lxc-instance-common.nix
|
||||
|
||||
../profiles/qemu-guest.nix
|
||||
@@ -31,60 +28,6 @@ in
|
||||
copyChannel = config.system.installer.channel.enable;
|
||||
};
|
||||
|
||||
system.build.repartImage = config.image.repart.image.overrideAttrs (previousAttrs: {
|
||||
nativeBuildInputs = previousAttrs.nativeBuildInputs ++ [ pkgs.qemu-utils ];
|
||||
|
||||
postBuild = ''
|
||||
qemu-img convert -f raw -O qcow2 -c ${config.image.baseName}.raw ${config.image.baseName}.qcow2
|
||||
rm ${config.image.baseName}.raw
|
||||
'';
|
||||
|
||||
# expose a hydra build product so lxc-ci can download it
|
||||
postInstall = ''
|
||||
mkdir $out/nix-support
|
||||
echo "file qcow2-image $out/${config.image.baseName}.qcow2" > $out/nix-support/hydra-build-products
|
||||
'';
|
||||
});
|
||||
|
||||
image.repart = {
|
||||
name = "nixos";
|
||||
version = null;
|
||||
sectorSize = 512;
|
||||
compression.enable = false;
|
||||
mkfsOptions.ext4 = [
|
||||
"-i"
|
||||
"8192"
|
||||
];
|
||||
partitions = {
|
||||
esp = {
|
||||
contents = {
|
||||
"/EFI/BOOT/BOOT${lib.toUpper efiArch}.EFI".source =
|
||||
"${config.systemd.package}/lib/systemd/boot/efi/systemd-boot${efiArch}.efi";
|
||||
"/EFI/Linux/${config.system.boot.loader.ukiFile}".source =
|
||||
"${config.system.build.uki}/${config.system.boot.loader.ukiFile}";
|
||||
};
|
||||
|
||||
repartConfig = {
|
||||
Type = "esp";
|
||||
Format = "vfat";
|
||||
Label = "ESP";
|
||||
# support 10 kernels, assuming 50MB on x86 and 100MB on aarch64
|
||||
SizeMinBytes = if pkgs.stdenv.hostPlatform.isx86 then "512M" else "1G";
|
||||
};
|
||||
};
|
||||
root = {
|
||||
storePaths = [ config.system.build.toplevel ];
|
||||
repartConfig = {
|
||||
Type = "root";
|
||||
Format = "ext4";
|
||||
Label = "nixos";
|
||||
Minimize = "guess";
|
||||
PaddingMinBytes = "512M";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
fileSystems = {
|
||||
"/" = {
|
||||
device = "/dev/disk/by-label/nixos";
|
||||
|
||||
@@ -437,7 +437,7 @@ rec {
|
||||
versionModule
|
||||
./maintainers/scripts/incus/incus-virtual-machine-image.nix
|
||||
];
|
||||
}).config.system.build.repartImage
|
||||
}).config.system.build.qemuImage
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -93,47 +93,26 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
let
|
||||
orderRenewScript = nodes.client.systemd.services."acme-order-renew-${domain}".script;
|
||||
in
|
||||
''
|
||||
${(import ./utils.nix).pythonUtils}
|
||||
testScript = ''
|
||||
${(import ./utils.nix).pythonUtils}
|
||||
|
||||
import shlex
|
||||
cert = "${domain}"
|
||||
|
||||
cert = "${domain}"
|
||||
dnsserver.start()
|
||||
acme.start()
|
||||
|
||||
with subtest("Disable DNS propagation checks"):
|
||||
order_renew_script = ${builtins.toJSON orderRenewScript}
|
||||
lego_run_commands = [
|
||||
line
|
||||
for line in order_renew_script.splitlines()
|
||||
if "lego run " in line
|
||||
]
|
||||
assert len(lego_run_commands) == 2, lego_run_commands
|
||||
for command in lego_run_commands:
|
||||
args = shlex.split(command)
|
||||
assert "--dns.propagation.disable-ans" in args, args
|
||||
assert "--dns.propagation.disable-rns" in args, args
|
||||
assert "--dns.propagation.wait" not in args, args
|
||||
wait_for_running(dnsserver)
|
||||
dnsserver.wait_for_open_port(53)
|
||||
wait_for_running(acme)
|
||||
acme.wait_for_open_port(443)
|
||||
|
||||
dnsserver.start()
|
||||
acme.start()
|
||||
with subtest("Boot and acquire a new cert"):
|
||||
client.start()
|
||||
wait_for_running(client)
|
||||
|
||||
wait_for_running(dnsserver)
|
||||
dnsserver.wait_for_open_port(53)
|
||||
wait_for_running(acme)
|
||||
acme.wait_for_open_port(443)
|
||||
|
||||
with subtest("Boot and acquire a new cert"):
|
||||
client.start()
|
||||
wait_for_running(client)
|
||||
|
||||
check_issuer(client, cert, "pebble")
|
||||
check_domain(client, cert, cert, fail=True)
|
||||
check_domain(client, cert, f"toodeep.nesting.{cert}", fail=True)
|
||||
check_domain(client, cert, f"whatever.{cert}")
|
||||
'';
|
||||
check_issuer(client, cert, "pebble")
|
||||
check_domain(client, cert, cert, fail=True)
|
||||
check_domain(client, cert, f"toodeep.nesting.{cert}", fail=True)
|
||||
check_domain(client, cert, f"whatever.{cert}")
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -6,31 +6,6 @@
|
||||
let
|
||||
domain = "example.test";
|
||||
ip = "192.168.1.2";
|
||||
migrationCertNames = [
|
||||
"migration-2.${domain}"
|
||||
"migration-3.${domain}"
|
||||
];
|
||||
migrationCerts = lib.genAttrs migrationCertNames (_: {
|
||||
listenHTTP = ":80";
|
||||
});
|
||||
migrationTestLego = pkgs.writeShellScriptBin "lego" ''
|
||||
if [[ ''${1-} == migrate ]]; then
|
||||
printf '1\n' >> /run/acme/test-migration-attempts
|
||||
if ! mkdir /run/acme/test-migration-active; then
|
||||
touch /run/acme/test-migration-collision
|
||||
exit 99
|
||||
fi
|
||||
trap 'rm -rf /run/acme/test-migration-active' EXIT
|
||||
sleep 2
|
||||
fi
|
||||
|
||||
${lib.getExe pkgs.lego} "$@"
|
||||
'';
|
||||
migrationServicePaths =
|
||||
lib.genAttrs (map (name: "acme-order-renew-${name}") migrationCertNames)
|
||||
(_: {
|
||||
path = lib.mkBefore [ migrationTestLego ];
|
||||
});
|
||||
in
|
||||
{
|
||||
name = "http01-builtin";
|
||||
@@ -63,12 +38,6 @@ in
|
||||
listenHTTP = ":80";
|
||||
};
|
||||
|
||||
# Delay account migration and fail a second concurrent invocation so
|
||||
# the shared-account migration race is deterministic.
|
||||
systemd.services."acme-order-renew-${config.networking.fqdn}".path = lib.mkBefore [
|
||||
migrationTestLego
|
||||
];
|
||||
|
||||
systemd.targets."renew-triggered" = {
|
||||
wantedBy = [ "acme-order-renew-${config.networking.fqdn}.service" ];
|
||||
after = [ "acme-order-renew-${config.networking.fqdn}.service" ];
|
||||
@@ -112,18 +81,6 @@ in
|
||||
|
||||
preservation.configuration = { };
|
||||
|
||||
migration_serial.configuration = {
|
||||
security.acme.maxConcurrentRenewals = 1;
|
||||
security.acme.certs = migrationCerts;
|
||||
systemd.services = migrationServicePaths;
|
||||
};
|
||||
|
||||
migration_parallel.configuration = {
|
||||
security.acme.maxConcurrentRenewals = 10;
|
||||
security.acme.certs = migrationCerts;
|
||||
systemd.services = migrationServicePaths;
|
||||
};
|
||||
|
||||
add_cert_and_domain.configuration = {
|
||||
security.acme.certs = {
|
||||
"${config.networking.fqdn}" = {
|
||||
@@ -139,10 +96,6 @@ in
|
||||
security.acme.maxConcurrentRenewals = 10;
|
||||
};
|
||||
|
||||
remove_domain.configuration = {
|
||||
security.acme.certs."${config.networking.fqdn}".extraDomainNames = lib.mkForce [ ip ];
|
||||
};
|
||||
|
||||
concurrency.configuration = {
|
||||
# As above, relying on port binding behaviour to assert that concurrency limit
|
||||
# prevents > 1 service running at a time.
|
||||
@@ -217,12 +170,9 @@ in
|
||||
|
||||
domain = "${domain}"
|
||||
ip = "${ip}"
|
||||
jq = "${lib.getExe pkgs.jq}"
|
||||
cert = "${certName}"
|
||||
cert2 = "builtin-2." + domain
|
||||
cert3 = "builtin-3." + domain
|
||||
migration_certs = [cert] + ${builtins.toJSON migrationCertNames}
|
||||
migration_units = [f"acme-order-renew-{name}.service" for name in migration_certs]
|
||||
legacy_account_dir = "/var/lib/acme/.lego/accounts/1ccf607d9aa280e9af00"
|
||||
|
||||
acme.start()
|
||||
@@ -237,93 +187,6 @@ in
|
||||
check_domain(builtin, cert, cert)
|
||||
check_ip(builtin, cert, ip)
|
||||
|
||||
with subtest("Handles v4 account and certificate state"):
|
||||
switch_to(builtin, "migration_serial")
|
||||
for migration_cert in migration_certs:
|
||||
check_issuer(builtin, migration_cert, "pebble")
|
||||
check_domain(builtin, migration_cert, migration_cert)
|
||||
|
||||
switch_to(builtin, "migration_parallel")
|
||||
builtin.succeed(f"systemctl start {' '.join(migration_units)}")
|
||||
|
||||
builtin.succeed(
|
||||
f"""
|
||||
set -euo pipefail
|
||||
|
||||
account_file=$(find /var/lib/acme/.lego/accounts -type f -name account.json -print -quit)
|
||||
test -n "$account_file"
|
||||
account_dir=$(dirname "$account_file")
|
||||
|
||||
account_id=$(basename "$account_dir")
|
||||
key_path="$account_dir/$account_id.key"
|
||||
test -f "$key_path"
|
||||
|
||||
mkdir "$account_dir/keys"
|
||||
mv "$key_path" "$account_dir/keys/$account_id.key"
|
||||
{jq} '{{
|
||||
email: .email,
|
||||
registration: {{
|
||||
body: (.registration | del(.accountURL)),
|
||||
uri: .registration.accountURL
|
||||
}}
|
||||
}}' "$account_dir/account.json" > "$account_dir/account.json.tmp"
|
||||
mv "$account_dir/account.json.tmp" "$account_dir/account.json"
|
||||
|
||||
{jq} -e '
|
||||
.registration.body.status == "valid"
|
||||
and (.registration.uri | startswith("https://acme.test/"))
|
||||
' "$account_dir/account.json" > /dev/stderr
|
||||
|
||||
cert_resource=$(find "/var/lib/acme/.lego/{cert}" -type f -name '{cert}.json' -print -quit)
|
||||
test -n "$cert_resource"
|
||||
{jq} '{{
|
||||
domain: .id,
|
||||
certUrl: .certUrl,
|
||||
certStableUrl: .certStableUrl
|
||||
}}' "$cert_resource" > "$cert_resource.tmp"
|
||||
mv "$cert_resource.tmp" "$cert_resource"
|
||||
|
||||
chown -R acme:acme /var/lib/acme/.lego/accounts "/var/lib/acme/.lego/{cert}"
|
||||
chmod -R u=rwX,g=,o= /var/lib/acme/.lego/accounts
|
||||
chmod -R u=rwX,g=rX,o= "/var/lib/acme/.lego/{cert}"
|
||||
rm -rf /run/acme/test-migration-*
|
||||
"""
|
||||
)
|
||||
|
||||
builtin.succeed(f"systemctl start {' '.join(migration_units)}")
|
||||
builtin.succeed('test "$(wc -l < /run/acme/test-migration-attempts)" -eq 1')
|
||||
builtin.succeed("test ! -e /run/acme/test-migration-active")
|
||||
builtin.succeed("test ! -e /run/acme/test-migration-collision")
|
||||
builtin.succeed(
|
||||
f"""
|
||||
set -euo pipefail
|
||||
|
||||
account_file=$(find /var/lib/acme/.lego/accounts -type f -name account.json -print -quit)
|
||||
test -n "$account_file"
|
||||
account_dir=$(dirname "$account_file")
|
||||
|
||||
account_id=$(basename "$account_dir")
|
||||
test -f "$account_dir/$account_id.key"
|
||||
test ! -e "$account_dir/keys"
|
||||
|
||||
{jq} -e '
|
||||
.origin == "migration"
|
||||
and .keyType == "EC256"
|
||||
and .server == "https://acme.test/dir"
|
||||
and .registration.status == "valid"
|
||||
and (.registration.accountURL | startswith("https://acme.test/"))
|
||||
' "$account_dir/account.json" > /dev/stderr
|
||||
"""
|
||||
)
|
||||
builtin.succeed(f"systemctl start acme-order-renew-{cert}.service")
|
||||
|
||||
check_issuer(builtin, cert, "pebble")
|
||||
check_domain(builtin, cert, cert)
|
||||
check_ip(builtin, cert, ip)
|
||||
for migration_cert in migration_certs[1:]:
|
||||
check_issuer(builtin, migration_cert, "pebble")
|
||||
check_domain(builtin, migration_cert, migration_cert)
|
||||
|
||||
with subtest("Validate permissions"):
|
||||
check_permissions(builtin, cert, "acme")
|
||||
|
||||
@@ -435,16 +298,6 @@ in
|
||||
check_permissions(builtin, cert, "acme")
|
||||
check_permissions(builtin, cert2, "acme")
|
||||
|
||||
with subtest("Remove an existing cert domain"):
|
||||
builtin.succeed("systemctl stop renew-triggered.target")
|
||||
switch_to(builtin, "remove_domain")
|
||||
builtin.wait_for_unit("renew-triggered.target")
|
||||
|
||||
check_issuer(builtin, cert, "pebble")
|
||||
check_domain(builtin, cert, f"builtin-alt.{domain}", fail=True)
|
||||
check_ip(builtin, cert, ip)
|
||||
check_permissions(builtin, cert, "acme")
|
||||
|
||||
with subtest("Check account hashing compatibility with pre-24.05 settings"):
|
||||
builtin.succeed("systemctl stop renew-triggered.target")
|
||||
switch_to(builtin, "legacy_account_hash"
|
||||
|
||||
@@ -15,8 +15,7 @@
|
||||
# Example callTest that just extracts the derivation from the test:
|
||||
# callTest = t: t.test;
|
||||
let
|
||||
inherit (pkgs) lib;
|
||||
inherit (lib)
|
||||
inherit (pkgs.lib)
|
||||
isAttrs
|
||||
isFunction
|
||||
mapAttrs
|
||||
@@ -99,25 +98,13 @@ let
|
||||
featureFlags.minimalModules = { };
|
||||
};
|
||||
evalMinimalConfig = module: nixosLib.evalModules { modules = [ module ]; };
|
||||
|
||||
/**
|
||||
On platforms where NixOS does not run natively, nixosTests is still available
|
||||
as a convenience for running VM tests. This makes them easier to use, and it
|
||||
sidesteps the question of nested virtualisation.
|
||||
|
||||
`runTest` already includes similar logic as part of its [public] interface.
|
||||
|
||||
[public]: https://nixos.org/manual/nixos/stable/#sec-call-nixos-test-outside-nixos
|
||||
*/
|
||||
inherit (pkgs) pkgsLinux;
|
||||
|
||||
evalSystem =
|
||||
module:
|
||||
import ../lib/eval-config.nix {
|
||||
system = null;
|
||||
modules = [
|
||||
../modules/misc/nixpkgs/read-only.nix
|
||||
{ nixpkgs.pkgs = pkgsLinux; }
|
||||
{ nixpkgs.pkgs = pkgs; }
|
||||
module
|
||||
];
|
||||
};
|
||||
@@ -236,7 +223,8 @@ in
|
||||
accountsservice = runTest ./accountsservice.nix;
|
||||
acl = pkgs.callPackage ./acl.nix { };
|
||||
acme = import ./acme/default.nix {
|
||||
inherit runTest lib;
|
||||
inherit runTest;
|
||||
inherit (pkgs) lib;
|
||||
};
|
||||
acme-dns = runTest ./acme-dns.nix;
|
||||
activation = pkgs.callPackage ../modules/system/activation/test.nix { };
|
||||
@@ -301,7 +289,6 @@ in
|
||||
audiobookshelf = runTest ./audiobookshelf.nix;
|
||||
audit = runTest ./audit.nix;
|
||||
audit-testsuite = runTest ./audit-testsuite.nix;
|
||||
aurral = runTest ./aurral.nix;
|
||||
auth-mysql = runTest ./auth-mysql.nix;
|
||||
authelia = runTest ./authelia.nix;
|
||||
auto-cpufreq = runTest ./auto-cpufreq.nix;
|
||||
@@ -375,7 +362,6 @@ in
|
||||
btrbk-doas = runTest ./btrbk-doas.nix;
|
||||
btrbk-no-timer = runTest ./btrbk-no-timer.nix;
|
||||
btrbk-section-order = runTest ./btrbk-section-order.nix;
|
||||
btrfs-autoscrub = runTest ./btrfs-autoscrub.nix;
|
||||
budgie = runTest ./budgie.nix;
|
||||
buildbot = runTest ./buildbot.nix;
|
||||
buildkite-agents = runTest ./buildkite-agents.nix;
|
||||
@@ -439,13 +425,12 @@ in
|
||||
inherit runTest;
|
||||
package = pkgs.clickhouse-lts;
|
||||
};
|
||||
cliproxyapi = runTest ./cliproxyapi.nix;
|
||||
cloud-init = runTest ./cloud-init.nix;
|
||||
cloud-init-hostname = runTest ./cloud-init-hostname.nix;
|
||||
cloudcompare = import ./cloudcompare.nix { inherit pkgs runTest; };
|
||||
cloudlog = runTest ./cloudlog.nix;
|
||||
cntr = import ./cntr.nix {
|
||||
inherit lib;
|
||||
inherit (pkgs) lib;
|
||||
runTest = runTestOn [
|
||||
"aarch64-linux"
|
||||
"x86_64-linux"
|
||||
@@ -624,15 +609,18 @@ in
|
||||
ergochat = runTest ./ergochat.nix;
|
||||
ersatztv = runTest ./ersatztv.nix;
|
||||
espanso = import ./espanso.nix {
|
||||
inherit lib runTest;
|
||||
inherit (pkgs) lib;
|
||||
inherit runTest;
|
||||
};
|
||||
esphome = runTest ./esphome.nix;
|
||||
etc = pkgsLinux.callPackage ../modules/system/etc/test.nix { inherit evalMinimalConfig; };
|
||||
etc = pkgs.callPackage ../modules/system/etc/test.nix { inherit evalMinimalConfig; };
|
||||
etcd = import ./etcd/default.nix { inherit pkgs runTest; };
|
||||
etebase-server = runTest ./etebase-server.nix;
|
||||
etesync-dav = runTest ./etesync-dav.nix;
|
||||
evcc = runTest ./evcc.nix;
|
||||
extra-initrd = import ./extra-initrd.nix { inherit runTest; };
|
||||
extra-initrd = import ./extra-initrd.nix {
|
||||
inherit runTest pkgs;
|
||||
};
|
||||
facter = runTest ./facter;
|
||||
fail2ban = runTest ./fail2ban.nix;
|
||||
fakeroute = runTest ./fakeroute.nix;
|
||||
@@ -693,7 +681,6 @@ in
|
||||
firewalld = runTest ./firewalld.nix;
|
||||
firezone = runTest ./firezone/firezone.nix;
|
||||
fish = runTest ./fish.nix;
|
||||
flame = runTest ./flame.nix;
|
||||
flannel = runTestOn [ "x86_64-linux" ] ./flannel.nix;
|
||||
flap-alerted = runTest ./flap-alerted.nix;
|
||||
flaresolverr = runTest ./flaresolverr.nix;
|
||||
@@ -706,12 +693,13 @@ in
|
||||
fontconfig-default-fonts = runTest ./fontconfig-default-fonts.nix;
|
||||
forgejo = import ./forgejo.nix {
|
||||
inherit runTest;
|
||||
forgejoPackage = pkgsLinux.forgejo;
|
||||
forgejoPackage = pkgs.forgejo;
|
||||
};
|
||||
forgejo-lts = import ./forgejo.nix {
|
||||
inherit runTest;
|
||||
forgejoPackage = pkgsLinux.forgejo-lts;
|
||||
forgejoPackage = pkgs.forgejo-lts;
|
||||
};
|
||||
freenet = runTest ./freenet.nix;
|
||||
freescout = import ./freescout {
|
||||
inherit runTest;
|
||||
};
|
||||
@@ -736,11 +724,11 @@ in
|
||||
gancio = runTest ./gancio.nix;
|
||||
garage_1 = import ./garage {
|
||||
inherit runTest;
|
||||
package = pkgsLinux.garage_1;
|
||||
package = pkgs.garage_1;
|
||||
};
|
||||
garage_2 = import ./garage {
|
||||
inherit runTest;
|
||||
package = pkgsLinux.garage_2;
|
||||
package = pkgs.garage_2;
|
||||
};
|
||||
gatus = runTest ./gatus.nix;
|
||||
gemstash = import ./gemstash.nix { inherit pkgs runTest; };
|
||||
@@ -754,9 +742,8 @@ in
|
||||
git-pages-modular = runTest ./git-pages.nix;
|
||||
gitdaemon = runTest ./gitdaemon.nix;
|
||||
gitea = import ./gitea.nix {
|
||||
inherit (pkgsLinux) gitea;
|
||||
inherit runTest;
|
||||
inherit lib;
|
||||
inherit pkgs runTest;
|
||||
inherit (pkgs) lib;
|
||||
};
|
||||
gitea-actions-runner = runTest ./gitea-actions-runner.nix;
|
||||
github-runner = runTest ./github-runner.nix;
|
||||
@@ -793,9 +780,7 @@ in
|
||||
gotify-server = runTest ./gotify-server.nix;
|
||||
gotosocial = runTest ./web-apps/gotosocial.nix;
|
||||
goupile = runTest ./web-apps/goupile;
|
||||
grafana = import ./grafana {
|
||||
inherit runTest;
|
||||
};
|
||||
grafana = handleTest ./grafana { };
|
||||
grafana-to-ntfy = runTest ./grafana-to-ntfy.nix;
|
||||
graphite = runTest ./graphite.nix;
|
||||
grav = runTest ./web-apps/grav.nix;
|
||||
@@ -815,7 +800,8 @@ in
|
||||
guix = handleTest ./guix { };
|
||||
gvisor = runTest ./gvisor.nix;
|
||||
h2o = import ./web-servers/h2o {
|
||||
inherit lib runTest;
|
||||
inherit runTest;
|
||||
inherit (pkgs) lib;
|
||||
};
|
||||
hadoop = import ./hadoop {
|
||||
inherit handleTestOn;
|
||||
@@ -861,7 +847,6 @@ in
|
||||
hibernate-systemd-stage-1 = handleTestOn [ "x86_64-linux" ] ./hibernate.nix {
|
||||
systemdStage1 = true;
|
||||
};
|
||||
hickory-dns = runTest ./hickory-dns.nix;
|
||||
hister = runTest ./hister.nix;
|
||||
hitch = runTest ./hitch;
|
||||
hledger-web = runTest ./hledger-web.nix;
|
||||
@@ -881,7 +866,6 @@ in
|
||||
hound = runTest ./hound.nix;
|
||||
hub = runTest ./git/hub.nix;
|
||||
hydra = runTest ./hydra;
|
||||
hyphanet = runTest ./hyphanet.nix;
|
||||
i18n = runTest ./i18n.nix;
|
||||
i2pd = runTest ./i2pd.nix;
|
||||
i3wm = runTest ./i3wm.nix;
|
||||
@@ -927,7 +911,6 @@ in
|
||||
inventree = runTest ./inventree.nix;
|
||||
invidious = runTest ./invidious.nix;
|
||||
invoiceplane = runTest ./invoiceplane.nix;
|
||||
iocaine = runTest ./iocaine.nix;
|
||||
iodine = runTest ./iodine.nix;
|
||||
iosched = runTest ./iosched.nix;
|
||||
ipget = runTest ./ipget.nix;
|
||||
@@ -944,22 +927,18 @@ in
|
||||
jibri = runTest ./jibri.nix;
|
||||
jirafeau = runTest ./jirafeau.nix;
|
||||
jitsi-meet = runTest ./jitsi-meet.nix;
|
||||
jool = import ./jool.nix {
|
||||
pkgs = pkgsLinux;
|
||||
inherit runTest;
|
||||
};
|
||||
jool = import ./jool.nix { inherit pkgs runTest; };
|
||||
jotta-cli = runTest ./jotta-cli.nix;
|
||||
k3s = import ./rancher {
|
||||
inherit lib pkgs;
|
||||
inherit pkgs;
|
||||
inherit (pkgs) lib;
|
||||
runTest = runTestOn [
|
||||
"aarch64-linux"
|
||||
"x86_64-linux"
|
||||
];
|
||||
rancherDistro = "k3s";
|
||||
};
|
||||
kafka = import ./kafka {
|
||||
inherit runTest pkgs;
|
||||
};
|
||||
kafka = handleTest ./kafka { };
|
||||
kaidan = runTest ./kaidan;
|
||||
kanboard = runTest ./web-apps/kanboard.nix;
|
||||
kanidm = runTest ./kanidm.nix;
|
||||
@@ -974,9 +953,7 @@ in
|
||||
keepalived = discoverTests (import ./keepalived.nix);
|
||||
keepassxc = runTest ./keepassxc.nix;
|
||||
kener = runTest ./kener.nix;
|
||||
kerberos = import ./kerberos/default.nix {
|
||||
inherit pkgs runTest;
|
||||
};
|
||||
kerberos = handleTest ./kerberos/default.nix { };
|
||||
kernel-generic = handleTest ./kernel-generic { };
|
||||
kernel-latest-ath-user-regd = runTest ./kernel-latest-ath-user-regd.nix;
|
||||
kernel-rust = handleTest ./kernel-rust.nix { };
|
||||
@@ -1006,7 +983,8 @@ in
|
||||
kthxbye = runTest ./kthxbye.nix;
|
||||
kubernetes = handleTestOn [ "x86_64-linux" ] ./kubernetes { };
|
||||
kubo = import ./kubo {
|
||||
inherit lib runTest;
|
||||
inherit runTest;
|
||||
inherit (pkgs) lib;
|
||||
};
|
||||
kvrocks = runTest ./kvrocks.nix;
|
||||
labgrid = runTest ./labgrid.nix;
|
||||
@@ -1115,7 +1093,7 @@ in
|
||||
matrix-tuwunel = runTest ./matrix/tuwunel.nix;
|
||||
matter-server = runTest ./matter-server.nix;
|
||||
matterjs-server = runTest ./matterjs-server.nix;
|
||||
mattermost = runTest ./mattermost;
|
||||
mattermost = handleTest ./mattermost { };
|
||||
mautrix-discord = runTest ./matrix/mautrix-discord.nix;
|
||||
mautrix-meta-postgres = runTest ./matrix/mautrix-meta-postgres.nix;
|
||||
mautrix-meta-sqlite = runTest ./matrix/mautrix-meta-sqlite.nix;
|
||||
@@ -1123,7 +1101,8 @@ in
|
||||
mediamtx = runTest ./mediamtx.nix;
|
||||
mediatomb = runTest ./mediatomb.nix;
|
||||
mediawiki = import ./mediawiki.nix {
|
||||
inherit lib runTest;
|
||||
inherit (pkgs) lib;
|
||||
inherit runTest;
|
||||
};
|
||||
meilisearch = runTest ./meilisearch.nix;
|
||||
memcached = runTest ./memcached.nix;
|
||||
@@ -1146,7 +1125,7 @@ in
|
||||
mobilizon = runTest ./mobilizon.nix;
|
||||
mod_perl = runTest ./mod_perl.nix;
|
||||
modular-service-etc = runTest ./modular-service-etc/test.nix;
|
||||
modularService = pkgsLinux.callPackage ../modules/system/service/systemd/test.nix {
|
||||
modularService = pkgs.callPackage ../modules/system/service/systemd/test.nix {
|
||||
inherit evalSystem;
|
||||
};
|
||||
moduleStateRevisions = pkgs.callPackage ./moduleStateRevisions.nix { };
|
||||
@@ -1177,7 +1156,8 @@ in
|
||||
morph-browser = discoverTests (import ./morph-browser.nix);
|
||||
mosquitto = runTest ./mosquitto.nix;
|
||||
movim = import ./web-apps/movim {
|
||||
inherit lib runTest;
|
||||
inherit runTest;
|
||||
inherit (pkgs) lib;
|
||||
};
|
||||
mpd = runTest ./mpd.nix;
|
||||
mpv = runTest ./mpv.nix;
|
||||
@@ -1278,7 +1258,7 @@ in
|
||||
nginx-status-page = runTest ./nginx-status-page.nix;
|
||||
nginx-tmpdir = runTest ./nginx-tmpdir.nix;
|
||||
nginx-unix-socket = runTest ./nginx-unix-socket.nix;
|
||||
nginx-variants = import ./nginx-variants.nix { inherit runTest; };
|
||||
nginx-variants = import ./nginx-variants.nix { inherit pkgs runTest; };
|
||||
nifi = runTestOn [ "x86_64-linux" ] ./web-apps/nifi.nix;
|
||||
nimdow = runTest ./nimdow.nix;
|
||||
nipap = runTest ./web-apps/nipap.nix;
|
||||
@@ -1382,7 +1362,6 @@ in
|
||||
ollama-cuda = runTestOn [ "x86_64-linux" "aarch64-linux" ] ./ollama-cuda.nix;
|
||||
ollama-rocm = runTestOn [ "x86_64-linux" "aarch64-linux" ] ./ollama-rocm.nix;
|
||||
ollama-vulkan = runTestOn [ "x86_64-linux" "aarch64-linux" ] ./ollama-vulkan.nix;
|
||||
omada = runTestOn [ "x86_64-linux" ] ./omada.nix;
|
||||
ombi = runTest ./ombi.nix;
|
||||
omnom = runTest ./omnom;
|
||||
oncall = runTest ./web-apps/oncall.nix;
|
||||
@@ -1449,7 +1428,8 @@ in
|
||||
patroni = handleTestOn [ "x86_64-linux" ] ./patroni.nix { };
|
||||
pcsclite = runTest ./pcsclite.nix;
|
||||
pdfding = import ./web-apps/pdfding {
|
||||
inherit lib runTest;
|
||||
inherit (pkgs) lib;
|
||||
inherit runTest;
|
||||
};
|
||||
pdns-recursor = runTest ./pdns-recursor.nix;
|
||||
pdudaemon = runTest ./pdudaemon.nix;
|
||||
@@ -1517,10 +1497,7 @@ in
|
||||
{ };
|
||||
postfix-tlspol = runTest ./postfix-tlspol.nix;
|
||||
postgres-websockets = runTest ./postgres-websockets.nix;
|
||||
postgresql = import ./postgresql {
|
||||
inherit runTest;
|
||||
pkgs = pkgsLinux;
|
||||
};
|
||||
postgresql = import ./postgresql { inherit runTest pkgs; };
|
||||
postgrest = runTest ./postgrest.nix;
|
||||
power-profiles-daemon = runTest ./power-profiles-daemon.nix;
|
||||
powerdns = runTest ./powerdns.nix;
|
||||
@@ -1558,7 +1535,8 @@ in
|
||||
privoxy = runTest ./privoxy.nix;
|
||||
prometheus = import ./prometheus { inherit runTest; };
|
||||
prometheus-exporters = import ./prometheus-exporters.nix {
|
||||
inherit lib runTest;
|
||||
inherit runTest;
|
||||
inherit (pkgs) lib;
|
||||
};
|
||||
prosody = runTest ./xmpp/prosody.nix;
|
||||
prosody-mysql = handleTest ./xmpp/prosody-mysql.nix { };
|
||||
@@ -1598,16 +1576,14 @@ in
|
||||
rathole = runTest ./rathole.nix;
|
||||
rauc = runTest ./rauc.nix;
|
||||
reaction = import ./reaction {
|
||||
inherit lib runTest;
|
||||
inherit (pkgs) lib;
|
||||
inherit runTest;
|
||||
};
|
||||
readarr = runTest ./readarr.nix;
|
||||
readeck = runTest ./readeck.nix;
|
||||
realm = runTest ./realm.nix;
|
||||
rebuilderd = runTest ./rebuilderd.nix;
|
||||
redis = runTest {
|
||||
imports = [ ./redis.nix ];
|
||||
_module.args.package = pkgs.redis;
|
||||
};
|
||||
redis = handleTest ./redis.nix { };
|
||||
redlib = runTest ./redlib.nix;
|
||||
redmine = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./redmine.nix { };
|
||||
refind = runTest ./refind.nix;
|
||||
@@ -1622,7 +1598,8 @@ in
|
||||
retroarch = runTest ./retroarch.nix;
|
||||
ringboard = runTest ./ringboard.nix;
|
||||
rke2 = import ./rancher {
|
||||
inherit lib pkgs;
|
||||
inherit pkgs;
|
||||
inherit (pkgs) lib;
|
||||
runTest = runTestOn [
|
||||
"aarch64-linux"
|
||||
"x86_64-linux"
|
||||
@@ -1750,7 +1727,8 @@ in
|
||||
sunshine = runTest ./sunshine.nix;
|
||||
suricata = runTest ./suricata.nix;
|
||||
suwayomi-server = import ./suwayomi-server.nix {
|
||||
inherit lib runTest;
|
||||
inherit runTest;
|
||||
inherit (pkgs) lib;
|
||||
};
|
||||
svnserve = runTest ./svnserve.nix;
|
||||
swap-file-btrfs = runTest ./swap-file-btrfs.nix;
|
||||
@@ -1791,18 +1769,19 @@ in
|
||||
system-services-compliance = recurseIntoAttrs (
|
||||
import ./system-services-compliance.nix {
|
||||
inherit
|
||||
pkgs
|
||||
evalSystem
|
||||
runTest
|
||||
callTest
|
||||
;
|
||||
pkgs = pkgsLinux;
|
||||
}
|
||||
);
|
||||
systemd = runTest ./systemd.nix;
|
||||
systemd-analyze = runTest ./systemd-analyze.nix;
|
||||
systemd-binfmt = handleTestOn [ "x86_64-linux" ] ./systemd-binfmt.nix { };
|
||||
systemd-boot = import ./systemd-boot.nix {
|
||||
inherit lib runTest runTestOn;
|
||||
inherit runTest runTestOn;
|
||||
inherit (pkgs) lib;
|
||||
};
|
||||
systemd-bpf = runTest ./systemd-bpf.nix;
|
||||
systemd-capsules = runTest ./systemd-capsules.nix;
|
||||
@@ -1986,13 +1965,9 @@ in
|
||||
utmp = runTest ./utmp.nix;
|
||||
uwsgi = runTest ./uwsgi.nix;
|
||||
v2ray = runTest ./v2ray.nix;
|
||||
valkey = runTest {
|
||||
imports = [ ./redis.nix ];
|
||||
_module.args.package = pkgs.valkey;
|
||||
};
|
||||
varnish80 = runTest {
|
||||
imports = [ ./varnish.nix ];
|
||||
_module.args.package = pkgsLinux.varnish80;
|
||||
_module.args.package = pkgs.varnish80;
|
||||
};
|
||||
vault = runTest ./vault.nix;
|
||||
vault-agent = runTest ./vault-agent.nix;
|
||||
@@ -2010,7 +1985,7 @@ in
|
||||
vikunja = runTest ./vikunja.nix;
|
||||
vinyl-cache_9 = runTest {
|
||||
imports = [ ./vinyl-cache.nix ];
|
||||
_module.args.package = pkgsLinux.vinyl-cache_9;
|
||||
_module.args.package = pkgs.vinyl-cache_9;
|
||||
};
|
||||
virtualbox = handleTestOn [ "x86_64-linux" ] ./virtualbox.nix { };
|
||||
vm-variant = handleTest ./vm-variant.nix { };
|
||||
@@ -2035,15 +2010,16 @@ in
|
||||
whoogle-search = runTest ./whoogle-search.nix;
|
||||
wiki-js = runTest ./wiki-js.nix;
|
||||
windmill = import ./windmill {
|
||||
inherit runTest;
|
||||
inherit pkgs runTest;
|
||||
inherit (pkgs) lib;
|
||||
};
|
||||
wine = import ./wine.nix {
|
||||
inherit pkgs runTest;
|
||||
inherit (pkgs) lib;
|
||||
};
|
||||
wireguard = import ./wireguard {
|
||||
pkgs = pkgsLinux;
|
||||
inherit runTest lib;
|
||||
inherit pkgs runTest;
|
||||
inherit (pkgs) lib;
|
||||
};
|
||||
without-nix = runTest ./without-nix.nix;
|
||||
wmderland = runTest ./wmderland.nix;
|
||||
@@ -2051,7 +2027,7 @@ in
|
||||
workout-tracker = runTest ./workout-tracker.nix;
|
||||
wpa_supplicant = import ./wpa_supplicant.nix { inherit pkgs runTest; };
|
||||
wrappers = runTest ./wrappers.nix;
|
||||
writefreely = import ./web-apps/writefreely.nix { inherit runTest; };
|
||||
writefreely = import ./web-apps/writefreely.nix { inherit pkgs runTest; };
|
||||
wstunnel = runTest ./wstunnel.nix;
|
||||
xandikos = runTest ./xandikos.nix;
|
||||
xautolock = runTest ./xautolock.nix;
|
||||
@@ -2069,14 +2045,14 @@ in
|
||||
yarr = runTest ./yarr.nix;
|
||||
yb = pkgs.callPackage ./yb.nix { inherit (pkgs.yb.passthru) ybPivHarnessTests testFixtures; };
|
||||
ydotool = import ./ydotool.nix {
|
||||
inherit lib runTest;
|
||||
inherit (pkgs) lib;
|
||||
inherit runTest;
|
||||
};
|
||||
yggdrasil = runTest ./yggdrasil.nix;
|
||||
your_spotify = runTest ./your_spotify.nix;
|
||||
zammad = runTest ./zammad.nix;
|
||||
zapret2 = runTest ./zapret2.nix;
|
||||
zenohd = runTest ./zenohd.nix;
|
||||
zerobyte = runTest ./zerobyte.nix;
|
||||
zeronet-conservancy = runTest ./zeronet-conservancy.nix;
|
||||
zfs = import ./zfs.nix { inherit system pkgs runTest; };
|
||||
zigbee2mqtt = runTest ./zigbee2mqtt.nix;
|
||||
|
||||
@@ -27,8 +27,6 @@
|
||||
};
|
||||
|
||||
image.repart = {
|
||||
enable = true;
|
||||
|
||||
verityStore = {
|
||||
enable = true;
|
||||
# by default the module works with systemd-boot, for simplicity this test directly boots the UKI
|
||||
|
||||
@@ -46,8 +46,6 @@ in
|
||||
};
|
||||
|
||||
image.repart = {
|
||||
enable = true;
|
||||
|
||||
name = "appliance-gpt-image";
|
||||
# OVMF does not work with the default repart sector size of 4096
|
||||
sectorSize = 512;
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
{ lib, ... }:
|
||||
{
|
||||
name = "aurral";
|
||||
meta = with lib.maintainers; {
|
||||
maintainers = [ hougo ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
machine =
|
||||
{ ... }:
|
||||
{
|
||||
services.aurral = {
|
||||
enable = true;
|
||||
environment = {
|
||||
DOWNLOAD_FOLDER = "/var/lib/aurral-downloads";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
machine.wait_for_unit("aurral.service")
|
||||
machine.wait_for_open_port(3001)
|
||||
|
||||
machine.succeed('curl --fail http://localhost:3001/api/health')
|
||||
'';
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
{ ... }:
|
||||
{
|
||||
name = "btrfs-autoscrub";
|
||||
|
||||
nodes.machine =
|
||||
{ ... }:
|
||||
{
|
||||
virtualisation.emptyDiskImages = [ 128 ];
|
||||
# test sandbox permissiveness and command line escaping
|
||||
virtualisation.fileSystems."/home/test/btrfs autoscrub test" = {
|
||||
fsType = "btrfs";
|
||||
device = "/dev/vdb";
|
||||
autoFormat = true;
|
||||
options = [ "X-mount.mkdir" ];
|
||||
};
|
||||
services.btrfs.autoScrub = {
|
||||
enable = true;
|
||||
# test that setting the limit works
|
||||
limit = "1G";
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
def run_scrub(fs):
|
||||
machine.start_job(f"'btrfs-scrub@{fs}.service'")
|
||||
machine.wait_until_fails(f"systemctl --quiet is-active 'btrfs-scrub@{fs}.service'")
|
||||
machine.fail(f"systemctl is-failed 'btrfs-scrub@{fs}.service'")
|
||||
invocation_id = machine.succeed(
|
||||
f"systemctl show --value -p InvocationID 'btrfs-scrub@{fs}.service'"
|
||||
)
|
||||
output = machine.succeed(
|
||||
f"journalctl --no-pager _SYSTEMD_INVOCATION_ID={invocation_id}"
|
||||
)
|
||||
t.assertNotRegex(output, "(?i)warning:|error:")
|
||||
|
||||
start_all()
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
|
||||
fs = "/home/test/btrfs autoscrub test"
|
||||
escaped = r"home-test-btrfs\x20autoscrub\x20test"
|
||||
|
||||
with subtest("Verify that the configured timers and file systems are active"):
|
||||
machine.require_unit_state(f"{escaped}.mount", "active")
|
||||
machine.require_unit_state(f"btrfs-scrub@{escaped}.timer", "active")
|
||||
|
||||
# disable timers (and possible triggered services) to prevent them
|
||||
# from interfering with the tests
|
||||
machine.stop_job(f"'btrfs-scrub@{escaped}.timer'")
|
||||
machine.stop_job(f"'btrfs-scrub@{escaped}.service'")
|
||||
|
||||
with subtest("Verify that scrubbing works"):
|
||||
run_scrub(escaped)
|
||||
result = machine.succeed(f"btrfs scrub status '{fs}'")
|
||||
t.assertRegex(result, r"Status:\s*finished")
|
||||
|
||||
with subtest("Verify that scrubbing causes filesystems to be mounted"):
|
||||
machine.stop_job(f"'{escaped}.mount'")
|
||||
run_scrub(escaped)
|
||||
machine.require_unit_state(f"{escaped}.mount", "active")
|
||||
|
||||
with subtest("Verify that the service can scrub private mountpoints"):
|
||||
machine.succeed(f"chmod 000 '{fs}'")
|
||||
machine.succeed("chmod 000 /home/test")
|
||||
run_scrub(escaped)
|
||||
|
||||
with subtest("Verify that the service can scrub device files directly"):
|
||||
run_scrub("dev-vdb")
|
||||
'';
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
pkgs,
|
||||
runTest,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{ runTest, pkgs }:
|
||||
{ runTest, pkgs, ... }:
|
||||
|
||||
let
|
||||
authKey = pkgs.writeText "auth-key" "1234ABCD";
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
{ lib, ... }:
|
||||
{
|
||||
name = "cliproxyapi";
|
||||
|
||||
meta.maintainers = [ lib.maintainers.anish ];
|
||||
|
||||
containers.machine =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
services.cliproxyapi = {
|
||||
enable = true;
|
||||
settings = {
|
||||
host = "127.0.0.1";
|
||||
port = 8317;
|
||||
api-keys = [ { _secret = "/etc/cliproxyapi-api-key"; } ];
|
||||
};
|
||||
};
|
||||
|
||||
environment.etc."cliproxyapi-api-key".text = "test-key";
|
||||
environment.systemPackages = [ pkgs.curl ];
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.wait_for_unit("cliproxyapi.service")
|
||||
machine.wait_for_open_port(8317)
|
||||
|
||||
# The API key secret must be substituted into config.yaml.
|
||||
machine.succeed("grep -q test-key /var/lib/cliproxyapi/config.yaml")
|
||||
|
||||
# Requests without a valid API key are rejected.
|
||||
status = machine.succeed(
|
||||
"curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8317/v1/models"
|
||||
).strip()
|
||||
assert status == "401", f"expected 401 for unauthenticated /v1/models, got {status}"
|
||||
|
||||
# Requests carrying the configured API key are accepted.
|
||||
machine.succeed(
|
||||
"curl -sf -H 'Authorization: Bearer test-key' http://127.0.0.1:8317/v1/models"
|
||||
)
|
||||
'';
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
pkgs,
|
||||
runTest,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
{ runTest }:
|
||||
{
|
||||
runTest,
|
||||
...
|
||||
}:
|
||||
let
|
||||
common =
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
{ lib, ... }:
|
||||
{
|
||||
name = "flame";
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ DerGrumpf ];
|
||||
|
||||
nodes.machine = {
|
||||
services.flame = {
|
||||
enable = true;
|
||||
passwordFile = "/etc/flame-password";
|
||||
|
||||
apps = [
|
||||
{
|
||||
name = "Test App";
|
||||
url = "http://example.com";
|
||||
}
|
||||
];
|
||||
|
||||
categories = [
|
||||
{
|
||||
name = "Test Category";
|
||||
bookmarks = [
|
||||
{
|
||||
name = "Nixpkgs";
|
||||
url = "https://github.com/NixOS/nixpkgs";
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
settings = {
|
||||
customTitle = "Test Flame";
|
||||
customUnknownKey = "test-value";
|
||||
};
|
||||
|
||||
customCSS = ''
|
||||
body { background: #123456; }
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"f /etc/flame-password 0400 root root - testpassword"
|
||||
];
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.wait_for_unit("flame.service")
|
||||
machine.wait_for_open_port(5005)
|
||||
machine.succeed("curl -f http://localhost:5005/")
|
||||
|
||||
machine.wait_for_unit("flame-seed.service")
|
||||
|
||||
machine.succeed("curl -f http://localhost:5005/api/apps | grep -q 'Test App'")
|
||||
machine.succeed("curl -f http://localhost:5005/api/categories | grep -q 'Test Category'")
|
||||
machine.succeed("curl -f http://localhost:5005/api/categories | grep -q Nixpkgs")
|
||||
machine.succeed("curl -f http://localhost:5005/api/config | grep -q 'Test Flame'")
|
||||
machine.succeed("curl -f http://localhost:5005/flame.css | grep -q '#123456'")
|
||||
|
||||
# Restart resilience
|
||||
machine.succeed("systemctl restart flame.service")
|
||||
machine.wait_for_unit("flame.service")
|
||||
machine.wait_for_open_port(5005)
|
||||
machine.succeed("curl -f http://localhost:5005/api/apps | grep -q 'Test App'")
|
||||
|
||||
# Freeform settings pass-through (unknown key, not explicitly declared)
|
||||
machine.succeed("curl -f http://localhost:5005/api/config | grep -q customUnknownKey")
|
||||
'';
|
||||
}
|
||||
@@ -1,21 +1,21 @@
|
||||
{ lib, ... }:
|
||||
|
||||
{
|
||||
name = "hyphanet";
|
||||
name = "freenet";
|
||||
meta = {
|
||||
maintainers = with lib.maintainers; [ nagy ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
machine = {
|
||||
services.hyphanet.enable = true;
|
||||
services.freenet.enable = true;
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.wait_for_unit("hyphanet.service")
|
||||
machine.wait_for_unit("freenet.service")
|
||||
machine.wait_for_open_port(8888)
|
||||
machine.wait_until_succeeds("curl -sfL http://localhost:8888/ | grep Freenet")
|
||||
machine.succeed("systemctl stop hyphanet")
|
||||
machine.succeed("systemctl stop freenet")
|
||||
'';
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
gitea,
|
||||
pkgs,
|
||||
lib,
|
||||
runTest,
|
||||
...
|
||||
@@ -28,18 +28,10 @@ let
|
||||
];
|
||||
makeGiteaTest =
|
||||
type:
|
||||
runTest {
|
||||
imports = [
|
||||
module
|
||||
];
|
||||
name = "${gitea.pname}-${type}";
|
||||
nodes.server.services.gitea.settings.database = { inherit type; };
|
||||
};
|
||||
module =
|
||||
{ config, ... }:
|
||||
{
|
||||
lib.nameValuePair type (runTest {
|
||||
name = "${pkgs.gitea.pname}-${type}";
|
||||
meta = {
|
||||
inherit (gitea.meta) maintainers;
|
||||
inherit (pkgs.gitea.meta) maintainers;
|
||||
};
|
||||
|
||||
nodes = {
|
||||
@@ -50,6 +42,7 @@ let
|
||||
services = {
|
||||
gitea = {
|
||||
enable = true;
|
||||
database = { inherit type; };
|
||||
package = pkgs.gitea;
|
||||
metricsTokenFile = (pkgs.writeText "metrics_secret" "fakesecret").outPath;
|
||||
settings = {
|
||||
@@ -77,7 +70,7 @@ let
|
||||
|
||||
testScript =
|
||||
let
|
||||
inherit (import ./ssh-keys.nix config.node.pkgs) snakeOilPrivateKey snakeOilPublicKey;
|
||||
inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
|
||||
in
|
||||
/* python */ ''
|
||||
GIT_SSH_COMMAND = "ssh -i $HOME/.ssh/privk -o StrictHostKeyChecking=no"
|
||||
@@ -105,7 +98,7 @@ let
|
||||
|
||||
server.succeed(
|
||||
"su -l gitea -c 'gpg --homedir /var/lib/gitea/data/home/.gnupg "
|
||||
+ "--import ${toString (config.node.pkgs.writeText "gitea.key" signingPrivateKey)}'"
|
||||
+ "--import ${toString (pkgs.writeText "gitea.key" signingPrivateKey)}'"
|
||||
)
|
||||
|
||||
assert "BEGIN PGP PUBLIC KEY BLOCK" in server.succeed("curl http://localhost:3000/api/v1/signing-key.gpg")
|
||||
@@ -157,7 +150,7 @@ let
|
||||
+ 'http://localhost:3000/metrics '
|
||||
+ '| grep gitea_accesses')
|
||||
'';
|
||||
};
|
||||
});
|
||||
in
|
||||
|
||||
lib.genAttrs supportedDbTypes makeGiteaTest
|
||||
lib.listToAttrs (map makeGiteaTest supportedDbTypes)
|
||||
|
||||
@@ -1,154 +1,156 @@
|
||||
{ lib, pkgs, ... }:
|
||||
import ../make-test-python.nix (
|
||||
{ lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
inherit (lib) mkMerge;
|
||||
let
|
||||
inherit (lib) mkMerge;
|
||||
|
||||
baseGrafanaConf = {
|
||||
services.grafana = {
|
||||
enable = true;
|
||||
settings = {
|
||||
analytics.reporting_enabled = false;
|
||||
baseGrafanaConf = {
|
||||
services.grafana = {
|
||||
enable = true;
|
||||
settings = {
|
||||
analytics.reporting_enabled = false;
|
||||
|
||||
server = {
|
||||
http_addr = "::1";
|
||||
domain = "localhost";
|
||||
};
|
||||
server = {
|
||||
http_addr = "::1";
|
||||
domain = "localhost";
|
||||
};
|
||||
|
||||
security = {
|
||||
admin_user = "testadmin";
|
||||
admin_password = "snakeoilpwd";
|
||||
secret_key = "11111111111111111111";
|
||||
security = {
|
||||
admin_user = "testadmin";
|
||||
admin_password = "snakeoilpwd";
|
||||
secret_key = "11111111111111111111";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
extraNodeConfs = {
|
||||
sqlite = { };
|
||||
extraNodeConfs = {
|
||||
sqlite = { };
|
||||
|
||||
socket =
|
||||
{ config, ... }:
|
||||
{
|
||||
services.grafana.settings.server = {
|
||||
protocol = "socket";
|
||||
socket = "/run/grafana/sock";
|
||||
socket_gid = config.users.groups.nginx.gid;
|
||||
socket =
|
||||
{ config, ... }:
|
||||
{
|
||||
services.grafana.settings.server = {
|
||||
protocol = "socket";
|
||||
socket = "/run/grafana/sock";
|
||||
socket_gid = config.users.groups.nginx.gid;
|
||||
};
|
||||
|
||||
users.users.grafana.extraGroups = [ "nginx" ];
|
||||
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
recommendedProxySettings = true;
|
||||
virtualHosts."_".locations."/".proxyPass = "http://unix:/run/grafana/sock";
|
||||
};
|
||||
};
|
||||
|
||||
users.users.grafana.extraGroups = [ "nginx" ];
|
||||
declarativePlugins = {
|
||||
services.grafana.declarativePlugins = [ pkgs.grafanaPlugins.grafana-clock-panel ];
|
||||
};
|
||||
|
||||
services.nginx = {
|
||||
postgresql = {
|
||||
services.grafana.settings.database = {
|
||||
host = "[::1]:5432";
|
||||
user = "grafana";
|
||||
};
|
||||
services.postgresql = {
|
||||
enable = true;
|
||||
recommendedProxySettings = true;
|
||||
virtualHosts."_".locations."/".proxyPass = "http://unix:/run/grafana/sock";
|
||||
ensureDatabases = [ "grafana" ];
|
||||
ensureUsers = [
|
||||
{
|
||||
name = "grafana";
|
||||
ensureDBOwnership = true;
|
||||
}
|
||||
];
|
||||
};
|
||||
systemd.services.grafana.after = [ "postgresql.target" ];
|
||||
};
|
||||
|
||||
declarativePlugins = {
|
||||
services.grafana.declarativePlugins = [ pkgs.grafanaPlugins.grafana-clock-panel ];
|
||||
mysql = {
|
||||
services.grafana.settings.database.user = "grafana";
|
||||
services.mysql = {
|
||||
enable = true;
|
||||
ensureDatabases = [ "grafana" ];
|
||||
ensureUsers = [
|
||||
{
|
||||
name = "grafana";
|
||||
ensurePermissions."grafana.*" = "ALL PRIVILEGES";
|
||||
}
|
||||
];
|
||||
package = pkgs.mariadb;
|
||||
};
|
||||
systemd.services.grafana.after = [ "mysql.service" ];
|
||||
};
|
||||
};
|
||||
|
||||
postgresql = {
|
||||
services.grafana.settings.database = {
|
||||
host = "[::1]:5432";
|
||||
user = "grafana";
|
||||
};
|
||||
services.postgresql = {
|
||||
enable = true;
|
||||
ensureDatabases = [ "grafana" ];
|
||||
ensureUsers = [
|
||||
{
|
||||
name = "grafana";
|
||||
ensureDBOwnership = true;
|
||||
}
|
||||
];
|
||||
};
|
||||
systemd.services.grafana.after = [ "postgresql.target" ];
|
||||
};
|
||||
containers = builtins.mapAttrs (
|
||||
_: val:
|
||||
mkMerge [
|
||||
val
|
||||
baseGrafanaConf
|
||||
]
|
||||
) extraNodeConfs;
|
||||
in
|
||||
{
|
||||
name = "grafana-basic";
|
||||
|
||||
mysql = {
|
||||
services.grafana.settings.database.user = "grafana";
|
||||
services.mysql = {
|
||||
enable = true;
|
||||
ensureDatabases = [ "grafana" ];
|
||||
ensureUsers = [
|
||||
{
|
||||
name = "grafana";
|
||||
ensurePermissions."grafana.*" = "ALL PRIVILEGES";
|
||||
}
|
||||
];
|
||||
package = pkgs.mariadb;
|
||||
};
|
||||
systemd.services.grafana.after = [ "mysql.service" ];
|
||||
};
|
||||
};
|
||||
meta.maintainers = [ ];
|
||||
|
||||
containers = builtins.mapAttrs (
|
||||
_: val:
|
||||
mkMerge [
|
||||
val
|
||||
baseGrafanaConf
|
||||
]
|
||||
) extraNodeConfs;
|
||||
in
|
||||
{
|
||||
name = "grafana-basic";
|
||||
inherit containers;
|
||||
|
||||
meta.maintainers = [ ];
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
inherit containers;
|
||||
with subtest("Declarative plugins installed"):
|
||||
declarativePlugins.wait_for_unit("grafana.service")
|
||||
declarativePlugins.wait_for_open_port(3000, addr="::1")
|
||||
declarativePlugins.succeed(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]:3000/api/plugins | grep grafana-clock-panel"
|
||||
)
|
||||
declarativePlugins.shutdown()
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
with subtest("Successful API query as admin user with sqlite db"):
|
||||
sqlite.wait_for_unit("grafana.service")
|
||||
sqlite.wait_for_open_port(3000)
|
||||
print(sqlite.succeed(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]:3000/api/org/users -i"
|
||||
))
|
||||
sqlite.succeed(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]:3000/api/org/users | grep admin\@localhost"
|
||||
)
|
||||
sqlite.shutdown()
|
||||
|
||||
with subtest("Declarative plugins installed"):
|
||||
declarativePlugins.wait_for_unit("grafana.service")
|
||||
declarativePlugins.wait_for_open_port(3000, addr="::1")
|
||||
declarativePlugins.succeed(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]:3000/api/plugins | grep grafana-clock-panel"
|
||||
)
|
||||
declarativePlugins.shutdown()
|
||||
with subtest("Successful API query as admin user with sqlite db listening on socket"):
|
||||
socket.wait_for_unit("grafana.service")
|
||||
socket.wait_for_open_port(80)
|
||||
print(socket.succeed(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]/api/org/users -i"
|
||||
))
|
||||
socket.succeed(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]/api/org/users | grep admin\@localhost"
|
||||
)
|
||||
socket.shutdown()
|
||||
|
||||
with subtest("Successful API query as admin user with sqlite db"):
|
||||
sqlite.wait_for_unit("grafana.service")
|
||||
sqlite.wait_for_open_port(3000)
|
||||
print(sqlite.succeed(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]:3000/api/org/users -i"
|
||||
))
|
||||
sqlite.succeed(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]:3000/api/org/users | grep admin\@localhost"
|
||||
)
|
||||
sqlite.shutdown()
|
||||
with subtest("Successful API query as admin user with postgresql db"):
|
||||
postgresql.wait_for_unit("grafana.service")
|
||||
postgresql.wait_for_unit("postgresql.target")
|
||||
postgresql.wait_for_open_port(3000)
|
||||
postgresql.wait_for_open_port(5432)
|
||||
postgresql.succeed(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]:3000/api/org/users | grep admin\@localhost"
|
||||
)
|
||||
postgresql.shutdown()
|
||||
|
||||
with subtest("Successful API query as admin user with sqlite db listening on socket"):
|
||||
socket.wait_for_unit("grafana.service")
|
||||
socket.wait_for_open_port(80)
|
||||
print(socket.succeed(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]/api/org/users -i"
|
||||
))
|
||||
socket.succeed(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]/api/org/users | grep admin\@localhost"
|
||||
)
|
||||
socket.shutdown()
|
||||
|
||||
with subtest("Successful API query as admin user with postgresql db"):
|
||||
postgresql.wait_for_unit("grafana.service")
|
||||
postgresql.wait_for_unit("postgresql.target")
|
||||
postgresql.wait_for_open_port(3000)
|
||||
postgresql.wait_for_open_port(5432)
|
||||
postgresql.succeed(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]:3000/api/org/users | grep admin\@localhost"
|
||||
)
|
||||
postgresql.shutdown()
|
||||
|
||||
with subtest("Successful API query as admin user with mysql db"):
|
||||
mysql.wait_for_unit("grafana.service")
|
||||
mysql.wait_for_unit("mysql.service")
|
||||
mysql.wait_for_open_port(3000)
|
||||
mysql.wait_for_open_port(3306)
|
||||
mysql.succeed(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]:3000/api/org/users | grep admin\@localhost"
|
||||
)
|
||||
mysql.shutdown()
|
||||
'';
|
||||
}
|
||||
with subtest("Successful API query as admin user with mysql db"):
|
||||
mysql.wait_for_unit("grafana.service")
|
||||
mysql.wait_for_unit("mysql.service")
|
||||
mysql.wait_for_open_port(3000)
|
||||
mysql.wait_for_open_port(3306)
|
||||
mysql.succeed(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]:3000/api/org/users | grep admin\@localhost"
|
||||
)
|
||||
mysql.shutdown()
|
||||
'';
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
{
|
||||
runTest,
|
||||
system ? builtins.currentSystem,
|
||||
config ? { },
|
||||
pkgs ? import ../../.. { inherit system config; },
|
||||
}:
|
||||
|
||||
{
|
||||
basic = runTest ./basic.nix;
|
||||
provision = runTest ./provision;
|
||||
basic = import ./basic.nix { inherit system pkgs; };
|
||||
provision = import ./provision { inherit system pkgs; };
|
||||
}
|
||||
|
||||
@@ -1,247 +1,249 @@
|
||||
{ lib, pkgs, ... }:
|
||||
import ../../make-test-python.nix (
|
||||
{ lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
inherit (lib) mkMerge;
|
||||
let
|
||||
inherit (lib) mkMerge;
|
||||
|
||||
baseGrafanaConf = {
|
||||
services.grafana = {
|
||||
enable = true;
|
||||
provision.enable = true;
|
||||
settings = {
|
||||
analytics.reporting_enabled = false;
|
||||
baseGrafanaConf = {
|
||||
services.grafana = {
|
||||
enable = true;
|
||||
provision.enable = true;
|
||||
settings = {
|
||||
analytics.reporting_enabled = false;
|
||||
|
||||
server = {
|
||||
http_addr = "::1";
|
||||
domain = "localhost";
|
||||
};
|
||||
|
||||
security = {
|
||||
admin_user = "testadmin";
|
||||
admin_password = "$__file{${pkgs.writeText "pwd" "snakeoilpwd"}}";
|
||||
secret_key = "11111111111111111111";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules =
|
||||
let
|
||||
dashboard = pkgs.writeText "test.json" (builtins.readFile ./test_dashboard.json);
|
||||
in
|
||||
[
|
||||
"d /var/lib/grafana/dashboards 0700 grafana grafana -"
|
||||
"C+ /var/lib/grafana/dashboards/test.json - - - - ${dashboard}"
|
||||
];
|
||||
};
|
||||
|
||||
extraNodeConfs = {
|
||||
provisionNix = {
|
||||
services.grafana.provision = {
|
||||
datasources.settings = {
|
||||
apiVersion = 1;
|
||||
datasources = [
|
||||
{
|
||||
name = "Test Datasource";
|
||||
type = "testdata";
|
||||
access = "proxy";
|
||||
uid = "test_datasource";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
dashboards.settings = {
|
||||
apiVersion = 1;
|
||||
providers = [
|
||||
{
|
||||
name = "default";
|
||||
options.path = "/var/lib/grafana/dashboards";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
alerting = {
|
||||
rules.settings = {
|
||||
groups = [
|
||||
{
|
||||
name = "test_rule_group";
|
||||
folder = "test_folder";
|
||||
interval = "60s";
|
||||
rules = [
|
||||
{
|
||||
uid = "test_rule";
|
||||
title = "Test Rule";
|
||||
condition = "A";
|
||||
data = [
|
||||
{
|
||||
refId = "A";
|
||||
datasourceUid = "-100";
|
||||
model = {
|
||||
conditions = [
|
||||
{
|
||||
evaluator = {
|
||||
params = [ 3 ];
|
||||
type = "git";
|
||||
};
|
||||
operator.type = "and";
|
||||
query.params = [ "A" ];
|
||||
reducer.type = "last";
|
||||
type = "query";
|
||||
}
|
||||
];
|
||||
datasource = {
|
||||
type = "__expr__";
|
||||
uid = "-100";
|
||||
};
|
||||
expression = "1==0";
|
||||
intervalMs = 1000;
|
||||
maxDataPoints = 43200;
|
||||
refId = "A";
|
||||
type = "math";
|
||||
};
|
||||
}
|
||||
];
|
||||
for = "60s";
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
server = {
|
||||
http_addr = "::1";
|
||||
domain = "localhost";
|
||||
};
|
||||
|
||||
contactPoints.settings = {
|
||||
contactPoints = [
|
||||
{
|
||||
name = "Test Contact Point";
|
||||
receivers = [
|
||||
{
|
||||
uid = "test_contact_point";
|
||||
type = "prometheus-alertmanager";
|
||||
settings.url = "http://localhost:9000";
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
policies.settings = {
|
||||
policies = [
|
||||
{
|
||||
receiver = "Test Contact Point";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
templates.settings = {
|
||||
templates = [
|
||||
{
|
||||
name = "Test Template";
|
||||
template = "Test message";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
muteTimings.settings = {
|
||||
muteTimes = [
|
||||
{
|
||||
name = "Test Mute Timing";
|
||||
}
|
||||
];
|
||||
security = {
|
||||
admin_user = "testadmin";
|
||||
admin_password = "$__file{${pkgs.writeText "pwd" "snakeoilpwd"}}";
|
||||
secret_key = "11111111111111111111";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules =
|
||||
let
|
||||
dashboard = pkgs.writeText "test.json" (builtins.readFile ./test_dashboard.json);
|
||||
in
|
||||
[
|
||||
"d /var/lib/grafana/dashboards 0700 grafana grafana -"
|
||||
"C+ /var/lib/grafana/dashboards/test.json - - - - ${dashboard}"
|
||||
];
|
||||
};
|
||||
|
||||
provisionYaml = {
|
||||
services.grafana.provision = {
|
||||
datasources.path = ./datasources.yaml;
|
||||
dashboards.path = ./dashboards.yaml;
|
||||
alerting = {
|
||||
rules.path = ./rules.yaml;
|
||||
contactPoints.path = ./contact-points.yaml;
|
||||
policies.path = ./policies.yaml;
|
||||
templates.path = ./templates.yaml;
|
||||
muteTimings.path = ./mute-timings.yaml;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
provisionYamlDirs =
|
||||
let
|
||||
mkdir = p: pkgs.writeTextDir (baseNameOf p) (builtins.readFile p);
|
||||
in
|
||||
{
|
||||
extraNodeConfs = {
|
||||
provisionNix = {
|
||||
services.grafana.provision = {
|
||||
datasources.path = mkdir ./datasources.yaml;
|
||||
dashboards.path = mkdir ./dashboards.yaml;
|
||||
datasources.settings = {
|
||||
apiVersion = 1;
|
||||
datasources = [
|
||||
{
|
||||
name = "Test Datasource";
|
||||
type = "testdata";
|
||||
access = "proxy";
|
||||
uid = "test_datasource";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
dashboards.settings = {
|
||||
apiVersion = 1;
|
||||
providers = [
|
||||
{
|
||||
name = "default";
|
||||
options.path = "/var/lib/grafana/dashboards";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
alerting = {
|
||||
rules.path = mkdir ./rules.yaml;
|
||||
contactPoints.path = mkdir ./contact-points.yaml;
|
||||
policies.path = mkdir ./policies.yaml;
|
||||
templates.path = mkdir ./templates.yaml;
|
||||
muteTimings.path = mkdir ./mute-timings.yaml;
|
||||
rules.settings = {
|
||||
groups = [
|
||||
{
|
||||
name = "test_rule_group";
|
||||
folder = "test_folder";
|
||||
interval = "60s";
|
||||
rules = [
|
||||
{
|
||||
uid = "test_rule";
|
||||
title = "Test Rule";
|
||||
condition = "A";
|
||||
data = [
|
||||
{
|
||||
refId = "A";
|
||||
datasourceUid = "-100";
|
||||
model = {
|
||||
conditions = [
|
||||
{
|
||||
evaluator = {
|
||||
params = [ 3 ];
|
||||
type = "git";
|
||||
};
|
||||
operator.type = "and";
|
||||
query.params = [ "A" ];
|
||||
reducer.type = "last";
|
||||
type = "query";
|
||||
}
|
||||
];
|
||||
datasource = {
|
||||
type = "__expr__";
|
||||
uid = "-100";
|
||||
};
|
||||
expression = "1==0";
|
||||
intervalMs = 1000;
|
||||
maxDataPoints = 43200;
|
||||
refId = "A";
|
||||
type = "math";
|
||||
};
|
||||
}
|
||||
];
|
||||
for = "60s";
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
contactPoints.settings = {
|
||||
contactPoints = [
|
||||
{
|
||||
name = "Test Contact Point";
|
||||
receivers = [
|
||||
{
|
||||
uid = "test_contact_point";
|
||||
type = "prometheus-alertmanager";
|
||||
settings.url = "http://localhost:9000";
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
policies.settings = {
|
||||
policies = [
|
||||
{
|
||||
receiver = "Test Contact Point";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
templates.settings = {
|
||||
templates = [
|
||||
{
|
||||
name = "Test Template";
|
||||
template = "Test message";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
muteTimings.settings = {
|
||||
muteTimes = [
|
||||
{
|
||||
name = "Test Mute Timing";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
containers = builtins.mapAttrs (
|
||||
_: val:
|
||||
mkMerge [
|
||||
val
|
||||
baseGrafanaConf
|
||||
]
|
||||
) extraNodeConfs;
|
||||
in
|
||||
{
|
||||
name = "grafana-provision";
|
||||
provisionYaml = {
|
||||
services.grafana.provision = {
|
||||
datasources.path = ./datasources.yaml;
|
||||
dashboards.path = ./dashboards.yaml;
|
||||
alerting = {
|
||||
rules.path = ./rules.yaml;
|
||||
contactPoints.path = ./contact-points.yaml;
|
||||
policies.path = ./policies.yaml;
|
||||
templates.path = ./templates.yaml;
|
||||
muteTimings.path = ./mute-timings.yaml;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = [ ];
|
||||
provisionYamlDirs =
|
||||
let
|
||||
mkdir = p: pkgs.writeTextDir (baseNameOf p) (builtins.readFile p);
|
||||
in
|
||||
{
|
||||
services.grafana.provision = {
|
||||
datasources.path = mkdir ./datasources.yaml;
|
||||
dashboards.path = mkdir ./dashboards.yaml;
|
||||
alerting = {
|
||||
rules.path = mkdir ./rules.yaml;
|
||||
contactPoints.path = mkdir ./contact-points.yaml;
|
||||
policies.path = mkdir ./policies.yaml;
|
||||
templates.path = mkdir ./templates.yaml;
|
||||
muteTimings.path = mkdir ./mute-timings.yaml;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
inherit containers;
|
||||
containers = builtins.mapAttrs (
|
||||
_: val:
|
||||
mkMerge [
|
||||
val
|
||||
baseGrafanaConf
|
||||
]
|
||||
) extraNodeConfs;
|
||||
in
|
||||
{
|
||||
name = "grafana-provision";
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
meta.maintainers = [ ];
|
||||
|
||||
nodeNix = ("Nix (new format)", provisionNix)
|
||||
nodeYaml = ("Nix (YAML)", provisionYaml)
|
||||
nodeYamlDir = ("Nix (YAML in dirs)", provisionYamlDirs)
|
||||
inherit containers;
|
||||
|
||||
for description, machine in [nodeNix, nodeYaml, nodeYamlDir]:
|
||||
with subtest(f"Should start provision node: {description}"):
|
||||
machine.wait_for_unit("grafana.service")
|
||||
machine.wait_for_open_port(3000, addr="::1")
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
with subtest(f"Successful datasource provision with {description}"):
|
||||
machine.wait_until_succeeds(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]:3000/api/datasources/uid/test_datasource | grep Test\ Datasource"
|
||||
)
|
||||
nodeNix = ("Nix (new format)", provisionNix)
|
||||
nodeYaml = ("Nix (YAML)", provisionYaml)
|
||||
nodeYamlDir = ("Nix (YAML in dirs)", provisionYamlDirs)
|
||||
|
||||
with subtest(f"Successful dashboard provision with {description}"):
|
||||
machine.wait_until_succeeds(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]:3000/api/dashboards/uid/test_dashboard | grep Test\ Dashboard"
|
||||
)
|
||||
for description, machine in [nodeNix, nodeYaml, nodeYamlDir]:
|
||||
with subtest(f"Should start provision node: {description}"):
|
||||
machine.wait_for_unit("grafana.service")
|
||||
machine.wait_for_open_port(3000, addr="::1")
|
||||
|
||||
with subtest(f"Successful rule provision with {description}"):
|
||||
machine.wait_until_succeeds(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]:3000/api/v1/provisioning/alert-rules/test_rule | grep Test\ Rule"
|
||||
)
|
||||
with subtest(f"Successful datasource provision with {description}"):
|
||||
machine.wait_until_succeeds(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]:3000/api/datasources/uid/test_datasource | grep Test\ Datasource"
|
||||
)
|
||||
|
||||
with subtest(f"Successful contact point provision with {description}"):
|
||||
machine.wait_until_succeeds(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]:3000/api/v1/provisioning/contact-points | grep Test\ Contact\ Point"
|
||||
)
|
||||
with subtest(f"Successful dashboard provision with {description}"):
|
||||
machine.wait_until_succeeds(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]:3000/api/dashboards/uid/test_dashboard | grep Test\ Dashboard"
|
||||
)
|
||||
|
||||
with subtest(f"Successful policy provision with {description}"):
|
||||
machine.wait_until_succeeds(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]:3000/api/v1/provisioning/policies | grep Test\ Contact\ Point"
|
||||
)
|
||||
with subtest(f"Successful rule provision with {description}"):
|
||||
machine.wait_until_succeeds(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]:3000/api/v1/provisioning/alert-rules/test_rule | grep Test\ Rule"
|
||||
)
|
||||
|
||||
with subtest(f"Successful template provision with {description}"):
|
||||
machine.wait_until_succeeds(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]:3000/api/v1/provisioning/templates | grep Test\ Template"
|
||||
)
|
||||
with subtest(f"Successful contact point provision with {description}"):
|
||||
machine.wait_until_succeeds(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]:3000/api/v1/provisioning/contact-points | grep Test\ Contact\ Point"
|
||||
)
|
||||
|
||||
with subtest("Successful mute timings provision with {description}"):
|
||||
machine.wait_until_succeeds(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]:3000/api/v1/provisioning/mute-timings | grep Test\ Mute\ Timing"
|
||||
)
|
||||
'';
|
||||
}
|
||||
with subtest(f"Successful policy provision with {description}"):
|
||||
machine.wait_until_succeeds(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]:3000/api/v1/provisioning/policies | grep Test\ Contact\ Point"
|
||||
)
|
||||
|
||||
with subtest(f"Successful template provision with {description}"):
|
||||
machine.wait_until_succeeds(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]:3000/api/v1/provisioning/templates | grep Test\ Template"
|
||||
)
|
||||
|
||||
with subtest("Successful mute timings provision with {description}"):
|
||||
machine.wait_until_succeeds(
|
||||
"curl -sSfN -u testadmin:snakeoilpwd http://[::1]:3000/api/v1/provisioning/mute-timings | grep Test\ Mute\ Timing"
|
||||
)
|
||||
'';
|
||||
}
|
||||
)
|
||||
|
||||
@@ -32,17 +32,21 @@
|
||||
machine.succeed("curl -sSf http://localhost/")
|
||||
|
||||
machine.succeed(
|
||||
"curl --cookie-jar cookies.txt -sSf -X POST http://localhost/login -d 'username=admin&password=admin'"
|
||||
"curl -c cookies -sSf -X POST http://localhost/login -d 'username=admin&password=admin'"
|
||||
)
|
||||
|
||||
cookie = machine.succeed(
|
||||
"grep -v '^#' cookies | awk '{ print $7 }' | sed -e '/^$/d' | perl -pe 'chomp'"
|
||||
)
|
||||
|
||||
machine.succeed(
|
||||
"curl -sSf -X POST http://localhost/api/objects/tasks --cookie cookies.txt "
|
||||
f"curl -sSf -X POST http://localhost/api/objects/tasks -b 'grocy_session={cookie}' "
|
||||
+ '-d \'{"assigned_to_user_id":1,"name":"Test Task","due_date":"1970-01-01"}\'''
|
||||
+ " --header 'Content-Type: application/json'"
|
||||
)
|
||||
|
||||
task_name = machine.succeed(
|
||||
"curl -sSf http://localhost/api/tasks --cookie cookies.txt --header 'Accept: application/json' | jq '.[].name' | xargs echo | perl -pe 'chomp'"
|
||||
f"curl -sSf http://localhost/api/tasks -b 'grocy_session={cookie}' --header 'Accept: application/json' | jq '.[].name' | xargs echo | perl -pe 'chomp'"
|
||||
)
|
||||
|
||||
assert task_name == "Test Task"
|
||||
@@ -58,7 +62,7 @@
|
||||
)
|
||||
|
||||
machine.succeed(
|
||||
"curl -sSf -X 'PUT' --cookie cookies.txt "
|
||||
f"curl -sSf -X 'PUT' -b 'grocy_session={cookie}' "
|
||||
+ f" 'http://localhost/api/files/equipmentmanuals/{file_name_base64_urlencode}' "
|
||||
+ " --header 'Accept: */*' "
|
||||
+ " --header 'Content-Type: application/octet-stream' "
|
||||
@@ -66,7 +70,7 @@
|
||||
)
|
||||
|
||||
machine.succeed(
|
||||
"curl -sSf -X 'GET' --cookie cookies.txt "
|
||||
f"curl -sSf -X 'GET' -b 'grocy_session={cookie}' "
|
||||
+ f" 'http://localhost/api/files/equipmentmanuals/{file_name_base64_urlencode}' "
|
||||
+ " --header 'Accept: application/octet-stream' "
|
||||
+ f" | cmp /tmp/{file_name}"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{ handleTestOn, package }:
|
||||
{ handleTestOn, package, ... }:
|
||||
|
||||
{
|
||||
all = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./hadoop.nix {
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
name = "hickory-dns";
|
||||
|
||||
meta.maintainers = with pkgs.lib.maintainers; [ adamcstephens ];
|
||||
|
||||
containers.machine = {
|
||||
environment.systemPackages = [ pkgs.doggo ];
|
||||
|
||||
services.hickory-dns = {
|
||||
enable = true;
|
||||
settings.zones = [
|
||||
{
|
||||
zone = "example.test";
|
||||
file = pkgs.writeText "example.test.zone" ''
|
||||
$ORIGIN example.test.
|
||||
$TTL 3600
|
||||
@ IN SOA ns.example.test. hostmaster.example.test. (1 3600 600 86400 3600)
|
||||
@ IN NS ns.example.test.
|
||||
ns IN A 127.0.0.1
|
||||
www IN A 192.0.2.1
|
||||
'';
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
import json
|
||||
|
||||
machine.start()
|
||||
machine.wait_for_unit("hickory-dns.service")
|
||||
machine.wait_for_open_port(53)
|
||||
|
||||
response = json.loads(machine.succeed("doggo @127.0.0.1 www.example.test. A --json"))
|
||||
answers = response["responses"][0]["answers"]
|
||||
assert [(answer["name"], answer["type"], answer["address"]) for answer in answers] == [
|
||||
("www.example.test.", "A", "192.0.2.1")
|
||||
], response
|
||||
'';
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
{ lib, ... }:
|
||||
{
|
||||
name = "iocaine";
|
||||
meta.maintainers = with lib.maintainers; [ poz ];
|
||||
|
||||
nodes = {
|
||||
iocaine_default = {
|
||||
services.iocaine = {
|
||||
enable = true;
|
||||
};
|
||||
};
|
||||
|
||||
reverse_proxy_integration = {
|
||||
services.iocaine = {
|
||||
enable = true;
|
||||
settings.server.main = {
|
||||
bind = "/run/iocaine/iocaine.socket";
|
||||
unix-socket-access = "group";
|
||||
mode = "http";
|
||||
use = {
|
||||
handler-from = "default";
|
||||
};
|
||||
};
|
||||
settings.handler.default = { };
|
||||
};
|
||||
|
||||
services.caddy = {
|
||||
enable = true;
|
||||
globalConfig = ''
|
||||
http_port 8080
|
||||
https_port 8081
|
||||
'';
|
||||
};
|
||||
|
||||
services.nginx.enable = true;
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
iocaine_default.wait_for_unit("iocaine.service")
|
||||
iocaine_default.fail("curl -s --show-error --fail http://127.0.0.1:42069/random-path/yes/")
|
||||
iocaine_default.fail("curl -s --show-error --fail http://127.0.0.1:42069/ -A 'Googlebot'")
|
||||
iocaine_default.succeed("curl -s --show-error --fail http://127.0.0.1:42069/a/path/very/deep/into/the/forest/ -A 'Perplexity'")
|
||||
iocaine_default.fail("curl -s --show-error --fail http://127.0.0.1:42042/metrics")
|
||||
|
||||
reverse_proxy_integration.wait_for_unit("iocaine.service")
|
||||
reverse_proxy_integration.wait_for_unit("caddy.service")
|
||||
reverse_proxy_integration.wait_for_unit("nginx.service")
|
||||
reverse_proxy_integration.stop_job("nginx")
|
||||
reverse_proxy_integration.stop_job("caddy")
|
||||
reverse_proxy_integration.stop_job("iocaine")
|
||||
reverse_proxy_integration.start_job("nginx")
|
||||
reverse_proxy_integration.succeed("systemctl is-active iocaine.service")
|
||||
reverse_proxy_integration.stop_job("nginx")
|
||||
reverse_proxy_integration.stop_job("iocaine")
|
||||
reverse_proxy_integration.start_job("caddy")
|
||||
reverse_proxy_integration.succeed("systemctl is-active iocaine.service")
|
||||
'';
|
||||
}
|
||||
@@ -127,14 +127,14 @@
|
||||
|
||||
|
||||
def api_get(path):
|
||||
return f"curl --fail 'http://localhost:8096{path}' -H 'Authorization:{auth_header}'"
|
||||
return f"curl --fail 'http://localhost:8096{path}' -H 'X-Emby-Authorization:{auth_header}'"
|
||||
|
||||
|
||||
def api_post(path, json_file=None):
|
||||
if json_file:
|
||||
return f"curl --fail -X post 'http://localhost:8096{path}' -d '@{json_file}' -H Content-Type:application/json -H 'Authorization:{auth_header}'"
|
||||
return f"curl --fail -X post 'http://localhost:8096{path}' -d '@{json_file}' -H Content-Type:application/json -H 'X-Emby-Authorization:{auth_header}'"
|
||||
else:
|
||||
return f"curl --fail -X post 'http://localhost:8096{path}' -H 'Authorization:{auth_header}'"
|
||||
return f"curl --fail -X post 'http://localhost:8096{path}' -H 'X-Emby-Authorization:{auth_header}'"
|
||||
|
||||
# Test dashboard-based configuration verification
|
||||
with subtest("Dashboard configuration verification"):
|
||||
@@ -149,7 +149,7 @@
|
||||
token = auth_result["AccessToken"]
|
||||
|
||||
def api_get_with_token(path):
|
||||
return f"curl --fail 'http://localhost:8096{path}' -H 'Authorization:MediaBrowser Client=\"Test\", DeviceId=\"test\", Token={token}'"
|
||||
return f"curl --fail 'http://localhost:8096{path}' -H 'X-Emby-Authorization:MediaBrowser Client=\"Test\", DeviceId=\"test\", Token={token}'"
|
||||
|
||||
# Get encoding config and verify key settings
|
||||
config = json.loads(machineWithTranscoding.succeed(api_get_with_token("/System/Configuration/encoding")))
|
||||
@@ -279,7 +279,7 @@
|
||||
|
||||
machine.succeed(
|
||||
"ffmpeg"
|
||||
+ f" -headers 'Authorization:{auth_header}'"
|
||||
+ f" -headers 'X-Emby-Authorization:{auth_header}'"
|
||||
+ f" -i http://localhost:8096/Videos/{video}/master.m3u8?mediaSourceId={media_source_id}"
|
||||
+ " /tmp/test.mkv"
|
||||
)
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
{
|
||||
pkgs,
|
||||
runTest,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
{ pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
with pkgs.lib;
|
||||
|
||||
let
|
||||
makeKafkaTest =
|
||||
name:
|
||||
{ kafkaPackage }:
|
||||
(runTest {
|
||||
(import ../make-test-python.nix {
|
||||
inherit name;
|
||||
|
||||
nodes = {
|
||||
|
||||
@@ -1,197 +1,199 @@
|
||||
{ lib, pkgs, ... }:
|
||||
import ../make-test-python.nix (
|
||||
{ lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
inherit (lib) mkMerge;
|
||||
let
|
||||
inherit (lib) mkMerge;
|
||||
|
||||
# Generate with `kafka-storage.sh random-uuid`
|
||||
clusterId = "ii5pZE5LRkSeWrnyBhMOYQ";
|
||||
# Generate with `kafka-storage.sh random-uuid`
|
||||
clusterId = "ii5pZE5LRkSeWrnyBhMOYQ";
|
||||
|
||||
kafkaConfig = {
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
9092
|
||||
9093
|
||||
];
|
||||
kafkaConfig = {
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
9092
|
||||
9093
|
||||
];
|
||||
|
||||
virtualisation.diskSize = 1024;
|
||||
virtualisation.memorySize = 1024 * 2;
|
||||
virtualisation.diskSize = 1024;
|
||||
virtualisation.memorySize = 1024 * 2;
|
||||
|
||||
environment.systemPackages = [ pkgs.apacheKafka ];
|
||||
environment.systemPackages = [ pkgs.apacheKafka ];
|
||||
|
||||
services.apache-kafka = {
|
||||
enable = true;
|
||||
services.apache-kafka = {
|
||||
enable = true;
|
||||
|
||||
clusterId = "${clusterId}";
|
||||
clusterId = "${clusterId}";
|
||||
|
||||
formatLogDirs = true;
|
||||
formatLogDirs = true;
|
||||
|
||||
settings = {
|
||||
listeners = [
|
||||
"PLAINTEXT://:9092"
|
||||
"CONTROLLER://:9093"
|
||||
];
|
||||
"listener.security.protocol.map" = [
|
||||
"PLAINTEXT:PLAINTEXT"
|
||||
"CONTROLLER:PLAINTEXT"
|
||||
];
|
||||
"controller.quorum.voters" = lib.imap1 (i: name: "${toString i}@${name}:9093") (
|
||||
builtins.attrNames kafkaNodes
|
||||
);
|
||||
"controller.listener.names" = [ "CONTROLLER" ];
|
||||
settings = {
|
||||
listeners = [
|
||||
"PLAINTEXT://:9092"
|
||||
"CONTROLLER://:9093"
|
||||
];
|
||||
"listener.security.protocol.map" = [
|
||||
"PLAINTEXT:PLAINTEXT"
|
||||
"CONTROLLER:PLAINTEXT"
|
||||
];
|
||||
"controller.quorum.voters" = lib.imap1 (i: name: "${toString i}@${name}:9093") (
|
||||
builtins.attrNames kafkaNodes
|
||||
);
|
||||
"controller.listener.names" = [ "CONTROLLER" ];
|
||||
|
||||
"process.roles" = [
|
||||
"broker"
|
||||
"controller"
|
||||
];
|
||||
"process.roles" = [
|
||||
"broker"
|
||||
"controller"
|
||||
];
|
||||
|
||||
"log.dirs" = [ "/var/lib/apache-kafka" ];
|
||||
"num.partitions" = 6;
|
||||
"offsets.topic.replication.factor" = 2;
|
||||
"transaction.state.log.replication.factor" = 2;
|
||||
"transaction.state.log.min.isr" = 2;
|
||||
"log.dirs" = [ "/var/lib/apache-kafka" ];
|
||||
"num.partitions" = 6;
|
||||
"offsets.topic.replication.factor" = 2;
|
||||
"transaction.state.log.replication.factor" = 2;
|
||||
"transaction.state.log.min.isr" = 2;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.apache-kafka = {
|
||||
after = [ "network-online.target" ];
|
||||
requires = [ "network-online.target" ];
|
||||
serviceConfig.StateDirectory = "apache-kafka";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.apache-kafka = {
|
||||
after = [ "network-online.target" ];
|
||||
requires = [ "network-online.target" ];
|
||||
serviceConfig.StateDirectory = "apache-kafka";
|
||||
};
|
||||
};
|
||||
extraKafkaConfig = {
|
||||
kafka1 = {
|
||||
services.apache-kafka.settings = {
|
||||
"node.id" = 1;
|
||||
"broker.rack" = 1;
|
||||
};
|
||||
};
|
||||
|
||||
extraKafkaConfig = {
|
||||
kafka1 = {
|
||||
services.apache-kafka.settings = {
|
||||
"node.id" = 1;
|
||||
"broker.rack" = 1;
|
||||
kafka2 = {
|
||||
services.apache-kafka.settings = {
|
||||
"node.id" = 2;
|
||||
"broker.rack" = 2;
|
||||
};
|
||||
};
|
||||
|
||||
kafka3 = {
|
||||
services.apache-kafka.settings = {
|
||||
"node.id" = 3;
|
||||
"broker.rack" = 3;
|
||||
};
|
||||
};
|
||||
|
||||
kafka4 = {
|
||||
services.apache-kafka.settings = {
|
||||
"node.id" = 4;
|
||||
"broker.rack" = 3;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
kafka2 = {
|
||||
services.apache-kafka.settings = {
|
||||
"node.id" = 2;
|
||||
"broker.rack" = 2;
|
||||
};
|
||||
kafkaNodes = builtins.mapAttrs (
|
||||
_: val:
|
||||
mkMerge [
|
||||
val
|
||||
kafkaConfig
|
||||
]
|
||||
) extraKafkaConfig;
|
||||
in
|
||||
{
|
||||
name = "kafka-cluster";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ jpds ];
|
||||
};
|
||||
|
||||
kafka3 = {
|
||||
services.apache-kafka.settings = {
|
||||
"node.id" = 3;
|
||||
"broker.rack" = 3;
|
||||
};
|
||||
nodes = {
|
||||
inherit (kafkaNodes)
|
||||
kafka1
|
||||
kafka2
|
||||
kafka3
|
||||
kafka4
|
||||
;
|
||||
|
||||
client =
|
||||
{ config, ... }:
|
||||
{
|
||||
environment.systemPackages = [ pkgs.apacheKafka ];
|
||||
virtualisation.diskSize = 1024;
|
||||
};
|
||||
};
|
||||
|
||||
kafka4 = {
|
||||
services.apache-kafka.settings = {
|
||||
"node.id" = 4;
|
||||
"broker.rack" = 3;
|
||||
};
|
||||
};
|
||||
};
|
||||
testScript = ''
|
||||
import json
|
||||
|
||||
kafkaNodes = builtins.mapAttrs (
|
||||
_: val:
|
||||
mkMerge [
|
||||
val
|
||||
kafkaConfig
|
||||
]
|
||||
) extraKafkaConfig;
|
||||
in
|
||||
{
|
||||
name = "kafka-cluster";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ jpds ];
|
||||
};
|
||||
for machine in kafka1, kafka2, kafka3, kafka4:
|
||||
machine.wait_for_unit("apache-kafka")
|
||||
|
||||
nodes = {
|
||||
inherit (kafkaNodes)
|
||||
kafka1
|
||||
kafka2
|
||||
kafka3
|
||||
kafka4
|
||||
;
|
||||
for machine in kafka1, kafka2, kafka3, kafka4:
|
||||
machine.wait_for_open_port(9092)
|
||||
machine.wait_for_open_port(9093)
|
||||
|
||||
client =
|
||||
{ config, ... }:
|
||||
{
|
||||
environment.systemPackages = [ pkgs.apacheKafka ];
|
||||
virtualisation.diskSize = 1024;
|
||||
};
|
||||
};
|
||||
machine.wait_until_succeeds(
|
||||
"journalctl -o cat -u apache-kafka.service | grep 'Transition from STARTING to STARTED'"
|
||||
)
|
||||
|
||||
testScript = ''
|
||||
import json
|
||||
machine.wait_until_succeeds(
|
||||
"journalctl -o cat -u apache-kafka.service | grep 'Kafka Server started'"
|
||||
)
|
||||
|
||||
for machine in kafka1, kafka2, kafka3, kafka4:
|
||||
machine.wait_for_unit("apache-kafka")
|
||||
machine.wait_until_succeeds(
|
||||
"journalctl -o cat -u apache-kafka.service | grep 'BrokerLifecycleManager' | grep 'Incarnation [[:graph:]]\+ of broker [[:digit:]] in cluster ${clusterId}'"
|
||||
)
|
||||
|
||||
for machine in kafka1, kafka2, kafka3, kafka4:
|
||||
machine.wait_for_open_port(9092)
|
||||
machine.wait_for_open_port(9093)
|
||||
current_voters_json = kafka1.wait_until_succeeds(
|
||||
"kafka-metadata-quorum.sh --bootstrap-server kafka1:9092,kafka2:9092,kafka3:9092 describe --status | grep CurrentVoters"
|
||||
).replace("CurrentVoters:", "")
|
||||
|
||||
machine.wait_until_succeeds(
|
||||
"journalctl -o cat -u apache-kafka.service | grep 'Transition from STARTING to STARTED'"
|
||||
voters = json.loads(current_voters_json)
|
||||
|
||||
assert len(voters) == 4
|
||||
|
||||
kafka1.wait_until_succeeds(
|
||||
"kafka-topics.sh --bootstrap-server kafka1:9092 --create --topic test-123 --replication-factor 2"
|
||||
)
|
||||
|
||||
machine.wait_until_succeeds(
|
||||
"journalctl -o cat -u apache-kafka.service | grep 'Kafka Server started'"
|
||||
for machine in kafka1, kafka2, kafka3, kafka4:
|
||||
machine.wait_until_succeeds(
|
||||
"journalctl -o cat -u apache-kafka.service | grep -E 'Created log for partition test-123-[[:digit:]] in /var/lib/apache-kafka/test-123-[[:digit:]] with properties'"
|
||||
)
|
||||
|
||||
kafka1.wait_until_succeeds(
|
||||
"kafka-topics.sh --bootstrap-server=kafka1:9092 --describe --topic test-123 | "
|
||||
+ "grep 'PartitionCount: 6'"
|
||||
)
|
||||
|
||||
machine.wait_until_succeeds(
|
||||
"journalctl -o cat -u apache-kafka.service | grep 'BrokerLifecycleManager' | grep 'Incarnation [[:graph:]]\+ of broker [[:digit:]] in cluster ${clusterId}'"
|
||||
# Should never see a replica on both 3 and 4 as they're in the same rack
|
||||
kafka1.fail(
|
||||
"kafka-topics.sh --bootstrap-server=kafka1:9092 --describe --topic test-123 | "
|
||||
+ "grep -E 'Replicas: (3,4|4,3)'"
|
||||
)
|
||||
|
||||
current_voters_json = kafka1.wait_until_succeeds(
|
||||
"kafka-metadata-quorum.sh --bootstrap-server kafka1:9092,kafka2:9092,kafka3:9092 describe --status | grep CurrentVoters"
|
||||
).replace("CurrentVoters:", "")
|
||||
|
||||
voters = json.loads(current_voters_json)
|
||||
|
||||
assert len(voters) == 4
|
||||
|
||||
kafka1.wait_until_succeeds(
|
||||
"kafka-topics.sh --bootstrap-server kafka1:9092 --create --topic test-123 --replication-factor 2"
|
||||
)
|
||||
|
||||
for machine in kafka1, kafka2, kafka3, kafka4:
|
||||
machine.wait_until_succeeds(
|
||||
"journalctl -o cat -u apache-kafka.service | grep -E 'Created log for partition test-123-[[:digit:]] in /var/lib/apache-kafka/test-123-[[:digit:]] with properties'"
|
||||
client.succeed(
|
||||
"echo 'test 2' | "
|
||||
+ "kafka-console-producer.sh "
|
||||
+ "--bootstrap-server kafka1:9092 "
|
||||
+ "--topic test-123"
|
||||
)
|
||||
assert "test 2" in client.succeed(
|
||||
"kafka-console-consumer.sh "
|
||||
+ "--bootstrap-server kafka2:9092 --topic test-123 "
|
||||
+ "--group readtest "
|
||||
+ "--from-beginning --max-messages 1"
|
||||
)
|
||||
|
||||
kafka1.wait_until_succeeds(
|
||||
"kafka-topics.sh --bootstrap-server=kafka1:9092 --describe --topic test-123 | "
|
||||
+ "grep 'PartitionCount: 6'"
|
||||
)
|
||||
|
||||
# Should never see a replica on both 3 and 4 as they're in the same rack
|
||||
kafka1.fail(
|
||||
"kafka-topics.sh --bootstrap-server=kafka1:9092 --describe --topic test-123 | "
|
||||
+ "grep -E 'Replicas: (3,4|4,3)'"
|
||||
)
|
||||
|
||||
client.succeed(
|
||||
"echo 'test 2' | "
|
||||
+ "kafka-console-producer.sh "
|
||||
+ "--bootstrap-server kafka1:9092 "
|
||||
+ "--topic test-123"
|
||||
)
|
||||
assert "test 2" in client.succeed(
|
||||
"kafka-console-consumer.sh "
|
||||
+ "--bootstrap-server kafka2:9092 --topic test-123 "
|
||||
+ "--group readtest "
|
||||
+ "--from-beginning --max-messages 1"
|
||||
)
|
||||
|
||||
client.succeed(
|
||||
"echo 'test 3' | "
|
||||
+ "kafka-console-producer.sh "
|
||||
+ "--bootstrap-server kafka2:9092 "
|
||||
+ "--topic test-123"
|
||||
)
|
||||
assert "test 3" in client.succeed(
|
||||
"kafka-console-consumer.sh "
|
||||
+ "--bootstrap-server kafka3:9092 --topic test-123 "
|
||||
+ "--group readtest "
|
||||
+ "--max-messages 1"
|
||||
)
|
||||
'';
|
||||
}
|
||||
client.succeed(
|
||||
"echo 'test 3' | "
|
||||
+ "kafka-console-producer.sh "
|
||||
+ "--bootstrap-server kafka2:9092 "
|
||||
+ "--topic test-123"
|
||||
)
|
||||
assert "test 3" in client.succeed(
|
||||
"kafka-console-consumer.sh "
|
||||
+ "--bootstrap-server kafka3:9092 --topic test-123 "
|
||||
+ "--group readtest "
|
||||
+ "--max-messages 1"
|
||||
)
|
||||
'';
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
{
|
||||
runTest,
|
||||
pkgs,
|
||||
system ? builtins.currentSystem,
|
||||
config ? { },
|
||||
pkgs ? import ../../.. { inherit system config; },
|
||||
}:
|
||||
|
||||
{
|
||||
base = import ./base.nix {
|
||||
inherit pkgs runTest;
|
||||
inherit (pkgs) lib;
|
||||
};
|
||||
cluster = runTest ./cluster.nix;
|
||||
mirrormaker = runTest ./mirrormaker.nix;
|
||||
base = import ./base.nix { inherit system pkgs; };
|
||||
cluster = import ./cluster.nix { inherit system pkgs; };
|
||||
mirrormaker = import ./mirrormaker.nix { inherit system pkgs; };
|
||||
}
|
||||
|
||||
@@ -1,238 +1,240 @@
|
||||
{ lib, pkgs, ... }:
|
||||
import ../make-test-python.nix (
|
||||
{ lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
inherit (lib) mkMerge;
|
||||
let
|
||||
inherit (lib) mkMerge;
|
||||
|
||||
# Generate with `kafka-storage.sh random-uuid`
|
||||
clusterAId = "ihzlrasUQ9O3Yy0ZWYkd6w";
|
||||
# Generate with `kafka-storage.sh random-uuid`
|
||||
clusterAId = "ihzlrasUQ9O3Yy0ZWYkd6w";
|
||||
|
||||
clusterBId = "Bnu_zrzKRH6-7KcK7t3I5Q";
|
||||
clusterBId = "Bnu_zrzKRH6-7KcK7t3I5Q";
|
||||
|
||||
kafkaConfig = {
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
9092
|
||||
9093
|
||||
];
|
||||
kafkaConfig = {
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
9092
|
||||
9093
|
||||
];
|
||||
|
||||
virtualisation.diskSize = 1024;
|
||||
virtualisation.memorySize = 1024 * 2;
|
||||
virtualisation.diskSize = 1024;
|
||||
virtualisation.memorySize = 1024 * 2;
|
||||
|
||||
environment.systemPackages = [ pkgs.apacheKafka ];
|
||||
environment.systemPackages = [ pkgs.apacheKafka ];
|
||||
|
||||
services.apache-kafka = {
|
||||
enable = true;
|
||||
|
||||
formatLogDirs = true;
|
||||
|
||||
settings = {
|
||||
listeners = [
|
||||
"PLAINTEXT://:9092"
|
||||
"CONTROLLER://:9093"
|
||||
];
|
||||
"listener.security.protocol.map" = [
|
||||
"PLAINTEXT:PLAINTEXT"
|
||||
"CONTROLLER:PLAINTEXT"
|
||||
];
|
||||
"controller.listener.names" = [ "CONTROLLER" ];
|
||||
|
||||
"process.roles" = [
|
||||
"broker"
|
||||
"controller"
|
||||
];
|
||||
|
||||
"log.dirs" = [ "/var/lib/apache-kafka" ];
|
||||
"num.partitions" = 1;
|
||||
"offsets.topic.replication.factor" = 1;
|
||||
"transaction.state.log.replication.factor" = 1;
|
||||
"transaction.state.log.min.isr" = 1;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.apache-kafka = {
|
||||
after = [ "network-online.target" ];
|
||||
requires = [ "network-online.target" ];
|
||||
serviceConfig.StateDirectory = "apache-kafka";
|
||||
};
|
||||
};
|
||||
|
||||
extraKafkaConfig = {
|
||||
kafkaa1 = {
|
||||
services.apache-kafka = {
|
||||
clusterId = "${clusterAId}";
|
||||
enable = true;
|
||||
|
||||
formatLogDirs = true;
|
||||
|
||||
settings = {
|
||||
"node.id" = 1;
|
||||
"controller.quorum.voters" = [ "1@kafkaa1:9093" ];
|
||||
listeners = [
|
||||
"PLAINTEXT://:9092"
|
||||
"CONTROLLER://:9093"
|
||||
];
|
||||
"listener.security.protocol.map" = [
|
||||
"PLAINTEXT:PLAINTEXT"
|
||||
"CONTROLLER:PLAINTEXT"
|
||||
];
|
||||
"controller.listener.names" = [ "CONTROLLER" ];
|
||||
|
||||
"process.roles" = [
|
||||
"broker"
|
||||
"controller"
|
||||
];
|
||||
|
||||
"log.dirs" = [ "/var/lib/apache-kafka" ];
|
||||
"num.partitions" = 1;
|
||||
"offsets.topic.replication.factor" = 1;
|
||||
"transaction.state.log.replication.factor" = 1;
|
||||
"transaction.state.log.min.isr" = 1;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.apache-kafka = {
|
||||
after = [ "network-online.target" ];
|
||||
requires = [ "network-online.target" ];
|
||||
serviceConfig.StateDirectory = "apache-kafka";
|
||||
};
|
||||
};
|
||||
|
||||
kafkab1 = {
|
||||
services.apache-kafka = {
|
||||
clusterId = "${clusterBId}";
|
||||
extraKafkaConfig = {
|
||||
kafkaa1 = {
|
||||
services.apache-kafka = {
|
||||
clusterId = "${clusterAId}";
|
||||
|
||||
settings = {
|
||||
"node.id" = 1;
|
||||
"controller.quorum.voters" = [ "1@kafkab1:9093" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
kafkaNodes = builtins.mapAttrs (
|
||||
_: val:
|
||||
mkMerge [
|
||||
val
|
||||
kafkaConfig
|
||||
]
|
||||
) extraKafkaConfig;
|
||||
|
||||
mirrorMakerProperties = pkgs.writeText "mm2.properties" ''
|
||||
name = A->B
|
||||
|
||||
clusters = A, B
|
||||
|
||||
A.bootstrap.servers = kafkaa1:9092
|
||||
B.bootstrap.servers = kafkab1:9092
|
||||
|
||||
A->B.enabled = true
|
||||
A->B.topics = .*
|
||||
|
||||
B->A.enabled = false
|
||||
B->A.topics = .*
|
||||
|
||||
replication.factor=1
|
||||
replication.policy.class=org.apache.kafka.connect.mirror.IdentityReplicationPolicy
|
||||
|
||||
tasks.max = 2
|
||||
refresh.topics.enabled = true
|
||||
refresh.topics.interval.seconds = 5
|
||||
sync.topic.configs.enabled = true
|
||||
|
||||
checkpoints.topic.replication.factor=1
|
||||
heartbeats.topic.replication.factor=1
|
||||
offset-syncs.topic.replication.factor=1
|
||||
|
||||
offset.storage.replication.factor=1
|
||||
status.storage.replication.factor=1
|
||||
config.storage.replication.factor=1
|
||||
|
||||
emit.checkpoints.enabled = true
|
||||
emit.checkpoints.interval.seconds = 5
|
||||
'';
|
||||
in
|
||||
{
|
||||
name = "kafka-mirrormaker";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ jpds ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
inherit (kafkaNodes) kafkaa1 kafkab1;
|
||||
|
||||
mirrormaker =
|
||||
{ config, ... }:
|
||||
{
|
||||
virtualisation.diskSize = 1024;
|
||||
virtualisation.memorySize = 1024 * 2;
|
||||
|
||||
# Define a mirrormaker systemd service
|
||||
systemd.services.kafka-connect-mirror-maker = {
|
||||
after = [ "network-online.target" ];
|
||||
requires = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = ''
|
||||
${pkgs.apacheKafka}/bin/connect-mirror-maker.sh ${mirrorMakerProperties}
|
||||
'';
|
||||
Restart = "on-failure";
|
||||
RestartSec = "5s";
|
||||
settings = {
|
||||
"node.id" = 1;
|
||||
"controller.quorum.voters" = [ "1@kafkaa1:9093" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
import json
|
||||
kafkab1 = {
|
||||
services.apache-kafka = {
|
||||
clusterId = "${clusterBId}";
|
||||
|
||||
for machine in kafkaa1, kafkab1:
|
||||
machine.wait_for_unit("apache-kafka")
|
||||
settings = {
|
||||
"node.id" = 1;
|
||||
"controller.quorum.voters" = [ "1@kafkab1:9093" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
for machine in kafkaa1, kafkab1:
|
||||
machine.wait_for_open_port(9092)
|
||||
machine.wait_for_open_port(9093)
|
||||
kafkaNodes = builtins.mapAttrs (
|
||||
_: val:
|
||||
mkMerge [
|
||||
val
|
||||
kafkaConfig
|
||||
]
|
||||
) extraKafkaConfig;
|
||||
|
||||
machine.wait_until_succeeds(
|
||||
"journalctl -o cat -u apache-kafka.service | grep 'Transition from STARTING to STARTED'"
|
||||
mirrorMakerProperties = pkgs.writeText "mm2.properties" ''
|
||||
name = A->B
|
||||
|
||||
clusters = A, B
|
||||
|
||||
A.bootstrap.servers = kafkaa1:9092
|
||||
B.bootstrap.servers = kafkab1:9092
|
||||
|
||||
A->B.enabled = true
|
||||
A->B.topics = .*
|
||||
|
||||
B->A.enabled = false
|
||||
B->A.topics = .*
|
||||
|
||||
replication.factor=1
|
||||
replication.policy.class=org.apache.kafka.connect.mirror.IdentityReplicationPolicy
|
||||
|
||||
tasks.max = 2
|
||||
refresh.topics.enabled = true
|
||||
refresh.topics.interval.seconds = 5
|
||||
sync.topic.configs.enabled = true
|
||||
|
||||
checkpoints.topic.replication.factor=1
|
||||
heartbeats.topic.replication.factor=1
|
||||
offset-syncs.topic.replication.factor=1
|
||||
|
||||
offset.storage.replication.factor=1
|
||||
status.storage.replication.factor=1
|
||||
config.storage.replication.factor=1
|
||||
|
||||
emit.checkpoints.enabled = true
|
||||
emit.checkpoints.interval.seconds = 5
|
||||
'';
|
||||
in
|
||||
{
|
||||
name = "kafka-mirrormaker";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ jpds ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
inherit (kafkaNodes) kafkaa1 kafkab1;
|
||||
|
||||
mirrormaker =
|
||||
{ config, ... }:
|
||||
{
|
||||
virtualisation.diskSize = 1024;
|
||||
virtualisation.memorySize = 1024 * 2;
|
||||
|
||||
# Define a mirrormaker systemd service
|
||||
systemd.services.kafka-connect-mirror-maker = {
|
||||
after = [ "network-online.target" ];
|
||||
requires = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = ''
|
||||
${pkgs.apacheKafka}/bin/connect-mirror-maker.sh ${mirrorMakerProperties}
|
||||
'';
|
||||
Restart = "on-failure";
|
||||
RestartSec = "5s";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
import json
|
||||
|
||||
for machine in kafkaa1, kafkab1:
|
||||
machine.wait_for_unit("apache-kafka")
|
||||
|
||||
for machine in kafkaa1, kafkab1:
|
||||
machine.wait_for_open_port(9092)
|
||||
machine.wait_for_open_port(9093)
|
||||
|
||||
machine.wait_until_succeeds(
|
||||
"journalctl -o cat -u apache-kafka.service | grep 'Transition from STARTING to STARTED'"
|
||||
)
|
||||
|
||||
machine.wait_until_succeeds(
|
||||
"journalctl -o cat -u apache-kafka.service | grep 'Kafka Server started'"
|
||||
)
|
||||
|
||||
for machine in kafkaa1, kafkab1:
|
||||
current_voters_json = machine.wait_until_succeeds(
|
||||
f"kafka-metadata-quorum.sh --bootstrap-server {machine.name}:9092 describe --status | grep CurrentVoters"
|
||||
).replace("CurrentVoters:", "")
|
||||
|
||||
voters = json.loads(current_voters_json)
|
||||
|
||||
assert len(voters) == 1
|
||||
|
||||
mirrormaker.wait_for_unit("kafka-connect-mirror-maker")
|
||||
|
||||
mirrormaker.wait_until_succeeds(
|
||||
"journalctl -o cat -u kafka-connect-mirror-maker.service | grep 'Kafka MirrorMaker initializing'"
|
||||
)
|
||||
mirrormaker.wait_until_succeeds(
|
||||
"journalctl -o cat -u kafka-connect-mirror-maker.service | grep 'Targeting clusters \[A, B\]'"
|
||||
)
|
||||
mirrormaker.wait_until_succeeds(
|
||||
"journalctl -o cat -u kafka-connect-mirror-maker.service | grep 'INFO \[Worker clientId=A->B, groupId=A-mm2\] Finished starting connectors and tasks'"
|
||||
)
|
||||
|
||||
machine.wait_until_succeeds(
|
||||
"journalctl -o cat -u apache-kafka.service | grep 'Kafka Server started'"
|
||||
mirrormaker.wait_until_succeeds(
|
||||
"""
|
||||
journalctl -o cat -u kafka-connect-mirror-maker.service | grep 'INFO \[MirrorSourceConnector\|task-0\] \[Producer clientId=A->B\|A->B-0\|offset-syncs-source-producer\] Cluster ID: ${clusterAId}'
|
||||
"""
|
||||
)
|
||||
|
||||
for machine in kafkaa1, kafkab1:
|
||||
current_voters_json = machine.wait_until_succeeds(
|
||||
f"kafka-metadata-quorum.sh --bootstrap-server {machine.name}:9092 describe --status | grep CurrentVoters"
|
||||
).replace("CurrentVoters:", "")
|
||||
|
||||
voters = json.loads(current_voters_json)
|
||||
|
||||
assert len(voters) == 1
|
||||
|
||||
mirrormaker.wait_for_unit("kafka-connect-mirror-maker")
|
||||
|
||||
mirrormaker.wait_until_succeeds(
|
||||
"journalctl -o cat -u kafka-connect-mirror-maker.service | grep 'Kafka MirrorMaker initializing'"
|
||||
)
|
||||
mirrormaker.wait_until_succeeds(
|
||||
"journalctl -o cat -u kafka-connect-mirror-maker.service | grep 'Targeting clusters \[A, B\]'"
|
||||
)
|
||||
mirrormaker.wait_until_succeeds(
|
||||
"journalctl -o cat -u kafka-connect-mirror-maker.service | grep 'INFO \[Worker clientId=A->B, groupId=A-mm2\] Finished starting connectors and tasks'"
|
||||
)
|
||||
|
||||
mirrormaker.wait_until_succeeds(
|
||||
"""
|
||||
journalctl -o cat -u kafka-connect-mirror-maker.service | grep 'INFO \[MirrorSourceConnector\|task-0\] \[Producer clientId=A->B\|A->B-0\|offset-syncs-source-producer\] Cluster ID: ${clusterAId}'
|
||||
"""
|
||||
)
|
||||
|
||||
kafkaa1.wait_until_succeeds(
|
||||
"journalctl -o cat -u apache-kafka.service | grep 'Stabilized group B-mm2'"
|
||||
)
|
||||
|
||||
kafkab1.wait_until_succeeds(
|
||||
"journalctl -o cat -u apache-kafka.service | grep 'Stabilized group A-mm2'"
|
||||
)
|
||||
|
||||
kafkaa1.wait_until_succeeds(
|
||||
"kafka-topics.sh --bootstrap-server localhost:9092 --create --topic test-mm-1 --partitions 1 --replication-factor 1"
|
||||
)
|
||||
|
||||
for machine in kafkaa1, kafkab1:
|
||||
machine.succeed(
|
||||
"kafka-topics.sh --bootstrap-server localhost:9092 --list | grep 'test-mm-1'"
|
||||
kafkaa1.wait_until_succeeds(
|
||||
"journalctl -o cat -u apache-kafka.service | grep 'Stabilized group B-mm2'"
|
||||
)
|
||||
|
||||
mirrormaker.wait_until_succeeds(
|
||||
"journalctl -o cat -u kafka-connect-mirror-maker.service | grep 'replicating [[:digit:]]\+ topic-partitions A->B: \[test-mm-1-0\]'"
|
||||
)
|
||||
kafkab1.wait_until_succeeds(
|
||||
"journalctl -o cat -u apache-kafka.service | grep 'Stabilized group A-mm2'"
|
||||
)
|
||||
|
||||
mirrormaker.wait_until_succeeds(
|
||||
"journalctl -o cat -u kafka-connect-mirror-maker.service | grep 'Found [[:digit:]]\+ new topic-partitions on A'"
|
||||
)
|
||||
kafkaa1.wait_until_succeeds(
|
||||
"kafka-topics.sh --bootstrap-server localhost:9092 --create --topic test-mm-1 --partitions 1 --replication-factor 1"
|
||||
)
|
||||
|
||||
kafkaa1.wait_until_succeeds(
|
||||
"kafka-verifiable-producer.sh --bootstrap-server kafkaa1:9092 --throughput 10 --max-messages 100 --topic test-mm-1"
|
||||
)
|
||||
for machine in kafkaa1, kafkab1:
|
||||
machine.succeed(
|
||||
"kafka-topics.sh --bootstrap-server localhost:9092 --list | grep 'test-mm-1'"
|
||||
)
|
||||
|
||||
mirrormaker.wait_until_succeeds(
|
||||
"journalctl -o cat -u kafka-connect-mirror-maker.service | grep 'Committing offsets for [[:digit:]]\+ acknowledged messages'"
|
||||
)
|
||||
mirrormaker.wait_until_succeeds(
|
||||
"journalctl -o cat -u kafka-connect-mirror-maker.service | grep 'replicating [[:digit:]]\+ topic-partitions A->B: \[test-mm-1-0\]'"
|
||||
)
|
||||
|
||||
kafkab1.wait_until_succeeds(
|
||||
"kafka-verifiable-consumer.sh --bootstrap-server kafkab1:9092 --topic test-mm-1 --group-id testreplication --max-messages 100"
|
||||
)
|
||||
'';
|
||||
}
|
||||
mirrormaker.wait_until_succeeds(
|
||||
"journalctl -o cat -u kafka-connect-mirror-maker.service | grep 'Found [[:digit:]]\+ new topic-partitions on A'"
|
||||
)
|
||||
|
||||
kafkaa1.wait_until_succeeds(
|
||||
"kafka-verifiable-producer.sh --bootstrap-server kafkaa1:9092 --throughput 10 --max-messages 100 --topic test-mm-1"
|
||||
)
|
||||
|
||||
mirrormaker.wait_until_succeeds(
|
||||
"journalctl -o cat -u kafka-connect-mirror-maker.service | grep 'Committing offsets for [[:digit:]]\+ acknowledged messages'"
|
||||
)
|
||||
|
||||
kafkab1.wait_until_succeeds(
|
||||
"kafka-verifiable-consumer.sh --bootstrap-server kafkab1:9092 --topic test-mm-1 --group-id testreplication --max-messages 100"
|
||||
)
|
||||
'';
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
pkgs,
|
||||
runTest,
|
||||
system ? builtins.currentSystem,
|
||||
pkgs ? import ../../.. { inherit system; },
|
||||
}:
|
||||
{
|
||||
mit = runTest ./mit.nix;
|
||||
heimdal = runTest ./heimdal.nix;
|
||||
ldap = import ./ldap { inherit pkgs runTest; };
|
||||
mit = import ./mit.nix { inherit system pkgs; };
|
||||
heimdal = import ./heimdal.nix { inherit system pkgs; };
|
||||
ldap = import ./ldap { inherit system pkgs; };
|
||||
}
|
||||
|
||||
@@ -1,257 +1,259 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
name = "kerberos_server-heimdal";
|
||||
import ../make-test-python.nix (
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
name = "kerberos_server-heimdal";
|
||||
|
||||
nodes = {
|
||||
server =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [ ../common/user-account.nix ];
|
||||
nodes = {
|
||||
server =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [ ../common/user-account.nix ];
|
||||
|
||||
users.users.alice.extraGroups = [ "wheel" ];
|
||||
users.users.alice.extraGroups = [ "wheel" ];
|
||||
|
||||
services.getty.autologinUser = "alice";
|
||||
services.getty.autologinUser = "alice";
|
||||
|
||||
virtualisation.vlans = [ 1 ];
|
||||
virtualisation.vlans = [ 1 ];
|
||||
|
||||
time.timeZone = "Etc/UTC";
|
||||
time.timeZone = "Etc/UTC";
|
||||
|
||||
networking = {
|
||||
domain = "foo.bar";
|
||||
useDHCP = false;
|
||||
firewall.enable = false;
|
||||
hosts."10.0.0.1" = [ "server.foo.bar" ];
|
||||
hosts."10.0.0.2" = [ "client.foo.bar" ];
|
||||
};
|
||||
networking = {
|
||||
domain = "foo.bar";
|
||||
useDHCP = false;
|
||||
firewall.enable = false;
|
||||
hosts."10.0.0.1" = [ "server.foo.bar" ];
|
||||
hosts."10.0.0.2" = [ "client.foo.bar" ];
|
||||
};
|
||||
|
||||
systemd.network.networks."01-eth1" = {
|
||||
name = "eth1";
|
||||
networkConfig.Address = "10.0.0.1/24";
|
||||
};
|
||||
systemd.network.networks."01-eth1" = {
|
||||
name = "eth1";
|
||||
networkConfig.Address = "10.0.0.1/24";
|
||||
};
|
||||
|
||||
security.krb5 = {
|
||||
enable = true;
|
||||
package = pkgs.heimdal;
|
||||
settings = {
|
||||
libdefaults.default_realm = "FOO.BAR";
|
||||
security.krb5 = {
|
||||
enable = true;
|
||||
package = pkgs.heimdal;
|
||||
settings = {
|
||||
libdefaults.default_realm = "FOO.BAR";
|
||||
|
||||
# Enable extra debug output
|
||||
logging = {
|
||||
admin_server = "SYSLOG:DEBUG:AUTH";
|
||||
default = "SYSLOG:DEBUG:AUTH";
|
||||
kdc = "SYSLOG:DEBUG:AUTH";
|
||||
# Enable extra debug output
|
||||
logging = {
|
||||
admin_server = "SYSLOG:DEBUG:AUTH";
|
||||
default = "SYSLOG:DEBUG:AUTH";
|
||||
kdc = "SYSLOG:DEBUG:AUTH";
|
||||
};
|
||||
|
||||
realms = {
|
||||
"FOO.BAR" = {
|
||||
admin_server = "server.foo.bar";
|
||||
kpasswd_server = "server.foo.bar";
|
||||
kdc = [ "server.foo.bar" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
realms = {
|
||||
services.kerberos_server = {
|
||||
enable = true;
|
||||
settings.realms = {
|
||||
"FOO.BAR" = {
|
||||
admin_server = "server.foo.bar";
|
||||
kpasswd_server = "server.foo.bar";
|
||||
kdc = [ "server.foo.bar" ];
|
||||
acl = [
|
||||
{
|
||||
principal = "kadmin/admin@FOO.BAR";
|
||||
access = "all";
|
||||
}
|
||||
{
|
||||
principal = "alice/admin@FOO.BAR";
|
||||
access = [
|
||||
"add"
|
||||
"cpw"
|
||||
"delete"
|
||||
"get"
|
||||
"list"
|
||||
"modify"
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.kerberos_server = {
|
||||
enable = true;
|
||||
settings.realms = {
|
||||
"FOO.BAR" = {
|
||||
acl = [
|
||||
{
|
||||
principal = "kadmin/admin@FOO.BAR";
|
||||
access = "all";
|
||||
}
|
||||
{
|
||||
principal = "alice/admin@FOO.BAR";
|
||||
access = [
|
||||
"add"
|
||||
"cpw"
|
||||
"delete"
|
||||
"get"
|
||||
"list"
|
||||
"modify"
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
client =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [ ../common/user-account.nix ];
|
||||
|
||||
users.users.alice.extraGroups = [ "wheel" ];
|
||||
|
||||
services.getty.autologinUser = "alice";
|
||||
|
||||
virtualisation.vlans = [ 1 ];
|
||||
|
||||
time.timeZone = "Etc/UTC";
|
||||
|
||||
networking = {
|
||||
domain = "foo.bar";
|
||||
useDHCP = false;
|
||||
hosts."10.0.0.1" = [ "server.foo.bar" ];
|
||||
hosts."10.0.0.2" = [ "client.foo.bar" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
client =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [ ../common/user-account.nix ];
|
||||
systemd.network.networks."01-eth1" = {
|
||||
name = "eth1";
|
||||
networkConfig.Address = "10.0.0.2/24";
|
||||
};
|
||||
|
||||
users.users.alice.extraGroups = [ "wheel" ];
|
||||
security.krb5 = {
|
||||
enable = true;
|
||||
package = pkgs.heimdal;
|
||||
settings = {
|
||||
libdefaults.default_realm = "FOO.BAR";
|
||||
|
||||
services.getty.autologinUser = "alice";
|
||||
logging = {
|
||||
admin_server = "SYSLOG:DEBUG:AUTH";
|
||||
default = "SYSLOG:DEBUG:AUTH";
|
||||
kdc = "SYSLOG:DEBUG:AUTH";
|
||||
};
|
||||
|
||||
virtualisation.vlans = [ 1 ];
|
||||
|
||||
time.timeZone = "Etc/UTC";
|
||||
|
||||
networking = {
|
||||
domain = "foo.bar";
|
||||
useDHCP = false;
|
||||
hosts."10.0.0.1" = [ "server.foo.bar" ];
|
||||
hosts."10.0.0.2" = [ "client.foo.bar" ];
|
||||
};
|
||||
|
||||
systemd.network.networks."01-eth1" = {
|
||||
name = "eth1";
|
||||
networkConfig.Address = "10.0.0.2/24";
|
||||
};
|
||||
|
||||
security.krb5 = {
|
||||
enable = true;
|
||||
package = pkgs.heimdal;
|
||||
settings = {
|
||||
libdefaults.default_realm = "FOO.BAR";
|
||||
|
||||
logging = {
|
||||
admin_server = "SYSLOG:DEBUG:AUTH";
|
||||
default = "SYSLOG:DEBUG:AUTH";
|
||||
kdc = "SYSLOG:DEBUG:AUTH";
|
||||
};
|
||||
|
||||
realms = {
|
||||
"FOO.BAR" = {
|
||||
admin_server = "server.foo.bar";
|
||||
kpasswd_server = "server.foo.bar";
|
||||
kdc = [ "server.foo.bar" ];
|
||||
realms = {
|
||||
"FOO.BAR" = {
|
||||
admin_server = "server.foo.bar";
|
||||
kpasswd_server = "server.foo.bar";
|
||||
kdc = [ "server.foo.bar" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
''
|
||||
import string
|
||||
import random
|
||||
random.seed(0)
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
''
|
||||
import string
|
||||
import random
|
||||
random.seed(0)
|
||||
|
||||
start_all()
|
||||
start_all()
|
||||
|
||||
with subtest("Server: initialize realm"):
|
||||
# for unit in ["kadmind.service", "kdc.socket", "kpasswdd.socket"]:
|
||||
for unit in ["kadmind.service", "kdc.service", "kpasswdd.service"]:
|
||||
server.wait_for_unit(unit)
|
||||
with subtest("Server: initialize realm"):
|
||||
# for unit in ["kadmind.service", "kdc.socket", "kpasswdd.socket"]:
|
||||
for unit in ["kadmind.service", "kdc.service", "kpasswdd.service"]:
|
||||
server.wait_for_unit(unit)
|
||||
|
||||
server.succeed("kadmin -l init --realm-max-ticket-life='8 day' --realm-max-renewable-life='10 day' FOO.BAR")
|
||||
server.succeed("kadmin -l init --realm-max-ticket-life='8 day' --realm-max-renewable-life='10 day' FOO.BAR")
|
||||
|
||||
for unit in ["kadmind.service", "kdc.service", "kpasswdd.service"]:
|
||||
server.systemctl(f"restart {unit}")
|
||||
for unit in ["kadmind.service", "kdc.service", "kpasswdd.service"]:
|
||||
server.systemctl(f"restart {unit}")
|
||||
|
||||
alice_krb_pw = "alice_hunter2"
|
||||
alice_old_krb_pw = ""
|
||||
alice_krb_admin_pw = "alice_admin_hunter2"
|
||||
alice_krb_pw = "alice_hunter2"
|
||||
alice_old_krb_pw = ""
|
||||
alice_krb_admin_pw = "alice_admin_hunter2"
|
||||
|
||||
def random_password():
|
||||
password_chars = string.ascii_letters + string.digits + string.punctuation.replace('"', "")
|
||||
return "".join(random.choice(password_chars) for _ in range(16))
|
||||
def random_password():
|
||||
password_chars = string.ascii_letters + string.digits + string.punctuation.replace('"', "")
|
||||
return "".join(random.choice(password_chars) for _ in range(16))
|
||||
|
||||
with subtest("Server: initialize user principals and keytabs"):
|
||||
server.succeed(f'kadmin -l add --password="{alice_krb_admin_pw}" --use-defaults alice/admin')
|
||||
server.succeed("kadmin -l ext_keytab --keytab=admin.keytab alice/admin")
|
||||
with subtest("Server: initialize user principals and keytabs"):
|
||||
server.succeed(f'kadmin -l add --password="{alice_krb_admin_pw}" --use-defaults alice/admin')
|
||||
server.succeed("kadmin -l ext_keytab --keytab=admin.keytab alice/admin")
|
||||
|
||||
server.succeed(f'kadmin -p alice/admin -K admin.keytab add --password="{alice_krb_pw}" --use-defaults alice')
|
||||
server.succeed("kadmin -l ext_keytab --keytab=alice.keytab alice")
|
||||
server.succeed(f'kadmin -p alice/admin -K admin.keytab add --password="{alice_krb_pw}" --use-defaults alice')
|
||||
server.succeed("kadmin -l ext_keytab --keytab=alice.keytab alice")
|
||||
|
||||
server.wait_for_unit("getty@tty1.service")
|
||||
server.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
|
||||
server.wait_for_unit("default.target")
|
||||
server.wait_for_unit("getty@tty1.service")
|
||||
server.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
|
||||
server.wait_for_unit("default.target")
|
||||
|
||||
with subtest("Server: initialize host principal with keytab"):
|
||||
server.send_chars("sudo ktutil get -p alice/admin host/server.foo.bar\n")
|
||||
server.wait_until_tty_matches("1", "password for alice:")
|
||||
server.send_chars("${nodes.server.users.users.alice.password}\n")
|
||||
server.wait_until_tty_matches("1", "alice/admin@FOO.BAR's Password:")
|
||||
server.send_chars(f'{alice_krb_admin_pw}\n')
|
||||
server.wait_for_file("/etc/krb5.keytab")
|
||||
with subtest("Server: initialize host principal with keytab"):
|
||||
server.send_chars("sudo ktutil get -p alice/admin host/server.foo.bar\n")
|
||||
server.wait_until_tty_matches("1", "password for alice:")
|
||||
server.send_chars("${nodes.server.users.users.alice.password}\n")
|
||||
server.wait_until_tty_matches("1", "alice/admin@FOO.BAR's Password:")
|
||||
server.send_chars(f'{alice_krb_admin_pw}\n')
|
||||
server.wait_for_file("/etc/krb5.keytab")
|
||||
|
||||
ktutil_list = server.succeed("sudo ktutil list")
|
||||
if not "host/server.foo.bar" in ktutil_list:
|
||||
exit(1)
|
||||
ktutil_list = server.succeed("sudo ktutil list")
|
||||
if not "host/server.foo.bar" in ktutil_list:
|
||||
exit(1)
|
||||
|
||||
server.send_chars("clear\n")
|
||||
server.send_chars("clear\n")
|
||||
|
||||
client.systemctl("start network-online.target")
|
||||
client.wait_for_unit("network-online.target")
|
||||
client.wait_for_unit("getty@tty1.service")
|
||||
client.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
|
||||
client.wait_for_unit("default.target")
|
||||
client.systemctl("start network-online.target")
|
||||
client.wait_for_unit("network-online.target")
|
||||
client.wait_for_unit("getty@tty1.service")
|
||||
client.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
|
||||
client.wait_for_unit("default.target")
|
||||
|
||||
with subtest("Client: initialize host principal with keytab"):
|
||||
client.succeed(
|
||||
f'echo "{alice_krb_admin_pw}" > pw.txt',
|
||||
"kinit -p --password-file=pw.txt alice/admin",
|
||||
)
|
||||
with subtest("Client: initialize host principal with keytab"):
|
||||
client.succeed(
|
||||
f'echo "{alice_krb_admin_pw}" > pw.txt',
|
||||
"kinit -p --password-file=pw.txt alice/admin",
|
||||
)
|
||||
|
||||
client.send_chars("sudo ktutil get -p alice/admin host/client.foo.bar\n")
|
||||
client.wait_until_tty_matches("1", "password for alice:")
|
||||
client.send_chars("${nodes.client.users.users.alice.password}\n")
|
||||
client.wait_until_tty_matches("1", "alice/admin@FOO.BAR's Password:")
|
||||
client.send_chars(f"{alice_krb_admin_pw}\n")
|
||||
client.wait_for_file("/etc/krb5.keytab")
|
||||
client.send_chars("sudo ktutil get -p alice/admin host/client.foo.bar\n")
|
||||
client.wait_until_tty_matches("1", "password for alice:")
|
||||
client.send_chars("${nodes.client.users.users.alice.password}\n")
|
||||
client.wait_until_tty_matches("1", "alice/admin@FOO.BAR's Password:")
|
||||
client.send_chars(f"{alice_krb_admin_pw}\n")
|
||||
client.wait_for_file("/etc/krb5.keytab")
|
||||
|
||||
ktutil_list = client.succeed("sudo ktutil list")
|
||||
if not "host/client.foo.bar" in ktutil_list:
|
||||
exit(1)
|
||||
ktutil_list = client.succeed("sudo ktutil list")
|
||||
if not "host/client.foo.bar" in ktutil_list:
|
||||
exit(1)
|
||||
|
||||
client.send_chars("clear\n")
|
||||
client.send_chars("clear\n")
|
||||
|
||||
with subtest("Client: kinit alice"):
|
||||
client.succeed(
|
||||
f"echo '{alice_krb_pw}' > pw.txt",
|
||||
"kinit -p --password-file=pw.txt alice",
|
||||
)
|
||||
tickets = client.succeed("klist")
|
||||
assert "Principal: alice@FOO.BAR" in tickets
|
||||
client.send_chars("clear\n")
|
||||
with subtest("Client: kinit alice"):
|
||||
client.succeed(
|
||||
f"echo '{alice_krb_pw}' > pw.txt",
|
||||
"kinit -p --password-file=pw.txt alice",
|
||||
)
|
||||
tickets = client.succeed("klist")
|
||||
assert "Principal: alice@FOO.BAR" in tickets
|
||||
client.send_chars("clear\n")
|
||||
|
||||
with subtest("Client: kpasswd alice"):
|
||||
alice_old_krb_pw = alice_krb_pw
|
||||
alice_krb_pw = random_password()
|
||||
client.send_chars("kpasswd\n")
|
||||
client.wait_until_tty_matches("1", "alice@FOO.BAR's Password:")
|
||||
client.send_chars(f"{alice_old_krb_pw}\n", 0.1)
|
||||
client.wait_until_tty_matches("1", "New password:")
|
||||
client.send_chars(f"{alice_krb_pw}\n", 0.1)
|
||||
client.wait_until_tty_matches("1", "Verify password - New password:")
|
||||
client.send_chars(f"{alice_krb_pw}\n", 0.1)
|
||||
with subtest("Client: kpasswd alice"):
|
||||
alice_old_krb_pw = alice_krb_pw
|
||||
alice_krb_pw = random_password()
|
||||
client.send_chars("kpasswd\n")
|
||||
client.wait_until_tty_matches("1", "alice@FOO.BAR's Password:")
|
||||
client.send_chars(f"{alice_old_krb_pw}\n", 0.1)
|
||||
client.wait_until_tty_matches("1", "New password:")
|
||||
client.send_chars(f"{alice_krb_pw}\n", 0.1)
|
||||
client.wait_until_tty_matches("1", "Verify password - New password:")
|
||||
client.send_chars(f"{alice_krb_pw}\n", 0.1)
|
||||
|
||||
client.wait_until_tty_matches("1", "Success : Password changed")
|
||||
client.wait_until_tty_matches("1", "Success : Password changed")
|
||||
|
||||
client.send_chars("clear\n")
|
||||
client.send_chars("clear\n")
|
||||
|
||||
with subtest("Server: kinit alice"):
|
||||
server.succeed(
|
||||
"echo 'alice_pw_2' > pw.txt"
|
||||
"kinit -p --password-file=pw.txt alice",
|
||||
)
|
||||
tickets = client.succeed("klist")
|
||||
assert "Principal: alice@FOO.BAR" in tickets
|
||||
server.send_chars("clear\n")
|
||||
with subtest("Server: kinit alice"):
|
||||
server.succeed(
|
||||
"echo 'alice_pw_2' > pw.txt"
|
||||
"kinit -p --password-file=pw.txt alice",
|
||||
)
|
||||
tickets = client.succeed("klist")
|
||||
assert "Principal: alice@FOO.BAR" in tickets
|
||||
server.send_chars("clear\n")
|
||||
|
||||
with subtest("Server: kpasswd alice"):
|
||||
alice_old_krb_pw = alice_krb_pw
|
||||
alice_krb_pw = random_password()
|
||||
server.send_chars("kpasswd\n")
|
||||
server.wait_until_tty_matches("1", "alice@FOO.BAR's Password:")
|
||||
server.send_chars(f"{alice_old_krb_pw}\n", 0.1)
|
||||
server.wait_until_tty_matches("1", "New password:")
|
||||
server.send_chars(f"{alice_krb_pw}\n", 0.1)
|
||||
server.wait_until_tty_matches("1", "Verify password - New password:")
|
||||
server.send_chars(f"{alice_krb_pw}\n", 0.1)
|
||||
with subtest("Server: kpasswd alice"):
|
||||
alice_old_krb_pw = alice_krb_pw
|
||||
alice_krb_pw = random_password()
|
||||
server.send_chars("kpasswd\n")
|
||||
server.wait_until_tty_matches("1", "alice@FOO.BAR's Password:")
|
||||
server.send_chars(f"{alice_old_krb_pw}\n", 0.1)
|
||||
server.wait_until_tty_matches("1", "New password:")
|
||||
server.send_chars(f"{alice_krb_pw}\n", 0.1)
|
||||
server.wait_until_tty_matches("1", "Verify password - New password:")
|
||||
server.send_chars(f"{alice_krb_pw}\n", 0.1)
|
||||
|
||||
server.wait_until_tty_matches("1", "Success : Password changed")
|
||||
server.wait_until_tty_matches("1", "Success : Password changed")
|
||||
|
||||
server.send_chars("clear\n")
|
||||
'';
|
||||
server.send_chars("clear\n")
|
||||
'';
|
||||
|
||||
meta.maintainers = pkgs.heimdal.meta.maintainers;
|
||||
}
|
||||
meta.maintainers = pkgs.heimdal.meta.maintainers;
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
pkgs,
|
||||
runTest,
|
||||
system ? builtins.currentSystem,
|
||||
pkgs ? import ../../../.. { inherit system; },
|
||||
}:
|
||||
{
|
||||
mit = runTest ./mit.nix;
|
||||
mit = import ./mit.nix { inherit system pkgs; };
|
||||
}
|
||||
|
||||
@@ -1,190 +1,192 @@
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
DITRoot = "dc=example,dc=com";
|
||||
realm = "EXAMPLE.COM";
|
||||
import ../../make-test-python.nix (
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
DITRoot = "dc=example,dc=com";
|
||||
realm = "EXAMPLE.COM";
|
||||
|
||||
krb5Package = pkgs.krb5.override { withLdap = true; };
|
||||
krb5Package = pkgs.krb5.override { withLdap = true; };
|
||||
|
||||
# Password used by Kerberos services to bind to their identities
|
||||
krbSrvPwd = "kerberos_service_password";
|
||||
# Stash file read by Kerberos daemons containing the service password
|
||||
# DO NOT DO THIS IN PRODUCTION! The stash file is a fundamental secret!
|
||||
krbPwdStash = pkgs.runCommand "krb-pwd-stash" { } ''
|
||||
for srv in cn=kadmin,${DITRoot} cn=kdc,${DITRoot}
|
||||
do
|
||||
echo -e "${krbSrvPwd}\n${krbSrvPwd}" | \
|
||||
${krb5Package}/bin/kdb5_ldap_util -r ${realm} stashsrvpw -f $out $srv 2>&1 > /dev/null
|
||||
done
|
||||
'';
|
||||
# Password used by Kerberos services to bind to their identities
|
||||
krbSrvPwd = "kerberos_service_password";
|
||||
# Stash file read by Kerberos daemons containing the service password
|
||||
# DO NOT DO THIS IN PRODUCTION! The stash file is a fundamental secret!
|
||||
krbPwdStash = pkgs.runCommand "krb-pwd-stash" { } ''
|
||||
for srv in cn=kadmin,${DITRoot} cn=kdc,${DITRoot}
|
||||
do
|
||||
echo -e "${krbSrvPwd}\n${krbSrvPwd}" | \
|
||||
${krb5Package}/bin/kdb5_ldap_util -r ${realm} stashsrvpw -f $out $srv 2>&1 > /dev/null
|
||||
done
|
||||
'';
|
||||
|
||||
# The LDAP schema for Kerberos 5 objects is part of the source distribution of Kerberos 5
|
||||
krbLdapSchema = pkgs.runCommand "krb-ldap-schema" { } ''
|
||||
tar -Oxf ${krb5Package.src} \
|
||||
${krb5Package.sourceRoot}/plugins/kdb/ldap/libkdb_ldap/kerberos.openldap.ldif > $out
|
||||
'';
|
||||
# The LDAP schema for Kerberos 5 objects is part of the source distribution of Kerberos 5
|
||||
krbLdapSchema = pkgs.runCommand "krb-ldap-schema" { } ''
|
||||
tar -Oxf ${krb5Package.src} \
|
||||
${krb5Package.sourceRoot}/plugins/kdb/ldap/libkdb_ldap/kerberos.openldap.ldif > $out
|
||||
'';
|
||||
|
||||
# Initial LDAP tree containing only the Kerberos services
|
||||
ldapDIT = ''
|
||||
dn: ${DITRoot}
|
||||
objectClass: organization
|
||||
objectClass: dcObject
|
||||
dc: example
|
||||
o: Example Company
|
||||
# Initial LDAP tree containing only the Kerberos services
|
||||
ldapDIT = ''
|
||||
dn: ${DITRoot}
|
||||
objectClass: organization
|
||||
objectClass: dcObject
|
||||
dc: example
|
||||
o: Example Company
|
||||
|
||||
dn: cn=kdc,${DITRoot}
|
||||
objectClass: krbKdcService
|
||||
objectClass: simpleSecurityObject
|
||||
cn: kdc
|
||||
userPassword: ${krbSrvPwd}
|
||||
dn: cn=kdc,${DITRoot}
|
||||
objectClass: krbKdcService
|
||||
objectClass: simpleSecurityObject
|
||||
cn: kdc
|
||||
userPassword: ${krbSrvPwd}
|
||||
|
||||
dn: cn=kadmin,${DITRoot}
|
||||
objectClass: krbAdmService
|
||||
objectClass: simpleSecurityObject
|
||||
cn: kadmin
|
||||
userPassword: ${krbSrvPwd}
|
||||
'';
|
||||
dn: cn=kadmin,${DITRoot}
|
||||
objectClass: krbAdmService
|
||||
objectClass: simpleSecurityObject
|
||||
cn: kadmin
|
||||
userPassword: ${krbSrvPwd}
|
||||
'';
|
||||
|
||||
rootDnPwd = "ldap_root_password";
|
||||
in
|
||||
{
|
||||
name = "kerberos_server-mit-ldap";
|
||||
rootDnPwd = "ldap_root_password";
|
||||
in
|
||||
{
|
||||
name = "kerberos_server-mit-ldap";
|
||||
|
||||
nodes.machine =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
nodes.machine =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
|
||||
services.openldap = {
|
||||
enable = true;
|
||||
urlList = [
|
||||
"ldapi:///"
|
||||
"ldap://"
|
||||
];
|
||||
declarativeContents."${DITRoot}" = ldapDIT;
|
||||
settings = {
|
||||
children = {
|
||||
"cn=schema".includes = [
|
||||
"${pkgs.openldap}/etc/schema/core.ldif"
|
||||
"${pkgs.openldap}/etc/schema/cosine.ldif"
|
||||
"${pkgs.openldap}/etc/schema/inetorgperson.ldif"
|
||||
"${pkgs.openldap}/etc/schema/nis.ldif"
|
||||
"${krbLdapSchema}"
|
||||
];
|
||||
"olcDatabase={0}config" = {
|
||||
attrs = {
|
||||
objectClass = [ "olcDatabaseConfig" ];
|
||||
olcDatabase = "{0}config";
|
||||
};
|
||||
};
|
||||
"olcDatabase={1}mdb" = {
|
||||
attrs = {
|
||||
objectClass = [
|
||||
"olcDatabaseConfig"
|
||||
"olcMdbConfig"
|
||||
];
|
||||
olcDatabase = "{1}mdb";
|
||||
olcDbDirectory = "/var/lib/openldap/db";
|
||||
olcSuffix = DITRoot;
|
||||
olcRootDN = "cn=root,${DITRoot}";
|
||||
olcRootPW = rootDnPwd;
|
||||
# A tiny but realistic ACL
|
||||
olcAccess = [
|
||||
''
|
||||
to attrs=userPassword
|
||||
by anonymous auth
|
||||
by * none''
|
||||
''
|
||||
to dn.subtree="cn=${realm},cn=realms,${DITRoot}"
|
||||
by dn.exact="cn=kdc,${DITRoot}" write
|
||||
by dn.exact="cn=kadmin,${DITRoot}" write
|
||||
by * none''
|
||||
''
|
||||
to *
|
||||
by * read''
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.kerberos_server = {
|
||||
enable = true;
|
||||
settings = {
|
||||
libdefaults.default_realm = realm;
|
||||
realms = {
|
||||
"${realm}" = {
|
||||
acl = [
|
||||
{
|
||||
principal = "admin";
|
||||
access = "all";
|
||||
}
|
||||
services.openldap = {
|
||||
enable = true;
|
||||
urlList = [
|
||||
"ldapi:///"
|
||||
"ldap://"
|
||||
];
|
||||
declarativeContents."${DITRoot}" = ldapDIT;
|
||||
settings = {
|
||||
children = {
|
||||
"cn=schema".includes = [
|
||||
"${pkgs.openldap}/etc/schema/core.ldif"
|
||||
"${pkgs.openldap}/etc/schema/cosine.ldif"
|
||||
"${pkgs.openldap}/etc/schema/inetorgperson.ldif"
|
||||
"${pkgs.openldap}/etc/schema/nis.ldif"
|
||||
"${krbLdapSchema}"
|
||||
];
|
||||
};
|
||||
};
|
||||
dbmodules = {
|
||||
"${realm}" = {
|
||||
db_library = "kldap";
|
||||
ldap_kerberos_container_dn = "cn=realms,${DITRoot}";
|
||||
ldap_kdc_dn = "cn=kdc,${DITRoot}";
|
||||
ldap_kadmind_dn = "cn=kadmin,${DITRoot}";
|
||||
ldap_service_password_file = toString krbPwdStash;
|
||||
ldap_servers = "ldapi:///";
|
||||
"olcDatabase={0}config" = {
|
||||
attrs = {
|
||||
objectClass = [ "olcDatabaseConfig" ];
|
||||
olcDatabase = "{0}config";
|
||||
};
|
||||
};
|
||||
"olcDatabase={1}mdb" = {
|
||||
attrs = {
|
||||
objectClass = [
|
||||
"olcDatabaseConfig"
|
||||
"olcMdbConfig"
|
||||
];
|
||||
olcDatabase = "{1}mdb";
|
||||
olcDbDirectory = "/var/lib/openldap/db";
|
||||
olcSuffix = DITRoot;
|
||||
olcRootDN = "cn=root,${DITRoot}";
|
||||
olcRootPW = rootDnPwd;
|
||||
# A tiny but realistic ACL
|
||||
olcAccess = [
|
||||
''
|
||||
to attrs=userPassword
|
||||
by anonymous auth
|
||||
by * none''
|
||||
''
|
||||
to dn.subtree="cn=${realm},cn=realms,${DITRoot}"
|
||||
by dn.exact="cn=kdc,${DITRoot}" write
|
||||
by dn.exact="cn=kadmin,${DITRoot}" write
|
||||
by * none''
|
||||
''
|
||||
to *
|
||||
by * read''
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
security.krb5 = {
|
||||
enable = true;
|
||||
package = krb5Package;
|
||||
settings = {
|
||||
libdefaults = {
|
||||
default_realm = realm;
|
||||
};
|
||||
realms = {
|
||||
"${realm}" = {
|
||||
admin_server = "machine";
|
||||
kdc = "machine";
|
||||
services.kerberos_server = {
|
||||
enable = true;
|
||||
settings = {
|
||||
libdefaults.default_realm = realm;
|
||||
realms = {
|
||||
"${realm}" = {
|
||||
acl = [
|
||||
{
|
||||
principal = "admin";
|
||||
access = "all";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
dbmodules = {
|
||||
"${realm}" = {
|
||||
db_library = "kldap";
|
||||
ldap_kerberos_container_dn = "cn=realms,${DITRoot}";
|
||||
ldap_kdc_dn = "cn=kdc,${DITRoot}";
|
||||
ldap_kadmind_dn = "cn=kadmin,${DITRoot}";
|
||||
ldap_service_password_file = toString krbPwdStash;
|
||||
ldap_servers = "ldapi:///";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
security.krb5 = {
|
||||
enable = true;
|
||||
package = krb5Package;
|
||||
settings = {
|
||||
libdefaults = {
|
||||
default_realm = realm;
|
||||
};
|
||||
realms = {
|
||||
"${realm}" = {
|
||||
admin_server = "machine";
|
||||
kdc = "machine";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
users.extraUsers.alice = {
|
||||
isNormalUser = true;
|
||||
};
|
||||
};
|
||||
|
||||
users.extraUsers.alice = {
|
||||
isNormalUser = true;
|
||||
};
|
||||
};
|
||||
testScript = ''
|
||||
machine.wait_for_unit("openldap.service")
|
||||
|
||||
testScript = ''
|
||||
machine.wait_for_unit("openldap.service")
|
||||
with subtest("realm container initialization"):
|
||||
machine.succeed(
|
||||
# Passing a master key directly avoids the need for a separate master key stash file
|
||||
"kdb5_ldap_util -D cn=root,${DITRoot} create -w ${rootDnPwd} -s -P master_key",
|
||||
)
|
||||
|
||||
with subtest("realm container initialization"):
|
||||
machine.succeed(
|
||||
# Passing a master key directly avoids the need for a separate master key stash file
|
||||
"kdb5_ldap_util -D cn=root,${DITRoot} create -w ${rootDnPwd} -s -P master_key",
|
||||
)
|
||||
# These units are bound to fail, as they are started before the directory service is ready
|
||||
machine.execute("systemctl restart kadmind.service kdc.service")
|
||||
|
||||
# These units are bound to fail, as they are started before the directory service is ready
|
||||
machine.execute("systemctl restart kadmind.service kdc.service")
|
||||
with subtest("service bind"):
|
||||
for unit in ["kadmind", "kdc"]:
|
||||
machine.wait_for_unit(f"{unit}.service")
|
||||
|
||||
with subtest("service bind"):
|
||||
for unit in ["kadmind", "kdc"]:
|
||||
machine.wait_for_unit(f"{unit}.service")
|
||||
with subtest("administration principal initialization"):
|
||||
machine.succeed("kadmin.local add_principal -pw admin_pw admin")
|
||||
|
||||
with subtest("administration principal initialization"):
|
||||
machine.succeed("kadmin.local add_principal -pw admin_pw admin")
|
||||
with subtest("user principal creation and kinit"):
|
||||
machine.succeed(
|
||||
"kadmin -p admin -w admin_pw addprinc -pw alice_pw alice",
|
||||
"echo alice_pw | sudo -u alice kinit",
|
||||
)
|
||||
# Make extra sure that the user principal actually exists in the directory
|
||||
machine.succeed(
|
||||
"ldapsearch -x -D cn=root,${DITRoot} -w ${rootDnPwd} \
|
||||
-b ${DITRoot} 'krbPrincipalName=alice@${realm}' | grep 'numEntries: 1'"
|
||||
)
|
||||
'';
|
||||
|
||||
with subtest("user principal creation and kinit"):
|
||||
machine.succeed(
|
||||
"kadmin -p admin -w admin_pw addprinc -pw alice_pw alice",
|
||||
"echo alice_pw | sudo -u alice kinit",
|
||||
)
|
||||
# Make extra sure that the user principal actually exists in the directory
|
||||
machine.succeed(
|
||||
"ldapsearch -x -D cn=root,${DITRoot} -w ${rootDnPwd} \
|
||||
-b ${DITRoot} 'krbPrincipalName=alice@${realm}' | grep 'numEntries: 1'"
|
||||
)
|
||||
'';
|
||||
|
||||
meta.maintainers = [ pkgs.lib.maintainers.nessdoor ];
|
||||
}
|
||||
meta.maintainers = [ pkgs.lib.maintainers.nessdoor ];
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,64 +1,66 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
name = "kerberos_server-mit";
|
||||
import ../make-test-python.nix (
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
name = "kerberos_server-mit";
|
||||
|
||||
nodes.machine =
|
||||
{
|
||||
config,
|
||||
libs,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
{
|
||||
services.kerberos_server = {
|
||||
enable = true;
|
||||
settings.realms = {
|
||||
"FOO.BAR".acl = [
|
||||
{
|
||||
principal = "admin";
|
||||
access = [
|
||||
"add"
|
||||
"cpw"
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
security.krb5 = {
|
||||
enable = true;
|
||||
package = pkgs.krb5;
|
||||
settings = {
|
||||
libdefaults = {
|
||||
default_realm = "FOO.BAR";
|
||||
nodes.machine =
|
||||
{
|
||||
config,
|
||||
libs,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
{
|
||||
services.kerberos_server = {
|
||||
enable = true;
|
||||
settings.realms = {
|
||||
"FOO.BAR".acl = [
|
||||
{
|
||||
principal = "admin";
|
||||
access = [
|
||||
"add"
|
||||
"cpw"
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
realms = {
|
||||
"FOO.BAR" = {
|
||||
admin_server = "machine";
|
||||
kdc = "machine";
|
||||
};
|
||||
security.krb5 = {
|
||||
enable = true;
|
||||
package = pkgs.krb5;
|
||||
settings = {
|
||||
libdefaults = {
|
||||
default_realm = "FOO.BAR";
|
||||
};
|
||||
realms = {
|
||||
"FOO.BAR" = {
|
||||
admin_server = "machine";
|
||||
kdc = "machine";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
users.extraUsers.alice = {
|
||||
isNormalUser = true;
|
||||
};
|
||||
};
|
||||
users.extraUsers.alice = {
|
||||
isNormalUser = true;
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.succeed(
|
||||
"kdb5_util create -s -r FOO.BAR -P master_key",
|
||||
"systemctl restart kadmind.service kdc.service",
|
||||
)
|
||||
testScript = ''
|
||||
machine.succeed(
|
||||
"kdb5_util create -s -r FOO.BAR -P master_key",
|
||||
"systemctl restart kadmind.service kdc.service",
|
||||
)
|
||||
|
||||
for unit in ["kadmind", "kdc"]:
|
||||
machine.wait_for_unit(f"{unit}.service")
|
||||
for unit in ["kadmind", "kdc"]:
|
||||
machine.wait_for_unit(f"{unit}.service")
|
||||
|
||||
machine.succeed(
|
||||
"kadmin.local add_principal -pw admin_pw admin",
|
||||
"kadmin -p admin -w admin_pw addprinc -pw alice_pw alice",
|
||||
"echo alice_pw | sudo -u alice kinit",
|
||||
)
|
||||
'';
|
||||
machine.succeed(
|
||||
"kadmin.local add_principal -pw admin_pw admin",
|
||||
"kadmin -p admin -w admin_pw addprinc -pw alice_pw alice",
|
||||
"echo alice_pw | sudo -u alice kinit",
|
||||
)
|
||||
'';
|
||||
|
||||
meta.maintainers = [ pkgs.lib.maintainers.dblsaiko ];
|
||||
}
|
||||
meta.maintainers = [ pkgs.lib.maintainers.dblsaiko ];
|
||||
}
|
||||
)
|
||||
|
||||
@@ -44,11 +44,6 @@
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
def with_unit_path(node, cmd):
|
||||
"""Run cmd with the PATH systemd hands to komodo-periphery.service."""
|
||||
unit_path = "$(systemctl show -p Environment --value komodo-periphery | grep -o 'PATH=[^ ]*' | cut -d= -f2)"
|
||||
node.succeed(f"export PATH={unit_path}; {cmd}")
|
||||
|
||||
start_all()
|
||||
|
||||
with subtest("Inbound periphery starts and serves /version"):
|
||||
@@ -64,15 +59,9 @@
|
||||
periphery.succeed("test -d /var/lib/komodo-periphery/keys")
|
||||
periphery.succeed("test -d /var/lib/komodo-periphery/ssl")
|
||||
|
||||
with subtest("Service PATH provides docker, docker compose and git"):
|
||||
with_unit_path(periphery, "command -v docker && command -v git && docker compose version")
|
||||
|
||||
with subtest("Outbound periphery stays active despite unreachable core"):
|
||||
peripheryOutbound.wait_for_unit("komodo-periphery.service")
|
||||
peripheryOutbound.sleep(15)
|
||||
peripheryOutbound.succeed("systemctl is-active komodo-periphery")
|
||||
|
||||
with subtest("Terminal-enabled periphery sees system-wide tools on PATH"):
|
||||
with_unit_path(peripheryOutbound, "command -v bash")
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
runTest,
|
||||
...
|
||||
}:
|
||||
{
|
||||
additionalFiles = runTest ./additional-files.nix;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -401,6 +401,17 @@ let
|
||||
driver = "nl80211";
|
||||
pkcs11 = {
|
||||
enable = true;
|
||||
package = pkgs.libp11.overrideAttrs {
|
||||
# TODO: Remove this override once a libp11 release includes the fix for
|
||||
# https://github.com/OpenSC/libp11/issues/672
|
||||
version = "0.4.21-unstable-2026-08-19";
|
||||
src = pkgs.fetchFromGitHub {
|
||||
owner = "OpenSC";
|
||||
repo = "libp11";
|
||||
rev = "e72a2014eb078c7b784e2a9be3e7abd3dce8fd5a";
|
||||
hash = "sha256-V9ZRPUJp2FkK+Zb/qYC13SDE7+oyJ/hlnO/XEN2zDD8=";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{ pkgs, runTest }:
|
||||
{ pkgs, runTest, ... }:
|
||||
let
|
||||
hosts = ''
|
||||
192.168.2.101 acme.test
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user