mirror of
https://github.com/NixOS/nixpkgs.git
synced 2024-12-04 12:53:05 +00:00
3893d6c6d7
The comment at the top of git-and-tools/default.nix said:
/* All git-relates tools live here, in a separate attribute set so that users
* can get a fast overview over what's available.
but unfortunately that hasn't actually held up in practice.
Git-related packages have continued to be added to the top level, or
into gitAndTools, or sometimes both, basically at random, so having
gitAndTools is just confusing. In fact, until I looked as part of
working on getting rid of gitAndTools, one program (ydiff) was
packaged twice independently, once in gitAndTools and once at the top
level (I fixed this in 98c3490196
).
So I think it's for the best if we move away from gitAndTools, and
just put all the packages it previously contained at the top level.
I've implemented this here by just making gitAndTools an alias for the
top level -- this saves having loads of lines in aliases.nix. This
means that people can keep referring to gitAndTools in their
configuration, but it won't be allowed to be used within Nixpkgs, and
it won't be presented to new users by e.g. nix search.
The only other change here that I'm aware of is that
appendToName "minimal" is not longer called on the default git
package, because doing that would have necessitated having a private
gitBase variable like before. I think it makes more sense not to do
that anyway, and reserve the "minimal" suffix only for gitMinimal.
610 lines
19 KiB
Nix
610 lines
19 KiB
Nix
{ config, lib, pkgs, ... }:
|
|
|
|
with lib;
|
|
|
|
let
|
|
cfg = config.services.gitea;
|
|
gitea = cfg.package;
|
|
pg = config.services.postgresql;
|
|
useMysql = cfg.database.type == "mysql";
|
|
usePostgresql = cfg.database.type == "postgres";
|
|
useSqlite = cfg.database.type == "sqlite3";
|
|
configFile = pkgs.writeText "app.ini" ''
|
|
APP_NAME = ${cfg.appName}
|
|
RUN_USER = ${cfg.user}
|
|
RUN_MODE = prod
|
|
|
|
${generators.toINI {} cfg.settings}
|
|
|
|
${optionalString (cfg.extraConfig != null) cfg.extraConfig}
|
|
'';
|
|
in
|
|
|
|
{
|
|
options = {
|
|
services.gitea = {
|
|
enable = mkOption {
|
|
default = false;
|
|
type = types.bool;
|
|
description = "Enable Gitea Service.";
|
|
};
|
|
|
|
package = mkOption {
|
|
default = pkgs.gitea;
|
|
type = types.package;
|
|
defaultText = "pkgs.gitea";
|
|
description = "gitea derivation to use";
|
|
};
|
|
|
|
useWizard = mkOption {
|
|
default = false;
|
|
type = types.bool;
|
|
description = "Do not generate a configuration and use gitea' installation wizard instead. The first registered user will be administrator.";
|
|
};
|
|
|
|
stateDir = mkOption {
|
|
default = "/var/lib/gitea";
|
|
type = types.str;
|
|
description = "gitea data directory.";
|
|
};
|
|
|
|
log = {
|
|
rootPath = mkOption {
|
|
default = "${cfg.stateDir}/log";
|
|
type = types.str;
|
|
description = "Root path for log files.";
|
|
};
|
|
level = mkOption {
|
|
default = "Trace";
|
|
type = types.enum [ "Trace" "Debug" "Info" "Warn" "Error" "Critical" ];
|
|
description = "General log level.";
|
|
};
|
|
};
|
|
|
|
user = mkOption {
|
|
type = types.str;
|
|
default = "gitea";
|
|
description = "User account under which gitea runs.";
|
|
};
|
|
|
|
database = {
|
|
type = mkOption {
|
|
type = types.enum [ "sqlite3" "mysql" "postgres" ];
|
|
example = "mysql";
|
|
default = "sqlite3";
|
|
description = "Database engine to use.";
|
|
};
|
|
|
|
host = mkOption {
|
|
type = types.str;
|
|
default = "127.0.0.1";
|
|
description = "Database host address.";
|
|
};
|
|
|
|
port = mkOption {
|
|
type = types.int;
|
|
default = (if !usePostgresql then 3306 else pg.port);
|
|
description = "Database host port.";
|
|
};
|
|
|
|
name = mkOption {
|
|
type = types.str;
|
|
default = "gitea";
|
|
description = "Database name.";
|
|
};
|
|
|
|
user = mkOption {
|
|
type = types.str;
|
|
default = "gitea";
|
|
description = "Database user.";
|
|
};
|
|
|
|
password = mkOption {
|
|
type = types.str;
|
|
default = "";
|
|
description = ''
|
|
The password corresponding to <option>database.user</option>.
|
|
Warning: this is stored in cleartext in the Nix store!
|
|
Use <option>database.passwordFile</option> instead.
|
|
'';
|
|
};
|
|
|
|
passwordFile = mkOption {
|
|
type = types.nullOr types.path;
|
|
default = null;
|
|
example = "/run/keys/gitea-dbpassword";
|
|
description = ''
|
|
A file containing the password corresponding to
|
|
<option>database.user</option>.
|
|
'';
|
|
};
|
|
|
|
socket = mkOption {
|
|
type = types.nullOr types.path;
|
|
default = if (cfg.database.createDatabase && usePostgresql) then "/run/postgresql" else if (cfg.database.createDatabase && useMysql) then "/run/mysqld/mysqld.sock" else null;
|
|
defaultText = "null";
|
|
example = "/run/mysqld/mysqld.sock";
|
|
description = "Path to the unix socket file to use for authentication.";
|
|
};
|
|
|
|
path = mkOption {
|
|
type = types.str;
|
|
default = "${cfg.stateDir}/data/gitea.db";
|
|
description = "Path to the sqlite3 database file.";
|
|
};
|
|
|
|
createDatabase = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Whether to create a local database automatically.";
|
|
};
|
|
};
|
|
|
|
dump = {
|
|
enable = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
Enable a timer that runs gitea dump to generate backup-files of the
|
|
current gitea database and repositories.
|
|
'';
|
|
};
|
|
|
|
interval = mkOption {
|
|
type = types.str;
|
|
default = "04:31";
|
|
example = "hourly";
|
|
description = ''
|
|
Run a gitea dump at this interval. Runs by default at 04:31 every day.
|
|
|
|
The format is described in
|
|
<citerefentry><refentrytitle>systemd.time</refentrytitle>
|
|
<manvolnum>7</manvolnum></citerefentry>.
|
|
'';
|
|
};
|
|
|
|
backupDir = mkOption {
|
|
type = types.str;
|
|
default = "${cfg.stateDir}/dump";
|
|
description = "Path to the dump files.";
|
|
};
|
|
};
|
|
|
|
ssh = {
|
|
enable = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Enable external SSH feature.";
|
|
};
|
|
|
|
clonePort = mkOption {
|
|
type = types.int;
|
|
default = 22;
|
|
example = 2222;
|
|
description = ''
|
|
SSH port displayed in clone URL.
|
|
The option is required to configure a service when the external visible port
|
|
differs from the local listening port i.e. if port forwarding is used.
|
|
'';
|
|
};
|
|
};
|
|
|
|
lfs = {
|
|
enable = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = "Enables git-lfs support.";
|
|
};
|
|
|
|
contentDir = mkOption {
|
|
type = types.str;
|
|
default = "${cfg.stateDir}/data/lfs";
|
|
description = "Where to store LFS files.";
|
|
};
|
|
};
|
|
|
|
appName = mkOption {
|
|
type = types.str;
|
|
default = "gitea: Gitea Service";
|
|
description = "Application name.";
|
|
};
|
|
|
|
repositoryRoot = mkOption {
|
|
type = types.str;
|
|
default = "${cfg.stateDir}/repositories";
|
|
description = "Path to the git repositories.";
|
|
};
|
|
|
|
domain = mkOption {
|
|
type = types.str;
|
|
default = "localhost";
|
|
description = "Domain name of your server.";
|
|
};
|
|
|
|
rootUrl = mkOption {
|
|
type = types.str;
|
|
default = "http://localhost:3000/";
|
|
description = "Full public URL of gitea server.";
|
|
};
|
|
|
|
httpAddress = mkOption {
|
|
type = types.str;
|
|
default = "0.0.0.0";
|
|
description = "HTTP listen address.";
|
|
};
|
|
|
|
httpPort = mkOption {
|
|
type = types.int;
|
|
default = 3000;
|
|
description = "HTTP listen port.";
|
|
};
|
|
|
|
enableUnixSocket = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = "Configure Gitea to listen on a unix socket instead of the default TCP port.";
|
|
};
|
|
|
|
cookieSecure = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
Marks session cookies as "secure" as a hint for browsers to only send
|
|
them via HTTPS. This option is recommend, if gitea is being served over HTTPS.
|
|
'';
|
|
};
|
|
|
|
staticRootPath = mkOption {
|
|
type = types.str;
|
|
default = "${gitea.data}";
|
|
example = "/var/lib/gitea/data";
|
|
description = "Upper level of template and static files path.";
|
|
};
|
|
|
|
mailerPasswordFile = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
example = "/var/lib/secrets/gitea/mailpw";
|
|
description = "Path to a file containing the SMTP password.";
|
|
};
|
|
|
|
disableRegistration = mkEnableOption "the registration lock" // {
|
|
description = ''
|
|
By default any user can create an account on this <literal>gitea</literal> instance.
|
|
This can be disabled by using this option.
|
|
|
|
<emphasis>Note:</emphasis> please keep in mind that this should be added after the initial
|
|
deploy unless <link linkend="opt-services.gitea.useWizard">services.gitea.useWizard</link>
|
|
is <literal>true</literal> as the first registered user will be the administrator if
|
|
no install wizard is used.
|
|
'';
|
|
};
|
|
|
|
settings = mkOption {
|
|
type = with types; attrsOf (attrsOf (oneOf [ bool int str ]));
|
|
default = {};
|
|
description = ''
|
|
Gitea configuration. Refer to <link xlink:href="https://docs.gitea.io/en-us/config-cheat-sheet/"/>
|
|
for details on supported values.
|
|
'';
|
|
example = literalExample ''
|
|
{
|
|
"cron.sync_external_users" = {
|
|
RUN_AT_START = true;
|
|
SCHEDULE = "@every 24h";
|
|
UPDATE_EXISTING = true;
|
|
};
|
|
mailer = {
|
|
ENABLED = true;
|
|
MAILER_TYPE = "sendmail";
|
|
FROM = "do-not-reply@example.org";
|
|
SENDMAIL_PATH = "${pkgs.system-sendmail}/bin/sendmail";
|
|
};
|
|
other = {
|
|
SHOW_FOOTER_VERSION = false;
|
|
};
|
|
}
|
|
'';
|
|
};
|
|
|
|
extraConfig = mkOption {
|
|
type = with types; nullOr str;
|
|
default = null;
|
|
description = "Configuration lines appended to the generated gitea configuration file.";
|
|
};
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
assertions = [
|
|
{ assertion = cfg.database.createDatabase -> cfg.database.user == cfg.user;
|
|
message = "services.gitea.database.user must match services.gitea.user if the database is to be automatically provisioned";
|
|
}
|
|
];
|
|
|
|
services.gitea.settings = {
|
|
database = mkMerge [
|
|
{
|
|
DB_TYPE = cfg.database.type;
|
|
}
|
|
(mkIf (useMysql || usePostgresql) {
|
|
HOST = if cfg.database.socket != null then cfg.database.socket else cfg.database.host + ":" + toString cfg.database.port;
|
|
NAME = cfg.database.name;
|
|
USER = cfg.database.user;
|
|
PASSWD = "#dbpass#";
|
|
})
|
|
(mkIf useSqlite {
|
|
PATH = cfg.database.path;
|
|
})
|
|
(mkIf usePostgresql {
|
|
SSL_MODE = "disable";
|
|
})
|
|
];
|
|
|
|
repository = {
|
|
ROOT = cfg.repositoryRoot;
|
|
};
|
|
|
|
server = mkMerge [
|
|
{
|
|
DOMAIN = cfg.domain;
|
|
STATIC_ROOT_PATH = cfg.staticRootPath;
|
|
LFS_JWT_SECRET = "#jwtsecret#";
|
|
ROOT_URL = cfg.rootUrl;
|
|
}
|
|
(mkIf cfg.enableUnixSocket {
|
|
PROTOCOL = "unix";
|
|
HTTP_ADDR = "/run/gitea/gitea.sock";
|
|
})
|
|
(mkIf (!cfg.enableUnixSocket) {
|
|
HTTP_ADDR = cfg.httpAddress;
|
|
HTTP_PORT = cfg.httpPort;
|
|
})
|
|
(mkIf cfg.ssh.enable {
|
|
DISABLE_SSH = false;
|
|
SSH_PORT = cfg.ssh.clonePort;
|
|
})
|
|
(mkIf (!cfg.ssh.enable) {
|
|
DISABLE_SSH = true;
|
|
})
|
|
(mkIf cfg.lfs.enable {
|
|
LFS_START_SERVER = true;
|
|
LFS_CONTENT_PATH = cfg.lfs.contentDir;
|
|
})
|
|
|
|
];
|
|
|
|
session = {
|
|
COOKIE_NAME = "session";
|
|
COOKIE_SECURE = cfg.cookieSecure;
|
|
};
|
|
|
|
security = {
|
|
SECRET_KEY = "#secretkey#";
|
|
INSTALL_LOCK = true;
|
|
};
|
|
|
|
log = {
|
|
ROOT_PATH = cfg.log.rootPath;
|
|
LEVEL = cfg.log.level;
|
|
};
|
|
|
|
service = {
|
|
DISABLE_REGISTRATION = cfg.disableRegistration;
|
|
};
|
|
|
|
mailer = mkIf (cfg.mailerPasswordFile != null) {
|
|
PASSWD = "#mailerpass#";
|
|
};
|
|
};
|
|
|
|
services.postgresql = optionalAttrs (usePostgresql && cfg.database.createDatabase) {
|
|
enable = mkDefault true;
|
|
|
|
ensureDatabases = [ cfg.database.name ];
|
|
ensureUsers = [
|
|
{ name = cfg.database.user;
|
|
ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
|
|
}
|
|
];
|
|
};
|
|
|
|
services.mysql = optionalAttrs (useMysql && cfg.database.createDatabase) {
|
|
enable = mkDefault true;
|
|
package = mkDefault pkgs.mariadb;
|
|
|
|
ensureDatabases = [ cfg.database.name ];
|
|
ensureUsers = [
|
|
{ name = cfg.database.user;
|
|
ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
|
|
}
|
|
];
|
|
};
|
|
|
|
systemd.tmpfiles.rules = [
|
|
"d '${cfg.dump.backupDir}' 0750 ${cfg.user} gitea - -"
|
|
"z '${cfg.dump.backupDir}' 0750 ${cfg.user} gitea - -"
|
|
"Z '${cfg.dump.backupDir}' - ${cfg.user} gitea - -"
|
|
"d '${cfg.lfs.contentDir}' 0750 ${cfg.user} gitea - -"
|
|
"z '${cfg.lfs.contentDir}' 0750 ${cfg.user} gitea - -"
|
|
"Z '${cfg.lfs.contentDir}' - ${cfg.user} gitea - -"
|
|
"d '${cfg.repositoryRoot}' 0750 ${cfg.user} gitea - -"
|
|
"z '${cfg.repositoryRoot}' 0750 ${cfg.user} gitea - -"
|
|
"Z '${cfg.repositoryRoot}' - ${cfg.user} gitea - -"
|
|
"d '${cfg.stateDir}' 0750 ${cfg.user} gitea - -"
|
|
"d '${cfg.stateDir}/conf' 0750 ${cfg.user} gitea - -"
|
|
"d '${cfg.stateDir}/custom' 0750 ${cfg.user} gitea - -"
|
|
"d '${cfg.stateDir}/custom/conf' 0750 ${cfg.user} gitea - -"
|
|
"d '${cfg.stateDir}/log' 0750 ${cfg.user} gitea - -"
|
|
"z '${cfg.stateDir}' 0750 ${cfg.user} gitea - -"
|
|
"z '${cfg.stateDir}/.ssh' 0700 ${cfg.user} gitea - -"
|
|
"z '${cfg.stateDir}/conf' 0750 ${cfg.user} gitea - -"
|
|
"z '${cfg.stateDir}/custom' 0750 ${cfg.user} gitea - -"
|
|
"z '${cfg.stateDir}/custom/conf' 0750 ${cfg.user} gitea - -"
|
|
"z '${cfg.stateDir}/log' 0750 ${cfg.user} gitea - -"
|
|
"Z '${cfg.stateDir}' - ${cfg.user} gitea - -"
|
|
|
|
# If we have a folder or symlink with gitea locales, remove it
|
|
# And symlink the current gitea locales in place
|
|
"L+ '${cfg.stateDir}/conf/locale' - - - - ${gitea.out}/locale"
|
|
];
|
|
|
|
systemd.services.gitea = {
|
|
description = "gitea";
|
|
after = [ "network.target" ] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service";
|
|
wantedBy = [ "multi-user.target" ];
|
|
path = [ gitea pkgs.git ];
|
|
|
|
preStart = let
|
|
runConfig = "${cfg.stateDir}/custom/conf/app.ini";
|
|
secretKey = "${cfg.stateDir}/custom/conf/secret_key";
|
|
jwtSecret = "${cfg.stateDir}/custom/conf/jwt_secret";
|
|
in ''
|
|
# copy custom configuration and generate a random secret key if needed
|
|
${optionalString (cfg.useWizard == false) ''
|
|
cp -f ${configFile} ${runConfig}
|
|
|
|
if [ ! -e ${secretKey} ]; then
|
|
${gitea}/bin/gitea generate secret SECRET_KEY > ${secretKey}
|
|
fi
|
|
|
|
if [ ! -e ${jwtSecret} ]; then
|
|
${gitea}/bin/gitea generate secret LFS_JWT_SECRET > ${jwtSecret}
|
|
fi
|
|
|
|
KEY="$(head -n1 ${secretKey})"
|
|
DBPASS="$(head -n1 ${cfg.database.passwordFile})"
|
|
JWTSECRET="$(head -n1 ${jwtSecret})"
|
|
${if (cfg.mailerPasswordFile == null) then ''
|
|
MAILERPASSWORD="#mailerpass#"
|
|
'' else ''
|
|
MAILERPASSWORD="$(head -n1 ${cfg.mailerPasswordFile} || :)"
|
|
''}
|
|
sed -e "s,#secretkey#,$KEY,g" \
|
|
-e "s,#dbpass#,$DBPASS,g" \
|
|
-e "s,#jwtsecret#,$JWTSECRET,g" \
|
|
-e "s,#mailerpass#,$MAILERPASSWORD,g" \
|
|
-i ${runConfig}
|
|
chmod 640 ${runConfig} ${secretKey} ${jwtSecret}
|
|
''}
|
|
|
|
# update all hooks' binary paths
|
|
HOOKS=$(find ${cfg.repositoryRoot} -mindepth 4 -maxdepth 6 -type f -wholename "*git/hooks/*")
|
|
if [ "$HOOKS" ]
|
|
then
|
|
sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gitea,${gitea}/bin/gitea,g' $HOOKS
|
|
sed -ri 's,/nix/store/[a-z0-9.-]+/bin/env,${pkgs.coreutils}/bin/env,g' $HOOKS
|
|
sed -ri 's,/nix/store/[a-z0-9.-]+/bin/bash,${pkgs.bash}/bin/bash,g' $HOOKS
|
|
sed -ri 's,/nix/store/[a-z0-9.-]+/bin/perl,${pkgs.perl}/bin/perl,g' $HOOKS
|
|
fi
|
|
|
|
# update command option in authorized_keys
|
|
if [ -r ${cfg.stateDir}/.ssh/authorized_keys ]
|
|
then
|
|
sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gitea,${gitea}/bin/gitea,g' ${cfg.stateDir}/.ssh/authorized_keys
|
|
fi
|
|
'';
|
|
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
User = cfg.user;
|
|
Group = "gitea";
|
|
WorkingDirectory = cfg.stateDir;
|
|
ExecStart = "${gitea}/bin/gitea web --pid /run/gitea/gitea.pid";
|
|
Restart = "always";
|
|
# Runtime directory and mode
|
|
RuntimeDirectory = "gitea";
|
|
RuntimeDirectoryMode = "0755";
|
|
# Access write directories
|
|
ReadWritePaths = [ cfg.dump.backupDir cfg.repositoryRoot cfg.stateDir cfg.lfs.contentDir ];
|
|
UMask = "0027";
|
|
# Capabilities
|
|
CapabilityBoundingSet = "";
|
|
# Security
|
|
NoNewPrivileges = true;
|
|
# Sandboxing
|
|
ProtectSystem = "strict";
|
|
ProtectHome = true;
|
|
PrivateTmp = true;
|
|
PrivateDevices = true;
|
|
PrivateUsers = true;
|
|
ProtectHostname = true;
|
|
ProtectClock = true;
|
|
ProtectKernelTunables = true;
|
|
ProtectKernelModules = true;
|
|
ProtectKernelLogs = true;
|
|
ProtectControlGroups = true;
|
|
RestrictAddressFamilies = [ "AF_UNIX AF_INET AF_INET6" ];
|
|
LockPersonality = true;
|
|
MemoryDenyWriteExecute = true;
|
|
RestrictRealtime = true;
|
|
RestrictSUIDSGID = true;
|
|
PrivateMounts = true;
|
|
# System Call Filtering
|
|
SystemCallArchitectures = "native";
|
|
SystemCallFilter = "~@clock @cpu-emulation @debug @keyring @memlock @module @mount @obsolete @raw-io @reboot @resources @setuid @swap";
|
|
};
|
|
|
|
environment = {
|
|
USER = cfg.user;
|
|
HOME = cfg.stateDir;
|
|
GITEA_WORK_DIR = cfg.stateDir;
|
|
};
|
|
};
|
|
|
|
users.users = mkIf (cfg.user == "gitea") {
|
|
gitea = {
|
|
description = "Gitea Service";
|
|
home = cfg.stateDir;
|
|
useDefaultShell = true;
|
|
group = "gitea";
|
|
isSystemUser = true;
|
|
};
|
|
};
|
|
|
|
users.groups.gitea = {};
|
|
|
|
warnings =
|
|
optional (cfg.database.password != "") ''
|
|
config.services.gitea.database.password will be stored as plaintext in the Nix store. Use database.passwordFile instead.'' ++
|
|
optional (cfg.extraConfig != null) ''
|
|
services.gitea.`extraConfig` is deprecated, please use services.gitea.`settings`.
|
|
'';
|
|
|
|
# Create database passwordFile default when password is configured.
|
|
services.gitea.database.passwordFile =
|
|
(mkDefault (toString (pkgs.writeTextFile {
|
|
name = "gitea-database-password";
|
|
text = cfg.database.password;
|
|
})));
|
|
|
|
systemd.services.gitea-dump = mkIf cfg.dump.enable {
|
|
description = "gitea dump";
|
|
after = [ "gitea.service" ];
|
|
wantedBy = [ "default.target" ];
|
|
path = [ gitea ];
|
|
|
|
environment = {
|
|
USER = cfg.user;
|
|
HOME = cfg.stateDir;
|
|
GITEA_WORK_DIR = cfg.stateDir;
|
|
};
|
|
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
User = cfg.user;
|
|
ExecStart = "${gitea}/bin/gitea dump";
|
|
WorkingDirectory = cfg.dump.backupDir;
|
|
};
|
|
};
|
|
|
|
systemd.timers.gitea-dump = mkIf cfg.dump.enable {
|
|
description = "Update timer for gitea-dump";
|
|
partOf = [ "gitea-dump.service" ];
|
|
wantedBy = [ "timers.target" ];
|
|
timerConfig.OnCalendar = cfg.dump.interval;
|
|
};
|
|
};
|
|
meta.maintainers = with lib.maintainers; [ srhb ma27 ];
|
|
}
|