0
0
Fork 0
mirror of https://github.com/NixOS/nixpkgs.git synced 2025-07-13 21:50:33 +03:00

Merge branch 'NixOS:master' into jxd/add-config

This commit is contained in:
Jia Xiaodong 2024-04-03 20:29:53 +08:00 committed by GitHub
commit a19bf3e045
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2026 changed files with 89111 additions and 27436 deletions

View file

@ -0,0 +1,45 @@
{ config, pkgs, lib, utils, ... }:
let
cfg = config.services.docuum;
inherit (lib) mkIf mkEnableOption mkOption getExe types;
in
{
options.services.docuum = {
enable = mkEnableOption "docuum daemon";
threshold = mkOption {
description = "Threshold for deletion in bytes, like `10 GB`, `10 GiB`, `10GB` or percentage-based thresholds like `50%`";
type = types.str;
default = "10 GB";
example = "50%";
};
};
config = mkIf cfg.enable {
assertions = [
{
assertion = config.virtualisation.docker.enable;
message = "docuum requires docker on the host";
}
];
systemd.services.docuum = {
after = [ "docker.socket" ];
requires = [ "docker.socket" ];
wantedBy = [ "multi-user.target" ];
path = [ config.virtualisation.docker.package ];
environment.HOME = "/var/lib/docuum";
serviceConfig = {
DynamicUser = true;
StateDirectory = "docuum";
SupplementaryGroups = [ "docker" ];
ExecStart = utils.escapeSystemdExecArgs [
(getExe pkgs.docuum)
"--threshold" cfg.threshold
];
};
};
};
}

View file

@ -9,6 +9,7 @@ in {
options = {
services.roon-server = {
enable = mkEnableOption (lib.mdDoc "Roon Server");
package = lib.mkPackageOption pkgs "roon-server" { };
openFirewall = mkOption {
type = types.bool;
default = false;
@ -43,7 +44,7 @@ in {
environment.ROON_ID_DIR = "/var/lib/${name}";
serviceConfig = {
ExecStart = "${pkgs.roon-server}/bin/RoonServer";
ExecStart = "${lib.getExe cfg.package}";
LimitNOFILE = 8192;
User = cfg.user;
Group = cfg.group;

View file

@ -21,22 +21,21 @@ A complete list of options for the Borgbase module may be found
## Basic usage for a local backup {#opt-services-backup-borgbackup-local-directory}
A very basic configuration for backing up to a locally accessible directory is:
```
```nix
{
opt.services.borgbackup.jobs = {
{ rootBackup = {
paths = "/";
exclude = [ "/nix" "/path/to/local/repo" ];
repo = "/path/to/local/repo";
doInit = true;
encryption = {
mode = "repokey";
passphrase = "secret";
};
compression = "auto,lzma";
startAt = "weekly";
rootBackup = {
paths = "/";
exclude = [ "/nix" "/path/to/local/repo" ];
repo = "/path/to/local/repo";
doInit = true;
encryption = {
mode = "repokey";
passphrase = "secret";
};
}
compression = "auto,lzma";
startAt = "weekly";
};
};
}
```
@ -59,7 +58,7 @@ ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID78zmOyA+5uPG4Ot0hfAy+sLDPU1L4AiIoRYEIVbbQ/
```
Add the following snippet to your NixOS configuration:
```
```nix
{
services.borgbackup.repos = {
my_borg_repo = {
@ -80,7 +79,7 @@ that you have stored a secret passphrasse in the file
{file}`/run/keys/borgbackup_passphrase`, which should be only
accessible by root
```
```nix
{
services.borgbackup.jobs = {
backupToLocalServer = {
@ -96,7 +95,7 @@ accessible by root
startAt = "hourly";
};
};
};
}
```
The following few commands (run as root) let you test your backup.

View file

@ -15,9 +15,11 @@ key-value store.
To enable FoundationDB, add the following to your
{file}`configuration.nix`:
```
services.foundationdb.enable = true;
services.foundationdb.package = pkgs.foundationdb71; # FoundationDB 7.1.x
```nix
{
services.foundationdb.enable = true;
services.foundationdb.package = pkgs.foundationdb71; # FoundationDB 7.1.x
}
```
The {option}`services.foundationdb.package` option is required, and
@ -109,8 +111,10 @@ default configuration. See below for more on scaling to increase this.
FoundationDB stores all data for all server processes under
{file}`/var/lib/foundationdb`. You can override this using
{option}`services.foundationdb.dataDir`, e.g.
```
services.foundationdb.dataDir = "/data/fdb";
```nix
{
services.foundationdb.dataDir = "/data/fdb";
}
```
Similarly, logs are stored under {file}`/var/log/foundationdb`
@ -265,8 +269,10 @@ directories.
For example, to create backups in {command}`/opt/fdb-backups`, first
set up the paths in the module options:
```
services.foundationdb.extraReadWritePaths = [ "/opt/fdb-backups" ];
```nix
{
services.foundationdb.extraReadWritePaths = [ "/opt/fdb-backups" ];
}
```
Restart the FoundationDB service, and it will now be able to write to this

View file

@ -15,9 +15,11 @@ PostgreSQL is an advanced, free relational database.
## Configuring {#module-services-postgres-configuring}
To enable PostgreSQL, add the following to your {file}`configuration.nix`:
```
services.postgresql.enable = true;
services.postgresql.package = pkgs.postgresql_15;
```nix
{
services.postgresql.enable = true;
services.postgresql.package = pkgs.postgresql_15;
}
```
Note that you are required to specify the desired version of PostgreSQL (e.g. `pkgs.postgresql_15`). Since upgrading your PostgreSQL version requires a database dump and reload (see below), NixOS cannot provide a default value for [](#opt-services.postgresql.package) such as the most recent release of PostgreSQL.
@ -35,8 +37,10 @@ alice=>
-->
By default, PostgreSQL stores its databases in {file}`/var/lib/postgresql/$psqlSchema`. You can override this using [](#opt-services.postgresql.dataDir), e.g.
```
services.postgresql.dataDir = "/data/postgresql";
```nix
{
services.postgresql.dataDir = "/data/postgresql";
}
```
## Initializing {#module-services-postgres-initializing}
@ -95,23 +99,26 @@ databases from `ensureDatabases` and `extraUser1` from `ensureUsers`
are already created.
```nix
{
systemd.services.postgresql.postStart = lib.mkAfter ''
$PSQL service1 -c 'GRANT SELECT ON ALL TABLES IN SCHEMA public TO "extraUser1"'
$PSQL service1 -c 'GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO "extraUser1"'
# ....
'';
}
```
##### in intermediate oneshot service {#module-services-postgres-initializing-extra-permissions-superuser-oneshot}
```nix
{
systemd.services."migrate-service1-db1" = {
serviceConfig.Type = "oneshot";
requiredBy = "service1.service";
before = "service1.service";
after = "postgresql.service";
serviceConfig.User = "postgres";
environment.PSQL = "psql --port=${toString services.postgresql.port}";
environment.PSQL = "psql --port=${toString services.postgresql.settings.port}";
path = [ postgresql ];
script = ''
$PSQL service1 -c 'GRANT SELECT ON ALL TABLES IN SCHEMA public TO "extraUser1"'
@ -119,6 +126,7 @@ are already created.
# ....
'';
};
}
```
#### as service user {#module-services-postgres-initializing-extra-permissions-service-user}
@ -130,25 +138,28 @@ are already created.
##### in service `preStart` {#module-services-postgres-initializing-extra-permissions-service-user-pre-start}
```nix
environment.PSQL = "psql --port=${toString services.postgresql.port}";
{
environment.PSQL = "psql --port=${toString services.postgresql.settings.port}";
path = [ postgresql ];
systemd.services."service1".preStart = ''
$PSQL -c 'GRANT SELECT ON ALL TABLES IN SCHEMA public TO "extraUser1"'
$PSQL -c 'GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO "extraUser1"'
# ....
'';
}
```
##### in intermediate oneshot service {#module-services-postgres-initializing-extra-permissions-service-user-oneshot}
```nix
{
systemd.services."migrate-service1-db1" = {
serviceConfig.Type = "oneshot";
requiredBy = "service1.service";
before = "service1.service";
after = "postgresql.service";
serviceConfig.User = "service1";
environment.PSQL = "psql --port=${toString services.postgresql.port}";
environment.PSQL = "psql --port=${toString services.postgresql.settings.port}";
path = [ postgresql ];
script = ''
$PSQL -c 'GRANT SELECT ON ALL TABLES IN SCHEMA public TO "extraUser1"'
@ -156,6 +167,7 @@ are already created.
# ....
'';
};
}
```
## Upgrading {#module-services-postgres-upgrading}
@ -174,7 +186,7 @@ $ nix-instantiate --eval -A postgresql_13.psqlSchema
"13"
```
For an upgrade, a script like this can be used to simplify the process:
```
```nix
{ config, pkgs, ... }:
{
environment.systemPackages = [
@ -256,16 +268,18 @@ postgresql_15.pkgs.pg_partman postgresql_15.pkgs.pgroonga
```
To add plugins via NixOS configuration, set `services.postgresql.extraPlugins`:
```
services.postgresql.package = pkgs.postgresql_12;
services.postgresql.extraPlugins = ps: with ps; [
pg_repack
postgis
];
```nix
{
services.postgresql.package = pkgs.postgresql_12;
services.postgresql.extraPlugins = ps: with ps; [
pg_repack
postgis
];
}
```
You can build custom PostgreSQL-with-plugins (to be used outside of NixOS) using function `.withPackages`. For example, creating a custom PostgreSQL package in an overlay can look like:
```
```nix
self: super: {
postgresql_custom = self.postgresql_12.withPackages (ps: [
ps.pg_repack
@ -275,7 +289,7 @@ self: super: {
```
Here's a recipe on how to override a particular plugin through an overlay:
```
```nix
self: super: {
postgresql_15 = super.postgresql_15// {
pkgs = super.postgresql_15.pkgs // {

View file

@ -27,7 +27,7 @@ let
else toString value;
# The main PostgreSQL configuration file.
configFile = pkgs.writeTextDir "postgresql.conf" (concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${toStr v}") cfg.settings));
configFile = pkgs.writeTextDir "postgresql.conf" (concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${toStr v}") (filterAttrs (const (x: x != null)) cfg.settings)));
configFileCheck = pkgs.runCommand "postgresql-configfile-check" {} ''
${cfg.package}/bin/postgres -D${configFile} -C config_file >/dev/null
@ -41,6 +41,9 @@ in
{
imports = [
(mkRemovedOptionModule [ "services" "postgresql" "extraConfig" ] "Use services.postgresql.settings instead.")
(mkRenamedOptionModule [ "services" "postgresql" "logLinePrefix" ] [ "services" "postgresql" "settings" "log_line_prefix" ])
(mkRenamedOptionModule [ "services" "postgresql" "port" ] [ "services" "postgresql" "settings" "port" ])
];
###### interface
@ -57,14 +60,6 @@ in
example = "postgresql_15";
};
port = mkOption {
type = types.port;
default = 5432;
description = lib.mdDoc ''
The port on which PostgreSQL listens.
'';
};
checkConfig = mkOption {
type = types.bool;
default = true;
@ -352,17 +347,6 @@ in
'';
};
logLinePrefix = mkOption {
type = types.str;
default = "[%p] ";
example = "%m [%p] ";
description = lib.mdDoc ''
A printf-style string that is output at the beginning of each log line.
Upstream default is `'%m [%p] '`, i.e. it includes the timestamp. We do
not include the timestamp, because journal has it anyway.
'';
};
extraPlugins = mkOption {
type = with types; coercedTo (listOf path) (path: _ignorePg: path) (functionTo (listOf path));
default = _: [];
@ -373,7 +357,38 @@ in
};
settings = mkOption {
type = with types; attrsOf (oneOf [ bool float int str ]);
type = with types; submodule {
freeformType = attrsOf (oneOf [ bool float int str ]);
options = {
shared_preload_libraries = mkOption {
type = nullOr (coercedTo (listOf str) (concatStringsSep ", ") str);
default = null;
example = literalExpression ''[ "auto_explain" "anon" ]'';
description = mdDoc ''
List of libraries to be preloaded.
'';
};
log_line_prefix = mkOption {
type = types.str;
default = "[%p] ";
example = "%m [%p] ";
description = lib.mdDoc ''
A printf-style string that is output at the beginning of each log line.
Upstream default is `'%m [%p] '`, i.e. it includes the timestamp. We do
not include the timestamp, because journal has it anyway.
'';
};
port = mkOption {
type = types.port;
default = 5432;
description = lib.mdDoc ''
The port on which PostgreSQL listens.
'';
};
};
};
default = {};
description = lib.mdDoc ''
PostgreSQL configuration. Refer to
@ -439,9 +454,7 @@ in
hba_file = "${pkgs.writeText "pg_hba.conf" cfg.authentication}";
ident_file = "${pkgs.writeText "pg_ident.conf" cfg.identMap}";
log_destination = "stderr";
log_line_prefix = cfg.logLinePrefix;
listen_addresses = if cfg.enableTCPIP then "*" else "localhost";
port = cfg.port;
jit = mkDefault (if cfg.enableJIT then "on" else "off");
};
@ -524,7 +537,7 @@ in
# Wait for PostgreSQL to be ready to accept connections.
postStart =
''
PSQL="psql --port=${toString cfg.port}"
PSQL="psql --port=${toString cfg.settings.port}"
while ! $PSQL -d postgres -c "" 2> /dev/null; do
if ! kill -0 "$MAINPID"; then exit 1; fi

View file

@ -7,8 +7,10 @@
TigerBeetle is a distributed financial accounting database designed for mission critical safety and performance.
To enable TigerBeetle, add the following to your {file}`configuration.nix`:
```
```nix
{
services.tigerbeetle.enable = true;
}
```
When first started, the TigerBeetle service will create its data file at {file}`/var/lib/tigerbeetle` unless the file already exists, in which case it will just use the existing file.
@ -20,13 +22,15 @@ By default, TigerBeetle will only listen on a local interface.
To configure it to listen on a different interface (and to configure it to connect to other replicas, if you're creating more than one), you'll have to set the `addresses` option.
Note that the TigerBeetle module won't open any firewall ports automatically, so if you configure it to listen on an external interface, you'll need to ensure that connections can reach it:
```
```nix
{
services.tigerbeetle = {
enable = true;
addresses = [ "0.0.0.0:3001" ];
};
networking.firewall.allowedTCPPorts = [ 3001 ];
}
```
A complete list of options for TigerBeetle can be found [here](#opt-services.tigerbeetle.enable).

View file

@ -286,6 +286,7 @@ in {
};
programs.kdeconnect.package = kdePackages.kdeconnect-kde;
programs.partition-manager.package = kdePackages.partitionmanager;
# FIXME: ugly hack. See #292632 for details.
system.userActivationScripts.rebuildSycoca = activationScript;

View file

@ -8,17 +8,21 @@ Flatpak is a system for building, distributing, and running sandboxed desktop
applications on Linux.
To enable Flatpak, add the following to your {file}`configuration.nix`:
```
```nix
{
services.flatpak.enable = true;
}
```
For the sandboxed apps to work correctly, desktop integration portals need to
be installed. If you run GNOME, this will be handled automatically for you;
in other cases, you will need to add something like the following to your
{file}`configuration.nix`:
```
```nix
{
xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
xdg.portal.config.common.default = "gtk";
}
```
Then, you will need to add a repository, for example,

View file

@ -1,14 +1,21 @@
# PipeWire service.
{ config, lib, pkgs, ... }:
with lib;
let
inherit (builtins) attrNames concatMap length;
inherit (lib) maintainers teams;
inherit (lib.attrsets) attrByPath attrsToList concatMapAttrs filterAttrs;
inherit (lib.lists) flatten optional optionals;
inherit (lib.modules) mkIf mkRemovedOptionModule;
inherit (lib.options) literalExpression mkEnableOption mkOption mkPackageOption;
inherit (lib.strings) concatMapStringsSep hasPrefix optionalString;
inherit (lib.types) attrsOf bool listOf package;
json = pkgs.formats.json {};
mapToFiles = location: config: concatMapAttrs (name: value: { "share/pipewire/${location}.conf.d/${name}.conf" = json.generate "${name}" value; }) config;
extraConfigPkgFromFiles = locations: filesSet: pkgs.runCommand "pipewire-extra-config" { } ''
mkdir -p ${lib.concatMapStringsSep " " (l: "$out/share/pipewire/${l}.conf.d") locations}
${lib.concatMapStringsSep ";" ({name, value}: "ln -s ${value} $out/${name}") (lib.attrsToList filesSet)}
mkdir -p ${concatMapStringsSep " " (l: "$out/share/pipewire/${l}.conf.d") locations}
${concatMapStringsSep ";" ({name, value}: "ln -s ${value} $out/${name}") (attrsToList filesSet)}
'';
cfg = config.services.pipewire;
enable32BitAlsaPlugins = cfg.alsa.support32Bit
@ -40,15 +47,15 @@ let
name = "pipewire-configs";
paths = configPackages
++ [ extraConfigPkg ]
++ lib.optionals cfg.wireplumber.enable cfg.wireplumber.configPackages;
++ optionals cfg.wireplumber.enable cfg.wireplumber.configPackages;
pathsToLink = [ "/share/pipewire" ];
};
requiredLv2Packages = lib.flatten
requiredLv2Packages = flatten
(
lib.concatMap
concatMap
(p:
lib.attrByPath ["passthru" "requiredLv2Packages"] [] p
attrByPath ["passthru" "requiredLv2Packages"] [] p
)
configPackages
);
@ -59,58 +66,58 @@ let
pathsToLink = [ "/lib/lv2" ];
};
in {
meta.maintainers = teams.freedesktop.members ++ [ lib.maintainers.k900 ];
meta.maintainers = teams.freedesktop.members ++ [ maintainers.k900 ];
###### interface
options = {
services.pipewire = {
enable = mkEnableOption (lib.mdDoc "PipeWire service");
enable = mkEnableOption "PipeWire service";
package = mkPackageOption pkgs "pipewire" { };
socketActivation = mkOption {
default = true;
type = types.bool;
description = lib.mdDoc ''
type = bool;
description = ''
Automatically run PipeWire when connections are made to the PipeWire socket.
'';
};
audio = {
enable = lib.mkOption {
type = lib.types.bool;
enable = mkOption {
type = bool;
# this is for backwards compatibility
default = cfg.alsa.enable || cfg.jack.enable || cfg.pulse.enable;
defaultText = lib.literalExpression "config.services.pipewire.alsa.enable || config.services.pipewire.jack.enable || config.services.pipewire.pulse.enable";
description = lib.mdDoc "Whether to use PipeWire as the primary sound server";
defaultText = literalExpression "config.services.pipewire.alsa.enable || config.services.pipewire.jack.enable || config.services.pipewire.pulse.enable";
description = "Whether to use PipeWire as the primary sound server";
};
};
alsa = {
enable = mkEnableOption (lib.mdDoc "ALSA support");
support32Bit = mkEnableOption (lib.mdDoc "32-bit ALSA support on 64-bit systems");
enable = mkEnableOption "ALSA support";
support32Bit = mkEnableOption "32-bit ALSA support on 64-bit systems";
};
jack = {
enable = mkEnableOption (lib.mdDoc "JACK audio emulation");
enable = mkEnableOption "JACK audio emulation";
};
raopOpenFirewall = mkOption {
type = lib.types.bool;
type = bool;
default = false;
description = lib.mdDoc ''
description = ''
Opens UDP/6001-6002, required by RAOP/Airplay for timing and control data.
'';
};
pulse = {
enable = mkEnableOption (lib.mdDoc "PulseAudio server emulation");
enable = mkEnableOption "PulseAudio server emulation";
};
systemWide = lib.mkOption {
type = lib.types.bool;
systemWide = mkOption {
type = bool;
default = false;
description = lib.mdDoc ''
description = ''
If true, a system-wide PipeWire service and socket is enabled
allowing all users in the "pipewire" group to use it simultaneously.
If false, then user units are used instead, restricting access to
@ -124,7 +131,7 @@ in {
extraConfig = {
pipewire = mkOption {
type = lib.types.attrsOf json.type;
type = attrsOf json.type;
default = {};
example = {
"10-clock-rate" = {
@ -138,7 +145,7 @@ in {
};
};
};
description = lib.mdDoc ''
description = ''
Additional configuration for the PipeWire server.
Every item in this attrset becomes a separate drop-in file in `/etc/pipewire/pipewire.conf.d`.
@ -157,7 +164,7 @@ in {
'';
};
client = mkOption {
type = lib.types.attrsOf json.type;
type = attrsOf json.type;
default = {};
example = {
"10-no-resample" = {
@ -166,7 +173,7 @@ in {
};
};
};
description = lib.mdDoc ''
description = ''
Additional configuration for the PipeWire client library, used by most applications.
Every item in this attrset becomes a separate drop-in file in `/etc/pipewire/client.conf.d`.
@ -177,7 +184,7 @@ in {
'';
};
client-rt = mkOption {
type = lib.types.attrsOf json.type;
type = attrsOf json.type;
default = {};
example = {
"10-alsa-linear-volume" = {
@ -186,7 +193,7 @@ in {
};
};
};
description = lib.mdDoc ''
description = ''
Additional configuration for the PipeWire client library, used by real-time applications and legacy ALSA clients.
Every item in this attrset becomes a separate drop-in file in `/etc/pipewire/client-rt.conf.d`.
@ -198,7 +205,7 @@ in {
'';
};
jack = mkOption {
type = lib.types.attrsOf json.type;
type = attrsOf json.type;
default = {};
example = {
"20-hide-midi" = {
@ -207,7 +214,7 @@ in {
};
};
};
description = lib.mdDoc ''
description = ''
Additional configuration for the PipeWire JACK server and client library.
Every item in this attrset becomes a separate drop-in file in `/etc/pipewire/jack.conf.d`.
@ -218,7 +225,7 @@ in {
'';
};
pipewire-pulse = mkOption {
type = lib.types.attrsOf json.type;
type = attrsOf json.type;
default = {};
example = {
"15-force-s16-info" = {
@ -232,7 +239,7 @@ in {
}];
};
};
description = lib.mdDoc ''
description = ''
Additional configuration for the PipeWire PulseAudio server.
Every item in this attrset becomes a separate drop-in file in `/etc/pipewire/pipewire-pulse.conf.d`.
@ -248,10 +255,32 @@ in {
};
};
configPackages = lib.mkOption {
type = lib.types.listOf lib.types.package;
configPackages = mkOption {
type = listOf package;
default = [];
description = lib.mdDoc ''
example = literalExpression ''[
(pkgs.writeTextDir "share/pipewire/pipewire.conf.d/10-loopback.conf" '''
context.modules = [
{ name = libpipewire-module-loopback
args = {
node.description = "Scarlett Focusrite Line 1"
capture.props = {
audio.position = [ FL ]
stream.dont-remix = true
node.target = "alsa_input.usb-Focusrite_Scarlett_Solo_USB_Y7ZD17C24495BC-00.analog-stereo"
node.passive = true
}
playback.props = {
node.name = "SF_mono_in_1"
media.class = "Audio/Source"
audio.position = [ MONO ]
}
}
}
]
''')
]'';
description = ''
List of packages that provide PipeWire configuration, in the form of
`share/pipewire/*/*.conf` files.
@ -260,11 +289,11 @@ in {
'';
};
extraLv2Packages = lib.mkOption {
type = lib.types.listOf lib.types.package;
extraLv2Packages = mkOption {
type = listOf package;
default = [];
example = lib.literalExpression "[ pkgs.lsp-plugins ]";
description = lib.mdDoc ''
example = literalExpression "[ pkgs.lsp-plugins ]";
description = ''
List of packages that provide LV2 plugins in `lib/lv2` that should
be made available to PipeWire for [filter chains][wiki-filter-chain].
@ -279,11 +308,11 @@ in {
};
imports = [
(lib.mkRemovedOptionModule ["services" "pipewire" "config"] ''
(mkRemovedOptionModule ["services" "pipewire" "config"] ''
Overriding default PipeWire configuration through NixOS options never worked correctly and is no longer supported.
Please create drop-in configuration files via `services.pipewire.extraConfig` instead.
'')
(lib.mkRemovedOptionModule ["services" "pipewire" "media-session"] ''
(mkRemovedOptionModule ["services" "pipewire" "media-session"] ''
pipewire-media-session is no longer supported upstream and has been removed.
Please switch to `services.pipewire.wireplumber` instead.
'')
@ -306,12 +335,12 @@ in {
message = "Using PipeWire's ALSA/PulseAudio compatibility layers requires running PipeWire as the sound server. Set `services.pipewire.audio.enable` to true.";
}
{
assertion = builtins.length
(builtins.attrNames
assertion = length
(attrNames
(
lib.filterAttrs
filterAttrs
(name: value:
lib.hasPrefix "pipewire/" name || name == "pipewire"
hasPrefix "pipewire/" name || name == "pipewire"
)
config.environment.etc
)) == 1;
@ -320,7 +349,7 @@ in {
];
environment.systemPackages = [ cfg.package ]
++ lib.optional cfg.jack.enable jack-libs;
++ optional cfg.jack.enable jack-libs;
systemd.packages = [ cfg.package ];
@ -336,16 +365,16 @@ in {
systemd.user.sockets.pipewire.enable = !cfg.systemWide;
systemd.user.services.pipewire.enable = !cfg.systemWide;
systemd.services.pipewire.environment.LV2_PATH = lib.mkIf cfg.systemWide "${lv2Plugins}/lib/lv2";
systemd.user.services.pipewire.environment.LV2_PATH = lib.mkIf (!cfg.systemWide) "${lv2Plugins}/lib/lv2";
systemd.services.pipewire.environment.LV2_PATH = mkIf cfg.systemWide "${lv2Plugins}/lib/lv2";
systemd.user.services.pipewire.environment.LV2_PATH = mkIf (!cfg.systemWide) "${lv2Plugins}/lib/lv2";
# Mask pw-pulse if it's not wanted
systemd.user.services.pipewire-pulse.enable = cfg.pulse.enable;
systemd.user.sockets.pipewire-pulse.enable = cfg.pulse.enable;
systemd.sockets.pipewire.wantedBy = lib.mkIf cfg.socketActivation [ "sockets.target" ];
systemd.user.sockets.pipewire.wantedBy = lib.mkIf cfg.socketActivation [ "sockets.target" ];
systemd.user.sockets.pipewire-pulse.wantedBy = lib.mkIf cfg.socketActivation [ "sockets.target" ];
systemd.sockets.pipewire.wantedBy = mkIf cfg.socketActivation [ "sockets.target" ];
systemd.user.sockets.pipewire.wantedBy = mkIf cfg.socketActivation [ "sockets.target" ];
systemd.user.sockets.pipewire-pulse.wantedBy = mkIf cfg.socketActivation [ "sockets.target" ];
services.udev.packages = [ cfg.package ];
@ -377,18 +406,18 @@ in {
};
environment.sessionVariables.LD_LIBRARY_PATH =
lib.mkIf cfg.jack.enable [ "${cfg.package.jack}/lib" ];
mkIf cfg.jack.enable [ "${cfg.package.jack}/lib" ];
networking.firewall.allowedUDPPorts = lib.mkIf cfg.raopOpenFirewall [ 6001 6002 ];
networking.firewall.allowedUDPPorts = mkIf cfg.raopOpenFirewall [ 6001 6002 ];
users = lib.mkIf cfg.systemWide {
users = mkIf cfg.systemWide {
users.pipewire = {
uid = config.ids.uids.pipewire;
group = "pipewire";
extraGroups = [
"audio"
"video"
] ++ lib.optional config.security.rtkit.enable "rtkit";
] ++ optional config.security.rtkit.enable "rtkit";
description = "PipeWire system service user";
isSystemUser = true;
home = "/var/lib/pipewire";

View file

@ -1,46 +1,65 @@
{ config, lib, pkgs, ... }:
let
inherit (builtins) attrNames concatMap length;
inherit (lib) maintainers;
inherit (lib.attrsets) attrByPath filterAttrs;
inherit (lib.lists) flatten optional;
inherit (lib.modules) mkIf;
inherit (lib.options) literalExpression mkOption;
inherit (lib.strings) hasPrefix;
inherit (lib.types) bool listOf package;
pwCfg = config.services.pipewire;
cfg = pwCfg.wireplumber;
pwUsedForAudio = pwCfg.audio.enable;
in
{
meta.maintainers = [ lib.maintainers.k900 ];
meta.maintainers = [ maintainers.k900 ];
options = {
services.pipewire.wireplumber = {
enable = lib.mkOption {
type = lib.types.bool;
default = config.services.pipewire.enable;
defaultText = lib.literalExpression "config.services.pipewire.enable";
description = lib.mdDoc "Whether to enable WirePlumber, a modular session / policy manager for PipeWire";
enable = mkOption {
type = bool;
default = pwCfg.enable;
defaultText = literalExpression "config.services.pipewire.enable";
description = "Whether to enable WirePlumber, a modular session / policy manager for PipeWire";
};
package = lib.mkOption {
type = lib.types.package;
package = mkOption {
type = package;
default = pkgs.wireplumber;
defaultText = lib.literalExpression "pkgs.wireplumber";
description = lib.mdDoc "The WirePlumber derivation to use.";
defaultText = literalExpression "pkgs.wireplumber";
description = "The WirePlumber derivation to use.";
};
configPackages = lib.mkOption {
type = lib.types.listOf lib.types.package;
configPackages = mkOption {
type = listOf package;
default = [ ];
description = lib.mdDoc ''
example = literalExpression ''[
(pkgs.writeTextDir "share/wireplumber/wireplumber.conf.d/10-bluez.conf" '''
monitor.bluez.properties = {
bluez5.roles = [ a2dp_sink a2dp_source bap_sink bap_source hsp_hs hsp_ag hfp_hf hfp_ag ]
bluez5.codecs = [ sbc sbc_xq aac ]
bluez5.enable-sbc-xq = true
bluez5.hfphsp-backend = "native"
}
''')
]'';
description = ''
List of packages that provide WirePlumber configuration, in the form of
`share/wireplumber/*/*.lua` files.
`share/wireplumber/*/*.conf` files.
LV2 dependencies will be picked up from config packages automatically
via `passthru.requiredLv2Packages`.
'';
};
extraLv2Packages = lib.mkOption {
type = lib.types.listOf lib.types.package;
extraLv2Packages = mkOption {
type = listOf package;
default = [];
example = lib.literalExpression "[ pkgs.lsp-plugins ]";
description = lib.mdDoc ''
example = literalExpression "[ pkgs.lsp-plugins ]";
description = ''
List of packages that provide LV2 plugins in `lib/lv2` that should
be made available to WirePlumber for [filter chains][wiki-filter-chain].
@ -78,8 +97,8 @@ in
'';
configPackages = cfg.configPackages
++ lib.optional (!pwUsedForAudio) pwNotForAudioConfigPkg
++ lib.optional config.services.pipewire.systemWide systemwideConfigPkg;
++ optional (!pwUsedForAudio) pwNotForAudioConfigPkg
++ optional pwCfg.systemWide systemwideConfigPkg;
configs = pkgs.buildEnv {
name = "wireplumber-configs";
@ -87,11 +106,11 @@ in
pathsToLink = [ "/share/wireplumber" ];
};
requiredLv2Packages = lib.flatten
requiredLv2Packages = flatten
(
lib.concatMap
concatMap
(p:
lib.attrByPath ["passthru" "requiredLv2Packages"] [] p
attrByPath ["passthru" "requiredLv2Packages"] [] p
)
configPackages
);
@ -102,19 +121,19 @@ in
pathsToLink = [ "/lib/lv2" ];
};
in
lib.mkIf cfg.enable {
mkIf cfg.enable {
assertions = [
{
assertion = !config.hardware.bluetooth.hsphfpd.enable;
message = "Using WirePlumber conflicts with hsphfpd, as it provides the same functionality. `hardware.bluetooth.hsphfpd.enable` needs be set to false";
}
{
assertion = builtins.length
(builtins.attrNames
assertion = length
(attrNames
(
lib.filterAttrs
filterAttrs
(name: value:
lib.hasPrefix "wireplumber/" name || name == "wireplumber"
hasPrefix "wireplumber/" name || name == "wireplumber"
)
config.environment.etc
)) == 1;
@ -128,19 +147,19 @@ in
systemd.packages = [ cfg.package ];
systemd.services.wireplumber.enable = config.services.pipewire.systemWide;
systemd.user.services.wireplumber.enable = !config.services.pipewire.systemWide;
systemd.services.wireplumber.enable = pwCfg.systemWide;
systemd.user.services.wireplumber.enable = !pwCfg.systemWide;
systemd.services.wireplumber.wantedBy = [ "pipewire.service" ];
systemd.user.services.wireplumber.wantedBy = [ "pipewire.service" ];
systemd.services.wireplumber.environment = lib.mkIf config.services.pipewire.systemWide {
systemd.services.wireplumber.environment = mkIf pwCfg.systemWide {
# Force WirePlumber to use system dbus.
DBUS_SESSION_BUS_ADDRESS = "unix:path=/run/dbus/system_bus_socket";
LV2_PATH = "${lv2Plugins}/lib/lv2";
};
systemd.user.services.wireplumber.environment.LV2_PATH =
lib.mkIf (!config.services.pipewire.systemWide) "${lv2Plugins}/lib/lv2";
mkIf (!pwCfg.systemWide) "${lv2Plugins}/lib/lv2";
};
}

View file

@ -18,7 +18,7 @@ A complete list of options for the Athens module may be found
## Basic usage for a caching proxy configuration {#opt-services-development-athens-caching-proxy}
A very basic configuration for Athens that acts as a caching and forwarding HTTP proxy is:
```
```nix
{
services.athens = {
enable = true;
@ -28,7 +28,7 @@ A very basic configuration for Athens that acts as a caching and forwarding HTTP
If you want to prevent Athens from writing to disk, you can instead configure it to cache modules only in memory:
```
```nix
{
services.athens = {
enable = true;
@ -39,10 +39,10 @@ If you want to prevent Athens from writing to disk, you can instead configure it
To use the local proxy in Go builds, you can set the proxy as environment variable:
```
```nix
{
environment.variables = {
GOPROXY = "http://localhost:3000"
GOPROXY = "http://localhost:3000";
};
}
```

View file

@ -7,7 +7,7 @@
[Blackfire](https://blackfire.io) is a proprietary tool for profiling applications. There are several languages supported by the product but currently only PHP support is packaged in Nixpkgs. The back-end consists of a module that is loaded into the language runtime (called *probe*) and a service (*agent*) that the probe connects to and that sends the profiles to the server.
To use it, you will need to enable the agent and the probe on your server. The exact method will depend on the way you use PHP but here is an example of NixOS configuration for PHP-FPM:
```
```nix
let
php = pkgs.php.withExtensions ({ enabled, all }: enabled ++ (with all; [
blackfire

View file

@ -9,7 +9,7 @@ Enabling the `livebook` service creates a user
[`systemd`](https://www.freedesktop.org/wiki/Software/systemd/) unit
which runs the server.
```
```nix
{ ... }:
{
@ -51,6 +51,8 @@ some features require additional packages. For example, the machine
learning Kinos require `gcc` and `gnumake`. To add these, use
`extraPackages`:
```
services.livebook.extraPackages = with pkgs; [ gcc gnumake ];
```nix
{
services.livebook.extraPackages = with pkgs; [ gcc gnumake ];
}
```

View file

@ -178,7 +178,7 @@ file {file}`configuration.nix` to make it contain:
::: {.example #module-services-emacs-configuration-nix}
### Custom Emacs in `configuration.nix`
```
```nix
{
environment.systemPackages = [
# [...]
@ -203,7 +203,7 @@ adding it to your {file}`~/.config/nixpkgs/config.nix` (see
::: {.example #module-services-emacs-config-nix}
### Custom Emacs in `~/.config/nixpkgs/config.nix`
```
```nix
{
packageOverrides = super: let self = super.pkgs; in {
myemacs = import ./emacs.nix { pkgs = self; };
@ -228,7 +228,7 @@ only use {command}`emacsclient`), you can change your file
::: {.example #ex-emacsGtk3Nix}
### Custom Emacs build
```
```nix
{ pkgs ? import <nixpkgs> {} }:
let
myEmacs = (pkgs.emacs.override {
@ -242,7 +242,7 @@ let
rm $out/share/applications/emacs.desktop
'';
});
in [...]
in [ /* ... */ ]
```
:::
@ -262,8 +262,10 @@ with the user's login session.
To install and enable the {command}`systemd` user service for Emacs
daemon, add the following to your {file}`configuration.nix`:
```
services.emacs.enable = true;
```nix
{
services.emacs.enable = true;
}
```
The {var}`services.emacs.package` option allows a custom
@ -323,9 +325,11 @@ In general, {command}`systemd` user services are globally enabled
by symlinks in {file}`/etc/systemd/user`. In the case where
Emacs daemon is not wanted for all users, it is possible to install the
service but not globally enable it:
```
services.emacs.enable = false;
services.emacs.install = true;
```nix
{
services.emacs.enable = false;
services.emacs.install = true;
}
```
To enable the {command}`systemd` user service for just the

View file

@ -94,7 +94,7 @@ in
{ source = "${etcFiles}/etc/opt/brother/scanner/brscan5"; };
environment.etc."opt/brother/scanner/models" =
{ source = "${etcFiles}/etc/opt/brother/scanner/brscan5/models"; };
environment.etc."sane.d/dll.d/brother5.conf".source = "${pkgs.brscan5}/etc/sane.d/dll.d/brother.conf";
environment.etc."sane.d/dll.d/brother5.conf".source = "${pkgs.brscan5}/etc/sane.d/dll.d/brother5.conf";
assertions = [
{ assertion = all (x: !(null != x.ip && null != x.nodename)) netDeviceList;

View file

@ -1,225 +0,0 @@
{ config
, lib
, pkgs
, ...
}:
let
cfg = config.services.homeassistant-satellite;
inherit (lib)
escapeShellArg
escapeShellArgs
mkOption
mdDoc
mkEnableOption
mkIf
mkPackageOption
types
;
inherit (builtins)
toString
;
# override the package with the relevant vad dependencies
package = cfg.package.overridePythonAttrs (oldAttrs: {
propagatedBuildInputs = oldAttrs.propagatedBuildInputs
++ lib.optional (cfg.vad == "webrtcvad") cfg.package.optional-dependencies.webrtc
++ lib.optional (cfg.vad == "silero") cfg.package.optional-dependencies.silerovad
++ lib.optional (cfg.pulseaudio.enable) cfg.package.optional-dependencies.pulseaudio;
});
in
{
meta.buildDocsInSandbox = false;
options.services.homeassistant-satellite = with types; {
enable = mkEnableOption (mdDoc "Home Assistant Satellite");
package = mkPackageOption pkgs "homeassistant-satellite" { };
user = mkOption {
type = str;
example = "alice";
description = mdDoc ''
User to run homeassistant-satellite under.
'';
};
group = mkOption {
type = str;
default = "users";
description = mdDoc ''
Group to run homeassistant-satellite under.
'';
};
host = mkOption {
type = str;
example = "home-assistant.local";
description = mdDoc ''
Hostname on which your Home Assistant instance can be reached.
'';
};
port = mkOption {
type = port;
example = 8123;
description = mdDoc ''
Port on which your Home Assistance can be reached.
'';
apply = toString;
};
protocol = mkOption {
type = enum [ "http" "https" ];
default = "http";
example = "https";
description = mdDoc ''
The transport protocol used to connect to Home Assistant.
'';
};
tokenFile = mkOption {
type = path;
example = "/run/keys/hass-token";
description = mdDoc ''
Path to a file containing a long-lived access token for your Home Assistant instance.
'';
apply = escapeShellArg;
};
sounds = {
awake = mkOption {
type = nullOr str;
default = null;
description = mdDoc ''
Audio file to play when the wake word is detected.
'';
};
done = mkOption {
type = nullOr str;
default = null;
description = mdDoc ''
Audio file to play when the voice command is done.
'';
};
};
vad = mkOption {
type = enum [ "disabled" "webrtcvad" "silero" ];
default = "disabled";
example = "silero";
description = mdDoc ''
Voice activity detection model. With `disabled` sound will be transmitted continously.
'';
};
pulseaudio = {
enable = mkEnableOption "recording/playback via PulseAudio or PipeWire";
socket = mkOption {
type = nullOr str;
default = null;
example = "/run/user/1000/pulse/native";
description = mdDoc ''
Path or hostname to connect with the PulseAudio server.
'';
};
duckingVolume = mkOption {
type = nullOr float;
default = null;
example = 0.4;
description = mdDoc ''
Reduce output volume (between 0 and 1) to this percentage value while recording.
'';
};
echoCancellation = mkEnableOption "acoustic echo cancellation";
};
extraArgs = mkOption {
type = listOf str;
default = [ ];
description = mdDoc ''
Extra arguments to pass to the commandline.
'';
apply = escapeShellArgs;
};
};
config = mkIf cfg.enable {
systemd.services."homeassistant-satellite" = {
description = "Home Assistant Satellite";
after = [
"network-online.target"
];
wants = [
"network-online.target"
];
wantedBy = [
"multi-user.target"
];
path = with pkgs; [
ffmpeg-headless
] ++ lib.optionals (!cfg.pulseaudio.enable) [
alsa-utils
];
serviceConfig = {
User = cfg.user;
Group = cfg.group;
# https://github.com/rhasspy/hassio-addons/blob/master/assist_microphone/rootfs/etc/s6-overlay/s6-rc.d/assist_microphone/run
ExecStart = ''
${package}/bin/homeassistant-satellite \
--host ${cfg.host} \
--port ${cfg.port} \
--protocol ${cfg.protocol} \
--token-file ${cfg.tokenFile} \
--vad ${cfg.vad} \
${lib.optionalString cfg.pulseaudio.enable "--pulseaudio"}${lib.optionalString (cfg.pulseaudio.socket != null) "=${cfg.pulseaudio.socket}"} \
${lib.optionalString (cfg.pulseaudio.enable && cfg.pulseaudio.duckingVolume != null) "--ducking-volume=${toString cfg.pulseaudio.duckingVolume}"} \
${lib.optionalString (cfg.pulseaudio.enable && cfg.pulseaudio.echoCancellation) "--echo-cancel"} \
${lib.optionalString (cfg.sounds.awake != null) "--awake-sound=${toString cfg.sounds.awake}"} \
${lib.optionalString (cfg.sounds.done != null) "--done-sound=${toString cfg.sounds.done}"} \
${cfg.extraArgs}
'';
CapabilityBoundingSet = "";
DeviceAllow = "";
DevicePolicy = "closed";
LockPersonality = true;
MemoryDenyWriteExecute = false; # onnxruntime/capi/onnxruntime_pybind11_state.so: cannot enable executable stack as shared object requires: Operation not permitted
PrivateDevices = true;
PrivateUsers = true;
ProtectHome = false; # Would deny access to local pulse/pipewire server
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectControlGroups = true;
ProtectProc = "invisible";
ProcSubset = "all"; # Error in cpuinfo: failed to parse processor information from /proc/cpuinfo
Restart = "always";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
SupplementaryGroups = [
"audio"
];
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"~@privileged"
];
UMask = "0077";
};
};
};
}

View file

@ -0,0 +1,244 @@
{ config
, lib
, pkgs
, ...
}:
let
cfg = config.services.wyoming.satellite;
inherit (lib)
elem
escapeShellArgs
getExe
literalExpression
mkOption
mkEnableOption
mkIf
mkPackageOption
optional
optionals
types
;
finalPackage = cfg.package.overridePythonAttrs (oldAttrs: {
propagatedBuildInputs = oldAttrs.propagatedBuildInputs
# for audio enhancements like auto-gain, noise suppression
++ cfg.package.optional-dependencies.webrtc
# vad is currently optional, because it is broken on aarch64-linux
++ optionals cfg.vad.enable cfg.package.optional-dependencies.silerovad;
});
in
{
meta.buildDocsInSandbox = false;
options.services.wyoming.satellite = with types; {
enable = mkEnableOption "Wyoming Satellite";
package = mkPackageOption pkgs "wyoming-satellite" { };
user = mkOption {
type = str;
example = "alice";
description = ''
User to run wyoming-satellite under.
'';
};
group = mkOption {
type = str;
default = "users";
description = ''
Group to run wyoming-satellite under.
'';
};
uri = mkOption {
type = str;
default = "tcp://0.0.0.0:10700";
description = ''
URI where wyoming-satellite will bind its socket.
'';
};
name = mkOption {
type = str;
default = config.networking.hostName;
defaultText = literalExpression ''
config.networking.hostName
'';
description = ''
Name of the satellite.
'';
};
area = mkOption {
type = nullOr str;
default = null;
example = "Kitchen";
description = ''
Area to the satellite.
'';
};
microphone = {
command = mkOption {
type = str;
default = "arecord -r 16000 -c 1 -f S16_LE -t raw";
description = ''
Program to run for audio input.
'';
};
autoGain = mkOption {
type = ints.between 0 31;
default = 5;
example = 15;
description = ''
Automatic gain control in dbFS, with 31 being the loudest value. Set to 0 to disable.
'';
};
noiseSuppression = mkOption {
type = ints.between 0 4;
default = 2;
example = 3;
description = ''
Noise suppression level with 4 being the maximum suppression,
which may cause audio distortion. Set to 0 to disable.
'';
};
};
sound = {
command = mkOption {
type = nullOr str;
default = "aplay -r 22050 -c 1 -f S16_LE -t raw";
description = ''
Program to run for sound output.
'';
};
};
sounds = {
awake = mkOption {
type = nullOr path;
default = null;
description = ''
Path to audio file in WAV format to play when wake word is detected.
'';
};
done = mkOption {
type = nullOr path;
default = null;
description = ''
Path to audio file in WAV format to play when voice command recording has ended.
'';
};
};
vad = {
enable = mkOption {
type = bool;
default = true;
description = ''
Whether to enable voice activity detection.
Enabling will result in only streaming audio, when speech gets
detected.
'';
};
};
extraArgs = mkOption {
type = listOf str;
default = [ ];
description = ''
Extra arguments to pass to the executable.
Check `wyoming-satellite --help` for possible options.
'';
};
};
config = mkIf cfg.enable {
systemd.services."wyoming-satellite" = {
description = "Wyoming Satellite";
after = [
"network-online.target"
"sound.target"
];
wants = [
"network-online.target"
"sound.target"
];
wantedBy = [
"multi-user.target"
];
path = with pkgs; [
alsa-utils
];
script = let
optionalParam = param: argument: optionals (!elem argument [ null 0 false ]) [
param argument
];
in ''
export XDG_RUNTIME_DIR=/run/user/$UID
${escapeShellArgs ([
(getExe finalPackage)
"--uri" cfg.uri
"--name" cfg.name
"--mic-command" cfg.microphone.command
]
++ optionalParam "--mic-auto-gain" cfg.microphone.autoGain
++ optionalParam "--mic-noise-suppression" cfg.microphone.noiseSuppression
++ optionalParam "--area" cfg.area
++ optionalParam "--snd-command" cfg.sound.command
++ optionalParam "--awake-wav" cfg.sounds.awake
++ optionalParam "--done-wav" cfg.sounds.done
++ optional cfg.vad.enable "--vad"
++ cfg.extraArgs)}
'';
serviceConfig = {
User = cfg.user;
Group = cfg.group;
# https://github.com/rhasspy/hassio-addons/blob/master/assist_microphone/rootfs/etc/s6-overlay/s6-rc.d/assist_microphone/run
CapabilityBoundingSet = "";
DeviceAllow = "";
DevicePolicy = "closed";
LockPersonality = true;
MemoryDenyWriteExecute = false; # onnxruntime/capi/onnxruntime_pybind11_state.so: cannot enable executable stack as shared object requires: Operation not permitted
PrivateDevices = true;
PrivateUsers = true;
ProtectHome = false; # Would deny access to local pulse/pipewire server
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectControlGroups = true;
ProtectProc = "invisible";
ProcSubset = "all"; # Error in cpuinfo: failed to parse processor information from /proc/cpuinfo
Restart = "always";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
"AF_NETLINK"
];
RestrictNamespaces = true;
RestrictRealtime = true;
SupplementaryGroups = [
"audio"
];
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"~@privileged"
];
UMask = "0077";
};
};
};
}

View file

@ -9,7 +9,7 @@ an existing, securely configured Postfix setup, as it does not automatically con
## Basic usage with Postfix {#module-services-mailman-basic-usage}
For a basic configuration with Postfix as the MTA, the following settings are suggested:
```
```nix
{ config, ... }: {
services.postfix = {
enable = true;
@ -50,7 +50,7 @@ necessary, but outside the scope of the Mailman module.
## Using with other MTAs {#module-services-mailman-other-mtas}
Mailman also supports other MTA, though with a little bit more configuration. For example, to use Mailman with Exim, you can use the following settings:
```
```nix
{ config, ... }: {
services = {
mailman = {

View file

@ -10,7 +10,9 @@ framework for Matrix.
2. If you want to use PostgreSQL instead of SQLite, do this:
```nix
services.maubot.settings.database = "postgresql://maubot@localhost/maubot";
{
services.maubot.settings.database = "postgresql://maubot@localhost/maubot";
}
```
If the PostgreSQL connection requires a password, you will have to
@ -18,54 +20,58 @@ framework for Matrix.
3. If you plan to expose your Maubot interface to the web, do something
like this:
```nix
services.nginx.virtualHosts."matrix.example.org".locations = {
"/_matrix/maubot/" = {
proxyPass = "http://127.0.0.1:${toString config.services.maubot.settings.server.port}";
proxyWebsockets = true;
{
services.nginx.virtualHosts."matrix.example.org".locations = {
"/_matrix/maubot/" = {
proxyPass = "http://127.0.0.1:${toString config.services.maubot.settings.server.port}";
proxyWebsockets = true;
};
};
};
services.maubot.settings.server.public_url = "matrix.example.org";
# do the following only if you want to use something other than /_matrix/maubot...
services.maubot.settings.server.ui_base_path = "/another/base/path";
services.maubot.settings.server.public_url = "matrix.example.org";
# do the following only if you want to use something other than /_matrix/maubot...
services.maubot.settings.server.ui_base_path = "/another/base/path";
}
```
4. Optionally, set `services.maubot.pythonPackages` to a list of python3
packages to make available for Maubot plugins.
5. Optionally, set `services.maubot.plugins` to a list of Maubot
plugins (full list available at https://plugins.maubot.xyz/):
```nix
services.maubot.plugins = with config.services.maubot.package.plugins; [
reactbot
# This will only change the default config! After you create a
# plugin instance, the default config will be copied into that
# instance's config in Maubot's database, and further base config
# changes won't affect the running plugin.
(rss.override {
base_config = {
update_interval = 60;
max_backoff = 7200;
spam_sleep = 2;
command_prefix = "rss";
admins = [ "@chayleaf:pavluk.org" ];
};
})
];
# ...or...
services.maubot.plugins = config.services.maubot.package.plugins.allOfficialPlugins;
# ...or...
services.maubot.plugins = config.services.maubot.package.plugins.allPlugins;
# ...or...
services.maubot.plugins = with config.services.maubot.package.plugins; [
(weather.override {
# you can pass base_config as a string
base_config = ''
default_location: New York
default_units: M
default_language:
show_link: true
show_image: false
'';
})
];
{
services.maubot.plugins = with config.services.maubot.package.plugins; [
reactbot
# This will only change the default config! After you create a
# plugin instance, the default config will be copied into that
# instance's config in Maubot's database, and further base config
# changes won't affect the running plugin.
(rss.override {
base_config = {
update_interval = 60;
max_backoff = 7200;
spam_sleep = 2;
command_prefix = "rss";
admins = [ "@chayleaf:pavluk.org" ];
};
})
];
# ...or...
services.maubot.plugins = config.services.maubot.package.plugins.allOfficialPlugins;
# ...or...
services.maubot.plugins = config.services.maubot.package.plugins.allPlugins;
# ...or...
services.maubot.plugins = with config.services.maubot.package.plugins; [
(weather.override {
# you can pass base_config as a string
base_config = ''
default_location: New York
default_units: M
default_language:
show_link: true
show_image: false
'';
})
];
}
```
6. Start Maubot at least once before doing the following steps (it's
necessary to generate the initial config).

View file

@ -46,7 +46,7 @@ autoconfigure a new Pantalaimon instance, which will connect to the homeserver
set in [services.mjolnir.homeserverUrl](#opt-services.mjolnir.homeserverUrl) and Mjolnir itself
will be configured to connect to the new Pantalaimon instance.
```
```nix
{
services.mjolnir = {
enable = true;
@ -78,7 +78,7 @@ uses across an entire homeserver.
To use the Antispam Module, add `matrix-synapse-plugins.matrix-synapse-mjolnir-antispam`
to the Synapse plugin list and enable the `mjolnir.Module` module.
```
```nix
{
services.matrix-synapse = {
plugins = with pkgs; [

View file

@ -23,7 +23,7 @@ synapse server for the `example.org` domain, served from
the host `myhostname.example.org`. For more information,
please refer to the
[installation instructions of Synapse](https://element-hq.github.io/synapse/latest/setup/installation.html) .
```
```nix
{ pkgs, lib, config, ... }:
let
fqdn = "${config.networking.hostName}.${config.networking.domain}";
@ -158,7 +158,7 @@ in an additional file like this:
by `matrix-synapse`.
- Include the file like this in your configuration:
```
```nix
{
services.matrix-synapse.extraConfigFiles = [
"/run/secrets/matrix-shared-secret"
@ -190,7 +190,7 @@ fill in the required connection details automatically when you enter your
Matrix Identifier. See
[Try Matrix Now!](https://matrix.org/docs/projects/try-matrix-now.html)
for a list of existing clients and their supported featureset.
```
```nix
{
services.nginx.virtualHosts."element.${fqdn}" = {
enableACME = true;

View file

@ -16,7 +16,7 @@ unit which runs the sync server with an isolated user using the systemd
`DynamicUser` option.
This can be done by enabling the `anki-sync-server` service:
```
```nix
{ ... }:
{
@ -27,7 +27,7 @@ This can be done by enabling the `anki-sync-server` service:
It is necessary to set at least one username-password pair under
{option}`services.anki-sync-server.users`. For example
```
```nix
{
services.anki-sync-server.users = [
{
@ -50,7 +50,7 @@ you want to expose the sync server directly to other computers (not recommended
in most circumstances, because the sync server doesn't use HTTPS), then set the
following options:
```
```nix
{
services.anki-sync-server.host = "0.0.0.0";
services.anki-sync-server.openFirewall = true;

View file

@ -57,23 +57,25 @@ locations and database, instead of having to copy or rename them.
Make sure to disable `services.gitea`, when doing this.
```nix
services.gitea.enable = false;
{
services.gitea.enable = false;
services.forgejo = {
enable = true;
user = "gitea";
group = "gitea";
stateDir = "/var/lib/gitea";
database.name = "gitea";
database.user = "gitea";
};
services.forgejo = {
enable = true;
user = "gitea";
group = "gitea";
stateDir = "/var/lib/gitea";
database.name = "gitea";
database.user = "gitea";
};
users.users.gitea = {
home = "/var/lib/gitea";
useDefaultShell = true;
group = "gitea";
isSystemUser = true;
};
users.users.gitea = {
home = "/var/lib/gitea";
useDefaultShell = true;
group = "gitea";
isSystemUser = true;
};
users.groups.gitea = {};
users.groups.gitea = {};
}
```

View file

@ -114,11 +114,11 @@ in
port = mkOption {
type = types.port;
default = if !usePostgresql then 3306 else pg.port;
default = if usePostgresql then pg.settings.port else 3306;
defaultText = literalExpression ''
if config.${opt.database.type} != "postgresql"
then 3306
else config.${options.services.postgresql.port}
else 5432
'';
description = mdDoc "Database host port.";
};

View file

@ -100,11 +100,11 @@ in
port = mkOption {
type = types.port;
default = if !usePostgresql then 3306 else pg.port;
default = if usePostgresql then pg.settings.port else 3306;
defaultText = literalExpression ''
if config.${opt.database.type} != "postgresql"
then 3306
else config.${options.services.postgresql.port}
else 5432
'';
description = lib.mdDoc "Database host port.";
};

View file

@ -10,19 +10,21 @@ configure a webserver to proxy HTTP requests to the socket.
For instance, the following configuration could be used to use nginx as
frontend proxy:
```
services.nginx = {
enable = true;
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
virtualHosts."git.example.com" = {
enableACME = true;
forceSSL = true;
locations."/".proxyPass = "http://unix:/run/gitlab/gitlab-workhorse.socket";
```nix
{
services.nginx = {
enable = true;
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
virtualHosts."git.example.com" = {
enableACME = true;
forceSSL = true;
locations."/".proxyPass = "http://unix:/run/gitlab/gitlab-workhorse.socket";
};
};
};
}
```
## Configuring {#module-services-gitlab-configuring}
@ -35,36 +37,38 @@ The default state dir is `/var/gitlab/state`. This is where
all data like the repositories and uploads will be stored.
A basic configuration with some custom settings could look like this:
```
services.gitlab = {
enable = true;
databasePasswordFile = "/var/keys/gitlab/db_password";
initialRootPasswordFile = "/var/keys/gitlab/root_password";
https = true;
host = "git.example.com";
port = 443;
user = "git";
group = "git";
smtp = {
```nix
{
services.gitlab = {
enable = true;
address = "localhost";
port = 25;
};
secrets = {
dbFile = "/var/keys/gitlab/db";
secretFile = "/var/keys/gitlab/secret";
otpFile = "/var/keys/gitlab/otp";
jwsFile = "/var/keys/gitlab/jws";
};
extraConfig = {
gitlab = {
email_from = "gitlab-no-reply@example.com";
email_display_name = "Example GitLab";
email_reply_to = "gitlab-no-reply@example.com";
default_projects_features = { builds = false; };
databasePasswordFile = "/var/keys/gitlab/db_password";
initialRootPasswordFile = "/var/keys/gitlab/root_password";
https = true;
host = "git.example.com";
port = 443;
user = "git";
group = "git";
smtp = {
enable = true;
address = "localhost";
port = 25;
};
secrets = {
dbFile = "/var/keys/gitlab/db";
secretFile = "/var/keys/gitlab/secret";
otpFile = "/var/keys/gitlab/otp";
jwsFile = "/var/keys/gitlab/jws";
};
extraConfig = {
gitlab = {
email_from = "gitlab-no-reply@example.com";
email_display_name = "Example GitLab";
email_reply_to = "gitlab-no-reply@example.com";
default_projects_features = { builds = false; };
};
};
};
};
}
```
If you're setting up a new GitLab instance, generate new

View file

@ -901,6 +901,16 @@ in {
'';
};
sidekiq.concurrency = mkOption {
type = with types; nullOr int;
default = null;
description = lib.mdDoc ''
How many processor threads to use for processing sidekiq background job queues. When null, the GitLab default is used.
See <https://docs.gitlab.com/ee/administration/sidekiq/extra_sidekiq_processes.html#manage-thread-counts-explicitly> for details.
'';
};
sidekiq.memoryKiller.enable = mkOption {
type = types.bool;
default = true;
@ -1454,12 +1464,17 @@ in {
TimeoutSec = "infinity";
Restart = "always";
WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab";
ExecStart = utils.escapeSystemdExecArgs [
"${cfg.packages.gitlab}/share/gitlab/bin/sidekiq-cluster"
"-e" "production"
"-r" "."
"*" # all queue groups
];
ExecStart = utils.escapeSystemdExecArgs (
[
"${cfg.packages.gitlab}/share/gitlab/bin/sidekiq-cluster"
"*" # all queue groups
] ++ lib.optionals (cfg.sidekiq.concurrency != null) [
"--concurrency" (toString cfg.sidekiq.concurrency)
] ++ [
"--environment" "production"
"--require" "."
]
);
};
};
@ -1578,7 +1593,9 @@ in {
rm "${cfg.statePath}/config/gitlab-workhorse.json"
'';
ExecStart =
"${cfg.packages.gitlab-workhorse}/bin/workhorse "
"${cfg.packages.gitlab-workhorse}/bin/${
optionalString (lib.versionAtLeast (lib.getVersion cfg.packages.gitlab-workhorse) "16.10") "gitlab-"
}workhorse "
+ "-listenUmask 0 "
+ "-listenNetwork unix "
+ "-listenAddr /run/gitlab/gitlab-workhorse.socket "

View file

@ -0,0 +1,121 @@
{
config,
lib,
pkgs,
...
}: let
cfg = config.services.invidious-router;
settingsFormat = pkgs.formats.yaml {};
configFile = settingsFormat.generate "config.yaml" cfg.settings;
in {
meta.maintainers = [lib.maintainers.s1ls];
options.services.invidious-router = {
enable = lib.mkEnableOption "Enables the invidious-router service";
port = lib.mkOption {
type = lib.types.port;
default = 8050;
description = lib.mdDoc ''
Port to bind to.
'';
};
address = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1";
description = lib.mdDoc ''
Address on which invidious-router should listen on.
'';
};
settings = lib.mkOption {
type = lib.types.submodule {
freeformType = settingsFormat.type;
};
default = {
app = {
listen = "127.0.0.1:8050";
enable_youtube_fallback = false;
reload_instance_list_interval = "60s";
};
api = {
enabled = true;
url = "https://api.invidious.io/instances.json";
filter_regions = true;
allowed_regions = [
"AT"
"DE"
"CH"
];
};
healthcheck = {
path = "/";
allowed_status_codes = [
200
];
timeout = "1s";
interval = "10s";
filter_by_response_time = {
enabled = true;
qty_of_top_results = 3;
};
minimum_ratio = 0.2;
remove_no_ratio = true;
text_not_present = "YouTube is currently trying to block Invidious instances";
};
};
description = lib.mdDoc ''
Configuration for invidious-router.
Check https://gitlab.com/gaincoder/invidious-router#configuration
for configuration options.
'';
};
package = lib.mkOption {
type = lib.types.package;
default = pkgs.invidious-router;
defaultText = lib.literalExpression "pkgs.invidious-router";
description = lib.mdDoc ''
The invidious-router package to use.
'';
};
nginx = {
enable = lib.mkEnableOption (lib.mdDoc ''
Automatic nginx proxy configuration
'');
domain = lib.mkOption {
type = lib.types.str;
example = "invidious-router.example.com";
description = lib.mdDoc ''
The domain on which invidious-router should be served.
'';
};
extraDomains = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [];
description = lib.mdDoc ''
Additional domains to serve invidious-router on.
'';
};
};
};
config = lib.mkIf cfg.enable {
systemd.services.invidious-router = {
wantedBy = ["multi-user.target"];
serviceConfig = {
Restart = "on-failure";
ExecStart = "${lib.getExe cfg.package} --configfile ${configFile}";
DynamicUser = "yes";
};
};
services.nginx.virtualHosts = lib.mkIf cfg.nginx.enable {
${cfg.nginx.domain} = {
locations."/" = {
recommendedProxySettings = true;
proxyPass = "http://${cfg.address}:${toString cfg.port}";
};
enableACME = true;
forceSSL = true;
serverAliases = cfg.nginx.extraDomains;
};
};
};
}

View file

@ -47,7 +47,7 @@ in
after = [ "network.target" ];
serviceConfig = {
DynamicUser = true;
ExecStart = "${cfg.package}/bin/libreddit ${args}";
ExecStart = "${lib.getExe cfg.package} ${args}";
AmbientCapabilities = lib.mkIf (cfg.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
Restart = "on-failure";
RestartSec = "2s";

View file

@ -20,7 +20,7 @@ in {
extraFlags = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "Extra flags passed to llama-cpp-server.";
example = ["-c" "4096" "-ngl" "32" "--numa"];
example = ["-c" "4096" "-ngl" "32" "--numa" "numactl"];
default = [];
};

View file

@ -0,0 +1,133 @@
{ config, lib, pkgs, ... }:
let
inherit (lib) getExe mkIf mkOption mkEnableOption optionals types;
cfg = config.services.mollysocket;
configuration = format.generate "mollysocket.conf" cfg.settings;
format = pkgs.formats.toml { };
package = pkgs.writeShellScriptBin "mollysocket" ''
MOLLY_CONF=${configuration} exec ${getExe pkgs.mollysocket} "$@"
'';
in {
options.services.mollysocket = {
enable = mkEnableOption ''
[MollySocket](https://github.com/mollyim/mollysocket) for getting Signal
notifications via UnifiedPush
'';
settings = mkOption {
default = { };
description = ''
Configuration for MollySocket. Available options are listed
[here](https://github.com/mollyim/mollysocket#configuration).
'';
type = types.submodule {
freeformType = format.type;
options = {
host = mkOption {
default = "127.0.0.1";
description = "Listening address of the web server";
type = types.str;
};
port = mkOption {
default = 8020;
description = "Listening port of the web server";
type = types.port;
};
allowed_endpoints = mkOption {
default = [ "*" ];
description = "List of UnifiedPush servers";
example = [ "https://ntfy.sh" ];
type = with types; listOf str;
};
allowed_uuids = mkOption {
default = [ "*" ];
description = "UUIDs of Signal accounts that may use this server";
example = [ "abcdef-12345-tuxyz-67890" ];
type = with types; listOf str;
};
};
};
};
environmentFile = mkOption {
default = null;
description = ''
Environment file (see {manpage}`systemd.exec(5)` "EnvironmentFile="
section for the syntax) passed to the service. This option can be
used to safely include secrets in the configuration.
'';
example = "/run/secrets/mollysocket";
type = with types; nullOr path;
};
logLevel = mkOption {
default = "info";
description = "Set the {env}`RUST_LOG` environment variable";
example = "debug";
type = types.str;
};
};
config = mkIf cfg.enable {
environment.systemPackages = [
package
];
# see https://github.com/mollyim/mollysocket/blob/main/mollysocket.service
systemd.services.mollysocket = {
description = "MollySocket";
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
environment.RUST_LOG = cfg.logLevel;
serviceConfig = let
capabilities = [ "" ] ++ optionals (cfg.settings.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
in {
EnvironmentFile = cfg.environmentFile;
ExecStart = "${getExe package} server";
KillSignal = "SIGINT";
Restart = "on-failure";
StateDirectory = "mollysocket";
TimeoutStopSec = 5;
WorkingDirectory = "/var/lib/mollysocket";
# hardening
AmbientCapabilities = capabilities;
CapabilityBoundingSet = capabilities;
DevicePolicy = "closed";
DynamicUser = true;
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateTmp = true;
PrivateUsers = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
RemoveIPC = true;
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = [ "@system-service" "~@resources" "~@privileged" ];
UMask = "0077";
};
};
};
meta.maintainers = with lib.maintainers; [ dotlambda ];
}

View file

@ -10,16 +10,22 @@ let
format = pkgs.formats.yaml {};
bundle = "${cfg.package}/share/redmine/bin/bundle";
databaseYml = pkgs.writeText "database.yml" ''
production:
adapter: ${cfg.database.type}
database: ${cfg.database.name}
host: ${if (cfg.database.type == "postgresql" && cfg.database.socket != null) then cfg.database.socket else cfg.database.host}
port: ${toString cfg.database.port}
username: ${cfg.database.user}
password: #dbpass#
${optionalString (cfg.database.type == "mysql2" && cfg.database.socket != null) "socket: ${cfg.database.socket}"}
'';
databaseSettings = {
production = {
adapter = cfg.database.type;
database = if cfg.database.type == "sqlite3" then "${cfg.stateDir}/database.sqlite3" else cfg.database.name;
} // optionalAttrs (cfg.database.type != "sqlite3") {
host = if (cfg.database.type == "postgresql" && cfg.database.socket != null) then cfg.database.socket else cfg.database.host;
port = cfg.database.port;
username = cfg.database.user;
} // optionalAttrs (cfg.database.type != "sqlite3" && cfg.database.passwordFile != null) {
password = "#dbpass#";
} // optionalAttrs (cfg.database.type == "mysql2" && cfg.database.socket != null) {
socket = cfg.database.socket;
};
};
databaseYml = format.generate "database.yml" databaseSettings;
configurationYml = format.generate "configuration.yml" cfg.settings;
additionalEnvironment = pkgs.writeText "additional_environment.rb" cfg.extraEnv;
@ -145,7 +151,7 @@ in
database = {
type = mkOption {
type = types.enum [ "mysql2" "postgresql" ];
type = types.enum [ "mysql2" "postgresql" "sqlite3" ];
example = "postgresql";
default = "mysql2";
description = lib.mdDoc "Database engine to use.";
@ -261,7 +267,7 @@ in
config = mkIf cfg.enable {
assertions = [
{ assertion = cfg.database.passwordFile != null || cfg.database.socket != null;
{ assertion = cfg.database.type != "sqlite3" -> cfg.database.passwordFile != null || cfg.database.socket != null;
message = "one of services.redmine.database.socket or services.redmine.database.passwordFile must be set";
}
{ assertion = cfg.database.createLocally -> cfg.database.user == cfg.user;
@ -270,8 +276,8 @@ in
{ assertion = pgsqlLocal -> cfg.database.user == cfg.database.name;
message = "services.redmine.database.user and services.redmine.database.name must be the same when using a local postgresql database";
}
{ assertion = cfg.database.createLocally -> cfg.database.socket != null;
message = "services.redmine.database.socket must be set if services.redmine.database.createLocally is set to true";
{ assertion = (cfg.database.createLocally && cfg.database.type != "sqlite3") -> cfg.database.socket != null;
message = "services.redmine.database.socket must be set if services.redmine.database.createLocally is set to true and no sqlite database is used";
}
{ assertion = cfg.database.createLocally -> cfg.database.host == "localhost";
message = "services.redmine.database.host must be set to localhost if services.redmine.database.createLocally is set to true";
@ -395,9 +401,13 @@ in
# handle database.passwordFile & permissions
DBPASS=${optionalString (cfg.database.passwordFile != null) "$(head -n1 ${cfg.database.passwordFile})"}
cp -f ${databaseYml} "${cfg.stateDir}/config/database.yml"
sed -e "s,#dbpass#,$DBPASS,g" -i "${cfg.stateDir}/config/database.yml"
${optionalString ((cfg.database.type != "sqlite3") && (cfg.database.passwordFile != null)) ''
DBPASS="$(head -n1 ${cfg.database.passwordFile})"
sed -e "s,#dbpass#,$DBPASS,g" -i "${cfg.stateDir}/config/database.yml"
''}
chmod 440 "${cfg.stateDir}/config/database.yml"

View file

@ -12,7 +12,7 @@ This NixOS module also provides basic configuration integrating Sourcehut into l
and `services.postgresql` services.
A very basic configuration may look like this:
```
```nix
{ pkgs, ... }:
let
fqdn =
@ -66,9 +66,9 @@ in {
# Settings to setup what certificates are used for which endpoint.
virtualHosts = {
"${fqdn}".enableACME = true;
"meta.${fqdn}".useACMEHost = fqdn:
"man.${fqdn}".useACMEHost = fqdn:
"git.${fqdn}".useACMEHost = fqdn:
"meta.${fqdn}".useACMEHost = fqdn;
"man.${fqdn}".useACMEHost = fqdn;
"git.${fqdn}".useACMEHost = fqdn;
};
};
}

View file

@ -0,0 +1,158 @@
{ config, lib, pkgs, ... }:
let
cfg = config.services.wastebin;
inherit (lib)
mkEnableOption mkPackageOption mkIf mkOption
types mapAttrs isBool getExe boolToString optionalAttrs;
in
{
options.services.wastebin = {
enable = mkEnableOption "Wastenbin pastebin service";
package = mkPackageOption pkgs "wastebin" { };
stateDir = mkOption {
type = types.path;
default = "/var/lib/wastebin";
description = "State directory of the daemon.";
};
secretFile = mkOption {
type = types.nullOr types.path;
default = null;
example = "/run/secrets/wastebin.env";
description = ''
Path to file containing sensitive environment variables.
Some variables that can be considered secrets are:
- WASTEBIN_PASSWORD_SALT:
salt used to hash user passwords used for encrypting pastes.
- WASTEBIN_SIGNING_KEY:
sets the key to sign cookies. If not set, a random key will be
generated which means cookies will become invalid after restarts and
paste creators will not be able to delete their pastes anymore.
'';
};
settings = mkOption {
description = ''
Additional configuration for wastebin, see
<https://github.com/matze/wastebin#usage> for supported values.
For secrets use secretFile option instead.
'';
type = types.submodule {
freeformType = with types; attrsOf (oneOf [ bool int str ]);
options = {
WASTEBIN_ADDRESS_PORT = mkOption {
type = types.str;
default = "0.0.0.0:8088";
description = "Address and port to bind to";
};
WASTEBIN_BASE_URL = mkOption {
default = "http://localhost";
example = "https://myhost.tld";
type = types.str;
description = ''
Base URL for the QR code display. If not set, the user agent's Host
header field is used as an approximation.
'';
};
WASTEBIN_CACHE_SIZE = mkOption {
default = 128;
type = types.int;
description = "Number of rendered syntax highlight items to cache. Can be disabled by setting to 0.";
};
WASTEBIN_DATABASE_PATH = mkOption {
default = "/var/lib/wastebin/sqlite3.db"; # TODO make this default to stateDir/sqlite3.db
type = types.str;
description = "Path to the sqlite3 database file. If not set, an in-memory database is used.";
};
WASTEBIN_HTTP_TIMEOUT = mkOption {
default = 5;
type = types.int;
description = "Maximum number of seconds a request can be processed until wastebin responds with 408";
};
WASTEBIN_MAX_BODY_SIZE = mkOption {
default = 1024;
type = types.int;
description = "Number of bytes to accept for POST requests";
};
WASTEBIN_TITLE = mkOption {
default = "wastebin";
type = types.str;
description = "Overrides the HTML page title";
};
RUST_LOG = mkOption {
default = "info";
type = types.str;
description =
''
Influences logging. Besides the typical trace, debug, info etc.
keys, you can also set the tower_http key to some log level to get
additional information request and response logs.
'';
};
};
};
default = { };
example = {
WASTEBIN_TITLE = "My awesome pastebin";
};
};
};
config = mkIf cfg.enable
{
systemd.services.wastebin = {
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
environment = mapAttrs (_: v: if isBool v then boolToString v else toString v) cfg.settings;
serviceConfig = {
CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
DevicePolicy = "closed";
DynamicUser = true;
ExecStart = "${getExe cfg.package}";
LockPersonality = true;
MemoryDenyWriteExecute = true;
PrivateDevices = true;
PrivateUsers = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
RestrictNamespaces = true;
RestrictRealtime = true;
SystemCallArchitectures = [ "native" ];
SystemCallFilter = [ "@system-service" ];
StateDirectory = baseNameOf cfg.stateDir;
ReadWritePaths = cfg.stateDir;
} // optionalAttrs (cfg.secretFile != null) {
EnvironmentFile = cfg.secretFile;
};
};
};
meta.maintainers = with lib.maintainers; [ pinpox ];
}

View file

@ -12,7 +12,7 @@ unit which runs the chat client in a detached
session.
This can be done by enabling the `weechat` service:
```
```nix
{ ... }:
{
@ -30,7 +30,7 @@ allow your another user to attach to this session, the
`screenrc` needs to be tweaked by adding
[multiuser](https://www.gnu.org/software/screen/manual/html_node/Multiuser.html#Multiuser)
support:
```
```nix
{
programs.screen.screenrc = ''
multiuser on

View file

@ -0,0 +1,83 @@
{
config,
lib,
pkgs,
...
}:
let
inherit (lib) types;
cfg = config.services.workout-tracker;
stateDir = "workout-tracker";
in
{
options = {
services.workout-tracker = {
enable = lib.mkEnableOption "workout tracking web application for personal use (or family, friends), geared towards running and other GPX-based activities";
package = lib.mkPackageOption pkgs "workout-tracker" { };
address = lib.mkOption {
type = types.str;
default = "127.0.0.1";
description = "Web interface address.";
};
port = lib.mkOption {
type = types.port;
default = 8080;
description = "Web interface port.";
};
environmentFile = lib.mkOption {
type = types.nullOr types.path;
default = null;
example = "/run/keys/workout-tracker.env";
description = ''
An environment file as defined in {manpage}`systemd.exec(5)`.
Secrets like `WT_JWT_ENCRYPTION_KEY` may be passed to the service without adding them
to the world-readable Nix store.
'';
};
settings = lib.mkOption {
type = types.attrsOf types.str;
default = { };
description = ''
Extra config options.
'';
example = {
WT_LOGGING = "true";
WT_DEBUG = "false";
WT_DATABASE_DRIVER = "sqlite";
WT_DSN = "./database.db";
};
};
};
};
config = lib.mkIf cfg.enable {
systemd.services.workout-tracker = {
description = "A workout tracking web application for personal use (or family, friends), geared towards running and other GPX-based activities";
wantedBy = [ "multi-user.target" ];
environment = {
WT_BIND = "${cfg.address}:${toString cfg.port}";
WT_DATABASE_DRIVER = "sqlite";
WT_DSN = "./database.db";
} // cfg.settings;
serviceConfig = {
ExecStart = lib.getExe cfg.package;
DynamicUser = true;
StateDirectory = stateDir;
WorkingDirectory = "%S/${stateDir}";
Restart = "always";
EnvironmentFile = lib.optional (cfg.environmentFile != null) cfg.environmentFile;
};
};
};
meta.maintainers = with lib.maintainers; [ bhankas ];
}

View file

@ -9,17 +9,19 @@ A basic config that notifies you of all certificate changes for your
domain would look as follows:
```nix
services.certspotter = {
enable = true;
# replace example.org with your domain name
watchlist = [ ".example.org" ];
emailRecipients = [ "webmaster@example.org" ];
};
{
services.certspotter = {
enable = true;
# replace example.org with your domain name
watchlist = [ ".example.org" ];
emailRecipients = [ "webmaster@example.org" ];
};
# Configure an SMTP client
programs.msmtp.enable = true;
# Or you can use any other module that provides sendmail, like
# services.nullmailer, services.opensmtpd, services.postfix
# Configure an SMTP client
programs.msmtp.enable = true;
# Or you can use any other module that provides sendmail, like
# services.nullmailer, services.opensmtpd, services.postfix
}
```
In this case, the leading dot in `".example.org"` means that Cert
@ -59,16 +61,18 @@ For example, you can remove `emailRecipients` and send email
notifications manually using the following hook:
```nix
services.certspotter.hooks = [
(pkgs.writeShellScript "certspotter-hook" ''
function print_email() {
echo "Subject: [certspotter] $SUMMARY"
echo "Mime-Version: 1.0"
echo "Content-Type: text/plain; charset=US-ASCII"
echo
cat "$TEXT_FILENAME"
}
print_email | ${config.services.certspotter.sendmailPath} -i webmaster@example.org
'')
];
{
services.certspotter.hooks = [
(pkgs.writeShellScript "certspotter-hook" ''
function print_email() {
echo "Subject: [certspotter] $SUMMARY"
echo "Mime-Version: 1.0"
echo "Content-Type: text/plain; charset=US-ASCII"
echo
cat "$TEXT_FILENAME"
}
print_email | ${config.services.certspotter.sendmailPath} -i webmaster@example.org
'')
];
}
```

View file

@ -7,7 +7,7 @@ for validating a server's configuration.
A minimal configuration looks like this:
```
```nix
{
services.goss = {
enable = true;

View file

@ -11,15 +11,17 @@ email address and saves them to a local Elasticsearch instance looks
like this:
```nix
services.parsedmarc = {
enable = true;
settings.imap = {
host = "imap.example.com";
user = "alice@example.com";
password = "/path/to/imap_password_file";
{
services.parsedmarc = {
enable = true;
settings.imap = {
host = "imap.example.com";
user = "alice@example.com";
password = "/path/to/imap_password_file";
};
provision.geoIp = false; # Not recommended!
};
provision.geoIp = false; # Not recommended!
};
}
```
Note that GeoIP provisioning is disabled in the example for
@ -37,16 +39,18 @@ configured in the domain's dmarc policy is
`dmarc@monitoring.example.com`.
```nix
services.parsedmarc = {
enable = true;
provision = {
localMail = {
enable = true;
hostname = monitoring.example.com;
{
services.parsedmarc = {
enable = true;
provision = {
localMail = {
enable = true;
hostname = monitoring.example.com;
};
geoIp = false; # Not recommended!
};
geoIp = false; # Not recommended!
};
};
}
```
## Grafana and GeoIP {#module-services-parsedmarc-grafana-geoip}
@ -58,55 +62,57 @@ is automatically added as a Grafana datasource, and the dashboard is
added to Grafana as well.
```nix
services.parsedmarc = {
enable = true;
provision = {
localMail = {
enable = true;
hostname = url;
};
grafana = {
datasource = true;
dashboard = true;
{
services.parsedmarc = {
enable = true;
provision = {
localMail = {
enable = true;
hostname = url;
};
grafana = {
datasource = true;
dashboard = true;
};
};
};
};
# Not required, but recommended for full functionality
services.geoipupdate = {
settings = {
AccountID = 000000;
LicenseKey = "/path/to/license_key_file";
# Not required, but recommended for full functionality
services.geoipupdate = {
settings = {
AccountID = 000000;
LicenseKey = "/path/to/license_key_file";
};
};
};
services.grafana = {
enable = true;
addr = "0.0.0.0";
domain = url;
rootUrl = "https://" + url;
protocol = "socket";
security = {
adminUser = "admin";
adminPasswordFile = "/path/to/admin_password_file";
secretKeyFile = "/path/to/secret_key_file";
services.grafana = {
enable = true;
addr = "0.0.0.0";
domain = url;
rootUrl = "https://" + url;
protocol = "socket";
security = {
adminUser = "admin";
adminPasswordFile = "/path/to/admin_password_file";
secretKeyFile = "/path/to/secret_key_file";
};
};
};
services.nginx = {
enable = true;
recommendedTlsSettings = true;
recommendedOptimisation = true;
recommendedGzipSettings = true;
recommendedProxySettings = true;
upstreams.grafana.servers."unix:/${config.services.grafana.socket}" = {};
virtualHosts.${url} = {
root = config.services.grafana.staticRootPath;
enableACME = true;
forceSSL = true;
locations."/".tryFiles = "$uri @grafana";
locations."@grafana".proxyPass = "http://grafana";
services.nginx = {
enable = true;
recommendedTlsSettings = true;
recommendedOptimisation = true;
recommendedGzipSettings = true;
recommendedProxySettings = true;
upstreams.grafana.servers."unix:/${config.services.grafana.socket}" = {};
virtualHosts.${url} = {
root = config.services.grafana.staticRootPath;
enableACME = true;
forceSSL = true;
locations."/".tryFiles = "$uri @grafana";
locations."@grafana".proxyPass = "http://grafana";
};
};
};
users.users.nginx.extraGroups = [ "grafana" ];
users.users.nginx.extraGroups = [ "grafana" ];
}
```

View file

@ -9,7 +9,8 @@ One of the most common exporters is the
[node exporter](https://github.com/prometheus/node_exporter),
it provides hardware and OS metrics from the host it's
running on. The exporter could be configured as follows:
```
```nix
{
services.prometheus.exporters.node = {
enable = true;
port = 9100;
@ -23,6 +24,7 @@ running on. The exporter could be configured as follows:
openFirewall = true;
firewallFilter = "-i br0 -p tcp -m tcp --dport 9100";
};
}
```
It should now serve all metrics from the collectors that are explicitly
enabled and the ones that are
@ -35,7 +37,8 @@ configuration see `man configuration.nix` or search through
the [available options](https://nixos.org/nixos/options.html#prometheus.exporters).
Prometheus can now be configured to consume the metrics produced by the exporter:
```
```nix
{
services.prometheus = {
# ...
@ -49,7 +52,8 @@ Prometheus can now be configured to consume the metrics produced by the exporter
];
# ...
}
};
}
```
## Adding a new exporter {#module-services-prometheus-exporters-new-exporter}
@ -68,6 +72,7 @@ example:
- `extraFlags`
- `openFirewall`
- `firewallFilter`
- `firewallRules`
- `user`
- `group`
- As there is already a package available, the module can now be added. This
@ -75,7 +80,7 @@ example:
`nixos/modules/services/monitoring/prometheus/exporters/`
directory, which will be called postfix.nix and contains all exporter
specific options and configuration:
```
```nix
# nixpkgs/nixos/modules/services/prometheus/exporters/postfix.nix
{ config, lib, pkgs, options }:
@ -148,7 +153,7 @@ example:
Should an exporter option change at some point, it is possible to add
information about the change to the exporter definition similar to
`nixpkgs/nixos/modules/rename.nix`:
```
```nix
{ config, lib, pkgs, options }:
with lib;

View file

@ -169,6 +169,17 @@ let
is true. It is used as `ip46tables -I nixos-fw firewallFilter -j nixos-fw-accept`.
'';
};
firewallRules = mkOption {
type = types.nullOr types.lines;
default = null;
example = literalExpression ''
iifname "eth0" tcp dport ${toString port} counter accept
'';
description = lib.mdDoc ''
Specify rules for nftables to add to the input chain
when {option}`services.prometheus.exporters.${name}.openFirewall` is true.
'';
};
user = mkOption {
type = types.str;
default = "${name}-exporter";
@ -194,6 +205,7 @@ let
} // extraOpts);
} ({ config, ... }: mkIf config.openFirewall {
firewallFilter = mkDefault "-p tcp -m tcp --dport ${toString config.port}";
firewallRules = mkDefault ''tcp dport ${toString config.port} accept comment "${name}-exporter"'';
})];
internal = true;
default = {};
@ -212,6 +224,7 @@ let
mkExporterConf = { name, conf, serviceOpts }:
let
enableDynamicUser = serviceOpts.serviceConfig.DynamicUser or true;
nftables = config.networking.nftables.enable;
in
mkIf conf.enable {
warnings = conf.warnings or [];
@ -223,10 +236,11 @@ let
users.groups = (mkIf (conf.group == "${name}-exporter" && !enableDynamicUser) {
"${name}-exporter" = {};
});
networking.firewall.extraCommands = mkIf conf.openFirewall (concatStrings [
networking.firewall.extraCommands = mkIf (conf.openFirewall && !nftables) (concatStrings [
"ip46tables -A nixos-fw ${conf.firewallFilter} "
"-m comment --comment ${name}-exporter -j nixos-fw-accept"
]);
networking.firewall.extraInputRules = mkIf (conf.openFirewall && nftables) conf.firewallRules;
systemd.services."prometheus-${name}-exporter" = mkMerge ([{
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];

View file

@ -103,11 +103,11 @@ in
port = mkOption {
type = types.port;
default = if cfg.database.type == "mysql" then mysql.port else pgsql.port;
default = if cfg.database.type == "mysql" then mysql.port else pgsql.services.port;
defaultText = literalExpression ''
if config.${opt.database.type} == "mysql"
then config.${options.services.mysql.port}
else config.${options.services.postgresql.port}
else config.services.postgresql.settings.port
'';
description = lib.mdDoc "Database host port.";
};

View file

@ -95,11 +95,11 @@ in
port = mkOption {
type = types.port;
default = if cfg.database.type == "mysql" then mysql.port else pgsql.port;
default = if cfg.database.type == "mysql" then mysql.port else pgsql.settings.port;
defaultText = literalExpression ''
if config.${opt.database.type} == "mysql"
then config.${options.services.mysql.port}
else config.${options.services.postgresql.port}
else config.services.postgresql.settings.port
'';
description = lib.mdDoc "Database host port.";
};

View file

@ -3,14 +3,22 @@
with lib;
let
toStr = value:
if true == value then "yes"
else if false == value then "no"
else toString value;
cfg = config.services.davfs2;
cfgFile = pkgs.writeText "davfs2.conf" ''
dav_user ${cfg.davUser}
dav_group ${cfg.davGroup}
format = pkgs.formats.toml { };
configFile = let
settings = mapAttrsToList (n: v: "${n} = ${toStr v}") cfg.settings;
in pkgs.writeText "davfs2.conf" ''
${concatStringsSep "\n" settings}
${cfg.extraConfig}
'';
in
{
options.services.davfs2 = {
enable = mkOption {
type = types.bool;
@ -49,13 +57,46 @@ in
'';
description = lib.mdDoc ''
Extra lines appended to the configuration of davfs2.
See {manpage}`davfs2.conf(5)` for available settings.
**Note**: Please pass structured settings via
{option}`settings` instead, this option
will get deprecated in the future.
'' ;
};
settings = mkOption {
type = types.submodule {
freeformType = format.type;
};
default = {};
example = literalExpression ''
{
kernel_fs = "coda";
proxy = "foo.bar:8080";
use_locks = 0;
}
'';
description = lib.mdDoc ''
Extra settings appended to the configuration of davfs2.
See {manpage}`davfs2.conf(5)` for available settings.
'' ;
};
};
config = mkIf cfg.enable {
warnings = lib.optional (cfg.extraConfig != null) ''
services.davfs2.extraConfig will be deprecated in future releases, please use the settings option now.
'';
environment.systemPackages = [ pkgs.davfs2 ];
environment.etc."davfs2/davfs2.conf".source = cfgFile;
environment.etc."davfs2/davfs2.conf".source = configFile;
services.davfs2.settings = {
dav_user = cfg.davUser;
dav_group = cfg.davGroup;
};
users.groups = optionalAttrs (cfg.davGroup == "davfs2") {
davfs2.gid = config.ids.gids.davfs2;

View file

@ -8,7 +8,7 @@ replication tool for SQLite.
Litestream service is managed by a dedicated user named `litestream`
which needs permission to the database file. Here's an example config which gives
required permissions to access [grafana database](#opt-services.grafana.settings.database.path):
```
```nix
{ pkgs, ... }:
{
users.users.litestream.extraGroups = [ "grafana" ];

View file

@ -113,25 +113,6 @@ in
config = mkIf cfg.enable {
services.nfs.extraConfig = ''
[nfsd]
threads=${toString cfg.nproc}
${optionalString (cfg.hostName != null) "host=${cfg.hostName}"}
${cfg.extraNfsdConfig}
[mountd]
${optionalString (cfg.mountdPort != null) "port=${toString cfg.mountdPort}"}
[statd]
${optionalString (cfg.statdPort != null) "port=${toString cfg.statdPort}"}
[lockd]
${optionalString (cfg.lockdPort != null) ''
port=${toString cfg.lockdPort}
udp-port=${toString cfg.lockdPort}
''}
'';
services.rpcbind.enable = true;
boot.supportedFilesystems = [ "nfs" ]; # needed for statd and idmapd

View file

@ -246,12 +246,8 @@ in
shopt -s lastpipe
${pkg}/bin/makekeys | { read private ipv6 public; }
umask 0077
echo "CJDNS_PRIVATE_KEY=$private" >> /etc/cjdns.keys
echo -e "CJDNS_IPV6=$ipv6\nCJDNS_PUBLIC_KEY=$public" > /etc/cjdns.public
chmod 600 /etc/cjdns.keys
chmod 444 /etc/cjdns.public
install -m 600 <(echo "CJDNS_PRIVATE_KEY=$private") /etc/cjdns.keys
install -m 444 <(echo -e "CJDNS_IPV6=$ipv6\nCJDNS_PUBLIC_KEY=$public") /etc/cjdns.public
fi
if [ -z "$CJDNS_ADMIN_PASSWORD" ]; then

View file

@ -7,19 +7,21 @@ A storage server for Firefox Sync that you can easily host yourself.
The absolute minimal configuration for the sync server looks like this:
```nix
services.mysql.package = pkgs.mariadb;
{
services.mysql.package = pkgs.mariadb;
services.firefox-syncserver = {
enable = true;
secrets = builtins.toFile "sync-secrets" ''
SYNC_MASTER_SECRET=this-secret-is-actually-leaked-to-/nix/store
'';
singleNode = {
services.firefox-syncserver = {
enable = true;
hostname = "localhost";
url = "http://localhost:5000";
secrets = builtins.toFile "sync-secrets" ''
SYNC_MASTER_SECRET=this-secret-is-actually-leaked-to-/nix/store
'';
singleNode = {
enable = true;
hostname = "localhost";
url = "http://localhost:5000";
};
};
};
}
```
This will start a sync server that is only accessible locally. Once the services is

View file

@ -7,14 +7,16 @@ Mosquitto is a MQTT broker often used for IoT or home automation data transport.
A minimal configuration for Mosquitto is
```nix
services.mosquitto = {
enable = true;
listeners = [ {
acl = [ "pattern readwrite #" ];
omitPasswordAuth = true;
settings.allow_anonymous = true;
} ];
};
{
services.mosquitto = {
enable = true;
listeners = [ {
acl = [ "pattern readwrite #" ];
omitPasswordAuth = true;
settings.allow_anonymous = true;
} ];
};
}
```
This will start a broker on port 1883, listening on all interfaces of the machine, allowing
@ -25,37 +27,42 @@ full read access to a user `monitor` and restricted write access to a user `serv
like
```nix
services.mosquitto = {
enable = true;
listeners = [ {
users = {
monitor = {
acl = [ "read #" ];
password = "monitor";
{
services.mosquitto = {
enable = true;
listeners = [ {
users = {
monitor = {
acl = [ "read #" ];
password = "monitor";
};
service = {
acl = [ "write service/#" ];
password = "service";
};
};
service = {
acl = [ "write service/#" ];
password = "service";
};
};
} ];
};
} ];
};
}
```
TLS authentication is configured by setting TLS-related options of the listener:
```nix
services.mosquitto = {
enable = true;
listeners = [ {
port = 8883; # port change is not required, but helpful to avoid mistakes
# ...
settings = {
cafile = "/path/to/mqtt.ca.pem";
certfile = "/path/to/mqtt.pem";
keyfile = "/path/to/mqtt.key";
};
} ];
{
services.mosquitto = {
enable = true;
listeners = [ {
port = 8883; # port change is not required, but helpful to avoid mistakes
# ...
settings = {
cafile = "/path/to/mqtt.ca.pem";
certfile = "/path/to/mqtt.pem";
keyfile = "/path/to/mqtt.key";
};
} ];
};
}
```
## Configuration {#module-services-mosquitto-config}

View file

@ -1,4 +1,4 @@
{ config, pkgs, lib, ... }:
{ config, pkgs, lib, utils, ... }:
let
cfg = config.services.mycelium;
@ -46,6 +46,15 @@ in
Adds the hosted peers from https://github.com/threefoldtech/mycelium#hosted-public-nodes.
'';
};
extraArgs = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = ''
Extra command-line arguments to pass to mycelium.
See `mycelium --help` for all available options.
'';
};
};
config = lib.mkIf cfg.enable {
networking.firewall.allowedTCPPorts = lib.optionals cfg.openFirewall [ 9651 ];
@ -87,6 +96,7 @@ in
)
"--tun-name"
"mycelium"
"${utils.escapeSystemdExecArgs cfg.extraArgs}"
] ++
(lib.optional (cfg.addHostedPublicNodes || cfg.peers != [ ]) "--peers")
++ cfg.peers ++ (lib.optionals cfg.addHostedPublicNodes [
@ -130,4 +140,3 @@ in
maintainers = with lib.maintainers; [ flokli lassulus ];
};
}

View file

@ -5,7 +5,9 @@
The absolute minimal configuration for the netbird daemon looks like this:
```nix
services.netbird.enable = true;
{
services.netbird.enable = true;
}
```
This will set up a netbird service listening on the port `51820` associated to the
@ -14,7 +16,9 @@ This will set up a netbird service listening on the port `51820` associated to t
It is strictly equivalent to setting:
```nix
services.netbird.tunnels.wt0.stateDir = "netbird";
{
services.netbird.tunnels.wt0.stateDir = "netbird";
}
```
The `enable` option is mainly kept for backward compatibility, as defining netbird
@ -29,11 +33,13 @@ The following configuration will start a netbird daemon using the interface `wt1
the port 51830. Its configuration file will then be located at `/var/lib/netbird-wt1/config.json`.
```nix
services.netbird.tunnels = {
wt1 = {
port = 51830;
{
services.netbird.tunnels = {
wt1 = {
port = 51830;
};
};
};
}
```
To interact with it, you will need to specify the correct daemon address:
@ -48,9 +54,11 @@ It is also possible to overwrite default options passed to the service, for
example:
```nix
services.netbird.tunnels.wt1.environment = {
NB_DAEMON_ADDR = "unix:///var/run/toto.sock"
};
{
services.netbird.tunnels.wt1.environment = {
NB_DAEMON_ADDR = "unix:///var/run/toto.sock";
};
}
```
This will set the socket to interact with the netbird service to `/var/run/toto.sock`.

View file

@ -390,7 +390,7 @@ in
};
});
default = [ ];
example = literalExpression ''[{ name = "03f0:4e1d"; script = "''${pkgs.modemmanager}/share/ModemManager/fcc-unlock.available.d/03f0:4e1d"; }]'';
example = literalExpression ''[{ id = "03f0:4e1d"; path = "''${pkgs.modemmanager}/share/ModemManager/fcc-unlock.available.d/03f0:4e1d"; }]'';
description = lib.mdDoc ''
List of FCC unlock scripts to enable on the system, behaving as described in
https://modemmanager.org/docs/modemmanager/fcc-unlock/#integration-with-third-party-fcc-unlock-tools.

View file

@ -81,7 +81,6 @@ let
zonesdir: "${stateDir}"
# the list of dynamically added zones.
database: "${stateDir}/var/nsd.db"
pidfile: "${pidFile}"
xfrdfile: "${stateDir}/var/xfrd.state"
xfrdir: "${stateDir}/tmp"
@ -112,6 +111,7 @@ let
${maybeString "version: " cfg.version}
xfrd-reload-timeout: ${toString cfg.xfrdReloadTimeout}
zonefiles-check: ${yesOrNo cfg.zonefilesCheck}
zonefiles-write: ${toString cfg.zonefilesWrite}
${maybeString "rrl-ipv4-prefix-length: " cfg.ratelimit.ipv4PrefixLength}
${maybeString "rrl-ipv6-prefix-length: " cfg.ratelimit.ipv6PrefixLength}
@ -173,6 +173,7 @@ let
${maybeToString "min-retry-time: " zone.minRetrySecs}
allow-axfr-fallback: ${yesOrNo zone.allowAXFRFallback}
multi-master-check: ${yesOrNo zone.multiMasterCheck}
${forEach " allow-notify: " zone.allowNotify}
${forEach " request-xfr: " zone.requestXFR}
@ -201,7 +202,7 @@ let
allowAXFRFallback = mkOption {
type = types.bool;
default = true;
description = lib.mdDoc ''
description = ''
If NSD as secondary server should be allowed to AXFR if the primary
server does not allow IXFR.
'';
@ -213,7 +214,7 @@ let
example = [ "192.0.2.0/24 NOKEY" "10.0.0.1-10.0.0.5 my_tsig_key_name"
"10.0.3.4&255.255.0.0 BLOCKED"
];
description = lib.mdDoc ''
description = ''
Listed primary servers are allowed to notify this secondary server.
Format: `<ip> <key-name | NOKEY | BLOCKED>`
@ -243,7 +244,7 @@ let
# to default values, breaking the parent inheriting function.
type = types.attrsOf types.anything;
default = {};
description = lib.mdDoc ''
description = ''
Children zones inherit all options of their parents. Attributes
defined in a child will overwrite the ones of its parent. Only
leaf zones will be actually served. This way it's possible to
@ -256,29 +257,29 @@ let
data = mkOption {
type = types.lines;
default = "";
description = lib.mdDoc ''
description = ''
The actual zone data. This is the content of your zone file.
Use imports or pkgs.lib.readFile if you don't want this data in your config file.
'';
};
dnssec = mkEnableOption (lib.mdDoc "DNSSEC");
dnssec = mkEnableOption "DNSSEC";
dnssecPolicy = {
algorithm = mkOption {
type = types.str;
default = "RSASHA256";
description = lib.mdDoc "Which algorithm to use for DNSSEC";
description = "Which algorithm to use for DNSSEC";
};
keyttl = mkOption {
type = types.str;
default = "1h";
description = lib.mdDoc "TTL for dnssec records";
description = "TTL for dnssec records";
};
coverage = mkOption {
type = types.str;
default = "1y";
description = lib.mdDoc ''
description = ''
The length of time to ensure that keys will be correct; no action will be taken to create new keys to be activated after this time.
'';
};
@ -289,7 +290,7 @@ let
postPublish = "1w";
rollPeriod = "1mo";
};
description = lib.mdDoc "Key policy for zone signing keys";
description = "Key policy for zone signing keys";
};
ksk = mkOption {
type = keyPolicy;
@ -298,14 +299,14 @@ let
postPublish = "1mo";
rollPeriod = "0";
};
description = lib.mdDoc "Key policy for key signing keys";
description = "Key policy for key signing keys";
};
};
maxRefreshSecs = mkOption {
type = types.nullOr types.int;
default = null;
description = lib.mdDoc ''
description = ''
Limit refresh time for secondary zones. This is the timer which
checks to see if the zone has to be refetched when it expires.
Normally the value from the SOA record is used, but this option
@ -316,7 +317,7 @@ let
minRefreshSecs = mkOption {
type = types.nullOr types.int;
default = null;
description = lib.mdDoc ''
description = ''
Limit refresh time for secondary zones.
'';
};
@ -324,7 +325,7 @@ let
maxRetrySecs = mkOption {
type = types.nullOr types.int;
default = null;
description = lib.mdDoc ''
description = ''
Limit retry time for secondary zones. This is the timeout after
a failed fetch attempt for the zone. Normally the value from
the SOA record is used, but this option restricts that value.
@ -334,17 +335,26 @@ let
minRetrySecs = mkOption {
type = types.nullOr types.int;
default = null;
description = lib.mdDoc ''
description = ''
Limit retry time for secondary zones.
'';
};
multiMasterCheck = mkOption {
type = types.bool;
default = false;
description = ''
If enabled, checks all masters for the last zone version.
It uses the higher version from all configured masters.
Useful if you have multiple masters that have different version numbers served.
'';
};
notify = mkOption {
type = types.listOf types.str;
default = [];
example = [ "10.0.0.1@3721 my_key" "::5 NOKEY" ];
description = lib.mdDoc ''
description = ''
This primary server will notify all given secondary servers about
zone changes.
@ -361,7 +371,7 @@ let
notifyRetry = mkOption {
type = types.int;
default = 5;
description = lib.mdDoc ''
description = ''
Specifies the number of retries for failed notifies. Set this along with notify.
'';
};
@ -370,7 +380,7 @@ let
type = types.nullOr types.str;
default = null;
example = "2000::1@1234";
description = lib.mdDoc ''
description = ''
This address will be used for zone-transfer requests if configured
as a secondary server or notifications in case of a primary server.
Supply either a plain IPv4 or IPv6 address with an optional port
@ -382,7 +392,7 @@ let
type = types.listOf types.str;
default = [];
example = [ "192.0.2.0/24 NOKEY" "192.0.2.0/24 my_tsig_key_name" ];
description = lib.mdDoc ''
description = ''
Allow these IPs and TSIG to transfer zones, addr TSIG|NOKEY|BLOCKED
address range 192.0.2.0/24, 1.2.3.4&255.255.0.0, 3.0.2.20-3.0.2.40
'';
@ -391,7 +401,7 @@ let
requestXFR = mkOption {
type = types.listOf types.str;
default = [];
description = lib.mdDoc ''
description = ''
Format: `[AXFR|UDP] <ip-address> <key-name | NOKEY>`
'';
};
@ -399,7 +409,7 @@ let
rrlWhitelist = mkOption {
type = with types; listOf (enum [ "nxdomain" "error" "referral" "any" "rrsig" "wildcard" "nodata" "dnskey" "positive" "all" ]);
default = [];
description = lib.mdDoc ''
description = ''
Whitelists the given rrl-types.
'';
};
@ -408,7 +418,7 @@ let
type = types.nullOr types.str;
default = null;
example = "%s";
description = lib.mdDoc ''
description = ''
When set to something distinct to null NSD is able to collect
statistics per zone. All statistics of this zone(s) will be added
to the group specified by this given name. Use "%s" to use the zones
@ -423,19 +433,19 @@ let
options = {
keySize = mkOption {
type = types.int;
description = lib.mdDoc "Key size in bits";
description = "Key size in bits";
};
prePublish = mkOption {
type = types.str;
description = lib.mdDoc "How long in advance to publish new keys";
description = "How long in advance to publish new keys";
};
postPublish = mkOption {
type = types.str;
description = lib.mdDoc "How long after deactivation to keep a key in the zone";
description = "How long after deactivation to keep a key in the zone";
};
rollPeriod = mkOption {
type = types.str;
description = lib.mdDoc "How frequently to change keys";
description = "How frequently to change keys";
};
};
};
@ -478,14 +488,14 @@ in
# options are ordered alphanumerically
options.services.nsd = {
enable = mkEnableOption (lib.mdDoc "NSD authoritative DNS server");
enable = mkEnableOption "NSD authoritative DNS server";
bind8Stats = mkEnableOption (lib.mdDoc "BIND8 like statistics");
bind8Stats = mkEnableOption "BIND8 like statistics";
dnssecInterval = mkOption {
type = types.str;
default = "1h";
description = lib.mdDoc ''
description = ''
How often to check whether dnssec key rollover is required
'';
};
@ -493,7 +503,7 @@ in
extraConfig = mkOption {
type = types.lines;
default = "";
description = lib.mdDoc ''
description = ''
Extra nsd config.
'';
};
@ -501,7 +511,7 @@ in
hideVersion = mkOption {
type = types.bool;
default = true;
description = lib.mdDoc ''
description = ''
Whether NSD should answer VERSION.BIND and VERSION.SERVER CHAOS class queries.
'';
};
@ -509,7 +519,7 @@ in
identity = mkOption {
type = types.str;
default = "unidentified server";
description = lib.mdDoc ''
description = ''
Identify the server (CH TXT ID.SERVER entry).
'';
};
@ -517,7 +527,7 @@ in
interfaces = mkOption {
type = types.listOf types.str;
default = [ "127.0.0.0" "::1" ];
description = lib.mdDoc ''
description = ''
What addresses the server should listen to.
'';
};
@ -525,7 +535,7 @@ in
ipFreebind = mkOption {
type = types.bool;
default = false;
description = lib.mdDoc ''
description = ''
Whether to bind to nonlocal addresses and interfaces that are down.
Similar to ip-transparent.
'';
@ -534,7 +544,7 @@ in
ipTransparent = mkOption {
type = types.bool;
default = false;
description = lib.mdDoc ''
description = ''
Allow binding to non local addresses.
'';
};
@ -542,7 +552,7 @@ in
ipv4 = mkOption {
type = types.bool;
default = true;
description = lib.mdDoc ''
description = ''
Whether to listen on IPv4 connections.
'';
};
@ -550,7 +560,7 @@ in
ipv4EDNSSize = mkOption {
type = types.int;
default = 4096;
description = lib.mdDoc ''
description = ''
Preferred EDNS buffer size for IPv4.
'';
};
@ -558,7 +568,7 @@ in
ipv6 = mkOption {
type = types.bool;
default = true;
description = lib.mdDoc ''
description = ''
Whether to listen on IPv6 connections.
'';
};
@ -566,7 +576,7 @@ in
ipv6EDNSSize = mkOption {
type = types.int;
default = 4096;
description = lib.mdDoc ''
description = ''
Preferred EDNS buffer size for IPv6.
'';
};
@ -574,7 +584,7 @@ in
logTimeAscii = mkOption {
type = types.bool;
default = true;
description = lib.mdDoc ''
description = ''
Log time in ascii, if false then in unix epoch seconds.
'';
};
@ -582,7 +592,7 @@ in
nsid = mkOption {
type = types.nullOr types.str;
default = null;
description = lib.mdDoc ''
description = ''
NSID identity (hex string, or "ascii_somestring").
'';
};
@ -590,7 +600,7 @@ in
port = mkOption {
type = types.port;
default = 53;
description = lib.mdDoc ''
description = ''
Port the service should bind do.
'';
};
@ -599,7 +609,7 @@ in
type = types.bool;
default = pkgs.stdenv.isLinux;
defaultText = literalExpression "pkgs.stdenv.isLinux";
description = lib.mdDoc ''
description = ''
Whether to enable SO_REUSEPORT on all used sockets. This lets multiple
processes bind to the same port. This speeds up operation especially
if the server count is greater than one and makes fast restarts less
@ -610,18 +620,18 @@ in
rootServer = mkOption {
type = types.bool;
default = false;
description = lib.mdDoc ''
description = ''
Whether this server will be a root server (a DNS root server, you
usually don't want that).
'';
};
roundRobin = mkEnableOption (lib.mdDoc "round robin rotation of records");
roundRobin = mkEnableOption "round robin rotation of records";
serverCount = mkOption {
type = types.int;
default = 1;
description = lib.mdDoc ''
description = ''
Number of NSD servers to fork. Put the number of CPUs to use here.
'';
};
@ -629,7 +639,7 @@ in
statistics = mkOption {
type = types.nullOr types.int;
default = null;
description = lib.mdDoc ''
description = ''
Statistics are produced every number of seconds. Prints to log.
If null no statistics are logged.
'';
@ -638,7 +648,7 @@ in
tcpCount = mkOption {
type = types.int;
default = 100;
description = lib.mdDoc ''
description = ''
Maximum number of concurrent TCP connections per server.
'';
};
@ -646,7 +656,7 @@ in
tcpQueryCount = mkOption {
type = types.int;
default = 0;
description = lib.mdDoc ''
description = ''
Maximum number of queries served on a single TCP connection.
0 means no maximum.
'';
@ -655,7 +665,7 @@ in
tcpTimeout = mkOption {
type = types.int;
default = 120;
description = lib.mdDoc ''
description = ''
TCP timeout in seconds.
'';
};
@ -663,7 +673,7 @@ in
verbosity = mkOption {
type = types.int;
default = 0;
description = lib.mdDoc ''
description = ''
Verbosity level.
'';
};
@ -671,7 +681,7 @@ in
version = mkOption {
type = types.nullOr types.str;
default = null;
description = lib.mdDoc ''
description = ''
The version string replied for CH TXT version.server and version.bind
queries. Will use the compiled package version on null.
See hideVersion for enabling/disabling this responses.
@ -681,7 +691,7 @@ in
xfrdReloadTimeout = mkOption {
type = types.int;
default = 1;
description = lib.mdDoc ''
description = ''
Number of seconds between reloads triggered by xfrd.
'';
};
@ -689,11 +699,22 @@ in
zonefilesCheck = mkOption {
type = types.bool;
default = true;
description = lib.mdDoc ''
description = ''
Whether to check mtime of all zone files on start and sighup.
'';
};
zonefilesWrite = mkOption {
type = types.int;
default = 0;
description = ''
Write changed secondary zones to their zonefile every N seconds.
If the zone (pattern) configuration has "" zonefile, it is not written.
Zones that have received zone transfer updates are written to their zonefile.
0 disables writing to zone files.
'';
};
keys = mkOption {
type = types.attrsOf (types.submodule {
@ -702,14 +723,14 @@ in
algorithm = mkOption {
type = types.str;
default = "hmac-sha256";
description = lib.mdDoc ''
description = ''
Authentication algorithm for this key.
'';
};
keyFile = mkOption {
type = types.path;
description = lib.mdDoc ''
description = ''
Path to the file which contains the actual base64 encoded
key. The key will be copied into "${stateDir}/private" before
NSD starts. The copied file is only accessibly by the NSD
@ -727,7 +748,7 @@ in
};
}
'';
description = lib.mdDoc ''
description = ''
Define your TSIG keys here.
'';
};
@ -735,12 +756,12 @@ in
ratelimit = {
enable = mkEnableOption (lib.mdDoc "ratelimit capabilities");
enable = mkEnableOption "ratelimit capabilities";
ipv4PrefixLength = mkOption {
type = types.nullOr types.int;
default = null;
description = lib.mdDoc ''
description = ''
IPv4 prefix length. Addresses are grouped by netblock.
'';
};
@ -748,7 +769,7 @@ in
ipv6PrefixLength = mkOption {
type = types.nullOr types.int;
default = null;
description = lib.mdDoc ''
description = ''
IPv6 prefix length. Addresses are grouped by netblock.
'';
};
@ -756,7 +777,7 @@ in
ratelimit = mkOption {
type = types.int;
default = 200;
description = lib.mdDoc ''
description = ''
Max qps allowed from any query source.
0 means unlimited. With an verbosity of 2 blocked and
unblocked subnets will be logged.
@ -766,7 +787,7 @@ in
slip = mkOption {
type = types.nullOr types.int;
default = null;
description = lib.mdDoc ''
description = ''
Number of packets that get discarded before replying a SLIP response.
0 disables SLIP responses. 1 will make every response a SLIP response.
'';
@ -775,7 +796,7 @@ in
size = mkOption {
type = types.int;
default = 1000000;
description = lib.mdDoc ''
description = ''
Size of the hashtable. More buckets use more memory but lower
the chance of hash hash collisions.
'';
@ -784,7 +805,7 @@ in
whitelistRatelimit = mkOption {
type = types.int;
default = 2000;
description = lib.mdDoc ''
description = ''
Max qps allowed from whitelisted sources.
0 means unlimited. Set the rrl-whitelist option for specific
queries to apply this limit instead of the default to them.
@ -796,12 +817,12 @@ in
remoteControl = {
enable = mkEnableOption (lib.mdDoc "remote control via nsd-control");
enable = mkEnableOption "remote control via nsd-control";
controlCertFile = mkOption {
type = types.path;
default = "/etc/nsd/nsd_control.pem";
description = lib.mdDoc ''
description = ''
Path to the client certificate signed with the server certificate.
This file is used by nsd-control and generated by nsd-control-setup.
'';
@ -810,7 +831,7 @@ in
controlKeyFile = mkOption {
type = types.path;
default = "/etc/nsd/nsd_control.key";
description = lib.mdDoc ''
description = ''
Path to the client private key, which is used by nsd-control
but not by the server. This file is generated by nsd-control-setup.
'';
@ -819,7 +840,7 @@ in
interfaces = mkOption {
type = types.listOf types.str;
default = [ "127.0.0.1" "::1" ];
description = lib.mdDoc ''
description = ''
Which interfaces NSD should bind to for remote control.
'';
};
@ -827,7 +848,7 @@ in
port = mkOption {
type = types.port;
default = 8952;
description = lib.mdDoc ''
description = ''
Port number for remote control operations (uses TLS over TCP).
'';
};
@ -835,7 +856,7 @@ in
serverCertFile = mkOption {
type = types.path;
default = "/etc/nsd/nsd_server.pem";
description = lib.mdDoc ''
description = ''
Path to the server self signed certificate, which is used by the server
but and by nsd-control. This file is generated by nsd-control-setup.
'';
@ -844,7 +865,7 @@ in
serverKeyFile = mkOption {
type = types.path;
default = "/etc/nsd/nsd_server.key";
description = lib.mdDoc ''
description = ''
Path to the server private key, which is used by the server
but not by nsd-control. This file is generated by nsd-control-setup.
'';
@ -886,7 +907,7 @@ in
};
}
'';
description = lib.mdDoc ''
description = ''
Define your zones here. Zones can cascade other zones and therefore
inherit settings from parent zones. Look at the definition of
children to learn about inheritance and child zones.

View file

@ -17,11 +17,13 @@ The `config.exs` file can be further customized following the instructions on th
## Initializing the database {#module-services-pleroma-initialize-db}
First, the Postgresql service must be enabled in the NixOS configuration
```
services.postgresql = {
enable = true;
package = pkgs.postgresql_13;
};
```nix
{
services.postgresql = {
enable = true;
package = pkgs.postgresql_13;
};
}
```
and activated with the usual
```ShellSession
@ -38,43 +40,45 @@ $ sudo -u postgres psql -f setup.psql
In this section we will enable the Pleroma service only locally, so its configurations can be improved incrementally.
This is an example of configuration, where [](#opt-services.pleroma.configs) option contains the content of the file `config.exs`, generated [in the first section](#module-services-pleroma-generate-config), but with the secrets (database password, endpoint secret key, salts, etc.) removed. Removing secrets is important, because otherwise they will be stored publicly in the Nix store.
```
services.pleroma = {
enable = true;
secretConfigFile = "/var/lib/pleroma/secrets.exs";
configs = [
''
import Config
```nix
{
services.pleroma = {
enable = true;
secretConfigFile = "/var/lib/pleroma/secrets.exs";
configs = [
''
import Config
config :pleroma, Pleroma.Web.Endpoint,
url: [host: "pleroma.example.net", scheme: "https", port: 443],
http: [ip: {127, 0, 0, 1}, port: 4000]
config :pleroma, Pleroma.Web.Endpoint,
url: [host: "pleroma.example.net", scheme: "https", port: 443],
http: [ip: {127, 0, 0, 1}, port: 4000]
config :pleroma, :instance,
name: "Test",
email: "admin@example.net",
notify_email: "admin@example.net",
limit: 5000,
registrations_open: true
config :pleroma, :instance,
name: "Test",
email: "admin@example.net",
notify_email: "admin@example.net",
limit: 5000,
registrations_open: true
config :pleroma, :media_proxy,
enabled: false,
redirect_on_failure: true
config :pleroma, :media_proxy,
enabled: false,
redirect_on_failure: true
config :pleroma, Pleroma.Repo,
adapter: Ecto.Adapters.Postgres,
username: "pleroma",
database: "pleroma",
hostname: "localhost"
config :pleroma, Pleroma.Repo,
adapter: Ecto.Adapters.Postgres,
username: "pleroma",
database: "pleroma",
hostname: "localhost"
# Configure web push notifications
config :web_push_encryption, :vapid_details,
subject: "mailto:admin@example.net"
# Configure web push notifications
config :web_push_encryption, :vapid_details,
subject: "mailto:admin@example.net"
# ... TO CONTINUE ...
''
];
};
# ... TO CONTINUE ...
''
];
};
}
```
Secrets must be moved into a file pointed by [](#opt-services.pleroma.secretConfigFile), in our case `/var/lib/pleroma/secrets.exs`. This file can be created copying the previously generated `config.exs` file and then removing all the settings, except the secrets. This is an example
@ -121,60 +125,62 @@ $ pleroma_ctl user new <nickname> <email> --admin --moderator --password <passw
In this configuration, Pleroma is listening only on the local port 4000. Nginx can be configured as a Reverse Proxy, for forwarding requests from public ports to the Pleroma service. This is an example of configuration, using
[Let's Encrypt](https://letsencrypt.org/) for the TLS certificates
```
security.acme = {
email = "root@example.net";
acceptTerms = true;
};
```nix
{
security.acme = {
email = "root@example.net";
acceptTerms = true;
};
services.nginx = {
enable = true;
addSSL = true;
services.nginx = {
enable = true;
addSSL = true;
recommendedTlsSettings = true;
recommendedOptimisation = true;
recommendedGzipSettings = true;
recommendedTlsSettings = true;
recommendedOptimisation = true;
recommendedGzipSettings = true;
recommendedProxySettings = false;
# NOTE: if enabled, the NixOS proxy optimizations will override the Pleroma
# specific settings, and they will enter in conflict.
recommendedProxySettings = false;
# NOTE: if enabled, the NixOS proxy optimizations will override the Pleroma
# specific settings, and they will enter in conflict.
virtualHosts = {
"pleroma.example.net" = {
http2 = true;
enableACME = true;
forceSSL = true;
virtualHosts = {
"pleroma.example.net" = {
http2 = true;
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:4000";
locations."/" = {
proxyPass = "http://127.0.0.1:4000";
extraConfig = ''
etag on;
gzip on;
extraConfig = ''
etag on;
gzip on;
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'POST, PUT, DELETE, GET, PATCH, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Idempotency-Key' always;
add_header 'Access-Control-Expose-Headers' 'Link, X-RateLimit-Reset, X-RateLimit-Limit, X-RateLimit-Remaining, X-Request-Id' always;
if ($request_method = OPTIONS) {
return 204;
}
add_header X-XSS-Protection "1; mode=block";
add_header X-Permitted-Cross-Domain-Policies none;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header Referrer-Policy same-origin;
add_header X-Download-Options noopen;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'POST, PUT, DELETE, GET, PATCH, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Idempotency-Key' always;
add_header 'Access-Control-Expose-Headers' 'Link, X-RateLimit-Reset, X-RateLimit-Limit, X-RateLimit-Remaining, X-Request-Id' always;
if ($request_method = OPTIONS) {
return 204;
}
add_header X-XSS-Protection "1; mode=block";
add_header X-Permitted-Cross-Domain-Policies none;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header Referrer-Policy same-origin;
add_header X-Download-Options noopen;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
client_max_body_size 16m;
# NOTE: increase if users need to upload very big files
'';
client_max_body_size 16m;
# NOTE: increase if users need to upload very big files
'';
};
};
};
};
};
}
```

View file

@ -25,25 +25,27 @@ A good configuration to start with, including a
[Multi User Chat (MUC)](https://xmpp.org/extensions/xep-0045.html)
endpoint as well as a [HTTP File Upload](https://xmpp.org/extensions/xep-0363.html)
endpoint will look like this:
```
services.prosody = {
enable = true;
admins = [ "root@example.org" ];
ssl.cert = "/var/lib/acme/example.org/fullchain.pem";
ssl.key = "/var/lib/acme/example.org/key.pem";
virtualHosts."example.org" = {
enabled = true;
domain = "example.org";
ssl.cert = "/var/lib/acme/example.org/fullchain.pem";
ssl.key = "/var/lib/acme/example.org/key.pem";
```nix
{
services.prosody = {
enable = true;
admins = [ "root@example.org" ];
ssl.cert = "/var/lib/acme/example.org/fullchain.pem";
ssl.key = "/var/lib/acme/example.org/key.pem";
virtualHosts."example.org" = {
enabled = true;
domain = "example.org";
ssl.cert = "/var/lib/acme/example.org/fullchain.pem";
ssl.key = "/var/lib/acme/example.org/key.pem";
};
muc = [ {
domain = "conference.example.org";
} ];
uploadHttp = {
domain = "upload.example.org";
};
};
muc = [ {
domain = "conference.example.org";
} ];
uploadHttp = {
domain = "upload.example.org";
};
};
}
```
## Let's Encrypt Configuration {#module-services-prosody-letsencrypt}
@ -57,16 +59,18 @@ certificate by leveraging the ACME
Provided the setup detailed in the previous section, you'll need the following acme configuration to generate
a TLS certificate for the three endponits:
```
security.acme = {
email = "root@example.org";
acceptTerms = true;
certs = {
"example.org" = {
webroot = "/var/www/example.org";
email = "root@example.org";
extraDomainNames = [ "conference.example.org" "upload.example.org" ];
```nix
{
security.acme = {
email = "root@example.org";
acceptTerms = true;
certs = {
"example.org" = {
webroot = "/var/www/example.org";
email = "root@example.org";
extraDomainNames = [ "conference.example.org" "upload.example.org" ];
};
};
};
};
}
```

View file

@ -0,0 +1,69 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.scion.scion-control;
toml = pkgs.formats.toml { };
defaultConfig = {
general = {
id = "cs";
config_dir = "/etc/scion";
reconnect_to_dispatcher = true;
};
beacon_db = {
connection = "/var/lib/scion-control/control.beacon.db";
};
path_db = {
connection = "/var/lib/scion-control/control.path.db";
};
trust_db = {
connection = "/var/lib/scion-control/control.trust.db";
};
log.console = {
level = "info";
};
};
configFile = toml.generate "scion-control.toml" (defaultConfig // cfg.settings);
in
{
options.services.scion.scion-control = {
enable = mkEnableOption (lib.mdDoc "the scion-control service");
settings = mkOption {
default = { };
type = toml.type;
example = literalExpression ''
{
path_db = {
connection = "/var/lib/scion-control/control.path.db";
};
log.console = {
level = "info";
};
}
'';
description = lib.mdDoc ''
scion-control configuration. Refer to
<https://docs.scion.org/en/latest/manuals/common.html>
for details on supported values.
'';
};
};
config = mkIf cfg.enable {
systemd.services.scion-control = {
description = "SCION Control Service";
after = [ "network-online.target" "scion-dispatcher.service" ];
wants = [ "network-online.target" "scion-dispatcher.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
Group = if (config.services.scion.scion-dispatcher.enable == true) then "scion" else null;
ExecStart = "${pkgs.scion}/bin/scion-control --config ${configFile}";
DynamicUser = true;
Restart = "on-failure";
BindPaths = [ "/dev/shm:/run/shm" ];
StateDirectory = "scion-control";
};
};
};
}

View file

@ -0,0 +1,64 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.scion.scion-daemon;
toml = pkgs.formats.toml { };
defaultConfig = {
general = {
id = "sd";
config_dir = "/etc/scion";
reconnect_to_dispatcher = true;
};
path_db = {
connection = "/var/lib/scion-daemon/sd.path.db";
};
trust_db = {
connection = "/var/lib/scion-daemon/sd.trust.db";
};
log.console = {
level = "info";
};
};
configFile = toml.generate "scion-daemon.toml" (defaultConfig // cfg.settings);
in
{
options.services.scion.scion-daemon = {
enable = mkEnableOption (lib.mdDoc "the scion-daemon service");
settings = mkOption {
default = { };
type = toml.type;
example = literalExpression ''
{
path_db = {
connection = "/var/lib/scion-daemon/sd.path.db";
};
log.console = {
level = "info";
};
}
'';
description = lib.mdDoc ''
scion-daemon configuration. Refer to
<https://docs.scion.org/en/latest/manuals/common.html>
for details on supported values.
'';
};
};
config = mkIf cfg.enable {
systemd.services.scion-daemon = {
description = "SCION Daemon";
after = [ "network-online.target" "scion-dispatcher.service" ];
wants = [ "network-online.target" "scion-dispatcher.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
ExecStart = "${pkgs.scion}/bin/scion-daemon --config ${configFile}";
Restart = "on-failure";
DynamicUser = true;
StateDirectory = "scion-daemon";
};
};
};
}

View file

@ -0,0 +1,74 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.scion.scion-dispatcher;
toml = pkgs.formats.toml { };
defaultConfig = {
dispatcher = {
id = "dispatcher";
socket_file_mode = "0770";
application_socket = "/dev/shm/dispatcher/default.sock";
};
log.console = {
level = "info";
};
};
configFile = toml.generate "scion-dispatcher.toml" (defaultConfig // cfg.settings);
in
{
options.services.scion.scion-dispatcher = {
enable = mkEnableOption (lib.mdDoc "the scion-dispatcher service");
settings = mkOption {
default = { };
type = toml.type;
example = literalExpression ''
{
dispatcher = {
id = "dispatcher";
socket_file_mode = "0770";
application_socket = "/dev/shm/dispatcher/default.sock";
};
log.console = {
level = "info";
};
}
'';
description = lib.mdDoc ''
scion-dispatcher configuration. Refer to
<https://docs.scion.org/en/latest/manuals/common.html>
for details on supported values.
'';
};
};
config = mkIf cfg.enable {
# Needed for group ownership of the dispatcher socket
users.groups.scion = {};
# scion programs hardcode path to dispatcher in /run/shm, and is not
# configurable at runtime upstream plans to obsolete the dispatcher in
# favor of an SCMP daemon, at which point this can be removed.
system.activationScripts.scion-dispatcher = ''
ln -sf /dev/shm /run/shm
'';
systemd.services.scion-dispatcher = {
description = "SCION Dispatcher";
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
Group = "scion";
DynamicUser = true;
BindPaths = [ "/dev/shm:/run/shm" ];
ExecStartPre = "${pkgs.coreutils}/bin/rm -rf /run/shm/dispatcher";
ExecStart = "${pkgs.scion}/bin/scion-dispatcher --config ${configFile}";
Restart = "on-failure";
StateDirectory = "scion-dispatcher";
};
};
};
}

View file

@ -0,0 +1,49 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.scion.scion-router;
toml = pkgs.formats.toml { };
defaultConfig = {
general = {
id = "br";
config_dir = "/etc/scion";
};
};
configFile = toml.generate "scion-router.toml" (defaultConfig // cfg.settings);
in
{
options.services.scion.scion-router = {
enable = mkEnableOption (lib.mdDoc "the scion-router service");
settings = mkOption {
default = { };
type = toml.type;
example = literalExpression ''
{
general.id = "br";
}
'';
description = lib.mdDoc ''
scion-router configuration. Refer to
<https://docs.scion.org/en/latest/manuals/common.html>
for details on supported values.
'';
};
};
config = mkIf cfg.enable {
systemd.services.scion-router = {
description = "SCION Router";
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
ExecStart = "${pkgs.scion}/bin/scion-router --config ${configFile}";
Restart = "on-failure";
DynamicUser = true;
StateDirectory = "scion-router";
};
};
};
}

View file

@ -0,0 +1,39 @@
{ config, lib, ... }:
with lib;
let
cfg = config.services.scion;
in
{
options.services.scion = {
enable = mkEnableOption (lib.mdDoc "all of the scion components and services");
bypassBootstrapWarning = mkOption {
type = types.bool;
default = false;
description = lib.mdDoc ''
bypass Nix warning about SCION PKI bootstrapping
'';
};
};
config = mkIf cfg.enable {
services.scion = {
scion-dispatcher.enable = true;
scion-daemon.enable = true;
scion-router.enable = true;
scion-control.enable = true;
};
assertions = [
{ assertion = cfg.bypassBootstrapWarning == true;
message = ''
SCION is a routing protocol and requires bootstrapping with a manual, imperative key signing ceremony. You may want to join an existing Isolation Domain (ISD) such as scionlab.org, or bootstrap your own. If you have completed and configured the public key infrastructure for SCION and are sure this process is complete, then add the following to your configuration:
services.scion.bypassBootstrapWarning = true;
refer to docs.scion.org for more information
'';
}
];
};
}

View file

@ -12,7 +12,7 @@ self-arranging IPv6 network.
### Simple ephemeral node {#module-services-networking-yggdrasil-configuration-simple}
An annotated example of a simple configuration:
```
```nix
{
services.yggdrasil = {
enable = true;
@ -39,7 +39,7 @@ An annotated example of a simple configuration:
### Persistent node with prefix {#module-services-networking-yggdrasil-configuration-prefix}
A node with a fixed address that announces a prefix:
```
```nix
let
address = "210:5217:69c0:9afc:1b95:b9f:8718:c3d2";
prefix = "310:5217:69c0:9afc";
@ -90,7 +90,7 @@ in {
A NixOS container attached to the Yggdrasil network via a node running on the
host:
```
```nix
let
yggPrefix64 = "310:5217:69c0:9afc";
# Again, taken from the output of "yggdrasilctl getself".

View file

@ -7,7 +7,9 @@ Meilisearch is a lightweight, fast and powerful search engine. Think elastic sea
the minimum to start meilisearch is
```nix
services.meilisearch.enable = true;
{
services.meilisearch.enable = true;
}
```
this will start the http server included with meilisearch on port 7700.

View file

@ -80,7 +80,7 @@ in
};
implicitPolicyTarget = mkOption {
type = policy;
type = types.enum [ "allow" "block" "reject" ];
default = "block";
description = lib.mdDoc ''
How to treat USB devices that don't match any rule in the policy.
@ -110,7 +110,7 @@ in
};
insertedDevicePolicy = mkOption {
type = policy;
type = types.enum [ "block" "reject" "apply-policy" ];
default = "apply-policy";
description = lib.mdDoc ''
How to treat USB devices that are already connected after the daemon

View file

@ -19,21 +19,23 @@ be run behind a HTTP proxy on `fediverse.example.com`.
```nix
services.akkoma.enable = true;
services.akkoma.config = {
":pleroma" = {
":instance" = {
name = "My Akkoma instance";
description = "More detailed description";
email = "admin@example.com";
registration_open = false;
};
{
services.akkoma.enable = true;
services.akkoma.config = {
":pleroma" = {
":instance" = {
name = "My Akkoma instance";
description = "More detailed description";
email = "admin@example.com";
registration_open = false;
};
"Pleroma.Web.Endpoint" = {
url.host = "fediverse.example.com";
"Pleroma.Web.Endpoint" = {
url.host = "fediverse.example.com";
};
};
};
};
}
```
Please refer to the [configuration cheat sheet](https://docs.akkoma.dev/stable/configuration/cheatsheet/)
@ -55,19 +57,21 @@ Although it is possible to expose Akkoma directly, it is common practice to oper
HTTP reverse proxy such as nginx.
```nix
services.akkoma.nginx = {
enableACME = true;
forceSSL = true;
};
{
services.akkoma.nginx = {
enableACME = true;
forceSSL = true;
};
services.nginx = {
enable = true;
services.nginx = {
enable = true;
clientMaxBodySize = "16m";
recommendedTlsSettings = true;
recommendedOptimisation = true;
recommendedGzipSettings = true;
};
clientMaxBodySize = "16m";
recommendedTlsSettings = true;
recommendedOptimisation = true;
recommendedGzipSettings = true;
};
}
```
Please refer to [](#module-security-acme) for details on how to provision an SSL/TLS certificate.
@ -78,51 +82,53 @@ Without the media proxy function, Akkoma does not store any remote media like pi
locally, and clients have to fetch them directly from the source server.
```nix
# Enable nginx slice module distributed with Tengine
services.nginx.package = pkgs.tengine;
{
# Enable nginx slice module distributed with Tengine
services.nginx.package = pkgs.tengine;
# Enable media proxy
services.akkoma.config.":pleroma".":media_proxy" = {
enabled = true;
proxy_opts.redirect_on_failure = true;
};
# Adjust the persistent cache size as needed:
# Assuming an average object size of 128 KiB, around 1 MiB
# of memory is required for the key zone per GiB of cache.
# Ensure that the cache directory exists and is writable by nginx.
services.nginx.commonHttpConfig = ''
proxy_cache_path /var/cache/nginx/cache/akkoma-media-cache
levels= keys_zone=akkoma_media_cache:16m max_size=16g
inactive=1y use_temp_path=off;
'';
services.akkoma.nginx = {
locations."/proxy" = {
proxyPass = "http://unix:/run/akkoma/socket";
extraConfig = ''
proxy_cache akkoma_media_cache;
# Cache objects in slices of 1 MiB
slice 1m;
proxy_cache_key $host$uri$is_args$args$slice_range;
proxy_set_header Range $slice_range;
# Decouple proxy and upstream responses
proxy_buffering on;
proxy_cache_lock on;
proxy_ignore_client_abort on;
# Default cache times for various responses
proxy_cache_valid 200 1y;
proxy_cache_valid 206 301 304 1h;
# Allow serving of stale items
proxy_cache_use_stale error timeout invalid_header updating;
'';
# Enable media proxy
services.akkoma.config.":pleroma".":media_proxy" = {
enabled = true;
proxy_opts.redirect_on_failure = true;
};
};
# Adjust the persistent cache size as needed:
# Assuming an average object size of 128 KiB, around 1 MiB
# of memory is required for the key zone per GiB of cache.
# Ensure that the cache directory exists and is writable by nginx.
services.nginx.commonHttpConfig = ''
proxy_cache_path /var/cache/nginx/cache/akkoma-media-cache
levels= keys_zone=akkoma_media_cache:16m max_size=16g
inactive=1y use_temp_path=off;
'';
services.akkoma.nginx = {
locations."/proxy" = {
proxyPass = "http://unix:/run/akkoma/socket";
extraConfig = ''
proxy_cache akkoma_media_cache;
# Cache objects in slices of 1 MiB
slice 1m;
proxy_cache_key $host$uri$is_args$args$slice_range;
proxy_set_header Range $slice_range;
# Decouple proxy and upstream responses
proxy_buffering on;
proxy_cache_lock on;
proxy_ignore_client_abort on;
# Default cache times for various responses
proxy_cache_valid 200 1y;
proxy_cache_valid 206 301 304 1h;
# Allow serving of stale items
proxy_cache_use_stale error timeout invalid_header updating;
'';
};
};
}
```
#### Prefetch remote media {#modules-services-akkoma-prefetch-remote-media}
@ -132,10 +138,12 @@ fetches all media associated with a post through the media proxy, as soon as the
received by the instance.
```nix
services.akkoma.config.":pleroma".":mrf".policies =
map (pkgs.formats.elixirConf { }).lib.mkRaw [
"Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy"
];
{
services.akkoma.config.":pleroma".":mrf".policies =
map (pkgs.formats.elixirConf { }).lib.mkRaw [
"Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy"
];
}
```
#### Media previews {#modules-services-akkoma-media-previews}
@ -143,11 +151,13 @@ services.akkoma.config.":pleroma".":mrf".policies =
Akkoma can generate previews for media.
```nix
services.akkoma.config.":pleroma".":media_preview_proxy" = {
enabled = true;
thumbnail_max_width = 1920;
thumbnail_max_height = 1080;
};
{
services.akkoma.config.":pleroma".":media_preview_proxy" = {
enabled = true;
thumbnail_max_width = 1920;
thumbnail_max_height = 1080;
};
}
```
## Frontend management {#modules-services-akkoma-frontend-management}
@ -160,29 +170,31 @@ The following example overrides the primary frontends default configuration u
derivation.
```nix
services.akkoma.frontends.primary.package = pkgs.runCommand "akkoma-fe" {
config = builtins.toJSON {
expertLevel = 1;
collapseMessageWithSubject = false;
stopGifs = false;
replyVisibility = "following";
webPushHideIfCW = true;
hideScopeNotice = true;
renderMisskeyMarkdown = false;
hideSiteFavicon = true;
postContentType = "text/markdown";
showNavShortcuts = false;
};
nativeBuildInputs = with pkgs; [ jq xorg.lndir ];
passAsFile = [ "config" ];
} ''
mkdir $out
lndir ${pkgs.akkoma-frontends.akkoma-fe} $out
{
services.akkoma.frontends.primary.package = pkgs.runCommand "akkoma-fe" {
config = builtins.toJSON {
expertLevel = 1;
collapseMessageWithSubject = false;
stopGifs = false;
replyVisibility = "following";
webPushHideIfCW = true;
hideScopeNotice = true;
renderMisskeyMarkdown = false;
hideSiteFavicon = true;
postContentType = "text/markdown";
showNavShortcuts = false;
};
nativeBuildInputs = with pkgs; [ jq xorg.lndir ];
passAsFile = [ "config" ];
} ''
mkdir $out
lndir ${pkgs.akkoma-frontends.akkoma-fe} $out
rm $out/static/config.json
jq -s add ${pkgs.akkoma-frontends.akkoma-fe}/static/config.json ${config} \
>$out/static/config.json
'';
rm $out/static/config.json
jq -s add ${pkgs.akkoma-frontends.akkoma-fe}/static/config.json ${config} \
>$out/static/config.json
'';
}
```
## Federation policies {#modules-services-akkoma-federation-policies}
@ -198,28 +210,30 @@ of the fediverse and providing a pleasant experience to the users of an instance
```nix
services.akkoma.config.":pleroma" = with (pkgs.formats.elixirConf { }).lib; {
":mrf".policies = map mkRaw [
"Pleroma.Web.ActivityPub.MRF.SimplePolicy"
];
{
services.akkoma.config.":pleroma" = with (pkgs.formats.elixirConf { }).lib; {
":mrf".policies = map mkRaw [
"Pleroma.Web.ActivityPub.MRF.SimplePolicy"
];
":mrf_simple" = {
# Tag all media as sensitive
media_nsfw = mkMap {
"nsfw.weird.kinky" = "Untagged NSFW content";
};
":mrf_simple" = {
# Tag all media as sensitive
media_nsfw = mkMap {
"nsfw.weird.kinky" = "Untagged NSFW content";
};
# Reject all activities except deletes
reject = mkMap {
"kiwifarms.cc" = "Persistent harassment of users, no moderation";
};
# Reject all activities except deletes
reject = mkMap {
"kiwifarms.cc" = "Persistent harassment of users, no moderation";
};
# Force posts to be visible by followers only
followers_only = mkMap {
"beta.birdsite.live" = "Avoid polluting timelines with Twitter posts";
# Force posts to be visible by followers only
followers_only = mkMap {
"beta.birdsite.live" = "Avoid polluting timelines with Twitter posts";
};
};
};
};
}
```
## Upload filters {#modules-services-akkoma-upload-filters}
@ -228,12 +242,14 @@ This example strips GPS and location metadata from uploads, deduplicates them an
the file name.
```nix
services.akkoma.config.":pleroma"."Pleroma.Upload".filters =
map (pkgs.formats.elixirConf { }).lib.mkRaw [
"Pleroma.Upload.Filter.Exiftool"
"Pleroma.Upload.Filter.Dedupe"
"Pleroma.Upload.Filter.AnonymizeFilename"
];
{
services.akkoma.config.":pleroma"."Pleroma.Upload".filters =
map (pkgs.formats.elixirConf { }).lib.mkRaw [
"Pleroma.Upload.Filter.Exiftool"
"Pleroma.Upload.Filter.Dedupe"
"Pleroma.Upload.Filter.AnonymizeFilename"
];
}
```
## Migration from Pleroma {#modules-services-akkoma-migration-pleroma}
@ -286,9 +302,11 @@ To reuse the Pleroma data in place, disable Pleroma and enable Akkoma, pointi
Pleroma database and upload directory.
```nix
# Adjust these settings according to the database name and upload directory path used by Pleroma
services.akkoma.config.":pleroma"."Pleroma.Repo".database = "pleroma";
services.akkoma.config.":pleroma".":instance".upload_dir = "/var/lib/pleroma/uploads";
{
# Adjust these settings according to the database name and upload directory path used by Pleroma
services.akkoma.config.":pleroma"."Pleroma.Repo".database = "pleroma";
services.akkoma.config.":pleroma".":instance".upload_dir = "/var/lib/pleroma/uploads";
}
```
Please keep in mind that after the Akkoma service has been started, any migrations applied by
@ -304,7 +322,9 @@ details.
The Akkoma systemd service may be confined to a chroot with
```nix
services.systemd.akkoma.confinement.enable = true;
{
services.systemd.akkoma.confinement.enable = true;
}
```
Confinement of services is not generally supported in NixOS and therefore disabled by default.

View file

@ -4,7 +4,7 @@ c2FmZQ is an application that can securely encrypt, store, and share files,
including but not limited to pictures and videos.
The service `c2fmzq-server` can be enabled by setting
```
```nix
{
services.c2fmzq-server.enable = true;
}
@ -17,7 +17,7 @@ In principle the server can be exposed directly on a public interface and there
are command line options to manage HTTPS certificates directly, but the module
is designed to be served behind a reverse proxy or only accessed via localhost.
```
```nix
{
services.c2fmzq-server = {
enable = true;

View file

@ -4,19 +4,22 @@ Castopod is an open-source hosting platform made for podcasters who want to enga
## Quickstart {#module-services-castopod-quickstart}
Configure ACME (https://nixos.org/manual/nixos/unstable/#module-security-acme).
Use the following configuration to start a public instance of Castopod on `castopod.example.com` domain:
```nix
networking.firewall.allowedTCPPorts = [ 80 443 ];
services.castopod = {
enable = true;
database.createLocally = true;
nginx.virtualHost = {
serverName = "castopod.example.com";
enableACME = true;
forceSSL = true;
{
networking.firewall.allowedTCPPorts = [ 80 443 ];
services.castopod = {
enable = true;
database.createLocally = true;
nginx.virtualHost = {
serverName = "castopod.example.com";
enableACME = true;
forceSSL = true;
};
};
};
}
```
Go to `https://castopod.example.com/cp-install` to create superadmin account after applying the above configuration.

View file

@ -4,7 +4,6 @@ let
fpm = config.services.phpfpm.pools.castopod;
user = "castopod";
stateDirectory = "/var/lib/castopod";
# https://docs.castopod.org/getting-started/install.html#requirements
phpPackage = pkgs.php.withExtensions ({ enabled, all }: with all; [
@ -29,6 +28,15 @@ in
defaultText = lib.literalMD "pkgs.castopod";
description = lib.mdDoc "Which Castopod package to use.";
};
dataDir = lib.mkOption {
type = lib.types.path;
default = "/var/lib/castopod";
description = lib.mdDoc ''
The path where castopod stores all data. This path must be in sync
with the castopod package (where it is hardcoded during the build in
accordance with its own `dataDir` argument).
'';
};
database = {
createLocally = lib.mkOption {
type = lib.types.bool;
@ -59,6 +67,8 @@ in
description = lib.mdDoc ''
A file containing the password corresponding to
[](#opt-services.castopod.database.user).
This file is loaded using systemd LoadCredentials.
'';
};
};
@ -85,6 +95,8 @@ in
Environment file to inject e.g. secrets into the configuration.
See [](https://code.castopod.org/adaures/castopod/-/blob/main/.env.example)
for available environment variables.
This file is loaded using systemd LoadCredentials.
'';
};
configureNginx = lib.mkOption {
@ -111,6 +123,19 @@ in
Options for Castopod's PHP pool. See the documentation on `php-fpm.conf` for details on configuration directives.
'';
};
maxUploadSize = lib.mkOption {
type = lib.types.str;
default = "512M";
description = lib.mdDoc ''
Maximum supported size for a file upload in. Maximum HTTP body
size is set to this value for nginx and PHP (because castopod doesn't
support chunked uploads yet:
https://code.castopod.org/adaures/castopod/-/issues/330).
Note, that practical upload size limit is smaller. For example, with
512 MiB setting - around 500 MiB is possible.
'';
};
};
};
@ -120,13 +145,13 @@ in
sslEnabled = with config.services.nginx.virtualHosts.${cfg.localDomain}; addSSL || forceSSL || onlySSL || enableACME || useACMEHost != null;
baseURL = "http${lib.optionalString sslEnabled "s"}://${cfg.localDomain}";
in
lib.mapAttrs (name: lib.mkDefault) {
lib.mapAttrs (_: lib.mkDefault) {
"app.forceGlobalSecureRequests" = sslEnabled;
"app.baseURL" = baseURL;
"media.baseURL" = "/";
"media.baseURL" = baseURL;
"media.root" = "media";
"media.storage" = stateDirectory;
"media.storage" = cfg.dataDir;
"admin.gateway" = "admin";
"auth.gateway" = "auth";
@ -142,13 +167,13 @@ in
services.phpfpm.pools.castopod = {
inherit user;
group = config.services.nginx.group;
phpPackage = phpPackage;
inherit phpPackage;
phpOptions = ''
# https://code.castopod.org/adaures/castopod/-/blob/main/docker/production/app/uploads.ini
# https://code.castopod.org/adaures/castopod/-/blob/develop/docker/production/common/uploads.template.ini
file_uploads = On
memory_limit = 512M
upload_max_filesize = 500M
post_max_size = 512M
upload_max_filesize = ${cfg.maxUploadSize}
post_max_size = ${cfg.maxUploadSize}
max_execution_time = 300
max_input_time = 300
'';
@ -165,45 +190,50 @@ in
path = [ pkgs.openssl phpPackage ];
script =
let
envFile = "${stateDirectory}/.env";
envFile = "${cfg.dataDir}/.env";
media = "${cfg.settings."media.storage"}/${cfg.settings."media.root"}";
in
''
mkdir -p ${stateDirectory}/writable/{cache,logs,session,temp,uploads}
mkdir -p ${cfg.dataDir}/writable/{cache,logs,session,temp,uploads}
if [ ! -d ${lib.escapeShellArg media} ]; then
cp --no-preserve=mode,ownership -r ${cfg.package}/share/castopod/public/media ${lib.escapeShellArg media}
fi
if [ ! -f ${stateDirectory}/salt ]; then
openssl rand -base64 33 > ${stateDirectory}/salt
if [ ! -f ${cfg.dataDir}/salt ]; then
openssl rand -base64 33 > ${cfg.dataDir}/salt
fi
cat <<'EOF' > ${envFile}
${lib.generators.toKeyValue { } cfg.settings}
EOF
echo "analytics.salt=$(cat ${stateDirectory}/salt)" >> ${envFile}
echo "analytics.salt=$(cat ${cfg.dataDir}/salt)" >> ${envFile}
${if (cfg.database.passwordFile != null) then ''
echo "database.default.password=$(cat ${lib.escapeShellArg cfg.database.passwordFile})" >> ${envFile}
echo "database.default.password=$(cat "$CREDENTIALS_DIRECTORY/dbpasswordfile)" >> ${envFile}
'' else ''
echo "database.default.password=" >> ${envFile}
''}
${lib.optionalString (cfg.environmentFile != null) ''
cat ${lib.escapeShellArg cfg.environmentFile}) >> ${envFile}
cat "$CREDENTIALS_DIRECTORY/envfile" >> ${envFile}
''}
php spark castopod:database-update
php ${cfg.package}/share/castopod/spark castopod:database-update
'';
serviceConfig = {
StateDirectory = "castopod";
LoadCredential = lib.optional (cfg.environmentFile != null)
"envfile:${cfg.environmentFile}"
++ (lib.optional (cfg.database.passwordFile != null)
"dbpasswordfile:${cfg.database.passwordFile}");
WorkingDirectory = "${cfg.package}/share/castopod";
Type = "oneshot";
RemainAfterExit = true;
User = user;
Group = config.services.nginx.group;
ReadWritePaths = cfg.dataDir;
};
};
@ -212,9 +242,7 @@ in
wantedBy = [ "multi-user.target" ];
path = [ phpPackage ];
script = ''
php public/index.php scheduled-activities
php public/index.php scheduled-websub-publish
php public/index.php scheduled-video-clips
php ${cfg.package}/share/castopod/spark tasks:run
'';
serviceConfig = {
StateDirectory = "castopod";
@ -222,6 +250,8 @@ in
Type = "oneshot";
User = user;
Group = config.services.nginx.group;
ReadWritePaths = cfg.dataDir;
LogLevelMax = "notice"; # otherwise periodic tasks flood the journal
};
};
@ -251,6 +281,7 @@ in
extraConfig = ''
try_files $uri $uri/ /index.php?$args;
index index.php index.html;
client_max_body_size ${cfg.maxUploadSize};
'';
locations."^~ /${cfg.settings."media.root"}/" = {
@ -278,7 +309,7 @@ in
};
};
users.users.${user} = lib.mapAttrs (name: lib.mkDefault) {
users.users.${user} = lib.mapAttrs (_: lib.mkDefault) {
description = "Castopod user";
isSystemUser = true;
group = config.services.nginx.group;

View file

@ -0,0 +1,32 @@
# Davis {#module-services-davis}
[Davis](https://github.com/tchapi/davis/) is a caldav and carrddav server. It
has a simple, fully translatable admin interface for sabre/dav based on Symfony
5 and Bootstrap 5, initially inspired by Baïkal.
## Basic Usage {#module-services-davis-basic-usage}
At first, an application secret is needed, this can be generated with:
```ShellSession
$ cat /dev/urandom | tr -dc a-zA-Z0-9 | fold -w 48 | head -n 1
```
After that, `davis` can be deployed like this:
```
{
services.davis = {
enable = true;
hostname = "davis.example.com";
mail = {
dsn = "smtp://username@example.com:25";
inviteFromAddress = "davis@example.com";
};
adminLogin = "admin";
adminPasswordFile = "/run/secrets/davis-admin-password";
appSecretFile = "/run/secrets/davis-app-secret";
nginx = {};
};
}
```
This deploys Davis using a sqlite database running out of `/var/lib/davis`.

View file

@ -0,0 +1,554 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.davis;
db = cfg.database;
mail = cfg.mail;
mysqlLocal = db.createLocally && db.driver == "mysql";
pgsqlLocal = db.createLocally && db.driver == "postgresql";
user = cfg.user;
group = cfg.group;
isSecret = v: lib.isAttrs v && v ? _secret && (lib.isString v._secret || builtins.isPath v._secret);
davisEnvVars = lib.generators.toKeyValue {
mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" {
mkValueString =
v:
if builtins.isInt v then
toString v
else if lib.isString v then
"\"${v}\""
else if true == v then
"true"
else if false == v then
"false"
else if null == v then
""
else if isSecret v then
if (lib.isString v._secret) then
builtins.hashString "sha256" v._secret
else
builtins.hashString "sha256" (builtins.readFile v._secret)
else
throw "unsupported type ${builtins.typeOf v}: ${(lib.generators.toPretty { }) v}";
};
};
secretPaths = lib.mapAttrsToList (_: v: v._secret) (lib.filterAttrs (_: isSecret) cfg.config);
mkSecretReplacement = file: ''
replace-secret ${
lib.escapeShellArgs [
(
if (lib.isString file) then
builtins.hashString "sha256" file
else
builtins.hashString "sha256" (builtins.readFile file)
)
file
"${cfg.dataDir}/.env.local"
]
}
'';
secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths;
filteredConfig = lib.converge (lib.filterAttrsRecursive (
_: v:
!lib.elem v [
{ }
null
]
)) cfg.config;
davisEnv = pkgs.writeText "davis.env" (davisEnvVars filteredConfig);
in
{
options.services.davis = {
enable = lib.mkEnableOption (lib.mdDoc "Davis is a caldav and carddav server");
user = lib.mkOption {
default = "davis";
description = lib.mdDoc "User davis runs as.";
type = lib.types.str;
};
group = lib.mkOption {
default = "davis";
description = lib.mdDoc "Group davis runs as.";
type = lib.types.str;
};
package = lib.mkPackageOption pkgs "davis" { };
dataDir = lib.mkOption {
type = lib.types.path;
default = "/var/lib/davis";
description = lib.mdDoc ''
Davis data directory.
'';
};
hostname = lib.mkOption {
type = lib.types.str;
example = "davis.yourdomain.org";
description = lib.mdDoc ''
Domain of the host to serve davis under. You may want to change it if you
run Davis on a different URL than davis.yourdomain.
'';
};
config = lib.mkOption {
type = lib.types.attrsOf (
lib.types.nullOr (
lib.types.either
(lib.types.oneOf [
lib.types.bool
lib.types.int
lib.types.port
lib.types.path
lib.types.str
])
(
lib.types.submodule {
options = {
_secret = lib.mkOption {
type = lib.types.nullOr (
lib.types.oneOf [
lib.types.str
lib.types.path
]
);
description = lib.mdDoc ''
The path to a file containing the value the
option should be set to in the final
configuration file.
'';
};
};
}
)
)
);
default = { };
example = '''';
description = lib.mdDoc '''';
};
adminLogin = lib.mkOption {
type = lib.types.str;
default = "root";
description = lib.mdDoc ''
Username for the admin account.
'';
};
adminPasswordFile = lib.mkOption {
type = lib.types.path;
description = lib.mdDoc ''
The full path to a file that contains the admin's password. Must be
readable by the user.
'';
example = "/run/secrets/davis-admin-pass";
};
appSecretFile = lib.mkOption {
type = lib.types.path;
description = lib.mdDoc ''
A file containing the Symfony APP_SECRET - Its value should be a series
of characters, numbers and symbols chosen randomly and the recommended
length is around 32 characters. Can be generated with <code>cat
/dev/urandom | tr -dc a-zA-Z0-9 | fold -w 48 | head -n 1</code>.
'';
example = "/run/secrets/davis-appsecret";
};
database = {
driver = lib.mkOption {
type = lib.types.enum [
"sqlite"
"postgresql"
"mysql"
];
default = "sqlite";
description = lib.mdDoc "Database type, required in all circumstances.";
};
urlFile = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
example = "/run/secrets/davis-db-url";
description = lib.mdDoc ''
A file containing the database connection url. If set then it
overrides all other database settings (except driver). This is
mandatory if you want to use an external database, that is when
`services.davis.database.createLocally` is `false`.
'';
};
name = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = "davis";
description = lib.mdDoc "Database name, only used when the databse is created locally.";
};
createLocally = lib.mkOption {
type = lib.types.bool;
default = true;
description = lib.mdDoc "Create the database and database user locally.";
};
};
mail = {
dsn = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = lib.mdDoc "Mail DSN for sending emails. Mutually exclusive with `services.davis.mail.dsnFile`.";
example = "smtp://username:password@example.com:25";
};
dsnFile = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "/run/secrets/davis-mail-dsn";
description = lib.mdDoc "A file containing the mail DSN for sending emails. Mutually exclusive with `servies.davis.mail.dsn`.";
};
inviteFromAddress = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = lib.mdDoc "Email address to send invitations from.";
example = "no-reply@dav.example.com";
};
};
nginx = lib.mkOption {
type = lib.types.submodule (
lib.recursiveUpdate (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) { }
);
default = null;
example = ''
{
serverAliases = [
"dav.''${config.networking.domain}"
];
# To enable encryption and let let's encrypt take care of certificate
forceSSL = true;
enableACME = true;
}
'';
description = lib.mdDoc ''
With this option, you can customize the nginx virtualHost settings.
'';
};
poolConfig = lib.mkOption {
type = lib.types.attrsOf (
lib.types.oneOf [
lib.types.str
lib.types.int
lib.types.bool
]
);
default = {
"pm" = "dynamic";
"pm.max_children" = 32;
"pm.start_servers" = 2;
"pm.min_spare_servers" = 2;
"pm.max_spare_servers" = 4;
"pm.max_requests" = 500;
};
description = lib.mdDoc ''
Options for the davis PHP pool. See the documentation on <literal>php-fpm.conf</literal>
for details on configuration directives.
'';
};
};
config =
let
defaultServiceConfig = {
ReadWritePaths = "${cfg.dataDir}";
User = user;
UMask = 77;
DeviceAllow = "";
LockPersonality = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateTmp = true;
PrivateUsers = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
RemoveIPC = true;
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"~@resources"
"~@privileged"
];
WorkingDirectory = "${cfg.package}/";
};
in
lib.mkIf cfg.enable {
assertions = [
{
assertion = db.createLocally -> db.urlFile == null;
message = "services.davis.database.urlFile must be unset if services.davis.database.createLocally is set true.";
}
{
assertion = db.createLocally || db.urlFile != null;
message = "One of services.davis.database.urlFile or services.davis.database.createLocally must be set.";
}
{
assertion = (mail.dsn != null) != (mail.dsnFile != null);
message = "One of (and only one of) services.davis.mail.dsn or services.davis.mail.dsnFile must be set.";
}
];
services.davis.config =
{
APP_ENV = "prod";
CACHE_DIR = "${cfg.dataDir}/var/cache";
# note: we do not need the log dir (we log to stdout/journald), by davis/symfony will try to create it, and the default value is one in the nix-store
# so we set it to a path under dataDir to avoid something like: Unable to create the "logs" directory (/nix/store/5cfskz0ybbx37s1161gjn5klwb5si1zg-davis-4.4.1/var/log).
LOG_DIR = "${cfg.dataDir}/var/log";
LOG_FILE_PATH = "/dev/stdout";
DATABASE_DRIVER = db.driver;
INVITE_FROM_ADDRESS = mail.inviteFromAddress;
APP_SECRET._secret = cfg.appSecretFile;
ADMIN_LOGIN = cfg.adminLogin;
ADMIN_PASSWORD._secret = cfg.adminPasswordFile;
APP_TIMEZONE = config.time.timeZone;
WEBDAV_ENABLED = false;
CALDAV_ENABLED = true;
CARDDAV_ENABLED = true;
}
// (if mail.dsn != null then { MAILER_DSN = mail.dsn; } else { MAILER_DSN._secret = mail.dsnFile; })
// (
if db.createLocally then
{
DATABASE_URL =
if db.driver == "sqlite" then
"sqlite:///${cfg.dataDir}/davis.db" # note: sqlite needs 4 slashes for an absolute path
else if
pgsqlLocal
# note: davis expects a non-standard postgres uri (due to the underlying doctrine library)
# specifically the charset query parameter, and the dummy hostname which is overriden by the host query parameter
then
"postgres://${user}@localhost/${db.name}?host=/run/postgresql&charset=UTF-8"
else if mysqlLocal then
"mysql://${user}@localhost/${db.name}?socket=/run/mysqld/mysqld.sock"
else
null;
}
else
{ DATABASE_URL._secret = db.urlFile; }
);
users = {
users = lib.mkIf (user == "davis") {
davis = {
description = "Davis service user";
group = cfg.group;
isSystemUser = true;
home = cfg.dataDir;
};
};
groups = lib.mkIf (group == "davis") { davis = { }; };
};
systemd.tmpfiles.rules = [
"d ${cfg.dataDir} 0710 ${user} ${group} - -"
"d ${cfg.dataDir}/var 0700 ${user} ${group} - -"
"d ${cfg.dataDir}/var/log 0700 ${user} ${group} - -"
"d ${cfg.dataDir}/var/cache 0700 ${user} ${group} - -"
];
services.phpfpm.pools.davis = {
inherit user group;
phpOptions = ''
log_errors = on
'';
phpEnv = {
ENV_DIR = "${cfg.dataDir}";
CACHE_DIR = "${cfg.dataDir}/var/cache";
#LOG_DIR = "${cfg.dataDir}/var/log";
};
settings =
{
"listen.mode" = "0660";
"pm" = "dynamic";
"pm.max_children" = 256;
"pm.start_servers" = 10;
"pm.min_spare_servers" = 5;
"pm.max_spare_servers" = 20;
}
// (
if cfg.nginx != null then
{
"listen.owner" = config.services.nginx.user;
"listen.group" = config.services.nginx.group;
}
else
{ }
)
// cfg.poolConfig;
};
# Reading the user-provided secret files requires root access
systemd.services.davis-env-setup = {
description = "Setup davis environment";
before = [
"phpfpm-davis.service"
"davis-db-migrate.service"
];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
path = [ pkgs.replace-secret ];
restartTriggers = [
cfg.package
davisEnv
];
script = ''
# error handling
set -euo pipefail
# create .env file with the upstream values
install -T -m 0600 -o ${user} ${cfg.package}/env-upstream "${cfg.dataDir}/.env"
# create .env.local file with the user-provided values
install -T -m 0600 -o ${user} ${davisEnv} "${cfg.dataDir}/.env.local"
${secretReplacements}
'';
};
systemd.services.davis-db-migrate = {
description = "Migrate davis database";
before = [ "phpfpm-davis.service" ];
after =
lib.optional mysqlLocal "mysql.service"
++ lib.optional pgsqlLocal "postgresql.service"
++ [ "davis-env-setup.service" ];
requires =
lib.optional mysqlLocal "mysql.service"
++ lib.optional pgsqlLocal "postgresql.service"
++ [ "davis-env-setup.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = defaultServiceConfig // {
Type = "oneshot";
RemainAfterExit = true;
Environment = [
"ENV_DIR=${cfg.dataDir}"
"CACHE_DIR=${cfg.dataDir}/var/cache"
"LOG_DIR=${cfg.dataDir}/var/log"
];
EnvironmentFile = "${cfg.dataDir}/.env.local";
};
restartTriggers = [
cfg.package
davisEnv
];
script = ''
set -euo pipefail
${cfg.package}/bin/console cache:clear --no-debug
${cfg.package}/bin/console cache:warmup --no-debug
${cfg.package}/bin/console doctrine:migrations:migrate
'';
};
systemd.services.phpfpm-davis.after = [
"davis-env-setup.service"
"davis-db-migrate.service"
];
systemd.services.phpfpm-davis.requires = [
"davis-env-setup.service"
"davis-db-migrate.service"
] ++ lib.optional mysqlLocal "mysql.service" ++ lib.optional pgsqlLocal "postgresql.service";
systemd.services.phpfpm-davis.serviceConfig.ReadWritePaths = [ cfg.dataDir ];
services.nginx = lib.mkIf (cfg.nginx != null) {
enable = lib.mkDefault true;
virtualHosts = {
"${cfg.hostname}" = lib.mkMerge [
cfg.nginx
{
root = lib.mkForce "${cfg.package}/public";
extraConfig = ''
charset utf-8;
index index.php;
'';
locations = {
"/" = {
extraConfig = ''
try_files $uri $uri/ /index.php$is_args$args;
'';
};
"~* ^/.well-known/(caldav|carddav)$" = {
extraConfig = ''
return 302 $http_x_forwarded_proto://$host/dav/;
'';
};
"~ ^(.+\.php)(.*)$" = {
extraConfig = ''
try_files $fastcgi_script_name =404;
include ${config.services.nginx.package}/conf/fastcgi_params;
include ${config.services.nginx.package}/conf/fastcgi.conf;
fastcgi_pass unix:${config.services.phpfpm.pools.davis.socket};
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_split_path_info ^(.+\.php)(.*)$;
fastcgi_param X-Forwarded-Proto $http_x_forwarded_proto;
fastcgi_param X-Forwarded-Port $http_x_forwarded_port;
'';
};
"~ /(\\.ht)" = {
extraConfig = ''
deny all;
return 404;
'';
};
};
}
];
};
};
services.mysql = lib.mkIf mysqlLocal {
enable = true;
package = lib.mkDefault pkgs.mariadb;
ensureDatabases = [ db.name ];
ensureUsers = [
{
name = user;
ensurePermissions = {
"${db.name}.*" = "ALL PRIVILEGES";
};
}
];
};
services.postgresql = lib.mkIf pgsqlLocal {
enable = true;
ensureDatabases = [ db.name ];
ensureUsers = [
{
name = user;
ensureDBOwnership = true;
}
];
};
};
meta = {
doc = ./davis.md;
maintainers = pkgs.davis.meta.maintainers;
};
}

View file

@ -6,20 +6,22 @@ modern and open source discussion platform.
## Basic usage {#module-services-discourse-basic-usage}
A minimal configuration using Let's Encrypt for TLS certificates looks like this:
```
services.discourse = {
enable = true;
hostname = "discourse.example.com";
admin = {
email = "admin@example.com";
username = "admin";
fullName = "Administrator";
passwordFile = "/path/to/password_file";
```nix
{
services.discourse = {
enable = true;
hostname = "discourse.example.com";
admin = {
email = "admin@example.com";
username = "admin";
fullName = "Administrator";
passwordFile = "/path/to/password_file";
};
secretKeyBaseFile = "/path/to/secret_key_base_file";
};
secretKeyBaseFile = "/path/to/secret_key_base_file";
};
security.acme.email = "me@example.com";
security.acme.acceptTerms = true;
security.acme.email = "me@example.com";
security.acme.acceptTerms = true;
}
```
Provided a proper DNS setup, you'll be able to connect to the
@ -34,20 +36,22 @@ the [](#opt-services.discourse.sslCertificate)
and [](#opt-services.discourse.sslCertificateKey)
options:
```
services.discourse = {
enable = true;
hostname = "discourse.example.com";
sslCertificate = "/path/to/ssl_certificate";
sslCertificateKey = "/path/to/ssl_certificate_key";
admin = {
email = "admin@example.com";
username = "admin";
fullName = "Administrator";
passwordFile = "/path/to/password_file";
```nix
{
services.discourse = {
enable = true;
hostname = "discourse.example.com";
sslCertificate = "/path/to/ssl_certificate";
sslCertificateKey = "/path/to/ssl_certificate_key";
admin = {
email = "admin@example.com";
username = "admin";
fullName = "Administrator";
passwordFile = "/path/to/password_file";
};
secretKeyBaseFile = "/path/to/secret_key_base_file";
};
secretKeyBaseFile = "/path/to/secret_key_base_file";
};
}
```
## Database access {#module-services-discourse-database}
@ -80,27 +84,29 @@ A basic setup which assumes you want to use your configured
[hostname](#opt-services.discourse.hostname) as
email domain can be done like this:
```
services.discourse = {
enable = true;
hostname = "discourse.example.com";
sslCertificate = "/path/to/ssl_certificate";
sslCertificateKey = "/path/to/ssl_certificate_key";
admin = {
email = "admin@example.com";
username = "admin";
fullName = "Administrator";
passwordFile = "/path/to/password_file";
```nix
{
services.discourse = {
enable = true;
hostname = "discourse.example.com";
sslCertificate = "/path/to/ssl_certificate";
sslCertificateKey = "/path/to/ssl_certificate_key";
admin = {
email = "admin@example.com";
username = "admin";
fullName = "Administrator";
passwordFile = "/path/to/password_file";
};
mail.outgoing = {
serverAddress = "smtp.emailprovider.com";
port = 587;
username = "user@emailprovider.com";
passwordFile = "/path/to/smtp_password_file";
};
mail.incoming.enable = true;
secretKeyBaseFile = "/path/to/secret_key_base_file";
};
mail.outgoing = {
serverAddress = "smtp.emailprovider.com";
port = 587;
username = "user@emailprovider.com";
passwordFile = "/path/to/smtp_password_file";
};
mail.incoming.enable = true;
secretKeyBaseFile = "/path/to/secret_key_base_file";
};
}
```
This assumes you have set up an MX record for the address you've
@ -162,44 +168,46 @@ The following example sets the title and description of the
Discourse instance and enables
GitHub login in the site settings,
and changes a few request limits in the backend settings:
```
services.discourse = {
enable = true;
hostname = "discourse.example.com";
sslCertificate = "/path/to/ssl_certificate";
sslCertificateKey = "/path/to/ssl_certificate_key";
admin = {
email = "admin@example.com";
username = "admin";
fullName = "Administrator";
passwordFile = "/path/to/password_file";
};
mail.outgoing = {
serverAddress = "smtp.emailprovider.com";
port = 587;
username = "user@emailprovider.com";
passwordFile = "/path/to/smtp_password_file";
};
mail.incoming.enable = true;
siteSettings = {
required = {
title = "My Cats";
site_description = "Discuss My Cats (and be nice plz)";
```nix
{
services.discourse = {
enable = true;
hostname = "discourse.example.com";
sslCertificate = "/path/to/ssl_certificate";
sslCertificateKey = "/path/to/ssl_certificate_key";
admin = {
email = "admin@example.com";
username = "admin";
fullName = "Administrator";
passwordFile = "/path/to/password_file";
};
login = {
enable_github_logins = true;
github_client_id = "a2f6dfe838cb3206ce20";
github_client_secret._secret = /run/keys/discourse_github_client_secret;
mail.outgoing = {
serverAddress = "smtp.emailprovider.com";
port = 587;
username = "user@emailprovider.com";
passwordFile = "/path/to/smtp_password_file";
};
mail.incoming.enable = true;
siteSettings = {
required = {
title = "My Cats";
site_description = "Discuss My Cats (and be nice plz)";
};
login = {
enable_github_logins = true;
github_client_id = "a2f6dfe838cb3206ce20";
github_client_secret._secret = /run/keys/discourse_github_client_secret;
};
};
backendSettings = {
max_reqs_per_ip_per_minute = 300;
max_reqs_per_ip_per_10_seconds = 60;
max_asset_reqs_per_ip_per_10_seconds = 250;
max_reqs_per_ip_mode = "warn+block";
};
secretKeyBaseFile = "/path/to/secret_key_base_file";
};
backendSettings = {
max_reqs_per_ip_per_minute = 300;
max_reqs_per_ip_per_10_seconds = 60;
max_asset_reqs_per_ip_per_10_seconds = 250;
max_reqs_per_ip_mode = "warn+block";
};
secretKeyBaseFile = "/path/to/secret_key_base_file";
};
}
```
In the resulting site settings file, the
@ -253,34 +261,36 @@ and [discourse-solved](https://github.com/discourse/discourse-solved)
plugins, and disable `discourse-spoiler-alert`
by default:
```
services.discourse = {
enable = true;
hostname = "discourse.example.com";
sslCertificate = "/path/to/ssl_certificate";
sslCertificateKey = "/path/to/ssl_certificate_key";
admin = {
email = "admin@example.com";
username = "admin";
fullName = "Administrator";
passwordFile = "/path/to/password_file";
};
mail.outgoing = {
serverAddress = "smtp.emailprovider.com";
port = 587;
username = "user@emailprovider.com";
passwordFile = "/path/to/smtp_password_file";
};
mail.incoming.enable = true;
plugins = with config.services.discourse.package.plugins; [
discourse-spoiler-alert
discourse-solved
];
siteSettings = {
plugins = {
spoiler_enabled = false;
```nix
{
services.discourse = {
enable = true;
hostname = "discourse.example.com";
sslCertificate = "/path/to/ssl_certificate";
sslCertificateKey = "/path/to/ssl_certificate_key";
admin = {
email = "admin@example.com";
username = "admin";
fullName = "Administrator";
passwordFile = "/path/to/password_file";
};
mail.outgoing = {
serverAddress = "smtp.emailprovider.com";
port = 587;
username = "user@emailprovider.com";
passwordFile = "/path/to/smtp_password_file";
};
mail.incoming.enable = true;
plugins = with config.services.discourse.package.plugins; [
discourse-spoiler-alert
discourse-solved
];
siteSettings = {
plugins = {
spoiler_enabled = false;
};
};
secretKeyBaseFile = "/path/to/secret_key_base_file";
};
secretKeyBaseFile = "/path/to/secret_key_base_file";
};
}
```

View file

@ -8,17 +8,19 @@ The following configuration sets up the PostgreSQL as database backend and binds
GoToSocial to `127.0.0.1:8080`, expecting to be run behind a HTTP proxy on `gotosocial.example.com`.
```nix
services.gotosocial = {
enable = true;
setupPostgresqlDB = true;
settings = {
application-name = "My GoToSocial";
host = "gotosocial.example.com";
protocol = "https";
bind-address = "127.0.0.1";
port = 8080;
{
services.gotosocial = {
enable = true;
setupPostgresqlDB = true;
settings = {
application-name = "My GoToSocial";
host = "gotosocial.example.com";
protocol = "https";
bind-address = "127.0.0.1";
port = 8080;
};
};
};
}
```
Please refer to the [GoToSocial Documentation](https://docs.gotosocial.org/en/latest/configuration/general/)
@ -30,24 +32,26 @@ Although it is possible to expose GoToSocial directly, it is common practice to
HTTP reverse proxy such as nginx.
```nix
networking.firewall.allowedTCPPorts = [ 80 443 ];
services.nginx = {
enable = true;
clientMaxBodySize = "40M";
virtualHosts = with config.services.gotosocial.settings; {
"${host}" = {
enableACME = true;
forceSSL = true;
locations = {
"/" = {
recommendedProxySettings = true;
proxyWebsockets = true;
proxyPass = "http://${bind-address}:${toString port}";
{
networking.firewall.allowedTCPPorts = [ 80 443 ];
services.nginx = {
enable = true;
clientMaxBodySize = "40M";
virtualHosts = with config.services.gotosocial.settings; {
"${host}" = {
enableACME = true;
forceSSL = true;
locations = {
"/" = {
recommendedProxySettings = true;
proxyWebsockets = true;
proxyPass = "http://${bind-address}:${toString port}";
};
};
};
};
};
};
}
```
Please refer to [](#module-security-acme) for details on how to provision an SSL/TLS certificate.

View file

@ -6,7 +6,7 @@
## Basic usage {#module-services-grocy-basic-usage}
A very basic configuration may look like this:
```
```nix
{ pkgs, ... }:
{
services.grocy = {
@ -29,7 +29,7 @@ of the application.
The configuration for `grocy` is located at `/etc/grocy/config.php`.
By default, the following settings can be defined in the NixOS-configuration:
```
```nix
{ pkgs, ... }:
{
services.grocy.settings = {
@ -56,7 +56,7 @@ By default, the following settings can be defined in the NixOS-configuration:
If you want to alter the configuration file on your own, you can do this manually with
an expression like this:
```
```nix
{ lib, ... }:
{
environment.etc."grocy/config.php".text = lib.mkAfter ''

View file

@ -346,8 +346,8 @@ in
port = lib.mkOption {
type = types.port;
default = options.services.postgresql.port.default;
defaultText = lib.literalExpression "options.services.postgresql.port.default";
default = config.services.postgresql.settings.port;
defaultText = lib.literalExpression "config.services.postgresql.settings.port";
description = lib.mdDoc ''
The port of the database Invidious should use.

View file

@ -6,7 +6,7 @@ private, self-hosted video conferencing solution.
## Basic usage {#module-services-jitsi-basic-usage}
A minimal configuration using Let's Encrypt for TLS certificates looks like this:
```
```nix
{
services.jitsi-meet = {
enable = true;
@ -22,7 +22,7 @@ A minimal configuration using Let's Encrypt for TLS certificates looks like this
## Configuration {#module-services-jitsi-configuration}
Here is the minimal configuration with additional configurations:
```
```nix
{
services.jitsi-meet = {
enable = true;

View file

@ -188,7 +188,14 @@ in
description = lib.mdDoc ''The port which the Excalidraw backend for Jitsi should listen to.'';
};
secureDomain.enable = mkEnableOption (lib.mdDoc "Authenticated room creation");
secureDomain = {
enable = mkEnableOption (lib.mdDoc "Authenticated room creation");
authentication = mkOption {
type = types.str;
default = "internal_hashed";
description = lib.mdDoc ''The authentication type to be used by jitsi'';
};
};
};
config = mkIf cfg.enable {
@ -309,7 +316,7 @@ in
enabled = true;
domain = cfg.hostName;
extraConfig = ''
authentication = ${if cfg.secureDomain.enable then "\"internal_hashed\"" else "\"jitsi-anonymous\""}
authentication = ${if cfg.secureDomain.enable then "\"${cfg.secureDomain.authentication}\"" else "\"jitsi-anonymous\""}
c2s_require_encryption = false
admins = { "focus@auth.${cfg.hostName}" }
smacks_max_unacked_stanzas = 5

View file

@ -2,7 +2,18 @@
let
cfg = config.services.kavita;
in {
settingsFormat = pkgs.formats.json { };
appsettings = settingsFormat.generate "appsettings.json" ({ TokenKey = "@TOKEN@"; } // cfg.settings);
in
{
imports = [
(lib.mkChangedOptionModule [ "services" "kavita" "ipAdresses" ] [ "services" "kavita" "settings" "IpAddresses" ] (config:
let value = lib.getAttrFromPath [ "services" "kavita" "ipAdresses" ] config; in
lib.concatStringsSep "," value
))
(lib.mkRenamedOptionModule [ "services" "kavita" "port" ] [ "services" "kavita" "settings" "Port" ])
];
options.services.kavita = {
enable = lib.mkEnableOption (lib.mdDoc "Kavita reading server");
@ -27,16 +38,31 @@ in {
It can be generated with `head -c 32 /dev/urandom | base64`.
'';
};
port = lib.mkOption {
default = 5000;
type = lib.types.port;
description = lib.mdDoc "Port to bind to.";
};
ipAdresses = lib.mkOption {
default = ["0.0.0.0" "::"];
type = lib.types.listOf lib.types.str;
description = lib.mdDoc "IP Addresses to bind to. The default is to bind
to all IPv4 and IPv6 addresses.";
settings = lib.mkOption {
default = { };
description = lib.mdDoc ''
Kavita configuration options, as configured in {file}`appsettings.json`.
'';
type = lib.types.submodule {
freeformType = settingsFormat.type;
options = {
Port = lib.mkOption {
default = 5000;
type = lib.types.port;
description = lib.mdDoc "Port to bind to.";
};
IpAddresses = lib.mkOption {
default = "0.0.0.0,::";
type = lib.types.commas;
description = lib.mdDoc ''
IP Addresses to bind to. The default is to bind to all IPv4 and IPv6 addresses.
'';
};
};
};
};
};
@ -46,18 +72,15 @@ in {
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
preStart = ''
umask u=rwx,g=rx,o=
cat > "${cfg.dataDir}/config/appsettings.json" <<EOF
{
"TokenKey": "$(cat ${cfg.tokenKeyFile})",
"Port": ${toString cfg.port},
"IpAddresses": "${lib.concatStringsSep "," cfg.ipAdresses}"
}
EOF
install -m600 ${appsettings} ${lib.escapeShellArg cfg.dataDir}/config/appsettings.json
${pkgs.replace-secret}/bin/replace-secret '@TOKEN@' \
''${CREDENTIALS_DIRECTORY}/token \
'${cfg.dataDir}/config/appsettings.json'
'';
serviceConfig = {
WorkingDirectory = cfg.dataDir;
ExecStart = "${lib.getExe cfg.package}";
LoadCredential = [ "token:${cfg.tokenKeyFile}" ];
ExecStart = lib.getExe cfg.package;
Restart = "always";
User = cfg.user;
};

View file

@ -126,16 +126,18 @@ should be set to. See the description of
## Example configuration {#module-services-keycloak-example-config}
A basic configuration with some custom settings could look like this:
```
services.keycloak = {
enable = true;
settings = {
hostname = "keycloak.example.com";
hostname-strict-backchannel = true;
```nix
{
services.keycloak = {
enable = true;
settings = {
hostname = "keycloak.example.com";
hostname-strict-backchannel = true;
};
initialAdminPassword = "e6Wcm0RrtegMEHl"; # change on first login
sslCertificate = "/run/keys/ssl_cert";
sslCertificateKey = "/run/keys/ssl_key";
database.passwordFile = "/run/keys/db_password";
};
initialAdminPassword = "e6Wcm0RrtegMEHl"; # change on first login
sslCertificate = "/run/keys/ssl_cert";
sslCertificateKey = "/run/keys/ssl_key";
database.passwordFile = "/run/keys/db_password";
};
}
```

View file

@ -7,13 +7,15 @@ Lemmy is a federated alternative to reddit in rust.
the minimum to start lemmy is
```nix
services.lemmy = {
enable = true;
settings = {
hostname = "lemmy.union.rocks";
database.createLocally = true;
{
services.lemmy = {
enable = true;
settings = {
hostname = "lemmy.union.rocks";
database.createLocally = true;
};
caddy.enable = true;
};
caddy.enable = true;
}
```

View file

@ -742,11 +742,16 @@ in {
umask 077
export PGPASSWORD="$(cat '${cfg.database.passwordFile}')"
'' + ''
if [ `psql -c \
"select count(*) from pg_class c \
join pg_namespace s on s.oid = c.relnamespace \
where s.nspname not in ('pg_catalog', 'pg_toast', 'information_schema') \
and s.nspname not like 'pg_temp%';" | sed -n 3p` -eq 0 ]; then
result="$(psql -t --csv -c \
"select count(*) from pg_class c \
join pg_namespace s on s.oid = c.relnamespace \
where s.nspname not in ('pg_catalog', 'pg_toast', 'information_schema') \
and s.nspname not like 'pg_temp%';")" || error_code=$?
if [ "''${error_code:-0}" -ne 0 ]; then
echo "Failure checking if database is seeded. psql gave exit code $error_code"
exit "$error_code"
fi
if [ "$result" -eq 0 ]; then
echo "Seeding database"
SAFETY_ASSURED=1 rails db:schema:load
rails db:seed

View file

@ -25,7 +25,7 @@ to `true`, Nextcloud will automatically be configured to connect to it through
socket.
A very basic configuration may look like this:
```
```nix
{ pkgs, ... }:
{
services.nextcloud = {
@ -130,7 +130,7 @@ settings `listen.owner` &amp; `listen.group` in the
[corresponding `phpfpm` pool](#opt-services.phpfpm.pools).
An exemplary configuration may look like this:
```
```nix
{ config, lib, pkgs, ... }: {
services.nginx.enable = false;
services.nextcloud = {
@ -205,7 +205,7 @@ If major-releases will be abandoned by upstream, we should check first if those
in NixOS for a safe upgrade-path before removing those. In that case we should keep those
packages, but mark them as insecure in an expression like this (in
`<nixpkgs/pkgs/servers/nextcloud/default.nix>`):
```
```nix
/* ... */
{
nextcloud17 = generic {

View file

@ -0,0 +1,113 @@
# ownCloud Infinite Scale {#module-services-ocis}
[ownCloud Infinite Scale](https://owncloud.dev/ocis/) (oCIS) is an open-source,
modern file-sync and sharing platform. It is a ground-up rewrite of the well-known PHP based ownCloud server.
The server setup can be automated using
[services.ocis](#opt-services.ocis.enable). The desktop client is packaged at
`pkgs.owncloud-client`.
## Basic usage {#module-services-ocis-basic-usage}
oCIS is a golang application and does not require an HTTP server (such as nginx)
in front of it, though you may optionally use one if you will.
oCIS is configured using a combination of yaml and environment variables. It is
recommended to familiarize yourself with upstream's available configuration
options and deployment instructions:
* [Getting Started](https://owncloud.dev/ocis/getting-started/)
* [Configuration](https://owncloud.dev/ocis/config/)
* [Basic Setup](https://owncloud.dev/ocis/deployment/basic-remote-setup/)
A very basic configuration may look like this:
```
{ pkgs, ... }:
{
services.ocis = {
enable = true;
configDir = "/etc/ocis/config";
};
}
```
This will start the oCIS server and make it available at `https://localhost:9200`
However to make this configuration work you will need generate a configuration.
You can do this with:
```console
$ nix-shell -p ocis-bin
$ mkdir scratch/
$ cd scratch/
$ ocis init --config-path . --admin-password "changeme"
```
You may need to pass `--insecure true` or provide the `OCIS_INSECURE = true;` to
[`services.ocis.environment`][mod-envFile], if TLS certificates are generated
and managed externally (e.g. if you are using oCIS behind reverse proxy).
If you want to manage the config file in your nix configuration, then it is
encouraged to use a secrets manager like sops-nix or agenix.
Be careful not to write files containing secrets to the globally readable nix
store.
Please note that current NixOS module for oCIS is configured to run in `fullstack`
mode, which starts all the services for owncloud on single instance. This will
start multiple ocis services and listen on multiple other ports.
Current known services and their ports are as below:
| Service | Group | Port |
|--------------------|---------|-------|
| gateway | api | 9142 |
| sharing | api | 9150 |
| app-registry | api | 9242 |
| ocdav | web | 45023 |
| auth-machine | api | 9166 |
| storage-system | api | 9215 |
| webdav | web | 9115 |
| webfinger | web | 46871 |
| storage-system | web | 9216 |
| web | web | 9100 |
| eventhistory | api | 33177 |
| ocs | web | 9110 |
| storage-publiclink | api | 9178 |
| settings | web | 9190 |
| ocm | api | 9282 |
| settings | api | 9191 |
| ocm | web | 9280 |
| app-provider | api | 9164 |
| storage-users | api | 9157 |
| auth-service | api | 9199 |
| thumbnails | web | 9186 |
| thumbnails | api | 9185 |
| storage-shares | api | 9154 |
| sse | sse | 46833 |
| userlog | userlog | 45363 |
| search | api | 9220 |
| proxy | web | 9200 |
| idp | web | 9130 |
| frontend | web | 9140 |
| groups | api | 9160 |
| graph | graph | 9120 |
| users | api | 9144 |
| auth-basic | api | 9146 |
## Configuration via environment variables
You can also eschew the config file entirely and pass everything to oCIS via
environment variables. For this make use of
[`services.ocis.environment`][mod-env] for non-sensitive
values, and
[`services.ocis.environmentFile`][mod-envFile] for
sensitive values.
Configuration in (`services.ocis.environment`)[mod-env] overrides those from
[`services.ocis.environmentFile`][mod-envFile] and will have highest
precedence
[mod-env]: #opt-services.ocis.environment
[mod-envFile]: #opt-services.ocis.environmentFile

View file

@ -0,0 +1,201 @@
{
config,
lib,
pkgs,
...
}:
let
inherit (lib) types;
cfg = config.services.ocis;
defaultUser = "ocis";
defaultGroup = defaultUser;
in
{
options = {
services.ocis = {
enable = lib.mkEnableOption "ownCloud Infinite Scale";
package = lib.mkPackageOption pkgs "ocis-bin" { };
configDir = lib.mkOption {
type = types.nullOr types.path;
default = null;
example = "/var/lib/ocis/config";
description = lib.mdDoc ''
Path to directory containing oCIS config file.
Example config can be generated by `ocis init --config-path fileName --admin-password "adminPass"`.
Add `--insecure true` if SSL certificates are generated and managed externally (e.g. using oCIS behind reverse proxy).
Note: This directory must contain at least a `ocis.yaml`. Ensure
[user](#opt-services.ocis.user) has read/write access to it. In some
circumstances you may need to add additional oCIS configuration files (e.g.,
`proxy.yaml`) to this directory.
'';
};
environmentFile = lib.mkOption {
type = types.nullOr types.path;
default = null;
example = "/run/keys/ocis.env";
description = lib.mdDoc ''
An environment file as defined in {manpage}`systemd.exec(5)`.
Configuration provided in this file will override those from [configDir](#opt-services.ocis.configDir)/ocis.yaml.
'';
};
user = lib.mkOption {
type = types.str;
default = defaultUser;
example = "yourUser";
description = lib.mdDoc ''
The user to run oCIS as.
By default, a user named `${defaultUser}` will be created whose home
directory is [stateDir](#opt-services.ocis.stateDir).
'';
};
group = lib.mkOption {
type = types.str;
default = defaultGroup;
example = "yourGroup";
description = lib.mdDoc ''
The group to run oCIS under.
By default, a group named `${defaultGroup}` will be created.
'';
};
address = lib.mkOption {
type = types.str;
default = "127.0.0.1";
description = "Web interface address.";
};
port = lib.mkOption {
type = types.port;
default = 9200;
description = "Web interface port.";
};
url = lib.mkOption {
type = types.str;
default = "https://localhost:9200";
example = "https://some-hostname-or-ip:9200";
description = "Web interface address.";
};
stateDir = lib.mkOption {
default = "/var/lib/ocis";
type = types.str;
description = "ownCloud data directory.";
};
environment = lib.mkOption {
type = types.attrsOf types.str;
default = { };
description = lib.mdDoc ''
Extra config options.
See [the documentation](https://doc.owncloud.com/ocis/next/deployment/services/services.html) for available options.
See [notes for environment variables](https://doc.owncloud.com/ocis/next/deployment/services/env-var-note.html) for more information.
Note that all the attributes here will be copied to /nix/store/ and will be world readable. Options like *_PASSWORD or *_SECRET should be part of [environmentFile](#opt-services.ocis.environmentFile) instead, and are only provided here for illustrative purpose.
Configuration here will override those from [environmentFile](#opt-services.ocis.environmentFile) and will have highest precedence, at the cost of security. Do NOT put security sensitive stuff here.
'';
example = {
OCIS_INSECURE = "false";
OCIS_LOG_LEVEL = "error";
OCIS_JWT_SECRET = "super_secret";
OCIS_TRANSFER_SECRET = "foo";
OCIS_MACHINE_AUTH_API_KEY = "foo";
OCIS_SYSTEM_USER_ID = "123";
OCIS_MOUNT_ID = "123";
OCIS_STORAGE_USERS_MOUNT_ID = "123";
GATEWAY_STORAGE_USERS_MOUNT_ID = "123";
CS3_ALLOW_INSECURE = "true";
OCIS_INSECURE_BACKENDS = "true";
TLS_INSECURE = "true";
TLS_SKIP_VERIFY_CLIENT_CERT = "true";
WEBDAV_ALLOW_INSECURE = "true";
IDP_TLS = "false";
GRAPH_APPLICATION_ID = "1234";
IDM_IDPSVC_PASSWORD = "password";
IDM_REVASVC_PASSWORD = "password";
IDM_SVC_PASSWORD = "password";
IDP_ISS = "https://localhost:9200";
OCIS_LDAP_BIND_PASSWORD = "password";
OCIS_SERVICE_ACCOUNT_ID = "foo";
OCIS_SERVICE_ACCOUNT_SECRET = "foo";
OCIS_SYSTEM_USER_API_KEY = "foo";
STORAGE_USERS_MOUNT_ID = "123";
};
};
};
};
config = lib.mkIf cfg.enable {
users.users.${defaultUser} = lib.mkIf (cfg.user == defaultUser) {
group = cfg.group;
home = cfg.stateDir;
isSystemUser = true;
createHome = true;
description = "ownCloud Infinite Scale daemon user";
};
users.groups = lib.mkIf (cfg.group == defaultGroup) { ${defaultGroup} = { }; };
systemd = {
services.ocis = {
description = "ownCloud Infinite Scale Stack";
wantedBy = [ "multi-user.target" ];
environment = {
PROXY_HTTP_ADDR = "${cfg.address}:${toString cfg.port}";
OCIS_URL = cfg.url;
OCIS_CONFIG_DIR = if (cfg.configDir == null) then "${cfg.stateDir}/config" else cfg.configDir;
OCIS_BASE_DATA_PATH = cfg.stateDir;
} // cfg.environment;
serviceConfig = {
Type = "simple";
ExecStart = "${lib.getExe cfg.package} server";
WorkingDirectory = cfg.stateDir;
User = cfg.user;
Group = cfg.group;
Restart = "always";
EnvironmentFile = lib.optional (cfg.environmentFile != null) cfg.environmentFile;
ReadWritePaths = [ cfg.stateDir ];
ReadOnlyPaths = [ cfg.configDir ];
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateTmp = true;
PrivateDevices = true;
ProtectSystem = "strict";
ProtectHome = true;
ProtectControlGroups = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectKernelLogs = true;
RestrictAddressFamilies = [
"AF_UNIX"
"AF_INET"
"AF_INET6"
"AF_NETLINK"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
LockPersonality = true;
SystemCallArchitectures = "native";
};
};
};
};
meta.maintainers = with lib.maintainers; [
bhankas
danth
ramblurr
];
}

View file

@ -783,6 +783,8 @@ in
# This working directory is required to find stuff like the set of
# onboarding files:
WorkingDirectory = "${cfg.package}/share/outline";
# In case this directory is not in /var/lib/outline, it needs to be made writable explicitly
ReadWritePaths = [ cfg.storage.localRootDir ];
};
};
};

View file

@ -61,18 +61,16 @@ let
eval -- "\$@"
'';
peertubeCli = pkgs.writeShellScriptBin "peertube" ''
node ~/dist/server/tools/peertube.js $@
nginxCommonHeaders = lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.forceSSL ''
add_header Strict-Transport-Security 'max-age=31536000';
'' + lib.optionalString (config.services.nginx.virtualHosts.${cfg.localDomain}.quic && config.services.nginx.virtualHosts.${cfg.localDomain}.http3) ''
add_header Alt-Svc 'h3=":$server_port"; ma=604800';
'';
nginxCommonHeaders = lib.optionalString cfg.enableWebHttps ''
add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains';
'' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 ''
add_header Alt-Svc 'h3=":443"; ma=86400';
'' + ''
add_header Access-Control-Allow-Origin '*';
add_header Access-Control-Allow-Methods 'GET, OPTIONS';
add_header Access-Control-Allow-Headers 'Range,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
nginxCommonHeadersExtra = ''
add_header Access-Control-Allow-Origin '*';
add_header Access-Control-Allow-Methods 'GET, OPTIONS';
add_header Access-Control-Allow-Headers 'Range,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
'';
in {
@ -330,6 +328,8 @@ in {
}
];
environment.systemPackages = [ cfg.package.cli ];
services.peertube.settings = lib.mkMerge [
{
listen = {
@ -355,12 +355,13 @@ in {
tmp_persistent = lib.mkDefault "/var/lib/peertube/storage/tmp_persistent/";
bin = lib.mkDefault "/var/lib/peertube/storage/bin/";
avatars = lib.mkDefault "/var/lib/peertube/storage/avatars/";
videos = lib.mkDefault "/var/lib/peertube/storage/videos/";
web_videos = lib.mkDefault "/var/lib/peertube/storage/web-videos/";
streaming_playlists = lib.mkDefault "/var/lib/peertube/storage/streaming-playlists/";
redundancy = lib.mkDefault "/var/lib/peertube/storage/redundancy/";
logs = lib.mkDefault "/var/lib/peertube/storage/logs/";
previews = lib.mkDefault "/var/lib/peertube/storage/previews/";
thumbnails = lib.mkDefault "/var/lib/peertube/storage/thumbnails/";
storyboards = lib.mkDefault "/var/lib/peertube/storage/storyboards/";
torrents = lib.mkDefault "/var/lib/peertube/storage/torrents/";
captions = lib.mkDefault "/var/lib/peertube/storage/captions/";
cache = lib.mkDefault "/var/lib/peertube/storage/cache/";
@ -428,7 +429,7 @@ in {
environment = env;
path = with pkgs; [ bashInteractive ffmpeg nodejs_18 openssl yarn python3 ];
path = with pkgs; [ nodejs_18 yarn ffmpeg-headless openssl ];
script = ''
#!/bin/sh
@ -456,7 +457,7 @@ in {
ln -sf ${cfg.package}/config/default.yaml /var/lib/peertube/config/default.yaml
ln -sf ${cfg.package}/client/dist -T /var/lib/peertube/www/client
ln -sf ${cfg.settings.storage.client_overrides} -T /var/lib/peertube/www/client-overrides
npm start
node dist/server
'';
serviceConfig = {
Type = "simple";
@ -488,6 +489,9 @@ in {
services.nginx = lib.mkIf cfg.configureNginx {
enable = true;
upstreams."peertube".servers = {
"127.0.0.1:${toString cfg.listenHttp}".fail_timeout = "0";
};
virtualHosts."${cfg.localDomain}" = {
root = "/var/lib/peertube/www";
@ -497,14 +501,14 @@ in {
priority = 1110;
};
locations."= /api/v1/videos/upload-resumable" = {
locations."~ ^/api/v1/videos/(upload-resumable|([^/]+/source/replace-resumable))$" = {
tryFiles = "/dev/null @api";
priority = 1120;
extraConfig = ''
client_max_body_size 0;
proxy_request_buffering off;
'';
client_max_body_size 0;
proxy_request_buffering off;
'' + nginxCommonHeaders;
};
locations."~ ^/api/v1/videos/(upload|([^/]+/studio/edit))$" = {
@ -513,13 +517,11 @@ in {
priority = 1130;
extraConfig = ''
client_max_body_size 12G;
add_header X-File-Maximum-Size 8G always;
'' + lib.optionalString cfg.enableWebHttps ''
add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains';
'' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 ''
add_header Alt-Svc 'h3=":443"; ma=86400';
'';
limit_except POST HEAD { deny all; }
client_max_body_size 12G;
add_header X-File-Maximum-Size 8G always;
'' + nginxCommonHeaders;
};
locations."~ ^/api/v1/runners/jobs/[^/]+/(update|success)$" = {
@ -528,13 +530,9 @@ in {
priority = 1135;
extraConfig = ''
client_max_body_size 12G;
add_header X-File-Maximum-Size 8G always;
'' + lib.optionalString cfg.enableWebHttps ''
add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains';
'' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 ''
add_header Alt-Svc 'h3=":443"; ma=86400';
'';
client_max_body_size 12G;
add_header X-File-Maximum-Size 8G always;
'' + nginxCommonHeaders;
};
locations."~ ^/api/v1/(videos|video-playlists|video-channels|users/me)" = {
@ -542,32 +540,28 @@ in {
priority = 1140;
extraConfig = ''
client_max_body_size 6M;
add_header X-File-Maximum-Size 4M always;
'' + lib.optionalString cfg.enableWebHttps ''
add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains';
'' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 ''
add_header Alt-Svc 'h3=":443"; ma=86400';
'';
client_max_body_size 6M;
add_header X-File-Maximum-Size 4M always;
'' + nginxCommonHeaders;
};
locations."@api" = {
proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
proxyPass = "http://peertube";
priority = 1150;
extraConfig = ''
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 10m;
proxy_connect_timeout 10m;
proxy_send_timeout 10m;
proxy_read_timeout 10m;
proxy_send_timeout 10m;
proxy_read_timeout 10m;
client_max_body_size 100k;
send_timeout 10m;
'';
client_max_body_size 100k;
send_timeout 10m;
''+ nginxCommonHeaders;
};
# Websocket
@ -581,7 +575,7 @@ in {
priority = 1220;
extraConfig = ''
proxy_read_timeout 15m;
proxy_read_timeout 15m;
'';
};
@ -591,84 +585,82 @@ in {
};
locations."@api_websocket" = {
proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
proxyPass = "http://peertube";
priority = 1240;
extraConfig = ''
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
'';
'' + nginxCommonHeaders;
};
# Bypass PeerTube for performance reasons.
locations."~ ^/client/(assets/images/(icons/icon-36x36\.png|icons/icon-48x48\.png|icons/icon-72x72\.png|icons/icon-96x96\.png|icons/icon-144x144\.png|icons/icon-192x192\.png|icons/icon-512x512\.png|logo\.svg|favicon\.png|default-playlist\.jpg|default-avatar-account\.png|default-avatar-account-48x48\.png|default-avatar-video-channel\.png|default-avatar-video-channel-48x48\.png))$" = {
tryFiles = "/client-overrides/$1 /client/$1 $1";
priority = 1310;
extraConfig = nginxCommonHeaders;
};
locations."~ ^/client/(.*\.(js|css|png|svg|woff2|otf|ttf|woff|eot))$" = {
alias = "${cfg.package}/client/dist/$1";
priority = 1320;
extraConfig = ''
add_header Cache-Control 'public, max-age=604800, immutable';
'' + lib.optionalString cfg.enableWebHttps ''
add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains';
'' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 ''
add_header Alt-Svc 'h3=":443"; ma=86400';
'';
add_header Cache-Control 'public, max-age=604800, immutable';
'' + nginxCommonHeaders;
};
locations."^~ /download/" = {
proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
proxyPass = "http://peertube";
priority = 1410;
extraConfig = ''
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_limit_rate 5M;
'';
proxy_limit_rate 5M;
'' + nginxCommonHeaders;
};
locations."^~ /static/streaming-playlists/private/" = {
proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
locations."^~ /static/streaming-playlists/hls/private/" = {
proxyPass = "http://peertube";
priority = 1420;
extraConfig = ''
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_limit_rate 5M;
'';
proxy_limit_rate 5M;
'' + nginxCommonHeaders;
};
locations."^~ /static/web-videos/private/" = {
proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
proxyPass = "http://peertube";
priority = 1430;
extraConfig = ''
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_limit_rate 5M;
'';
proxy_limit_rate 5M;
'' + nginxCommonHeaders;
};
locations."^~ /static/webseed/private/" = {
proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
proxyPass = "http://peertube";
priority = 1440;
extraConfig = ''
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_limit_rate 5M;
'';
proxy_limit_rate 5M;
'' + nginxCommonHeaders;
};
locations."^~ /static/redundancy/" = {
@ -676,33 +668,35 @@ in {
root = cfg.settings.storage.redundancy;
priority = 1450;
extraConfig = ''
set $peertube_limit_rate 800k;
set $peertube_limit_rate 800k;
if ($request_uri ~ -fragmented.mp4$) {
set $peertube_limit_rate 5M;
set $peertube_limit_rate 5M;
}
if ($request_method = 'OPTIONS') {
${nginxCommonHeaders}
add_header Access-Control-Max-Age 1728000;
add_header Content-Type 'text/plain charset=UTF-8';
add_header Content-Length 0;
return 204;
${nginxCommonHeadersExtra}
add_header Access-Control-Max-Age 1728000;
add_header Content-Type 'text/plain charset=UTF-8';
add_header Content-Length 0;
return 204;
}
if ($request_method = 'GET') {
${nginxCommonHeaders}
${nginxCommonHeadersExtra}
access_log off;
access_log off;
}
aio threads;
sendfile on;
sendfile_max_chunk 1M;
aio threads;
sendfile on;
sendfile_max_chunk 1M;
limit_rate $peertube_limit_rate;
limit_rate_after 5M;
limit_rate $peertube_limit_rate;
limit_rate_after 5M;
rewrite ^/static/redundancy/(.*)$ /$1 break;
rewrite ^/static/redundancy/(.*)$ /$1 break;
'';
};
@ -711,109 +705,111 @@ in {
root = cfg.settings.storage.streaming_playlists;
priority = 1460;
extraConfig = ''
set $peertube_limit_rate 800k;
set $peertube_limit_rate 800k;
if ($request_uri ~ -fragmented.mp4$) {
set $peertube_limit_rate 5M;
set $peertube_limit_rate 5M;
}
if ($request_method = 'OPTIONS') {
${nginxCommonHeaders}
add_header Access-Control-Max-Age 1728000;
add_header Content-Type 'text/plain charset=UTF-8';
add_header Content-Length 0;
return 204;
${nginxCommonHeadersExtra}
add_header Access-Control-Max-Age 1728000;
add_header Content-Type 'text/plain charset=UTF-8';
add_header Content-Length 0;
return 204;
}
if ($request_method = 'GET') {
${nginxCommonHeaders}
${nginxCommonHeadersExtra}
access_log off;
access_log off;
}
aio threads;
sendfile on;
sendfile_max_chunk 1M;
aio threads;
sendfile on;
sendfile_max_chunk 1M;
limit_rate $peertube_limit_rate;
limit_rate_after 5M;
limit_rate $peertube_limit_rate;
limit_rate_after 5M;
rewrite ^/static/streaming-playlists/(.*)$ /$1 break;
rewrite ^/static/streaming-playlists/(.*)$ /$1 break;
'';
};
locations."^~ /static/web-videos/" = {
tryFiles = "$uri @api";
root = cfg.settings.storage.streaming_playlists;
root = cfg.settings.storage.web_videos;
priority = 1470;
extraConfig = ''
set $peertube_limit_rate 800k;
set $peertube_limit_rate 800k;
if ($request_uri ~ -fragmented.mp4$) {
set $peertube_limit_rate 5M;
set $peertube_limit_rate 5M;
}
if ($request_method = 'OPTIONS') {
${nginxCommonHeaders}
add_header Access-Control-Max-Age 1728000;
add_header Content-Type 'text/plain charset=UTF-8';
add_header Content-Length 0;
return 204;
${nginxCommonHeadersExtra}
add_header Access-Control-Max-Age 1728000;
add_header Content-Type 'text/plain charset=UTF-8';
add_header Content-Length 0;
return 204;
}
if ($request_method = 'GET') {
${nginxCommonHeaders}
${nginxCommonHeadersExtra}
access_log off;
access_log off;
}
aio threads;
sendfile on;
sendfile_max_chunk 1M;
aio threads;
sendfile on;
sendfile_max_chunk 1M;
limit_rate $peertube_limit_rate;
limit_rate_after 5M;
limit_rate $peertube_limit_rate;
limit_rate_after 5M;
rewrite ^/static/streaming-playlists/(.*)$ /$1 break;
rewrite ^/static/web-videos/(.*)$ /$1 break;
'';
};
locations."^~ /static/webseed/" = {
tryFiles = "$uri @api";
root = cfg.settings.storage.videos;
root = cfg.settings.storage.web_videos;
priority = 1480;
extraConfig = ''
set $peertube_limit_rate 800k;
set $peertube_limit_rate 800k;
if ($request_uri ~ -fragmented.mp4$) {
set $peertube_limit_rate 5M;
set $peertube_limit_rate 5M;
}
if ($request_method = 'OPTIONS') {
${nginxCommonHeaders}
add_header Access-Control-Max-Age 1728000;
add_header Content-Type 'text/plain charset=UTF-8';
add_header Content-Length 0;
return 204;
${nginxCommonHeadersExtra}
add_header Access-Control-Max-Age 1728000;
add_header Content-Type 'text/plain charset=UTF-8';
add_header Content-Length 0;
return 204;
}
if ($request_method = 'GET') {
${nginxCommonHeaders}
${nginxCommonHeadersExtra}
access_log off;
access_log off;
}
aio threads;
sendfile on;
sendfile_max_chunk 1M;
aio threads;
sendfile on;
sendfile_max_chunk 1M;
limit_rate $peertube_limit_rate;
limit_rate_after 5M;
limit_rate $peertube_limit_rate;
limit_rate_after 5M;
rewrite ^/static/webseed/(.*)$ /$1 break;
rewrite ^/static/webseed/(.*)$ /web-videos/$1 break;
'';
};
extraConfig = lib.optionalString cfg.enableWebHttps ''
add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains';
'';
};
};
@ -848,7 +844,7 @@ in {
home = cfg.package;
};
})
(lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package peertubeEnv peertubeCli pkgs.ffmpeg pkgs.nodejs_18 pkgs.yarn ])
(lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ peertubeEnv pkgs.nodejs_18 pkgs.yarn pkgs.ffmpeg-headless ])
(lib.mkIf cfg.redis.enableUnixSocket {${config.services.peertube.user}.extraGroups = [ "redis-peertube" ];})
];

View file

@ -7,7 +7,9 @@ pict-rs is a a simple image hosting service.
the minimum to start pict-rs is
```nix
services.pict-rs.enable = true;
{
services.pict-rs.enable = true;
}
```
this will start the http server on port 8080 by default.

View file

@ -11,7 +11,7 @@ $ openssl rand -base64 64
```
After that, `plausible` can be deployed like this:
```
```nix
{
services.plausible = {
enable = true;

View file

@ -63,7 +63,7 @@ in
};
options.services.pretix = {
enable = mkEnableOption "pretix";
enable = mkEnableOption "Pretix, a ticket shop application for conferences, festivals, concerts, etc.";
package = mkPackageOption pkgs "pretix" { };

View file

@ -100,7 +100,7 @@ Not all the configuration options are available directly in this module, but you
server = {
port = 4567;
autoDownloadNewChapters = false;
maxSourcesInParallel" = 6;
maxSourcesInParallel = 6;
extensionRepos = [
"https://raw.githubusercontent.com/MY_ACCOUNT/MY_REPO/repo/index.min.json"
];

View file

@ -76,11 +76,11 @@ in
type = types.port;
default =
if cfg.database.type == "mysql" then config.services.mysql.port
else if cfg.database.type == "pgsql" then config.services.postgresql.port
else if cfg.database.type == "pgsql" then config.services.postgresql.settings.port
else 1521;
defaultText = literalExpression ''
if config.${opt.database.type} == "mysql" then config.${options.services.mysql.port}
else if config.${opt.database.type} == "pgsql" then config.${options.services.postgresql.port}
else if config.${opt.database.type} == "pgsql" then config.services.postgresql.settings.port
else 1521
'';
description = lib.mdDoc "Database host port.";

View file

@ -80,7 +80,7 @@ If major-releases will be abandoned by upstream, we should check first if those
in NixOS for a safe upgrade-path before removing those. In that case we should keep those
packages, but mark them as insecure in an expression like this (in
`<nixpkgs/pkgs/tools/filesystem/garage/default.nix>`):
```
```nix
/* ... */
{
garage_0_7_3 = generic {

View file

@ -8,9 +8,11 @@ All of the core apps, optional apps, games, and core developer tools from GNOME
To enable the GNOME desktop use:
```
services.xserver.desktopManager.gnome.enable = true;
services.xserver.displayManager.gdm.enable = true;
```nix
{
services.xserver.desktopManager.gnome.enable = true;
services.xserver.displayManager.gdm.enable = true;
}
```
::: {.note}
@ -23,8 +25,10 @@ The default applications used in NixOS are very minimal, inspired by the default
If youd like to only use the GNOME desktop and not the apps, you can disable them with:
```
services.gnome.core-utilities.enable = false;
```nix
{
services.gnome.core-utilities.enable = false;
}
```
and none of them will be installed.
@ -37,9 +41,11 @@ Note that this mechanism can only exclude core utilities, games and core develop
It is also possible to disable many of the [core services](https://github.com/NixOS/nixpkgs/blob/b8ec4fd2a4edc4e30d02ba7b1a2cc1358f3db1d5/nixos/modules/services/x11/desktop-managers/gnome.nix#L329-L348). For example, if you do not need indexing files, you can disable Tracker with:
```
services.gnome.tracker-miners.enable = false;
services.gnome.tracker.enable = false;
```nix
{
services.gnome.tracker-miners.enable = false;
services.gnome.tracker.enable = false;
}
```
Note, however, that doing so is not supported and might break some applications. Notably, GNOME Music cannot work without Tracker.
@ -48,39 +54,47 @@ Note, however, that doing so is not supported and might break some applications.
You can install all of the GNOME games with:
```
services.gnome.games.enable = true;
```nix
{
services.gnome.games.enable = true;
}
```
### GNOME core developer tools {#sec-gnome-core-developer-tools}
You can install GNOME core developer tools with:
```
services.gnome.core-developer-tools.enable = true;
```nix
{
services.gnome.core-developer-tools.enable = true;
}
```
## Enabling GNOME Flashback {#sec-gnome-enable-flashback}
GNOME Flashback provides a desktop environment based on the classic GNOME 2 architecture. You can enable the default GNOME Flashback session, which uses the Metacity window manager, with:
```
services.xserver.desktopManager.gnome.flashback.enableMetacity = true;
```nix
{
services.xserver.desktopManager.gnome.flashback.enableMetacity = true;
}
```
It is also possible to create custom sessions that replace Metacity with a different window manager using [](#opt-services.xserver.desktopManager.gnome.flashback.customSessions).
The following example uses `xmonad` window manager:
```
services.xserver.desktopManager.gnome.flashback.customSessions = [
{
wmName = "xmonad";
wmLabel = "XMonad";
wmCommand = "${pkgs.haskellPackages.xmonad}/bin/xmonad";
enableGnomePanel = false;
}
];
```nix
{
services.xserver.desktopManager.gnome.flashback.customSessions = [
{
wmName = "xmonad";
wmLabel = "XMonad";
wmCommand = "${pkgs.haskellPackages.xmonad}/bin/xmonad";
enableGnomePanel = false;
}
];
}
```
## Icons and GTK Themes {#sec-gnome-icons-and-gtk-themes}
@ -104,12 +118,14 @@ Some packages that include Shell extensions, like `gnome.gpaste`, dont have t
You can install them like any other package:
```
environment.systemPackages = [
gnomeExtensions.dash-to-dock
gnomeExtensions.gsconnect
gnomeExtensions.mpris-indicator-button
];
```nix
{
environment.systemPackages = [
gnomeExtensions.dash-to-dock
gnomeExtensions.gsconnect
gnomeExtensions.mpris-indicator-button
];
}
```
Unfortunately, we lack a way for these to be managed in a completely declarative way.
@ -136,23 +152,25 @@ You can use `dconf-editor` tool to explore which GSettings you can set.
### Example {#sec-gnome-gsettings-overrides-example}
```
services.xserver.desktopManager.gnome = {
extraGSettingsOverrides = ''
# Change default background
[org.gnome.desktop.background]
picture-uri='file://${pkgs.nixos-artwork.wallpapers.mosaic-blue.gnomeFilePath}'
```nix
{
services.xserver.desktopManager.gnome = {
extraGSettingsOverrides = ''
# Change default background
[org.gnome.desktop.background]
picture-uri='file://${pkgs.nixos-artwork.wallpapers.mosaic-blue.gnomeFilePath}'
# Favorite apps in gnome-shell
[org.gnome.shell]
favorite-apps=['org.gnome.Console.desktop', 'org.gnome.Nautilus.desktop']
'';
# Favorite apps in gnome-shell
[org.gnome.shell]
favorite-apps=['org.gnome.Console.desktop', 'org.gnome.Nautilus.desktop']
'';
extraGSettingsOverridePackages = [
pkgs.gsettings-desktop-schemas # for org.gnome.desktop
pkgs.gnome.gnome-shell # for org.gnome.shell
];
};
extraGSettingsOverridePackages = [
pkgs.gsettings-desktop-schemas # for org.gnome.desktop
pkgs.gnome.gnome-shell # for org.gnome.shell
];
};
}
```
## Frequently Asked Questions {#sec-gnome-faq}

View file

@ -5,17 +5,23 @@ Pantheon is the desktop environment created for the elementary OS distribution.
## Enabling Pantheon {#sec-pantheon-enable}
All of Pantheon is working in NixOS and the applications should be available, aside from a few [exceptions](https://github.com/NixOS/nixpkgs/issues/58161). To enable Pantheon, set
```
services.xserver.desktopManager.pantheon.enable = true;
```nix
{
services.xserver.desktopManager.pantheon.enable = true;
}
```
This automatically enables LightDM and Pantheon's LightDM greeter. If you'd like to disable this, set
```
services.xserver.displayManager.lightdm.greeters.pantheon.enable = false;
services.xserver.displayManager.lightdm.enable = false;
```nix
{
services.xserver.displayManager.lightdm.greeters.pantheon.enable = false;
services.xserver.displayManager.lightdm.enable = false;
}
```
but please be aware using Pantheon without LightDM as a display manager will break screenlocking from the UI. The NixOS module for Pantheon installs all of Pantheon's default applications. If you'd like to not install Pantheon's apps, set
```
services.pantheon.apps.enable = false;
```nix
{
services.pantheon.apps.enable = false;
}
```
You can also use [](#opt-environment.pantheon.excludePackages) to remove any other app (like `elementary-mail`).
@ -29,30 +35,33 @@ Wingpanel and Switchboard work differently than they do in other distributions,
to configure the programs with plugs or indicators.
The difference in NixOS is both these programs are patched to load plugins from a directory that is the value of an environment variable. All of which is controlled in Nix. If you need to configure the particular packages manually you can override the packages like:
```
```nix
wingpanel-with-indicators.override {
indicators = [
pkgs.some-special-indicator
];
};
}
```
```nix
switchboard-with-plugs.override {
plugs = [
pkgs.some-special-plug
];
};
}
```
please note that, like how the NixOS options describe these as extra plugins, this would only add to the default plugins included with the programs. If for some reason you'd like to configure which plugins to use exactly, both packages have an argument for this:
```
```nix
wingpanel-with-indicators.override {
useDefaultIndicators = false;
indicators = specialListOfIndicators;
};
}
```
```nix
switchboard-with-plugs.override {
useDefaultPlugs = false;
plugs = specialListOfPlugs;
};
}
```
this could be most useful for testing a particular plug-in in isolation.

View file

@ -174,12 +174,38 @@ in
# https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1443
pkgs.pantheon.mutter
];
systemd.packages = [
pkgs.pantheon.gnome-settings-daemon
systemd.packages = with pkgs; [
gnome.gnome-session
pantheon.gala
pantheon.gnome-settings-daemon
pantheon.elementary-session-settings
];
programs.dconf.enable = true;
networking.networkmanager.enable = mkDefault true;
systemd.user.targets."gnome-session-x11-services".wants = [
"org.gnome.SettingsDaemon.XSettings.service"
];
systemd.user.targets."gnome-session-x11-services-ready".wants = [
"org.gnome.SettingsDaemon.XSettings.service"
];
# https://github.com/elementary/gala/issues/1826#issuecomment-1890461298
systemd.user.services."io.elementary.gala.daemon@" = {
unitConfig = {
Description = "Gala Daemon";
BindsTo = "io.elementary.gala@.service";
After = "io.elementary.gala@.service";
};
serviceConfig = {
Type = "dbus";
BusName = "org.pantheon.gala.daemon";
ExecStart = "${pkgs.pantheon.gala}/bin/gala-daemon";
Slice = "session.slice";
};
};
# Global environment
environment.systemPackages = (with pkgs.pantheon; [
elementary-session-settings

View file

@ -40,7 +40,7 @@ let
IFS=:
for i in $XDG_CURRENT_DESKTOP; do
case $i in
KDE|GNOME|X-NIXOS-SYSTEMD-AWARE) echo "1"; exit; ;;
KDE|GNOME|Pantheon|X-NIXOS-SYSTEMD-AWARE) echo "1"; exit; ;;
*) ;;
esac
done

View file

@ -251,7 +251,6 @@ in
environment.systemPackages = [pkgs.xpra];
virtualisation.virtualbox.guest.x11 = false;
hardware.pulseaudio.enable = mkDefault cfg.pulseaudio;
hardware.pulseaudio.systemWide = mkDefault cfg.pulseaudio;
};

View file

@ -111,7 +111,7 @@ let
}
''
echo 'Section "Files"' >> $out
echo $fontpath >> $out
echo "$fontpath" >> $out
for i in ${toString fontsForXServer}; do
if test "''${i:0:''${#NIX_STORE}}" == "$NIX_STORE"; then
@ -121,11 +121,9 @@ let
fi
done
for i in $(find ${toString cfg.modules} -type d | sort); do
if test $(echo $i/*.so* | wc -w) -ne 0; then
echo " ModulePath \"$i\"" >> $out
fi
done
${concatMapStrings (m: ''
echo " ModulePath \"${m}/lib/xorg/modules\"" >> "$out"
'') cfg.modules}
echo '${cfg.filesSection}' >> $out
echo 'EndSection' >> $out