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:
commit
a19bf3e045
2026 changed files with 89111 additions and 27436 deletions
45
nixos/modules/services/admin/docuum.nix
Normal file
45
nixos/modules/services/admin/docuum.nix
Normal 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
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 // {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
};
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ];
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
244
nixos/modules/services/home-automation/wyoming/satellite.nix
Normal file
244
nixos/modules/services/home-automation/wyoming/satellite.nix
Normal 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";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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 = {
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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; [
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 = {};
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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.";
|
||||
};
|
||||
|
|
|
@ -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.";
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 "
|
||||
|
|
121
nixos/modules/services/misc/invidious-router.nix
Normal file
121
nixos/modules/services/misc/invidious-router.nix
Normal 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;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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";
|
||||
|
|
|
@ -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 = [];
|
||||
};
|
||||
|
||||
|
|
133
nixos/modules/services/misc/mollysocket.nix
Normal file
133
nixos/modules/services/misc/mollysocket.nix
Normal 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 ];
|
||||
}
|
|
@ -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"
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
158
nixos/modules/services/misc/wastebin.nix
Normal file
158
nixos/modules/services/misc/wastebin.nix
Normal 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 ];
|
||||
}
|
|
@ -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
|
||||
|
|
83
nixos/modules/services/misc/workout-tracker.nix
Normal file
83
nixos/modules/services/misc/workout-tracker.nix
Normal 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 ];
|
||||
}
|
|
@ -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
|
||||
'')
|
||||
];
|
||||
}
|
||||
```
|
||||
|
|
|
@ -7,7 +7,7 @@ for validating a server's configuration.
|
|||
|
||||
A minimal configuration looks like this:
|
||||
|
||||
```
|
||||
```nix
|
||||
{
|
||||
services.goss = {
|
||||
enable = true;
|
||||
|
|
|
@ -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" ];
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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" ];
|
||||
|
|
|
@ -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.";
|
||||
};
|
||||
|
|
|
@ -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.";
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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" ];
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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 ];
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
|
69
nixos/modules/services/networking/scion/scion-control.nix
Normal file
69
nixos/modules/services/networking/scion/scion-control.nix
Normal 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";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
64
nixos/modules/services/networking/scion/scion-daemon.nix
Normal file
64
nixos/modules/services/networking/scion/scion-daemon.nix
Normal 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";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
74
nixos/modules/services/networking/scion/scion-dispatcher.nix
Normal file
74
nixos/modules/services/networking/scion/scion-dispatcher.nix
Normal 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";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
49
nixos/modules/services/networking/scion/scion-router.nix
Normal file
49
nixos/modules/services/networking/scion/scion-router.nix
Normal 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";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
39
nixos/modules/services/networking/scion/scion.nix
Normal file
39
nixos/modules/services/networking/scion/scion.nix
Normal 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
|
||||
'';
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
@ -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".
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 frontend’s 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 re‐use 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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
|
@ -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;
|
32
nixos/modules/services/web-apps/davis.md
Normal file
32
nixos/modules/services/web-apps/davis.md
Normal 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`.
|
554
nixos/modules/services/web-apps/davis.nix
Normal file
554
nixos/modules/services/web-apps/davis.nix
Normal 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;
|
||||
};
|
||||
}
|
|
@ -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";
|
||||
};
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 ''
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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";
|
||||
};
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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` & `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 {
|
||||
|
|
113
nixos/modules/services/web-apps/ocis.md
Normal file
113
nixos/modules/services/web-apps/ocis.md
Normal 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
|
201
nixos/modules/services/web-apps/ocis.nix
Normal file
201
nixos/modules/services/web-apps/ocis.nix
Normal 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
|
||||
];
|
||||
}
|
|
@ -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 ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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" ];})
|
||||
];
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -11,7 +11,7 @@ $ openssl rand -base64 64
|
|||
```
|
||||
|
||||
After that, `plausible` can be deployed like this:
|
||||
```
|
||||
```nix
|
||||
{
|
||||
services.plausible = {
|
||||
enable = true;
|
||||
|
|
|
@ -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" { };
|
||||
|
||||
|
|
|
@ -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"
|
||||
];
|
||||
|
|
|
@ -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.";
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 you’d 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`, don’t 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}
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue