nixos/reposilite: init module (#381197)

Changed files
+501 -1
nixos
doc
manual
release-notes
modules
services
web-apps
tests
pkgs
by-name
re
reposilite
+2
nixos/doc/manual/release-notes/rl-2505.section.md
···
- [MaryTTS](https://github.com/marytts/marytts), an open-source, multilingual text-to-speech synthesis system written in pure Java. Available as [services.marytts](options.html#opt-services.marytts).
- [networking.modemmanager](options.html#opt-networking.modemmanager) has been split out of [networking.networkmanager](options.html#opt-networking.networkmanager). NetworkManager still enables ModemManager by default, but options exist now to run NetworkManager without ModemManager.
- [Routinator 3000](https://nlnetlabs.nl/projects/routing/routinator/), a full-featured RPKI Relying Party software package that runs as a service which periodically downloads and verifies RPKI data.
···
- [MaryTTS](https://github.com/marytts/marytts), an open-source, multilingual text-to-speech synthesis system written in pure Java. Available as [services.marytts](options.html#opt-services.marytts).
+
- [Reposilite](https://reposilite.com), a lightweight and easy-to-use repository manager for Maven-based artifacts in the JVM ecosystem. Available as [services.reposilite](options.html#opt-services.reposilite).
+
- [networking.modemmanager](options.html#opt-networking.modemmanager) has been split out of [networking.networkmanager](options.html#opt-networking.networkmanager). NetworkManager still enables ModemManager by default, but options exist now to run NetworkManager without ModemManager.
- [Routinator 3000](https://nlnetlabs.nl/projects/routing/routinator/), a full-featured RPKI Relying Party software package that runs as a service which periodically downloads and verifies RPKI data.
+1
nixos/modules/module-list.nix
···
./services/web-apps/pretix.nix
./services/web-apps/privatebin.nix
./services/web-apps/prosody-filer.nix
./services/web-apps/rimgo.nix
./services/web-apps/rutorrent.nix
./services/web-apps/screego.nix
···
./services/web-apps/pretix.nix
./services/web-apps/privatebin.nix
./services/web-apps/prosody-filer.nix
+
./services/web-apps/reposilite.nix
./services/web-apps/rimgo.nix
./services/web-apps/rutorrent.nix
./services/web-apps/screego.nix
+439
nixos/modules/services/web-apps/reposilite.nix
···
···
+
{
+
lib,
+
config,
+
pkgs,
+
...
+
}:
+
let
+
cfg = config.services.reposilite;
+
format = pkgs.formats.cdn { };
+
configFile = format.generate "reposilite.cdn" cfg.settings;
+
+
useEmbeddedDb = cfg.database.type == "sqlite" || cfg.database.type == "h2";
+
useMySQL = cfg.database.type == "mariadb" || cfg.database.type == "mysql";
+
usePostgres = cfg.database.type == "postgresql";
+
+
# db password is appended at runtime by the service script (if needed)
+
dbString =
+
if useEmbeddedDb then
+
"${cfg.database.type} ${cfg.database.path}"
+
else
+
"${cfg.database.type} ${cfg.database.host}:${builtins.toString cfg.database.port} ${cfg.database.dbname} ${cfg.database.user} $(<${cfg.database.passwordFile})";
+
+
certDir = config.security.acme.certs.${cfg.useACMEHost}.directory;
+
+
databaseModule = {
+
options = {
+
type = lib.mkOption {
+
type = lib.types.enum [
+
"h2"
+
"mariadb"
+
"mysql"
+
"postgresql"
+
"sqlite"
+
];
+
description = ''
+
Database engine to use.
+
'';
+
default = "sqlite";
+
};
+
+
path = lib.mkOption {
+
type = lib.types.str;
+
description = ''
+
Path to the embedded database file. Set to `--temporary` to use an in-memory database.
+
'';
+
default = "reposilite.db";
+
};
+
+
host = lib.mkOption {
+
type = lib.types.str;
+
description = ''
+
Database host address.
+
'';
+
default = "127.0.0.1";
+
};
+
+
port = lib.mkOption {
+
type = lib.types.port;
+
description = ''
+
Database TCP port.
+
'';
+
defaultText = lib.literalExpression ''
+
if type == "postgresql" then 5432 else 3306
+
'';
+
default = if usePostgres then config.services.postgresql.settings.port else 3306;
+
};
+
+
dbname = lib.mkOption {
+
type = lib.types.str;
+
description = ''
+
Database name.
+
'';
+
default = "reposilite";
+
};
+
+
user = lib.mkOption {
+
type = lib.types.str;
+
description = ''
+
Database user.
+
'';
+
default = "reposilite";
+
};
+
+
passwordFile = lib.mkOption {
+
type = lib.types.nullOr lib.types.path;
+
description = ''
+
Path to the file containing the password for the database connection.
+
This file must be readable by {option}`services.reposilite.user`.
+
'';
+
default = null;
+
};
+
};
+
};
+
+
settingsModule = {
+
freeformType = format.type;
+
options = {
+
hostname = lib.mkOption {
+
type = lib.types.str;
+
description = ''
+
The hostname to bind to. Set to `0.0.0.0` to accept connections from everywhere, or `127.0.0.1` to restrict to localhost."
+
'';
+
default = "0.0.0.0";
+
example = "127.0.0.1";
+
};
+
+
port = lib.mkOption {
+
type = lib.types.port;
+
description = ''
+
The TCP port to bind to.
+
'';
+
default = 3000;
+
};
+
+
database = lib.mkOption {
+
type = lib.types.nullOr lib.types.str;
+
description = ''
+
Database connection string. Please use {option}`services.reposilite.database` instead.
+
See https://reposilite.com/guide/general#local-configuration for valid values.
+
'';
+
default = null;
+
};
+
+
sslEnabled = lib.mkOption {
+
type = lib.types.bool;
+
description = ''
+
Whether to listen for encrypted connections on {option}`settings.sslPort`.
+
'';
+
default = false;
+
};
+
+
sslPort = lib.mkOption {
+
type = lib.types.port; # cant be null
+
description = "SSL port to bind to. SSL needs to be enabled explicitly via {option}`settings.enableSsl`.";
+
default = 443;
+
};
+
+
keyPath = lib.mkOption {
+
type = lib.types.nullOr lib.types.str;
+
description = ''
+
Path to the .jsk KeyStore or paths to the PKCS#8 certificate and private key, separated by a space (see example).
+
You can use `''${WORKING_DIRECTORY}` to refer to paths relative to Reposilite's working directory.
+
If you are using a Java KeyStore, don't forget to specify the password via the {var}`REPOSILITE_LOCAL_KEYPASSWORD` environment variable.
+
See https://reposilite.com/guide/ssl for more information on how to set SSL up.
+
'';
+
default = null;
+
example = "\${WORKING_DIRECTORY}/cert.pem \${WORKING_DIRECTORY}/key.pem";
+
};
+
+
keyPassword = lib.mkOption {
+
type = lib.types.nullOr lib.types.str;
+
description = ''
+
Plaintext password used to unlock the Java KeyStore set in {option}`services.reposilite.settings.keyPath`.
+
WARNING: this option is insecure and should not be used to store the password.
+
Consider using {option}`services.reposilite.keyPasswordFile` instead.
+
'';
+
default = null;
+
};
+
+
enforceSsl = lib.mkOption {
+
type = lib.types.bool;
+
description = ''
+
Whether to redirect all traffic to SSL.
+
'';
+
default = false;
+
};
+
+
webThreadPool = lib.mkOption {
+
type = lib.types.ints.between 5 65535;
+
description = ''
+
Maximum amount of threads used by the core thread pool. (min: 5)
+
The web thread pool handles the first few steps of incoming HTTP connections, tasks are redirected as soon as possible to the IO thread pool.
+
'';
+
default = 16;
+
};
+
+
ioThreadPool = lib.mkOption {
+
type = lib.types.ints.between 2 65535;
+
description = ''
+
The IO thread pool handles all tasks that may benefit from non-blocking IO. (min: 2)
+
Because most tasks are redirected to IO thread pool, it might be a good idea to keep it at least equal to web thread pool.
+
'';
+
default = 8;
+
};
+
+
databaseThreadPool = lib.mkOption {
+
type = lib.types.ints.positive;
+
description = ''
+
Maximum amount of concurrent connections to the database. (one per thread)
+
Embedded databases (sqlite, h2) do not support truly concurrent connections, so the value will always be `1` if they are used.
+
'';
+
default = 1;
+
};
+
+
compressionStrategy = lib.mkOption {
+
type = lib.types.enum [
+
"none"
+
"gzip"
+
];
+
description = ''
+
Compression algorithm used by this instance of Reposilite.
+
`none` reduces usage of CPU & memory, but requires transfering more data.
+
'';
+
default = "none";
+
};
+
+
idleTimeout = lib.mkOption {
+
type = lib.types.ints.unsigned;
+
description = ''
+
Default idle timeout used by Jetty.
+
'';
+
default = 30000;
+
};
+
+
bypassExternalCache = lib.mkOption {
+
type = lib.types.bool;
+
description = ''
+
Add cache bypass headers to responses from /api/* to avoid issues with proxies such as Cloudflare.
+
'';
+
default = true;
+
};
+
+
cachedLogSize = lib.mkOption {
+
type = lib.types.ints.unsigned;
+
description = ''
+
Amount of messages stored in the cache logger.
+
'';
+
default = 50;
+
};
+
+
defaultFrontend = lib.mkOption {
+
type = lib.types.bool;
+
description = ''
+
Whether to enable the default included frontend with a dashboard.
+
'';
+
default = true;
+
};
+
+
basePath = lib.mkOption {
+
type = lib.types.str;
+
description = ''
+
Custom base path for this Reposilite instance.
+
It is not recommended changing this, you should instead prioritize using a different subdomain.
+
'';
+
default = "/";
+
};
+
+
debugEnabled = lib.mkOption {
+
type = lib.types.bool;
+
description = ''
+
Whether to enable debug mode.
+
'';
+
default = false;
+
};
+
};
+
};
+
in
+
{
+
options.services.reposilite = {
+
enable = lib.mkEnableOption "Reposilite";
+
package = lib.mkPackageOption pkgs "reposilite" { } // {
+
apply =
+
pkg:
+
pkg.override (old: {
+
plugins = (old.plugins or [ ]) ++ cfg.plugins;
+
});
+
};
+
+
plugins = lib.mkOption {
+
type = lib.types.listOf lib.types.package;
+
description = ''
+
List of plugins to add to Reposilite.
+
'';
+
default = [ ];
+
example = "with reposilitePlugins; [ checksum groovy ]";
+
};
+
+
database = lib.mkOption {
+
description = "Database options.";
+
default = { };
+
type = lib.types.submodule databaseModule;
+
};
+
+
keyPasswordFile = lib.mkOption {
+
type = lib.types.nullOr lib.types.path;
+
description = ''
+
Path the the file containing the password used to unlock the Java KeyStore file specified in {option}`services.reposilite.settings.keyPath`.
+
This file must be readable my {option}`services.reposilite.user`.
+
'';
+
default = null;
+
};
+
+
useACMEHost = lib.mkOption {
+
type = lib.types.nullOr lib.types.str;
+
description = ''
+
Host of an existing Let's Encrypt certificate to use for SSL.
+
Make sure that the certificate directory is readable by the `reposilite` user or group, for example via {option}`security.acme.certs.<cert>.group`.
+
*Note that this option does not create any certificates, nor it does add subdomains to existing ones – you will need to create them manually using {option}`security.acme.certs`*
+
'';
+
default = null;
+
};
+
+
settings = lib.mkOption {
+
description = "Configuration written to the reposilite.cdn file";
+
default = { };
+
type = lib.types.submodule settingsModule;
+
};
+
+
workingDirectory = lib.mkOption {
+
type = lib.types.path;
+
description = ''
+
Working directory for Reposilite.
+
'';
+
default = "/var/lib/reposilite";
+
};
+
+
extraArgs = lib.mkOption {
+
type = lib.types.listOf lib.types.str;
+
description = ''
+
Extra arguments/parameters passed to the Reposilite. Can be used for first token generation.
+
'';
+
default = [ ];
+
example = lib.literalExpression ''[ "--token" "name:tempsecrettoken" ]'';
+
};
+
+
user = lib.mkOption {
+
type = lib.types.str;
+
description = ''
+
The user to run Reposilite under.
+
'';
+
default = "reposilite";
+
};
+
+
group = lib.mkOption {
+
type = lib.types.str;
+
description = ''
+
The group to run Reposilite under.
+
'';
+
default = "reposilite";
+
};
+
+
openFirewall = lib.mkOption {
+
type = lib.types.bool;
+
description = ''
+
Whether to open the firewall ports for Reposilite. If SSL is enabled, its port will be opened too.
+
'';
+
default = false;
+
};
+
};
+
+
config = lib.mkIf cfg.enable {
+
assertions = [
+
{
+
assertion = cfg.settings.sslEnabled -> cfg.settings.keyPath != null;
+
message = ''
+
Reposilite was configured to enable SSL, but no valid paths to certificate files were provided via `settings.keyPath`.
+
Read more about SSL certificates here: https://reposilite.com/guide/ssl
+
'';
+
}
+
{
+
assertion = cfg.settings.enforceSsl -> cfg.settings.sslEnabled;
+
message = "You cannot enforce SSL if SSL is not enabled.";
+
}
+
{
+
assertion = !useEmbeddedDb -> cfg.database.passwordFile != null;
+
message = "You need to set `services.reposilite.database.passwordFile` when using MySQL or Postgres.";
+
}
+
];
+
+
services.reposilite.settings.keyPath = lib.mkIf (
+
cfg.useACMEHost != null
+
) "${certDir}/fullchain.pem ${certDir}/key.pem";
+
+
environment.systemPackages = [ cfg.package ];
+
+
users = {
+
groups.${cfg.group} = lib.mkIf (cfg.group == "reposilite") { };
+
users.${cfg.user} = lib.mkIf (cfg.user == "reposilite") {
+
isSystemUser = true;
+
group = cfg.group;
+
};
+
};
+
+
networking.firewall = lib.mkIf cfg.openFirewall (
+
lib.mkMerge [
+
{
+
allowedTCPPorts = [ cfg.settings.port ];
+
}
+
(lib.mkIf cfg.settings.sslEnabled {
+
allowedTCPPorts = [ cfg.settings.sslPort ];
+
})
+
]
+
);
+
+
systemd.services.reposilite = {
+
enable = true;
+
wantedBy = [ "multi-user.target" ];
+
after =
+
[ "network.target" ]
+
++ (lib.optional useMySQL "mysql.service")
+
++ (lib.optional usePostgres "postgresql.service");
+
+
script =
+
lib.optionalString (cfg.keyPasswordFile != null && cfg.settings.keyPassword == null) ''
+
export REPOSILITE_LOCAL_KEYPASSWORD="$(<${cfg.keyPasswordFile})"
+
''
+
+ ''
+
export REPOSILITE_LOCAL_DATABASE="${dbString}"
+
+
${lib.getExe cfg.package} --local-configuration ${configFile} --local-configuration-mode none --working-directory ${cfg.workingDirectory} ${lib.escapeShellArgs cfg.extraArgs}
+
'';
+
+
serviceConfig = lib.mkMerge [
+
(lib.mkIf (builtins.dirOf cfg.workingDirectory == "/var/lib") {
+
StateDirectory = builtins.baseNameOf cfg.workingDirectory;
+
StateDirectoryMode = "700";
+
})
+
{
+
Type = "exec";
+
Restart = "on-failure";
+
+
User = cfg.user;
+
Group = cfg.group;
+
WorkingDirectory = cfg.workingDirectory;
+
+
# TODO better hardening
+
LimitNOFILE = "1048576";
+
PrivateTmp = true;
+
PrivateDevices = true;
+
ProtectHome = true;
+
ProtectSystem = "strict";
+
AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+
}
+
];
+
};
+
};
+
+
meta.maintainers = [ lib.maintainers.uku3lig ];
+
}
+1
nixos/tests/all-tests.nix
···
redmine = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./redmine.nix { };
renovate = handleTest ./renovate.nix { };
replace-dependencies = handleTest ./replace-dependencies { };
restartByActivationScript = handleTest ./restart-by-activation-script.nix { };
restic-rest-server = handleTest ./restic-rest-server.nix { };
restic = handleTest ./restic.nix { };
···
redmine = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./redmine.nix { };
renovate = handleTest ./renovate.nix { };
replace-dependencies = handleTest ./replace-dependencies { };
+
reposilite = runTest ./reposilite.nix;
restartByActivationScript = handleTest ./restart-by-activation-script.nix { };
restic-rest-server = handleTest ./restic-rest-server.nix { };
restic = handleTest ./restic.nix { };
+53
nixos/tests/reposilite.nix
···
···
+
{ lib, ... }:
+
{
+
name = "reposilite";
+
+
nodes = {
+
machine =
+
{ pkgs, ... }:
+
{
+
services = {
+
mysql = {
+
enable = true;
+
package = pkgs.mariadb;
+
ensureDatabases = [ "reposilite" ];
+
initialScript = pkgs.writeText "reposilite-test-db-init" ''
+
CREATE USER 'reposilite'@'localhost' IDENTIFIED BY 'ReposiliteDBPass';
+
GRANT ALL PRIVILEGES ON reposilite.* TO 'reposilite'@'localhost';
+
FLUSH PRIVILEGES;
+
'';
+
};
+
+
reposilite = {
+
enable = true;
+
plugins = with pkgs.reposilitePlugins; [
+
checksum
+
groovy
+
];
+
extraArgs = [
+
"--token"
+
"test:SuperSecretTestToken"
+
];
+
database = {
+
type = "mariadb";
+
passwordFile = "/run/reposiliteDbPass";
+
};
+
settings.port = 8080;
+
};
+
};
+
};
+
};
+
+
testScript = ''
+
machine.start()
+
+
machine.execute("echo \"ReposiliteDBPass\" > /run/reposiliteDbPass && chmod 600 /run/reposiliteDbPass && chown reposilite:reposilite /run/reposiliteDbPass")
+
machine.wait_for_unit("reposilite.service")
+
machine.wait_for_open_port(8080)
+
+
machine.fail("curl -Sf localhost:8080/api/auth/me")
+
machine.succeed("curl -Sfu test:SuperSecretTestToken localhost:8080/api/auth/me")
+
'';
+
+
meta.maintainers = [ lib.maintainers.uku3lig ];
+
}
+5 -1
pkgs/by-name/re/reposilite/package.nix
···
jre_headless,
linkFarm,
makeWrapper,
plugins ? [ ],
}:
let
···
runHook postInstall
'';
-
passthru.updateScript = ./update.sh;
meta = {
description = "Lightweight and easy-to-use repository management software dedicated for the Maven based artifacts in the JVM ecosystem";
···
jre_headless,
linkFarm,
makeWrapper,
+
nixosTests,
plugins ? [ ],
}:
let
···
runHook postInstall
'';
+
passthru = {
+
tests = nixosTests.reposilite;
+
updateScript = ./update.sh;
+
};
meta = {
description = "Lightweight and easy-to-use repository management software dedicated for the Maven based artifacts in the JVM ecosystem";