mirror of
https://github.com/NixOS/nixpkgs.git
synced 2026-09-13 19:00:15 +00:00
Compare commits
1 Commits
staging-ne
...
pr/github-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7006e9ed51 |
4
.github/workflows/periodic-merge.yml
vendored
4
.github/workflows/periodic-merge.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
client-id: ${{ vars.NIXPKGS_CI_CLIENT_ID }}
|
||||
private-key: ${{ secrets.NIXPKGS_CI_APP_PRIVATE_KEY }}
|
||||
permission-contents: write
|
||||
permission-issues: write
|
||||
permission-pull-requests: write
|
||||
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
@@ -66,4 +66,4 @@ jobs:
|
||||
Periodic merge from `${{ inputs.from }}` into [`${{ inputs.into }}`](https://github.com/NixOS/nixpkgs/tree/${{ inputs.into }}) has [failed](https://github.com/NixOS/nixpkgs/actions/runs/${{ github.run_id }}).
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
run: |
|
||||
gh issue comment 562905 --body "$BODY_TEXT"
|
||||
gh pr comment 105153 --body "$BODY_TEXT"
|
||||
|
||||
@@ -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/
|
||||
- ...
|
||||
```
|
||||
|
||||
@@ -73,7 +73,7 @@ Controls the flags passed to `cargo tauri build`.
|
||||
|
||||
#### `tauriBundleType` {#tauri-bundle-type}
|
||||
|
||||
The [bundle type](https://tauri.app/reference/javascript/api/namespaceapp/#bundletype) to build.
|
||||
The [bundle type](https://tauri.app/v1/guides/building/) to build.
|
||||
|
||||
#### `dontTauriBuild` {#dont-tauri-build}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ The `nix:narinfo` properties should be accompanied by a `nix:store_path` propert
|
||||
| `nix:narinfo:system` | The hardware and software platform on which this component is produced. |
|
||||
| `nix:narinfo:sig` | Signatures claiming that this component is what it claims to be. |
|
||||
| `nix:narinfo:ca` | Content address of this store object's file system object, used to compute its store path. |
|
||||
| `nix:narinfo:references` | A whitespace-separated array of store paths that this component references. |
|
||||
| `nix:narinfo:references` | A whitespace separated array of store paths that this component references. |
|
||||
|
||||
### `nix:fod` {#sec-interop.cylonedx-fod}
|
||||
|
||||
|
||||
1089
doc/nav.json
1089
doc/nav.json
File diff suppressed because it is too large
Load Diff
@@ -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,10 +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.
|
||||
|
||||
- Linux kernel configuration has been moved out of the `linux-kernel` field of the platform structure into the kernel builders:
|
||||
@@ -125,8 +114,6 @@
|
||||
`lib.systems.{examples,platforms}.{sheevaplug,pogoplug4}` have been unified into `lib.systems.examples.armv5tel-multiplatform`.
|
||||
Note that there is no official support for ARMv5 and it is not possible to build even a simple NixOS configuration out of the box.
|
||||
|
||||
- `tcl.mkTclDerivation` now enables `strictDeps` and `__structuredAttrs`.
|
||||
|
||||
- `pdns` has been updated from `5.0.x` to `5.1.x`. Please be sure to review the [Upgrade Notes](https://doc.powerdns.com/authoritative/upgrading.html#to-5-1-0) before upgrading. Namely LUA record updates are no longer allowed by default, and the embedded webserver no longer includes a `access-control-allow-origin: *` header by default.
|
||||
|
||||
- LibreOffice upstream switched from Fresh/Still stable branches to a single Stable branch; `libreoffice` and `libreoffice-qt` work as before, but more specific aliases like `libreoffice-fresh` should be replaced.
|
||||
@@ -143,9 +130,6 @@
|
||||
Users who must continue using ingress-nginx will now need to manually provide the rke2-images-ingress-nginx tarball.
|
||||
- Future Removal: The ingress-nginx chart will not receive any additional updates and will be completely removed in v1.37 for community users.
|
||||
|
||||
- `replaceVarsWith` now enables `strictDeps` and `__structuredAttrs` and passing these attributes to the function is no longer allowed.
|
||||
By extension, `replaceVars` now also enables `strictDeps` and `__structuredAttrs`.
|
||||
|
||||
- `buildFHSEnvChroot` has been removed after deprecation in 23.05.
|
||||
|
||||
- `leafnode` has been removed, as it was an unmaintained alpha-release of leafnode 2 and has a dependency on the EOL PRCE-library. Consider using `leafnode1` instead, which is still maintained.
|
||||
@@ -164,8 +148,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 +186,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. -->
|
||||
|
||||
@@ -493,7 +493,7 @@ When using native compilation, `stdenv` is lenient towards incorrect placement o
|
||||
|
||||
While convenient for getting to a package quickly, this behavior can break cross-compilation. Adding `strictDeps = true` as a parameter to `mkDerivation` or any of its language specific wrappers disables this behavior.
|
||||
|
||||
The specialized `build*` functions for dlang, emacs, go, nim, ocaml, python, rust, and `tcl.mkTclDerivation` enable this option by default.
|
||||
The specialized `build*` functions for dlang, emacs, go, nim, ocaml, python, and rust enable this option by default.
|
||||
|
||||
## Attributes {#ssec-stdenv-attributes}
|
||||
|
||||
|
||||
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
|
||||
@@ -745,9 +742,6 @@ checkConfigOutput '^38|27$' options.submoduleLine38.declarationPositions.1.line
|
||||
# nested options work
|
||||
checkConfigOutput '^34$' options.nested.nestedLine34.declarationPositions.0.line ./declaration-positions.nix
|
||||
|
||||
# types.pathWith in-module assertions
|
||||
checkConfigOutput '"ok"' config.assertionsResult ./pathWith.nix
|
||||
|
||||
# types.pathWith { inStore = true; }
|
||||
checkConfigOutput '".*/store/0lz9p8xhf89kb1c1kk6jxrzskaiygnlh-bash-5.2-p15.drv"' config.pathInStore.ok1 ./pathWith.nix
|
||||
checkConfigOutput '".*/store/0fb3ykw9r5hpayd05sr0cizwadzq1d8q-bash-5.2-p15"' config.pathInStore.ok2 ./pathWith.nix
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{ config, lib, ... }:
|
||||
{ lib, ... }:
|
||||
let
|
||||
inherit (builtins)
|
||||
storeDir
|
||||
@@ -12,8 +12,6 @@ in
|
||||
imports = [
|
||||
{
|
||||
options = {
|
||||
assertionsResult = mkOption { };
|
||||
|
||||
pathInStore = mkOption { type = types.lazyAttrsOf (types.pathWith { inStore = true; }); };
|
||||
pathNotInStore = mkOption { type = types.lazyAttrsOf (types.pathWith { inStore = false; }); };
|
||||
anyPath = mkOption { type = types.lazyAttrsOf (types.pathWith { }); };
|
||||
@@ -25,13 +23,6 @@ in
|
||||
}
|
||||
);
|
||||
};
|
||||
absolutePath = mkOption {
|
||||
type = types.lazyAttrsOf (
|
||||
types.pathWith {
|
||||
absolute = true;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
# This conflicts with `conflictingPathOptionType` below.
|
||||
conflictingPathOptionType = mkOption { type = types.pathWith { absolute = true; }; };
|
||||
@@ -94,21 +85,7 @@ in
|
||||
absolutePathNotInStore.bad1 = "./this/is/relative";
|
||||
absolutePathNotInStore.bad2 = "${storeDir}/0fb3ykw9r5hpayd05sr0cizwadzq1d8q-bash-5.2-p15";
|
||||
|
||||
absolutePath.ok1 = "/var/lib/postgresql";
|
||||
absolutePath.ok2 = {
|
||||
type = "derivation";
|
||||
outPath = abort "do not instantiate (via outPath)";
|
||||
drvPath = abort "do not instantiate (via drvPath)";
|
||||
meta.description = "just a test fixture";
|
||||
};
|
||||
|
||||
conflictingPathOptionType = "/foo/bar";
|
||||
|
||||
impossiblePathOptionType = "/foo/bar";
|
||||
|
||||
assertionsResult =
|
||||
assert config.absolutePath.ok1 == "/var/lib/postgresql";
|
||||
assert config.absolutePath.ok2.type == "derivation";
|
||||
assert config.absolutePath.ok2.meta.description == "just a test fixture";
|
||||
"ok";
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -695,7 +695,7 @@ rec {
|
||||
else
|
||||
/. + builtins.unsafeDiscardStringContext x
|
||||
);
|
||||
isAbsolute = x.type or null == "derivation" || substring 0 1 (toString x) == "/";
|
||||
isAbsolute = substring 0 1 (toString x) == "/";
|
||||
isExpectedType = (
|
||||
if inStore == null || inStore then isStringLike x else isString x # Do not allow a true path, which could be copied to the store later on.
|
||||
);
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -193,6 +193,7 @@
|
||||
"Emin017": 99674037,
|
||||
"Et7f3": 29592775,
|
||||
"Guanran928": 68757440,
|
||||
"KristijanZic": 10299892,
|
||||
"LnL7": 689294,
|
||||
"Mastermindaxe": 33257997,
|
||||
"Moraxyc": 69713071,
|
||||
|
||||
@@ -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";
|
||||
@@ -4479,14 +4474,6 @@
|
||||
githubId = 510553;
|
||||
name = "Jos van Bakel";
|
||||
};
|
||||
c2fc2f = {
|
||||
name = "c2fc2f";
|
||||
github = "c2fc2f";
|
||||
githubId = 59392138;
|
||||
email = "contact@c2fc2f.com";
|
||||
matrix = "@c2fc2f:sagbot.com";
|
||||
keys = [ { fingerprint = "6EF1 8507 76B5 ABCE 5BF0 C0F8 42E0 E1D1 0B61 1208"; } ];
|
||||
};
|
||||
c31io = {
|
||||
email = "celiogrand@outlook.com";
|
||||
github = "c31io";
|
||||
@@ -6234,12 +6221,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 +6758,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 +7429,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 +8802,6 @@
|
||||
githubId = 965612;
|
||||
name = "York Wong";
|
||||
};
|
||||
etiennelescot = {
|
||||
github = "EtienneLescot";
|
||||
githubId = 215859519;
|
||||
name = "Etienne Lescot";
|
||||
};
|
||||
Etjean = {
|
||||
email = "et.jean@outlook.fr";
|
||||
github = "Etjean";
|
||||
@@ -10411,7 +10387,7 @@
|
||||
};
|
||||
gibbert = {
|
||||
email = "gbjgms@gmail.com";
|
||||
github = "2gibbert";
|
||||
github = "zgibberish";
|
||||
githubId = 67570424;
|
||||
name = "gibbert";
|
||||
};
|
||||
@@ -14636,11 +14612,6 @@
|
||||
githubId = 6544084;
|
||||
name = "Kai Harries";
|
||||
};
|
||||
kaistarkk = {
|
||||
github = "KaiStarkk";
|
||||
githubId = 1722064;
|
||||
name = "KaiStarkk";
|
||||
};
|
||||
kalbasit = {
|
||||
email = "wael.nasreddine@gmail.com";
|
||||
matrix = "@kalbasit:matrix.org";
|
||||
@@ -14844,12 +14815,6 @@
|
||||
githubId = 32649612;
|
||||
keys = [ { fingerprint = "65DF D21C 22A9 E4CD FD1A 0804 C3D7 16E7 29B3 C86A"; } ];
|
||||
};
|
||||
keksnino = {
|
||||
name = "KeksNino";
|
||||
email = "KeksNino@proton.me";
|
||||
github = "KeksNino";
|
||||
githubId = 87879013;
|
||||
};
|
||||
keldu = {
|
||||
email = "mail@keldu.de";
|
||||
github = "keldu";
|
||||
@@ -19377,12 +19342,6 @@
|
||||
githubId = 708570;
|
||||
name = "Manuel Mendez";
|
||||
};
|
||||
mmulqueen = {
|
||||
email = "michael@mulqueen.me.uk";
|
||||
github = "mmulqueen";
|
||||
githubId = 6616321;
|
||||
name = "Michael Mulqueen";
|
||||
};
|
||||
mmusnjak = {
|
||||
email = "marko.musnjak@gmail.com";
|
||||
github = "mmusnjak";
|
||||
@@ -25336,14 +25295,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 +31390,6 @@
|
||||
githubId = 28888242;
|
||||
name = "WORLDofPEACE";
|
||||
};
|
||||
wormt = {
|
||||
github = "wormt";
|
||||
githubId = 209373679;
|
||||
name = "wormt";
|
||||
};
|
||||
WoutSwinkels = {
|
||||
name = "Wout Swinkels";
|
||||
email = "nixpkgs@woutswinkels.com";
|
||||
@@ -32238,6 +32184,12 @@
|
||||
githubId = 65394961;
|
||||
name = "Yves Straten";
|
||||
};
|
||||
yvnth = {
|
||||
email = "yashupress@gmail.com";
|
||||
github = "yvnth";
|
||||
githubId = 201552597;
|
||||
name = "Yashwanth Prasannakumar";
|
||||
};
|
||||
yzx9 = {
|
||||
email = "yuan.zx@outlook.com";
|
||||
github = "yzx9";
|
||||
|
||||
@@ -157,17 +157,15 @@ def main(pkgset: str, version: str, nixpkgs: pathlib.Path, sources_url: str | No
|
||||
json.dump(results, fd, indent=2)
|
||||
|
||||
for project_name in projects_to_update_rust:
|
||||
print(f"Updating cargoDeps hash for {pkgset}/{project_name}...")
|
||||
pkg_dir = set_dir / project_name
|
||||
pkg_file = pkg_dir / "default.nix"
|
||||
subprocess.run([
|
||||
"nix-update",
|
||||
f"kdePackages.{project_name}",
|
||||
"--version",
|
||||
"skip",
|
||||
"--override-filename",
|
||||
pkg_file
|
||||
])
|
||||
print(f"Updating cargoDeps hash for {pkgset}/{project_name}...")
|
||||
subprocess.run([
|
||||
"nix-update",
|
||||
f"kdePackages.{project_name}",
|
||||
"--version",
|
||||
"skip",
|
||||
"--override-filename",
|
||||
pkg_file
|
||||
])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -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
|
||||
];
|
||||
|
||||
@@ -92,10 +92,6 @@ cd /my/git/clone/of/nixpkgs
|
||||
nix-build -A nixosTests.hostname
|
||||
```
|
||||
|
||||
In-tree tests run with a few changes to defaults, notably
|
||||
- The `pkgs.*` are read only by default. You can opt out with `node.pkgsReadOnly = false;` at the test level.
|
||||
- `nix.enable` defaults to `false` to reduce build closure size; in particular the reverse build closure of `nix` and its dependencies.
|
||||
|
||||
### Testing outside the NixOS project {#sec-call-nixos-test-outside-nixos}
|
||||
|
||||
Outside the `nixpkgs` repository, you can use the `runNixOSTest` function from
|
||||
@@ -114,8 +110,6 @@ pkgs.testers.runNixOSTest {
|
||||
|
||||
`runNixOSTest` returns a derivation that runs the test.
|
||||
|
||||
Out-of-tree tests evaluate with a set of defaults that balances the principle of least surprise in the general case, but has a few differences from in-tree NixOS tests. See [Testing within NixOS](#sec-call-nixos-test-in-nixos).
|
||||
|
||||
## Test machines {#ssec-nixos-test-machines}
|
||||
|
||||
A NixOS test usually consists of one or more test machines. Each machine is either a
|
||||
|
||||
@@ -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.
|
||||
@@ -223,14 +207,10 @@
|
||||
|
||||
- `fail2ban` has been updated to 1.1.1, which has a few breaking changes compared to 1.1.0 ([changelog](https://github.com/fail2ban/fail2ban/blob/1.1.1/ChangeLog))
|
||||
|
||||
- `services.neo4j.tls.<policy_name>`: This policy is no longer enabled by default. If you rely on this policy, you must now explicitly opt-in by setting `services.neo4j.tls.<policy_name>.enable = true;`.
|
||||
|
||||
- `systemd.user.extraConfig` has been removed in favor of the structured [](#opt-systemd.user.settings.Manager) option. Use `systemd.user.settings.Manager` to set any `systemd-user.conf(5)` option directly. For example, replace `systemd.user.extraConfig = "DefaultTimeoutStartSec=60";` with `systemd.user.settings.Manager.DefaultTimeoutStartSec = 60;`.
|
||||
|
||||
- `matrix-appservice-discord` was removed from nixpkgs along with its NixOS module (`services.matrix-appservice-discord`) as it is no longer actively maintained upstream. Use the actively-maintained puppeting bridge [`mautrix-discord`](#opt-services.mautrix-discord.enable) instead.
|
||||
|
||||
- `services.logrotate.configFile` has been removed. If you still need to load a config from another directory, use `include` directives within `services.logrotate.settings`.
|
||||
|
||||
- Home Assistant 2026.8.0 migrated its HTTP configuration from YAML into the frontend. After upgrading, any options configured under `services.home-assistant.config.http` can be removed. HTTP settings can now be configured from the Home Assistant frontend under [Settings → System → Network](https://my.home-assistant.io/redirect/network). If no HTTP settings were previously configured, Home Assistant will default to listening on all interfaces on port 8123.
|
||||
|
||||
- `services.timesyncd.extraConfig` has been removed in favor of the structured [](#opt-services.timesyncd.settings.Time) option. Use `services.timesyncd.settings.Time` to set any `timesyncd.conf(5)` option directly. For example, replace `services.timesyncd.extraConfig = "PollIntervalMaxSec=180";` with `services.timesyncd.settings.Time.PollIntervalMaxSec = 180;`.
|
||||
@@ -258,8 +238,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)
|
||||
@@ -332,13 +310,6 @@
|
||||
|
||||
- The `programs.fuse` module, which provides the `fusermount3` executable and the `/etc/fuse.conf` config file, is now opt-in. The obligation to enable it has been shifted to its various consumers (e.g. gvfs, flatpak, appimage, sshfs). This can break fuse consumers at runtime, that don't explicitly declare that dependency with a module, e.g the mounting functionality in various backup tools (borg, restic, rclone, ...).
|
||||
|
||||
- The [Grav CMS][https://getgrav.org/] package `grav` is now deprecated and users are directed to `grav_2`.
|
||||
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.
|
||||
@@ -369,6 +340,3 @@
|
||||
- `nix` now supports running in "daemonless" mode by setting `nix.daemon.enable = false`. Under this mode all store operations must go through the [local store type](https://nix.dev/manual/nix/latest/store/types/local-store), which typically requires root permissions.
|
||||
|
||||
- [Hister](https://github.com/asciimoo/hister), a web history service offering blazing fast, content-based search across visited websites. Available as [services.hister](#opt-services.hister.enable).
|
||||
|
||||
- The default of [`networking.firewall.checkReversePath`](#opt-networking.firewall.checkReversePath) changed to `"loose"` to align with upstream systemd.
|
||||
If your environment requires strict reverse path filtering, you must now explicitly configure it with `networking.firewall.checkReversePath = "strict";`.
|
||||
|
||||
@@ -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";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -80,18 +80,17 @@ class JunitXMLLogger(AbstractLogger):
|
||||
self.failure = False
|
||||
|
||||
def __init__(self, outfile: Path) -> None:
|
||||
self.testsuite = JunitXMLLogger.TestCaseState()
|
||||
self.tests: dict[str, JunitXMLLogger.TestCaseState] = {}
|
||||
self.currentSubtest = None
|
||||
self.tests: dict[str, JunitXMLLogger.TestCaseState] = {
|
||||
"main": self.TestCaseState()
|
||||
}
|
||||
self.currentSubtest = "main"
|
||||
self.outfile: Path = outfile
|
||||
self._print_serial_logs = True
|
||||
self._log_level = LogLevel.INFO
|
||||
atexit.register(self.close)
|
||||
|
||||
def log(self, message: str, attributes: dict[str, str] = {}) -> None:
|
||||
self.testsuite.stdout += message + os.linesep
|
||||
if self.currentSubtest:
|
||||
self.tests[self.currentSubtest].stdout += message + os.linesep
|
||||
self.tests[self.currentSubtest].stdout += message + os.linesep
|
||||
|
||||
@contextmanager
|
||||
def subtest(self, name: str, attributes: dict[str, str] = {}) -> Iterator[None]:
|
||||
@@ -110,28 +109,19 @@ class JunitXMLLogger(AbstractLogger):
|
||||
|
||||
def debug(self, *args, **kwargs) -> None:
|
||||
if self._log_level <= LogLevel.DEBUG:
|
||||
self.testsuite.stdout += args[0] + os.linesep
|
||||
if self.currentSubtest:
|
||||
self.tests[self.currentSubtest].stdout += args[0] + os.linesep
|
||||
self.tests[self.currentSubtest].stdout += args[0] + os.linesep
|
||||
|
||||
def info(self, *args, **kwargs) -> None:
|
||||
if self._log_level <= LogLevel.INFO:
|
||||
self.testsuite.stdout += args[0] + os.linesep
|
||||
if self.currentSubtest:
|
||||
self.tests[self.currentSubtest].stdout += args[0] + os.linesep
|
||||
self.tests[self.currentSubtest].stdout += args[0] + os.linesep
|
||||
|
||||
def warning(self, *args, **kwargs) -> None:
|
||||
if self._log_level <= LogLevel.WARNING:
|
||||
self.testsuite.stdout += args[0] + os.linesep
|
||||
if self.currentSubtest:
|
||||
self.tests[self.currentSubtest].stdout += args[0] + os.linesep
|
||||
self.tests[self.currentSubtest].stdout += args[0] + os.linesep
|
||||
|
||||
def error(self, *args, **kwargs) -> None:
|
||||
self.testsuite.stderr += args[0] + os.linesep
|
||||
self.testsuite.failure = True
|
||||
if self.currentSubtest:
|
||||
self.tests[self.currentSubtest].stderr += args[0] + os.linesep
|
||||
self.tests[self.currentSubtest].failure = True
|
||||
self.tests[self.currentSubtest].stderr += args[0] + os.linesep
|
||||
self.tests[self.currentSubtest].failure = True
|
||||
|
||||
def log_test_error(self, *args, **kwargs) -> None:
|
||||
self.error(*args, **kwargs)
|
||||
@@ -151,9 +141,6 @@ class JunitXMLLogger(AbstractLogger):
|
||||
def close(self) -> None:
|
||||
with open(self.outfile, "w") as f:
|
||||
test_cases = []
|
||||
if len(self.tests) == 0:
|
||||
self.tests.setdefault("main", self.TestCaseState())
|
||||
self.tests["main"].failure = self.testsuite.failure
|
||||
for name, test_case_state in self.tests.items():
|
||||
tc = TestCase(
|
||||
name,
|
||||
@@ -164,12 +151,7 @@ class JunitXMLLogger(AbstractLogger):
|
||||
tc.add_failure_info("test case failed")
|
||||
|
||||
test_cases.append(tc)
|
||||
ts = TestSuite(
|
||||
"NixOS integration test",
|
||||
test_cases,
|
||||
stdout=self.testsuite.stdout,
|
||||
stderr=self.testsuite.stderr,
|
||||
)
|
||||
ts = TestSuite("NixOS integration test", test_cases)
|
||||
f.write(TestSuite.to_xml_string([ts]))
|
||||
|
||||
|
||||
|
||||
@@ -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";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -109,7 +109,6 @@ let
|
||||
"btrfs" = [ btrfs-progs ];
|
||||
"xfs" = [ xfsprogs ];
|
||||
"swap" = [ util-linux ];
|
||||
"empty" = [ ];
|
||||
};
|
||||
|
||||
fileSystemTools = builtins.concatMap (f: fileSystemToolMapping."${f}") fileSystems;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
# Relies on bash scripts
|
||||
powerManagement.enable = lib.mkDefault false;
|
||||
users.manageLingering = lib.mkDefault false;
|
||||
# Relies on the gzip command which depends on bash
|
||||
services.logrotate.enable = lib.mkDefault false;
|
||||
# The resize helper pulls in bash
|
||||
services.lvm.resizeHelper.enable = false;
|
||||
|
||||
|
||||
@@ -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" { };
|
||||
};
|
||||
|
||||
@@ -204,5 +204,5 @@ in
|
||||
]
|
||||
);
|
||||
|
||||
meta.maintainers = [ ];
|
||||
meta.maintainers = with lib.maintainers; [ yvnth ];
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1252,7 +1252,7 @@ let
|
||||
name = "fscrypt";
|
||||
enable = config.security.pam.enableFscrypt;
|
||||
control = "optional";
|
||||
modulePath = "${pkgs.fscrypt}/lib/security/pam_fscrypt.so";
|
||||
modulePath = "${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so";
|
||||
}
|
||||
{
|
||||
name = "zfs_key";
|
||||
@@ -1449,7 +1449,7 @@ let
|
||||
name = "fscrypt";
|
||||
enable = config.security.pam.enableFscrypt;
|
||||
control = "optional";
|
||||
modulePath = "${pkgs.fscrypt}/lib/security/pam_fscrypt.so";
|
||||
modulePath = "${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so";
|
||||
}
|
||||
{
|
||||
name = "zfs_key";
|
||||
@@ -1610,7 +1610,7 @@ let
|
||||
name = "fscrypt";
|
||||
enable = config.security.pam.enableFscrypt;
|
||||
control = "optional";
|
||||
modulePath = "${pkgs.fscrypt}/lib/security/pam_fscrypt.so";
|
||||
modulePath = "${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so";
|
||||
}
|
||||
{
|
||||
name = "zfs_key-skip-systemd";
|
||||
@@ -2663,7 +2663,7 @@ in
|
||||
++ lib.optionals config.security.pam.enableOTPW [ pkgs.otpw ]
|
||||
++ lib.optionals config.security.pam.oath.enable [ pkgs.oath-toolkit ]
|
||||
++ lib.optionals config.security.pam.p11.enable [ pkgs.pam_p11 ]
|
||||
++ lib.optionals config.security.pam.enableFscrypt [ pkgs.fscrypt ]
|
||||
++ lib.optionals config.security.pam.enableFscrypt [ pkgs.fscrypt-experimental ]
|
||||
++ lib.optionals config.security.pam.u2f.enable [ pkgs.pam_u2f ];
|
||||
|
||||
security.wrappers = {
|
||||
|
||||
@@ -151,17 +151,6 @@ in
|
||||
# ~/.config/Yubico/u2f_keys (the default key file location)
|
||||
ProtectHome = "read-only";
|
||||
})
|
||||
(mkIf config.security.pam.yubico.enable {
|
||||
# Override upstream PrivateDevices=yes to allow access to /dev/hidraw*
|
||||
PrivateDevices = false;
|
||||
DeviceAllow = [ "char-hidraw rw" ];
|
||||
})
|
||||
(mkIf config.services.fprintd.enable {
|
||||
# Override upstream PrivateDevices=yes to allow access to /dev/bus/usb/**
|
||||
PrivateDevices = false;
|
||||
DeviceAllow = [ "char-usb_device rw" ];
|
||||
RestrictAddressFamilies = [ "AF_NETLINK" ];
|
||||
})
|
||||
(mkIf config.security.pam.zfs.enable {
|
||||
PrivateDevices = false;
|
||||
DeviceAllow = [
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -84,6 +84,7 @@ let
|
||||
}
|
||||
}
|
||||
'';
|
||||
__structuredAttrs = true;
|
||||
};
|
||||
checkPhase = ''
|
||||
printf "%s" "$expectScript" | ${lib.getExe pkgs.buildPackages.expect} -f -
|
||||
|
||||
@@ -362,6 +362,7 @@ in
|
||||
{
|
||||
description = "Syncoid ZFS synchronization from ${c.source} to ${c.target}";
|
||||
after = [ "zfs.target" ];
|
||||
startAt = cfg.interval;
|
||||
# syncoid may need zpool to get feature@extensible_dataset
|
||||
path = [ "/run/booted-system/sw/bin/" ];
|
||||
serviceConfig = {
|
||||
@@ -472,23 +473,6 @@ in
|
||||
]
|
||||
)
|
||||
) cfg.commands;
|
||||
|
||||
systemd.timers = lib.concatMapAttrs (
|
||||
name: c:
|
||||
lib.optionalAttrs
|
||||
(config.systemd.services."syncoid-${escapeUnitName name}".enable && cfg.interval != [ ])
|
||||
{
|
||||
"syncoid-${escapeUnitName name}" = {
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnCalendar = cfg.interval;
|
||||
# Backup timers should catch up on missed windows (e.g. the
|
||||
# machine was powered off), like restic and btrbk do.
|
||||
Persistent = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
) cfg.commands;
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [
|
||||
|
||||
@@ -167,7 +167,7 @@ in
|
||||
zookeeperQuorum = lib.mkOption {
|
||||
description = ''
|
||||
This option will set "hbase.zookeeper.quorum" in hbase-site.xml.
|
||||
Comma-separated list of servers in the ZooKeeper ensemble.
|
||||
Comma separated list of servers in the ZooKeeper ensemble.
|
||||
'';
|
||||
type = with lib.types; nullOr commas;
|
||||
example = "zk1.internal,zk2.internal,zk3.internal";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -13,7 +13,6 @@ let
|
||||
opt: lib.isOption opt && opt.type == lib.types.path && opt.highestPrio >= 1500;
|
||||
|
||||
sslPolicies = lib.mapAttrsToList (name: conf: ''
|
||||
dbms.ssl.policy.${name}.enabled=${lib.boolToString conf.enable}
|
||||
dbms.ssl.policy.${name}.allow_key_generation=${lib.boolToString conf.allowKeyGeneration}
|
||||
dbms.ssl.policy.${name}.base_directory=${conf.baseDirectory}
|
||||
${lib.optionalString (conf.ciphers != null) ''
|
||||
@@ -36,7 +35,7 @@ let
|
||||
dbms.ssl.policy.${name}.tls_versions=${lib.concatStringsSep "," conf.tlsVersions}
|
||||
dbms.ssl.policy.${name}.trust_all=${lib.boolToString conf.trustAll}
|
||||
dbms.ssl.policy.${name}.trusted_dir=${conf.trustedDir}
|
||||
'') (lib.filterAttrs (_: v: v.enable) cfg.ssl.policies);
|
||||
'') cfg.ssl.policies;
|
||||
|
||||
serverConfig = pkgs.writeText "neo4j.conf" ''
|
||||
# General
|
||||
@@ -468,8 +467,6 @@ in
|
||||
}:
|
||||
{
|
||||
options = {
|
||||
enable = lib.mkEnableOption "this policy";
|
||||
|
||||
allowKeyGeneration = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
@@ -717,6 +714,6 @@ in
|
||||
};
|
||||
|
||||
meta = {
|
||||
maintainers = [ lib.maintainers.c2fc2f ];
|
||||
maintainers = [ ];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -10,6 +10,13 @@ let
|
||||
|
||||
cfg = config.services.udev;
|
||||
|
||||
initrdUdevRules = pkgs.runCommand "initrd-udev-rules" { } ''
|
||||
mkdir -p $out/etc/udev/rules.d
|
||||
for f in 60-cdrom_id 60-persistent-storage 75-net-description 80-drivers 80-net-setup-link; do
|
||||
ln -s ${config.boot.initrd.systemd.package}/lib/udev/rules.d/$f.rules $out/etc/udev/rules.d
|
||||
done
|
||||
'';
|
||||
|
||||
extraUdevRules = pkgs.writeTextFile {
|
||||
name = "extra-udev-rules";
|
||||
text = cfg.extraRules;
|
||||
@@ -52,11 +59,8 @@ let
|
||||
nativeBuildInputs = [
|
||||
# We only include the out output here to avoid needing to include all
|
||||
# other outputs in the installer tests as well
|
||||
# We only need the udevadm command anyway.
|
||||
#
|
||||
# We cannot use systemdMinimal here because it doesn't contain all
|
||||
# the udev builtins, most notably uacess.
|
||||
pkgs.buildPackages.systemd.out
|
||||
# We only need the udevadm command anyway
|
||||
pkgs.buildPackages.systemdMinimal.out
|
||||
];
|
||||
}
|
||||
''
|
||||
@@ -70,7 +74,7 @@ let
|
||||
# Add the udev rules from other packages.
|
||||
for i in $packages; do
|
||||
echo "Adding rules for package $i"
|
||||
for j in $i/{etc,lib}/udev/rules.d/*.rules; do
|
||||
for j in $i/{etc,lib}/udev/rules.d/*; do
|
||||
echo "Copying $j to $out/$(basename $j)"
|
||||
cat $j > $out/$(basename $j)
|
||||
done
|
||||
@@ -97,9 +101,7 @@ let
|
||||
run_progs=$(grep -v '^[[:space:]]*#' $out/* | grep 'RUN+="[^/$]' |
|
||||
sed -e 's/.*RUN+="\([^ "]*\)[ "].*/\1/' | uniq)
|
||||
for i in $import_progs $run_progs; do
|
||||
# Remove the check for tpm2_id when systemd v262 lands where this is
|
||||
# correctly identified as a builtin.
|
||||
if [[ ! -x ${udev}/lib/udev/$i && ! $i =~ socket:.* && ! $i == tpm2_id ]]; then
|
||||
if [[ ! -x ${udev}/lib/udev/$i && ! $i =~ socket:.* ]]; then
|
||||
echo "FAIL"
|
||||
echo "$i is called in udev rules but not installed by udev"
|
||||
exit 1
|
||||
@@ -152,15 +154,10 @@ let
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Only run these checks if the systemd used to check is at least as new
|
||||
# as the one configured to run on the system. This is necessary to be
|
||||
# able to test newer systemd versions.
|
||||
${lib.optionalString (lib.versionAtLeast pkgs.buildPackages.systemdMinimal.version systemd.version) ''
|
||||
# Verify all the udev rules
|
||||
echo "Verifying udev rules using udevadm verify..."
|
||||
udevadm verify --resolve-names=late --no-style $out
|
||||
echo "OK"
|
||||
''}
|
||||
# Verify all the udev rules
|
||||
echo "Verifying udev rules using udevadm verify..."
|
||||
udevadm verify --resolve-names=late --no-style $out
|
||||
echo "OK"
|
||||
|
||||
# If auto-configuration is disabled, then remove
|
||||
# udev's 80-drivers.rules file, which contains rules for
|
||||
@@ -429,7 +426,6 @@ in
|
||||
services.udev.extraRules = nixosRules;
|
||||
|
||||
services.udev.packages = [
|
||||
config.systemd.package
|
||||
extraUdevRules
|
||||
extraHwdbFile
|
||||
];
|
||||
@@ -494,7 +490,7 @@ in
|
||||
};
|
||||
# Insert initrd rules
|
||||
boot.initrd.services.udev.packages = [
|
||||
config.boot.initrd.systemd.package
|
||||
initrdUdevRules
|
||||
(lib.mkIf (config.boot.initrd.services.udev.rules != "") (
|
||||
pkgs.writeTextFile {
|
||||
name = "initrd-udev-rules";
|
||||
|
||||
@@ -141,11 +141,6 @@ in
|
||||
"logrotate"
|
||||
"paths"
|
||||
] "Add attributes to services.logrotate.settings instead")
|
||||
(lib.mkRemovedOptionModule [
|
||||
"services"
|
||||
"logrotate"
|
||||
"configFile"
|
||||
] "Use services.logrotate.settings instead to specify extra configuration")
|
||||
];
|
||||
|
||||
options = {
|
||||
@@ -260,6 +255,27 @@ in
|
||||
);
|
||||
};
|
||||
|
||||
configFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = configFile;
|
||||
defaultText = ''
|
||||
A configuration file automatically generated by NixOS.
|
||||
'';
|
||||
description = ''
|
||||
Override the configuration file used by logrotate. By default,
|
||||
NixOS generates one automatically from [](#opt-services.logrotate.settings).
|
||||
'';
|
||||
example = lib.literalExpression ''
|
||||
pkgs.writeText "logrotate.conf" '''
|
||||
missingok
|
||||
"/var/log/*.log" {
|
||||
rotate 4
|
||||
weekly
|
||||
}
|
||||
''';
|
||||
'';
|
||||
};
|
||||
|
||||
checkConfig = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
@@ -290,18 +306,6 @@ in
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
# logrotate config has an include directive. As such, always generate the built-in config
|
||||
# in case it is useful to the user.
|
||||
# Users could choose to disable it with environment.etc."logrotate.conf".enable = false;
|
||||
environment.etc."logrotate.conf" = {
|
||||
source = configFile;
|
||||
# logrotate mandates that its config is always owned by root.
|
||||
# Copy into /etc to avoid user namespacing issues.
|
||||
mode = "0644";
|
||||
user = "root";
|
||||
group = "root";
|
||||
};
|
||||
|
||||
systemd.services.logrotate = {
|
||||
description = "Logrotate Service";
|
||||
documentation = [
|
||||
@@ -312,7 +316,7 @@ in
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = "${lib.getExe pkgs.logrotate} ${utils.escapeSystemdExecArgs cfg.extraArgs} ${mailOption} /etc/logrotate.conf";
|
||||
ExecStart = "${lib.getExe pkgs.logrotate} ${utils.escapeSystemdExecArgs cfg.extraArgs} ${mailOption} ${cfg.configFile}";
|
||||
|
||||
# performance
|
||||
Nice = 19;
|
||||
@@ -366,7 +370,7 @@ in
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStart = "${pkgs.logrotate}/sbin/logrotate ${utils.escapeSystemdExecArgs cfg.extraArgs} --debug /etc/logrotate.conf";
|
||||
ExecStart = "${pkgs.logrotate}/sbin/logrotate ${utils.escapeSystemdExecArgs cfg.extraArgs} --debug ${cfg.configFile}";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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 ];
|
||||
};
|
||||
}
|
||||
@@ -298,7 +298,7 @@ in
|
||||
popd
|
||||
'';
|
||||
|
||||
restartTriggers = lib.optionals (!cfg.mutableConfig) [ printerConfig ];
|
||||
restartTriggers = lib.optional (!cfg.mutableConfig) [ printerConfig ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/klippy ${klippyArgs} ${cfg.configDir}/printer.cfg";
|
||||
|
||||
@@ -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 { };
|
||||
|
||||
@@ -16,7 +16,6 @@ let
|
||||
getExe
|
||||
literalExpression
|
||||
optional
|
||||
optionals
|
||||
attrValues
|
||||
mapAttrs
|
||||
;
|
||||
@@ -159,7 +158,7 @@ in
|
||||
"LOG_LEVEL=${cfg.logLevel}"
|
||||
"PHX_SERVER=true"
|
||||
]
|
||||
++ optionals cfg.selfhosted [ "RUN_CONTEXT=selfhosted" ]
|
||||
++ optional cfg.selfhosted [ "RUN_CONTEXT=selfhosted" ]
|
||||
++ optional (!isNull config.time.timeZone) "TZ=${config.time.timeZone}"
|
||||
++ attrValues (mapAttrs (name: value: name + "=" + toString value) cfg.extraConfig);
|
||||
EnvironmentFile = optional (cfg.secretsFile != null) cfg.secretsFile;
|
||||
|
||||
@@ -16,7 +16,7 @@ let
|
||||
mapAttrs'
|
||||
splitString
|
||||
toUpper
|
||||
optionals
|
||||
optional
|
||||
optionalAttrs
|
||||
nameValuePair
|
||||
;
|
||||
@@ -145,7 +145,7 @@ in
|
||||
LoadCredential = [
|
||||
"RESTIC_PASSWORD_FILE:${cfg.passwordFile}"
|
||||
]
|
||||
++ optionals (cfg.repositoryFile != null) [ "RESTIC_REPOSITORY:${cfg.repositoryFile}" ];
|
||||
++ optional (cfg.repositoryFile != null) [ "RESTIC_REPOSITORY:${cfg.repositoryFile}" ];
|
||||
};
|
||||
environment =
|
||||
let
|
||||
|
||||
@@ -152,7 +152,7 @@ in
|
||||
startCLIList
|
||||
++ lib.optionals (cfg.prometheusConfig != { }) [ "-promscrape.config=${prometheusConfigYml}" ]
|
||||
);
|
||||
LoadCredential = lib.optionals (cfg.remoteWrite.basicAuthPasswordFile != null) [
|
||||
LoadCredential = lib.optional (cfg.remoteWrite.basicAuthPasswordFile != null) [
|
||||
"remote_write_basic_auth_password:${cfg.remoteWrite.basicAuthPasswordFile}"
|
||||
];
|
||||
};
|
||||
|
||||
@@ -205,8 +205,8 @@ in
|
||||
"loose"
|
||||
]
|
||||
);
|
||||
default = "loose";
|
||||
defaultText = "loose";
|
||||
default = true;
|
||||
defaultText = lib.literalMD "`true` except if the iptables based firewall is in use and the kernel lacks rpfilter support";
|
||||
example = "loose";
|
||||
description = ''
|
||||
Performs a reverse path filter test on a packet. If a reply
|
||||
|
||||
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 ];
|
||||
};
|
||||
}
|
||||
@@ -254,7 +254,6 @@ in
|
||||
wantedBy = [ "sockets.target" ];
|
||||
socketConfig = {
|
||||
ListenDatagram = "${cfg.listen.ipAddress}:${toString cfg.listen.port}";
|
||||
ListenStream = "${cfg.listen.ipAddress}:${toString cfg.listen.port}";
|
||||
Service = "netfoil.service";
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -503,32 +503,9 @@ in
|
||||
|
||||
environment.systemPackages = [ cfg.pihole ];
|
||||
|
||||
services.logrotate.settings = {
|
||||
pihole-dnsmasq = {
|
||||
files = [ "${cfg.logDirectory}/pihole.log" ];
|
||||
frequency = "daily";
|
||||
create = "640 ${cfg.user} ${cfg.group}";
|
||||
rotate = 5;
|
||||
compress = true;
|
||||
delaycompress = true;
|
||||
# FTL keeps this log open; SIGUSR2 closes and reopens it after rotation.
|
||||
# https://docs.pi-hole.net/ftldns/signals/#sigusr2
|
||||
postrotate = ''
|
||||
${getExe' pkgs.systemd "systemctl"} kill --kill-whom=main --signal=USR2 pihole-ftl.service 2>/dev/null || true
|
||||
'';
|
||||
};
|
||||
|
||||
pihole-ftl = {
|
||||
files = [
|
||||
"${cfg.logDirectory}/FTL.log"
|
||||
"${cfg.logDirectory}/webserver.log"
|
||||
];
|
||||
frequency = "weekly";
|
||||
create = "640 ${cfg.user} ${cfg.group}";
|
||||
rotate = 3;
|
||||
compress = true;
|
||||
delaycompress = true;
|
||||
};
|
||||
services.logrotate.settings.pihole-ftl = {
|
||||
enable = true;
|
||||
files = [ "${cfg.logDirectory}/FTL.log" ];
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -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 [ ] ''
|
||||
|
||||
@@ -226,7 +226,7 @@ in
|
||||
The value of this option is an attribute set. Each attribute configures a
|
||||
{command}`wpa_supplicant` service, where the attribute name specifies
|
||||
the name of the interface that {command}`wpa_supplicant` operates on.
|
||||
The attribute name can be a space-separated list of interfaces.
|
||||
The attribute name can be a space separated list of interfaces.
|
||||
The attribute names `WLAN`, `LAN` and `DBUS`
|
||||
have a special meaning. `WLAN` and `LAN` are
|
||||
configurations for universal {command}`wpa_supplicant` service that is
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -168,7 +168,7 @@ in
|
||||
default = pkgs.writeText "userlist" (concatMapStrings (x: "${x}\n") cfg.userlist);
|
||||
defaultText = literalExpression ''pkgs.writeText "userlist" (concatMapStrings (x: "''${x}\n") cfg.userlist)'';
|
||||
description = ''
|
||||
Newline-separated list of names to be allowed/denied if {option}`userlistEnable`
|
||||
Newline separated list of names to be allowed/denied if {option}`userlistEnable`
|
||||
is `true`. Meaning see {option}`userlistDeny`.
|
||||
|
||||
The default is a file containing the users from {option}`userlist`.
|
||||
|
||||
@@ -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 ];
|
||||
}
|
||||
|
||||
@@ -45,17 +45,6 @@ in
|
||||
default = null;
|
||||
};
|
||||
|
||||
databaseEncryptionKeysFile = mkOption {
|
||||
description = ''
|
||||
Path to file containing encryption key(s) to encrypt target credentials stored in database.
|
||||
Should be a env-like file: `WARPGATE_ENCRYPTION_KEY=$(openssl rand -base64 32)`.
|
||||
If you are rotating key, move the old key to `WARPGATE_ENCRYPTION_KEY_OLD`.
|
||||
See [Encrypting credentials at rest](https://warpgate.null.page/encryption/).
|
||||
'';
|
||||
type = nullOr str;
|
||||
default = null;
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
description = "Warpgate configuration.";
|
||||
type = submodule {
|
||||
@@ -131,6 +120,18 @@ in
|
||||
]
|
||||
'';
|
||||
};
|
||||
recordings = {
|
||||
enable = mkOption {
|
||||
description = "Whether to enable session recording.";
|
||||
default = true;
|
||||
type = bool;
|
||||
};
|
||||
path = mkOption {
|
||||
description = "Path to store session recordings.";
|
||||
default = "/var/lib/warpgate/recordings";
|
||||
type = str;
|
||||
};
|
||||
};
|
||||
external_host = mkOption {
|
||||
description = ''
|
||||
Configure the domain name of this Warpgate instance.
|
||||
@@ -159,11 +160,6 @@ in
|
||||
default = "[::]:2222";
|
||||
type = str;
|
||||
};
|
||||
proxy_protocol = mkOption {
|
||||
description = "Accept HAProxy PROXY protocol v1/v2 headers from upstream reverse proxy.";
|
||||
default = false;
|
||||
type = bool;
|
||||
};
|
||||
external_host = mkOption {
|
||||
description = "The SSH listener is reachable via this domain name externally.";
|
||||
default = null;
|
||||
@@ -205,11 +201,6 @@ in
|
||||
default = "[::]:8888";
|
||||
type = str;
|
||||
};
|
||||
proxy_protocol = mkOption {
|
||||
description = "Accept HAProxy PROXY protocol v1/v2 headers from upstream reverse proxy.";
|
||||
default = false;
|
||||
type = bool;
|
||||
};
|
||||
external_host = mkOption {
|
||||
description = "The HTTP listener is reachable via this domain name externally.";
|
||||
default = null;
|
||||
@@ -279,88 +270,6 @@ in
|
||||
type = str;
|
||||
};
|
||||
};
|
||||
rdp = {
|
||||
enable = mkOption {
|
||||
description = "Whether to enable RDP listener.";
|
||||
default = false;
|
||||
type = bool;
|
||||
};
|
||||
listen = mkOption {
|
||||
description = "Listen endpoint of RDP listener.";
|
||||
default = "[::]:3389";
|
||||
type = str;
|
||||
};
|
||||
proxy_protocol = mkOption {
|
||||
description = "Accept HAProxy PROXY protocol v1/v2 headers from the listener's peer.";
|
||||
default = false;
|
||||
type = bool;
|
||||
};
|
||||
external_host = mkOption {
|
||||
description = "The RDP listener is reachable via this domain name externally.";
|
||||
default = null;
|
||||
type = nullOr str;
|
||||
};
|
||||
external_port = mkOption {
|
||||
description = "The RDP listener is reachable via this port externally.";
|
||||
default = null;
|
||||
type = nullOr str;
|
||||
};
|
||||
certificate = mkOption {
|
||||
description = "Path to RDP listener certificate.";
|
||||
default = "/var/lib/warpgate/tls.certificate.pem";
|
||||
type = str;
|
||||
};
|
||||
key = mkOption {
|
||||
description = "Path to RDP listener private key.";
|
||||
default = "/var/lib/warpgate/tls.key.pem";
|
||||
type = str;
|
||||
};
|
||||
};
|
||||
vnc = {
|
||||
enable = mkOption {
|
||||
description = "Whether to enable VNC listener.";
|
||||
default = false;
|
||||
type = bool;
|
||||
};
|
||||
listen = mkOption {
|
||||
description = "Listen endpoint of VNC listener.";
|
||||
default = "[::]:5900";
|
||||
type = str;
|
||||
};
|
||||
proxy_protocol = mkOption {
|
||||
description = "Accept HAProxy PROXY protocol v1/v2 headers from the listener's peer.";
|
||||
default = false;
|
||||
type = bool;
|
||||
};
|
||||
external_host = mkOption {
|
||||
description = "The VNC listener is reachable via this domain name externally.";
|
||||
default = null;
|
||||
type = nullOr str;
|
||||
};
|
||||
external_port = mkOption {
|
||||
description = "The VNC listener is reachable via this port externally.";
|
||||
default = null;
|
||||
type = nullOr str;
|
||||
};
|
||||
certificate = mkOption {
|
||||
description = "Path to VNC listener certificate.";
|
||||
default = "/var/lib/warpgate/tls.certificate.pem";
|
||||
type = str;
|
||||
};
|
||||
key = mkOption {
|
||||
description = "Path to VNC listener private key.";
|
||||
default = "/var/lib/warpgate/tls.key.pem";
|
||||
type = str;
|
||||
};
|
||||
enable_ard_auth = mkOption {
|
||||
description = ''
|
||||
Enable Apple-DH (Apple Remote Desktop / type 30) auth, which is to ensure compatibility with Apple clients.
|
||||
However [connections from macOS built-in VNC client with ARD auth is not supported](https://github.com/warp-tech/warpgate/blob/47e676969a0b1e0b8456f9a5f1474d6c58648c4f/warpgate-protocol-vnc/src/server/rfb.rs#L8-L10).
|
||||
'';
|
||||
default = false;
|
||||
type = bool;
|
||||
};
|
||||
};
|
||||
mysql = {
|
||||
enable = mkOption {
|
||||
description = "Whether to enable MySQL listener.";
|
||||
@@ -372,11 +281,6 @@ in
|
||||
default = "[::]:33306";
|
||||
type = str;
|
||||
};
|
||||
proxy_protocol = mkOption {
|
||||
description = "Accept HAProxy PROXY protocol v1/v2 headers from upstream reverse proxy.";
|
||||
default = false;
|
||||
type = bool;
|
||||
};
|
||||
external_host = mkOption {
|
||||
description = "The MySQL listener is reachable via this domain name externally.";
|
||||
default = null;
|
||||
@@ -397,14 +301,6 @@ in
|
||||
default = "/var/lib/warpgate/tls.key.pem";
|
||||
type = str;
|
||||
};
|
||||
advertised_version = mkOption {
|
||||
description = ''
|
||||
The server version advertised to clients during the handshake.
|
||||
Warpgate can't auto-match the target's version since the target is only known after the handshake, but Warpgate's clients use it to pick a protocol dialect.
|
||||
'';
|
||||
default = "8.0.3-Warpgate";
|
||||
type = str;
|
||||
};
|
||||
};
|
||||
postgres = {
|
||||
enable = mkOption {
|
||||
@@ -417,11 +313,6 @@ in
|
||||
default = "[::]:55432";
|
||||
type = str;
|
||||
};
|
||||
proxy_protocol = mkOption {
|
||||
description = "Accept HAProxy PROXY protocol v1/v2 headers from upstream reverse proxy.";
|
||||
default = false;
|
||||
type = bool;
|
||||
};
|
||||
external_host = mkOption {
|
||||
description = "The PostgreSQL listener is reachable via this domain name externally.";
|
||||
default = null;
|
||||
@@ -454,11 +345,6 @@ in
|
||||
default = "[::]:8443";
|
||||
type = str;
|
||||
};
|
||||
proxy_protocol = mkOption {
|
||||
description = "Accept HAProxy PROXY protocol v1/v2 headers from upstream reverse proxy.";
|
||||
default = false;
|
||||
type = bool;
|
||||
};
|
||||
external_host = mkOption {
|
||||
description = "The Kubernetes listener is reachable via this domain name externally.";
|
||||
default = null;
|
||||
@@ -534,45 +420,36 @@ in
|
||||
any
|
||||
map
|
||||
head
|
||||
optional
|
||||
reverseList
|
||||
;
|
||||
inherit (lib.strings)
|
||||
optionalString
|
||||
splitString
|
||||
toIntBase10
|
||||
;
|
||||
inherit (lib.strings) splitString toIntBase10;
|
||||
|
||||
renderedYamlConfig = yaml.generate "warpgate-config" cfg.settings;
|
||||
|
||||
startupScript = pkgs.writeShellScript "warpgate-run" ''
|
||||
CFGFILE=$STATE_DIRECTORY/config.yaml
|
||||
preStartScript = pkgs.writers.writeBash "warpgate-init" ''
|
||||
CFGFILE=/var/lib/warpgate/config.yaml
|
||||
if [ ! -O $CFGFILE ] || [ ! -s $CFGFILE ]; then
|
||||
INITPWD=$(tr -dc 'A-Za-z0-9!?%=' </dev/urandom 2>/dev/null | head -c 16)
|
||||
${lib.getExe cfg.package} \
|
||||
--config $CFGFILE unattended-setup \
|
||||
--data-path $STATE_DIRECTORY \
|
||||
--data-path /var/lib/warpgate \
|
||||
--http-port 8888 \
|
||||
--admin-password $INITPWD
|
||||
fi
|
||||
cp --no-preserve=ownership ${renderedYamlConfig} $CFGFILE
|
||||
${optionalString (cfg.databaseUrlFile != null) ''
|
||||
sed -e '/^database_url: null/d' ${renderedYamlConfig} > $CFGFILE
|
||||
cat $CREDENTIALS_DIRECTORY/databaseUrl >> $CFGFILE
|
||||
''}
|
||||
${optionalString (cfg.databaseEncryptionKeysFile != null) ''
|
||||
set -a
|
||||
source $CREDENTIALS_DIRECTORY/dbEncryptionKeys
|
||||
set +a
|
||||
''}
|
||||
${lib.getExe cfg.package} --config $CFGFILE run
|
||||
${
|
||||
if cfg.databaseUrlFile != null then
|
||||
''
|
||||
sed -e '/^database_url: null/d' ${yaml.generate "warpgate-config" cfg.settings} > $CFGFILE
|
||||
cat /run/credentials/warpgate.service/databaseUrl >> $CFGFILE
|
||||
''
|
||||
else
|
||||
"cp --no-preserve=ownership ${yaml.generate "warpgate-config" cfg.settings} $CFGFILE"
|
||||
}
|
||||
'';
|
||||
bindOnPrivilegedPorts = any (x: toIntBase10 x < 1025) (
|
||||
map (x: head (reverseList (splitString ":" x))) (
|
||||
[ cfg.settings.http.listen ]
|
||||
++ optional cfg.settings.ssh.enable cfg.settings.ssh.listen
|
||||
++ optional cfg.settings.mysql.enable cfg.settings.mysql.listen
|
||||
++ optional cfg.settings.postgres.enable cfg.settings.postgres.listen
|
||||
++ lib.optional cfg.settings.ssh.enable cfg.settings.ssh.listen
|
||||
++ lib.optional cfg.settings.mysql.enable cfg.settings.mysql.listen
|
||||
++ lib.optional cfg.settings.postgres.enable cfg.settings.postgres.listen
|
||||
)
|
||||
);
|
||||
in
|
||||
@@ -590,10 +467,6 @@ in
|
||||
assertion = !(lib.hasAttr "config_provider" cfg.settings);
|
||||
message = "`services.warpgate.settings.config_provider` is a legacy option that has been removed since 0.14.0. Please do not set this option.";
|
||||
}
|
||||
{
|
||||
assertion = !(lib.hasAttr "recordings" cfg.settings);
|
||||
message = "`services.warpgate.settings.recordings` has been deprecated by S3 recording storage support in 0.27.0. Please remove this section from your config and set it from admin UI.";
|
||||
}
|
||||
];
|
||||
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
@@ -601,16 +474,14 @@ in
|
||||
systemd.services.warpgate = {
|
||||
description = "Warpgate smart bastion";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
after = [ "network.target" ];
|
||||
startLimitBurst = 5;
|
||||
serviceConfig = {
|
||||
LoadCredential =
|
||||
optional (cfg.databaseUrlFile != null) "databaseUrl:${cfg.databaseUrlFile}"
|
||||
++ optional (
|
||||
cfg.databaseEncryptionKeysFile != null
|
||||
) "dbEncryptionKeys:${cfg.databaseEncryptionKeysFile}";
|
||||
ExecStart = startupScript;
|
||||
LoadCredential = "${
|
||||
if cfg.databaseUrlFile != null then "databaseUrl:${cfg.databaseUrlFile}" else ""
|
||||
}";
|
||||
ExecStartPre = preStartScript;
|
||||
ExecStart = "${lib.getExe cfg.package} --config /var/lib/warpgate/config.yaml run";
|
||||
DynamicUser = true;
|
||||
RestartSec = 3;
|
||||
Restart = "on-failure";
|
||||
|
||||
@@ -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 ];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -40,21 +40,7 @@ in
|
||||
options.services.grav = {
|
||||
enable = mkEnableOption "grav";
|
||||
|
||||
package =
|
||||
let
|
||||
stateVersionAtLeast = lib.versionAtLeast config.system.stateVersion;
|
||||
in
|
||||
mkPackageOption pkgs "grav" {
|
||||
default = if stateVersionAtLeast "26.11" then "grav_2" else "grav";
|
||||
}
|
||||
// {
|
||||
defaultText = lib.literalExpression ''
|
||||
if versionAtLeast config.system.stateVersion "26.11" then
|
||||
pkgs.grav_2
|
||||
else
|
||||
pkgs.grav
|
||||
'';
|
||||
};
|
||||
package = mkPackageOption pkgs "grav" { };
|
||||
|
||||
root = mkOption {
|
||||
type = types.path;
|
||||
|
||||
@@ -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)) ];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -133,7 +133,7 @@ in
|
||||
# if data_dir is a list, the actual path will in in the `path` attribute of each item
|
||||
# see https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#data_dir
|
||||
++ lib.optional (lib.isList data_dir) (map (item: item.path) data_dir)
|
||||
++ lib.optionals (lib.isString data_dir) [ data_dir ]
|
||||
++ lib.optional (lib.isString data_dir) [ data_dir ]
|
||||
);
|
||||
isDefault = lib.hasPrefix "/var/lib/garage";
|
||||
isDefaultStateDirectory = lib.any isDefault paths;
|
||||
|
||||
@@ -195,11 +195,6 @@ let
|
||||
error_log ${cfg.logError};
|
||||
daemon off;
|
||||
|
||||
# load_module is a main-context directive that must precede events{}/http{}.
|
||||
${optionalString (
|
||||
(cfg.package.dynamicModules or [ ]) != [ ]
|
||||
) "include ${cfg.package}/etc/nginx/dynamic-modules.conf;"}
|
||||
|
||||
${optionalString cfg.enableQuicBPF ''
|
||||
quic_bpf on;
|
||||
''}
|
||||
@@ -1647,7 +1642,7 @@ in
|
||||
SystemCallFilter = [
|
||||
"~@cpu-emulation @debug @keyring @mount @obsolete @privileged @setuid"
|
||||
]
|
||||
++ optionals cfg.enableQuicBPF [ "bpf" ];
|
||||
++ optional cfg.enableQuicBPF [ "bpf" ];
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -271,7 +271,7 @@ in
|
||||
"~CAP_SYS_BOOT"
|
||||
"~CAP_NET_ADMIN"
|
||||
]
|
||||
++ lib.lists.optionals (!cfg.allowCgiUser) [
|
||||
++ lib.lists.optional (!cfg.allowCgiUser) [
|
||||
"~CAP_SETGID"
|
||||
"~CAP_SETUID"
|
||||
];
|
||||
@@ -279,7 +279,7 @@ in
|
||||
SystemCallFilter = [
|
||||
"~@cpu-emulation @debug @keyring @mount @obsolete"
|
||||
]
|
||||
++ lib.lists.optionals (!cfg.allowCgiUser) [ "@privileged @setuid" ];
|
||||
++ lib.lists.optional (!cfg.allowCgiUser) [ "@privileged @setuid" ];
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -70,7 +70,9 @@ let
|
||||
];
|
||||
|
||||
in
|
||||
if val == "y" || val == "m" || val == "n" then
|
||||
if (val == "") then
|
||||
"\"\""
|
||||
else if val == "y" || val == "m" || val == "n" then
|
||||
val
|
||||
else if all isNumber (stringToCharacters val) then
|
||||
val
|
||||
@@ -85,9 +87,6 @@ let
|
||||
# VIRTIO_BLK y
|
||||
# VIRTIO_CONSOLE n
|
||||
# NET_9P_VIRTIO? y
|
||||
# LOCALVERSION
|
||||
#
|
||||
# A line without a value means an empty string.
|
||||
#
|
||||
# Borrowed from copumpkin https://github.com/NixOS/nixpkgs/pull/12158
|
||||
# returns a string, expr should be an attribute set
|
||||
@@ -101,8 +100,9 @@ let
|
||||
let
|
||||
val = if item.freeform != null then item.freeform else item.tristate;
|
||||
in
|
||||
optionalString (val != null)
|
||||
"${key}${optionalString item.optional "?"}${optionalString (val != "") " ${mkValue val}"}\n";
|
||||
optionalString (val != null) (
|
||||
if (item.optional) then "${key}? ${mkValue val}\n" else "${key} ${mkValue val}\n"
|
||||
);
|
||||
|
||||
mkConf = cfg: concatStrings (mapAttrsToList mkConfigLine cfg);
|
||||
in
|
||||
@@ -119,7 +119,6 @@ in
|
||||
example = ''
|
||||
USB? y
|
||||
DEBUG n
|
||||
LOCALVERSION
|
||||
'';
|
||||
description = ''
|
||||
The result of converting the structured kernel configuration in settings
|
||||
|
||||
@@ -360,7 +360,7 @@ in
|
||||
default = null;
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
description = ''
|
||||
A semicolon-separated array of 8 colors in the format RRGGBB:
|
||||
A ; seperated array of 8 colors in the format RRGGBB:
|
||||
black, red, green, brown, blue, magenta, cyan, and gray.
|
||||
'';
|
||||
};
|
||||
@@ -369,7 +369,7 @@ in
|
||||
default = null;
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
description = ''
|
||||
A semicolon-separated array of 8 colors in the format RRGGBB:
|
||||
A ; seperated array of 8 colors in the format RRGGBB:
|
||||
dark gray, bright red, bright green, yellow, bright blue, bright magenta, bright cyan, and white.
|
||||
'';
|
||||
};
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
options,
|
||||
pkgs,
|
||||
utils,
|
||||
...
|
||||
@@ -202,12 +201,8 @@ let
|
||||
--notify-ready=yes \
|
||||
--kill-signal=SIGRTMIN+3 \
|
||||
--bind-ro=/nix/store:/nix/store$NIX_BIND_OPT \
|
||||
${optionalString config.nix.enable "--bind-ro=/nix/var/nix/db:/nix/var/nix/db$NIX_BIND_OPT"} \
|
||||
${
|
||||
optionalString (
|
||||
config.nix.enable && config.nix.daemon.enable
|
||||
) "--bind-ro=/nix/var/nix/daemon-socket:/nix/var/nix/daemon-socket$NIX_BIND_OPT"
|
||||
} \
|
||||
--bind-ro=/nix/var/nix/db:/nix/var/nix/db$NIX_BIND_OPT \
|
||||
--bind-ro=/nix/var/nix/daemon-socket:/nix/var/nix/daemon-socket$NIX_BIND_OPT \
|
||||
--bind="/nix/var/nix/profiles/per-container/$INSTANCE:/nix/var/nix/profiles$NIX_BIND_OPT" \
|
||||
--bind="/nix/var/nix/gcroots/per-container/$INSTANCE:/nix/var/nix/gcroots$NIX_BIND_OPT" \
|
||||
${optionalString (!cfg.ephemeral) "--link-journal=try-guest"} \
|
||||
@@ -999,10 +994,7 @@ in
|
||||
mapper =
|
||||
name: cfg:
|
||||
optional (cfg.networkNamespace != null && (cfg.privateNetwork || cfg.interfaces != [ ]))
|
||||
"containers.${name}.networkNamespace is mutally exclusive to containers.${name}.privateNetwork and containers.${name}.interfaces."
|
||||
++
|
||||
optional (cfg.config.nix.enable && cfg.config.nix.daemon.enable && !config.nix.daemon.enable)
|
||||
"${options.containers}.${strings.escapeNixIdentifier name} requires a Nix daemon but the host does not provided it, as option ${options.nix.daemon.enable} is disabled";
|
||||
"containers.${name}.networkNamespace is mutally exclusive to containers.${name}.privateNetwork and containers.${name}.interfaces.";
|
||||
in
|
||||
mkMerge (mapAttrsToList mapper config.containers);
|
||||
}
|
||||
|
||||
@@ -80,7 +80,6 @@ in
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
requires = [ "ovsdb.socket" ];
|
||||
after = [ "ovsdb.socket" ];
|
||||
wants = [ "ovs-delete-transient-ports.service" ];
|
||||
path = [ cfg.package ];
|
||||
restartTriggers = [
|
||||
db
|
||||
@@ -130,19 +129,6 @@ in
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.ovs-delete-transient-ports = {
|
||||
description = "Open vSwitch Delete Transient Ports";
|
||||
after = [ "ovsdb.service" ];
|
||||
before = [ "ovs-vswitchd.service" ];
|
||||
path = [ cfg.package ];
|
||||
unitConfig.AssertPathExists = "${runDir}/db.sock";
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStart = "${cfg.package}/share/openvswitch/scripts/ovs-ctl delete-transient-ports";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.ovs-vswitchd = {
|
||||
description = "Open_vSwitch Daemon";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
@@ -324,24 +324,6 @@ let
|
||||
''
|
||||
)}
|
||||
|
||||
echo "Starting virtiofs daemons..."
|
||||
NIX_VIRTIOFS_DIR=$(mktemp -d)
|
||||
${lib.concatLines (
|
||||
lib.mapAttrsToList (tag: share: ''
|
||||
${lib.getExe hostPkgs.virtiofsd} \
|
||||
--socket-path="$NIX_VIRTIOFS_DIR"/"${tag}" \
|
||||
--shared-dir="${share.source}" \
|
||||
${if share.writable then "--writeback" else "--readonly"} \
|
||||
--sandbox=none \
|
||||
--seccomp=none \
|
||||
--cache=always \
|
||||
--no-announce-submounts \
|
||||
--translate-uid=host:65534:0:1 \
|
||||
--translate-gid=host:65534:0:1 \
|
||||
&
|
||||
'') cfg.sharedDirectories
|
||||
)}
|
||||
|
||||
# Start QEMU.
|
||||
exec ${
|
||||
qemu-common.qemuBinaryWith {
|
||||
@@ -354,6 +336,14 @@ let
|
||||
-smp ${toString config.virtualisation.cores} \
|
||||
-device virtio-rng-pci \
|
||||
${concatStringsSep " " config.virtualisation.qemu.networkingOptions} \
|
||||
${
|
||||
concatStringsSep " \\\n " (
|
||||
mapAttrsToList (
|
||||
tag: share:
|
||||
"-virtfs local,path=${share.source},security_model=${share.securityModel},mount_tag=${tag}"
|
||||
) config.virtualisation.sharedDirectories
|
||||
)
|
||||
} \
|
||||
${drivesCmdLine config.virtualisation.qemu.drives} \
|
||||
${concatStringsSep " \\\n " config.virtualisation.qemu.options} \
|
||||
$QEMU_OPTS \
|
||||
@@ -434,14 +424,6 @@ in
|
||||
"virtualisation"
|
||||
"useSecureBoot"
|
||||
] "The default OVMF now always supports Secure Boot.")
|
||||
(mkRemovedOptionModule [
|
||||
"virtualisation"
|
||||
"msize"
|
||||
] "9p was replaced with virtiofs and thus this option is obsolete.")
|
||||
(mkRemovedOptionModule [
|
||||
"virtualisation"
|
||||
"nixStore9pCache"
|
||||
] "9p was replaced with virtiofs and thus this option is obsolete.")
|
||||
];
|
||||
|
||||
options = {
|
||||
@@ -456,6 +438,16 @@ in
|
||||
'';
|
||||
};
|
||||
|
||||
virtualisation.msize = mkOption {
|
||||
type = types.ints.positive;
|
||||
default = 16384;
|
||||
description = ''
|
||||
The msize (maximum packet size) option passed to 9p file systems, in
|
||||
bytes. Increasing this should increase performance significantly,
|
||||
at the cost of higher RAM usage.
|
||||
'';
|
||||
};
|
||||
|
||||
virtualisation.diskImage = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = "./${config.system.name}.qcow2";
|
||||
@@ -578,8 +570,22 @@ in
|
||||
type = types.path;
|
||||
description = "The mount point of the directory inside the virtual machine";
|
||||
};
|
||||
options.writable = lib.mkEnableOption "" // {
|
||||
description = "Whether the directory is writable on the host and guest.";
|
||||
options.securityModel = mkOption {
|
||||
type = types.enum [
|
||||
"passthrough"
|
||||
"mapped-xattr"
|
||||
"mapped-file"
|
||||
"none"
|
||||
];
|
||||
default = "mapped-xattr";
|
||||
description = ''
|
||||
The security model to use for this share:
|
||||
|
||||
- `passthrough`: files are stored using the same credentials as they are created on the guest (this requires QEMU to run as root)
|
||||
- `mapped-xattr`: some of the file attributes like uid, gid, mode bits and link target are stored as file attributes
|
||||
- `mapped-file`: the attributes are stored in the hidden .virtfs_metadata directory. Directories exported by this security model cannot interact with other unix tools
|
||||
- `none`: same as "passthrough" except the sever won't report failures if it fails to set file attributes like ownership
|
||||
'';
|
||||
};
|
||||
}
|
||||
);
|
||||
@@ -604,10 +610,11 @@ in
|
||||
A list of paths whose closure should be made available to
|
||||
the VM.
|
||||
|
||||
When the Nix store is mounted from the host, the closure is registered
|
||||
in the Nix database in the VM. All other paths in the host Nix store
|
||||
appear in the guest Nix store as well, but are considered garbage
|
||||
(because they are not registered in the Nix database of the guest).
|
||||
When 9p is used, the closure is registered in the Nix
|
||||
database in the VM. All other paths in the host Nix store
|
||||
appear in the guest Nix store as well, but are considered
|
||||
garbage (because they are not registered in the Nix
|
||||
database of the guest).
|
||||
|
||||
When {option}`virtualisation.useNixStoreImage` is
|
||||
set, the closure is copied to the Nix store image.
|
||||
@@ -860,7 +867,7 @@ in
|
||||
default = false;
|
||||
description = ''
|
||||
Build and use a disk image for the Nix store, instead of
|
||||
accessing the host's one.
|
||||
accessing the host's one through 9p.
|
||||
|
||||
For applications which do a lot of reads from the store,
|
||||
this can drastically improve performance, but at the cost of
|
||||
@@ -882,7 +889,24 @@ in
|
||||
default = !cfg.useNixStoreImage && !cfg.useBootLoader;
|
||||
defaultText = literalExpression "!cfg.useNixStoreImage && !cfg.useBootLoader";
|
||||
description = ''
|
||||
Mount the host Nix store via a virtual filesystem.
|
||||
Mount the host Nix store as a 9p mount.
|
||||
'';
|
||||
};
|
||||
|
||||
virtualisation.nixStore9pCache = mkOption {
|
||||
type = types.enum [
|
||||
"loose"
|
||||
"none"
|
||||
"fscache"
|
||||
];
|
||||
default = "loose";
|
||||
description = ''
|
||||
Type of 9p cache to use when mounting host nix store. "none" provides
|
||||
no caching. "loose" enables Linux's local VFS cache. "fscache" uses Linux's
|
||||
fscache subsystem.
|
||||
|
||||
This option is only respected when {option}`virtualisation.mountHostNixStore`
|
||||
is enabled.
|
||||
'';
|
||||
};
|
||||
|
||||
@@ -1218,20 +1242,22 @@ in
|
||||
# Always mount this to /nix/.ro-store because we never want to actually
|
||||
# write to the host Nix Store.
|
||||
target = "/nix/.ro-store";
|
||||
securityModel = "none";
|
||||
};
|
||||
xchg = {
|
||||
source = ''"$TMPDIR"/xchg'';
|
||||
securityModel = "none";
|
||||
target = "/tmp/xchg";
|
||||
writable = true;
|
||||
};
|
||||
shared = {
|
||||
source = ''"''${SHARED_DIR:-$TMPDIR/xchg}"'';
|
||||
target = "/tmp/shared";
|
||||
writable = true;
|
||||
securityModel = "none";
|
||||
};
|
||||
certs = mkIf cfg.useHostCerts {
|
||||
source = ''"$TMPDIR"/certs'';
|
||||
target = "/etc/ssl/certs";
|
||||
securityModel = "none";
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1277,12 +1303,6 @@ in
|
||||
"-object memory-backend-memfd,id=mem0,size=${toString config.virtualisation.memorySize}M,share=on"
|
||||
"-machine memory-backend=mem0"
|
||||
])
|
||||
(lib.flatten (
|
||||
lib.mapAttrsToList (tag: share: [
|
||||
"-chardev socket,id=${tag},path=$NIX_VIRTIOFS_DIR/${tag}"
|
||||
"-device vhost-user-fs-pci,chardev=${tag},tag=${tag}"
|
||||
]) cfg.sharedDirectories
|
||||
))
|
||||
(
|
||||
let
|
||||
alphaNumericChars = lowerChars ++ upperChars ++ (map toString (range 0 9));
|
||||
@@ -1368,78 +1388,86 @@ in
|
||||
|
||||
virtualisation.diskSizeAutoSupported = false;
|
||||
|
||||
virtualisation.fileSystems = lib.mkMerge [
|
||||
(lib.mapAttrs' (tag: share: {
|
||||
name = share.target;
|
||||
value = {
|
||||
device = tag;
|
||||
fsType = "virtiofs";
|
||||
neededForBoot = true;
|
||||
options = lib.mkIf (!share.writable) [ "ro" ];
|
||||
virtualisation.fileSystems =
|
||||
let
|
||||
mkSharedDir = tag: share: {
|
||||
name = share.target;
|
||||
value.device = tag;
|
||||
value.fsType = "9p";
|
||||
value.neededForBoot = true;
|
||||
value.options = [
|
||||
"trans=virtio"
|
||||
"version=9p2000.L"
|
||||
"msize=${toString cfg.msize}"
|
||||
"x-systemd.requires=modprobe@9pnet_virtio.service"
|
||||
]
|
||||
++ lib.optional (tag == "nix-store") "cache=${cfg.nixStore9pCache}";
|
||||
};
|
||||
}) cfg.sharedDirectories)
|
||||
{
|
||||
"/" = lib.mkIf cfg.useDefaultFilesystems (
|
||||
if cfg.diskImage == null then
|
||||
{
|
||||
device = "tmpfs";
|
||||
fsType = "tmpfs";
|
||||
options = [ "mode=755" ];
|
||||
}
|
||||
else
|
||||
{
|
||||
device = cfg.rootDevice;
|
||||
fsType = "ext4";
|
||||
}
|
||||
);
|
||||
"/tmp" = lib.mkIf config.boot.tmp.useTmpfs {
|
||||
device = "tmpfs";
|
||||
fsType = "tmpfs";
|
||||
neededForBoot = true;
|
||||
# Sync with systemd's tmp.mount;
|
||||
options = [
|
||||
"mode=1777"
|
||||
"strictatime"
|
||||
"nosuid"
|
||||
"nodev"
|
||||
"size=${toString config.boot.tmp.tmpfsSize}"
|
||||
];
|
||||
};
|
||||
"/nix/store" = lib.mkIf (cfg.useNixStoreImage || cfg.mountHostNixStore) (
|
||||
if cfg.writableStore then
|
||||
{
|
||||
overlay = {
|
||||
lowerdir = [ "/nix/.ro-store" ];
|
||||
upperdir = "/nix/.rw-store/upper";
|
||||
workdir = "/nix/.rw-store/work";
|
||||
in
|
||||
lib.mkMerge [
|
||||
(lib.mapAttrs' mkSharedDir cfg.sharedDirectories)
|
||||
{
|
||||
"/" = lib.mkIf cfg.useDefaultFilesystems (
|
||||
if cfg.diskImage == null then
|
||||
{
|
||||
device = "tmpfs";
|
||||
fsType = "tmpfs";
|
||||
options = [ "mode=755" ];
|
||||
}
|
||||
else
|
||||
{
|
||||
device = cfg.rootDevice;
|
||||
fsType = "ext4";
|
||||
}
|
||||
);
|
||||
"/tmp" = lib.mkIf config.boot.tmp.useTmpfs {
|
||||
device = "tmpfs";
|
||||
fsType = "tmpfs";
|
||||
neededForBoot = true;
|
||||
# Sync with systemd's tmp.mount;
|
||||
options = [
|
||||
"mode=1777"
|
||||
"strictatime"
|
||||
"nosuid"
|
||||
"nodev"
|
||||
"size=${toString config.boot.tmp.tmpfsSize}"
|
||||
];
|
||||
};
|
||||
"/nix/store" = lib.mkIf (cfg.useNixStoreImage || cfg.mountHostNixStore) (
|
||||
if cfg.writableStore then
|
||||
{
|
||||
overlay = {
|
||||
lowerdir = [ "/nix/.ro-store" ];
|
||||
upperdir = "/nix/.rw-store/upper";
|
||||
workdir = "/nix/.rw-store/work";
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
device = "/nix/.ro-store";
|
||||
fsType = "none";
|
||||
options = [ "bind" ];
|
||||
}
|
||||
);
|
||||
"/nix/.ro-store" = lib.mkIf cfg.useNixStoreImage {
|
||||
device = "/dev/disk/by-label/${nixStoreFilesystemLabel}";
|
||||
fsType = "erofs";
|
||||
neededForBoot = true;
|
||||
options = [ "ro" ];
|
||||
};
|
||||
"/nix/.rw-store" = lib.mkIf (cfg.writableStore && cfg.writableStoreUseTmpfs) {
|
||||
fsType = "tmpfs";
|
||||
options = [ "mode=0755" ];
|
||||
neededForBoot = true;
|
||||
};
|
||||
"${config.boot.loader.efi.efiSysMountPoint}" =
|
||||
lib.mkIf (cfg.useBootLoader && cfg.bootPartition != null)
|
||||
{
|
||||
device = cfg.bootPartition;
|
||||
fsType = "vfat";
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
device = "/nix/.ro-store";
|
||||
fsType = "none";
|
||||
options = [ "bind" ];
|
||||
}
|
||||
);
|
||||
"/nix/.ro-store" = lib.mkIf cfg.useNixStoreImage {
|
||||
device = "/dev/disk/by-label/${nixStoreFilesystemLabel}";
|
||||
fsType = "erofs";
|
||||
neededForBoot = true;
|
||||
options = [ "ro" ];
|
||||
};
|
||||
"/nix/.rw-store" = lib.mkIf (cfg.writableStore && cfg.writableStoreUseTmpfs) {
|
||||
fsType = "tmpfs";
|
||||
options = [ "mode=0755" ];
|
||||
neededForBoot = true;
|
||||
};
|
||||
"${config.boot.loader.efi.efiSysMountPoint}" =
|
||||
lib.mkIf (cfg.useBootLoader && cfg.bootPartition != null)
|
||||
{
|
||||
device = cfg.bootPartition;
|
||||
fsType = "vfat";
|
||||
};
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
swapDevices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) [ ];
|
||||
boot.initrd.luks.devices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) { };
|
||||
|
||||
@@ -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}")
|
||||
'';
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user