820 lines
25 KiB
Nix
820 lines
25 KiB
Nix
{
|
|
config,
|
|
pkgs,
|
|
lib,
|
|
...
|
|
}:
|
|
let
|
|
cfg = config.services.ncps;
|
|
|
|
logLevels = [
|
|
"trace"
|
|
"debug"
|
|
"info"
|
|
"warn"
|
|
"error"
|
|
"fatal"
|
|
"panic"
|
|
];
|
|
|
|
ncpsWrapper = pkgs.writeShellScript "ncps-wrapper" ''
|
|
${lib.optionalString (cfg.cache.secretKeyPath != null) ''
|
|
export CACHE_SECRET_KEY_PATH="$CREDENTIALS_DIRECTORY/secretKey"
|
|
''}
|
|
|
|
${lib.optionalString (cfg.cache.storage.s3 != null) ''
|
|
export CACHE_STORAGE_S3_ACCESS_KEY_ID="$(cat "$CREDENTIALS_DIRECTORY/s3AccessKeyId")"
|
|
export CACHE_STORAGE_S3_SECRET_ACCESS_KEY="$(cat "$CREDENTIALS_DIRECTORY/s3SecretAccessKey")"
|
|
''}
|
|
|
|
${lib.optionalString (cfg.cache.redis != null) (
|
|
if cfg.cache.redis.passwordFile != null then
|
|
''export CACHE_REDIS_PASSWORD="$(cat "$CREDENTIALS_DIRECTORY/redisPassword")"''
|
|
else if cfg.cache.redis.password != null then
|
|
''export CACHE_REDIS_PASSWORD="${cfg.cache.redis.password}"''
|
|
else
|
|
""
|
|
)}
|
|
|
|
${lib.optionalString (cfg.cache.databaseURLFile != null) ''
|
|
export CACHE_DATABASE_URL="$(cat "$CREDENTIALS_DIRECTORY/databaseURL")"
|
|
''}
|
|
|
|
exec ${lib.getExe cfg.package} --config "${configFile}" "$@"
|
|
'';
|
|
|
|
settings = {
|
|
log.level = cfg.logLevel;
|
|
opentelemetry = lib.optionalAttrs cfg.openTelemetry.enable {
|
|
enabled = true;
|
|
grpc-url = cfg.openTelemetry.grpcURL;
|
|
};
|
|
prometheus = lib.optionalAttrs cfg.prometheus.enable {
|
|
enabled = true;
|
|
};
|
|
analytics.reporting = {
|
|
enabled = cfg.analytics.reporting.enable;
|
|
samples = cfg.analytics.reporting.samples;
|
|
};
|
|
server.addr = cfg.server.addr;
|
|
cache = {
|
|
allow-delete-verb = cfg.cache.allowDeleteVerb;
|
|
allow-put-verb = cfg.cache.allowPutVerb;
|
|
hostname = cfg.cache.hostName;
|
|
database-url = cfg.cache.databaseURL;
|
|
database.pool = {
|
|
max-open-conns = cfg.cache.database.pool.maxOpenConns;
|
|
max-idle-conns = cfg.cache.database.pool.maxIdleConns;
|
|
};
|
|
cdc = {
|
|
inherit (cfg.cache.cdc)
|
|
enabled
|
|
min
|
|
avg
|
|
max
|
|
;
|
|
};
|
|
max-size = cfg.cache.maxSize;
|
|
lru = {
|
|
schedule = cfg.cache.lru.schedule;
|
|
timezone = cfg.cache.lru.scheduleTimeZone;
|
|
};
|
|
sign-narinfo = cfg.cache.signNarinfo;
|
|
storage =
|
|
if cfg.cache.storage.s3 != null then
|
|
{
|
|
s3 = {
|
|
bucket = cfg.cache.storage.s3.bucket;
|
|
endpoint = cfg.cache.storage.s3.endpoint;
|
|
region = cfg.cache.storage.s3.region;
|
|
force-path-style = cfg.cache.storage.s3.forcePathStyle;
|
|
};
|
|
}
|
|
else
|
|
{
|
|
local = cfg.cache.storage.local;
|
|
};
|
|
temp-path = cfg.cache.tempPath;
|
|
netrc-file = cfg.netrcFile;
|
|
upstream = {
|
|
urls = cfg.cache.upstream.urls;
|
|
public-keys = cfg.cache.upstream.publicKeys;
|
|
dialer-timeout = cfg.cache.upstream.dialerTimeout;
|
|
response-header-timeout = cfg.cache.upstream.responseHeaderTimeout;
|
|
};
|
|
lock = {
|
|
backend = cfg.cache.lock.backend;
|
|
redis.key-prefix = cfg.cache.lock.redisKeyPrefix;
|
|
download-lock-ttl = cfg.cache.lock.downloadTTL;
|
|
lru-lock-ttl = cfg.cache.lock.lruTTL;
|
|
retry = {
|
|
max-attempts = cfg.cache.lock.retry.maxAttempts;
|
|
initial-delay = cfg.cache.lock.retry.initialDelay;
|
|
max-delay = cfg.cache.lock.retry.maxDelay;
|
|
jitter = cfg.cache.lock.retry.jitter;
|
|
};
|
|
allow-degraded-mode = cfg.cache.lock.allowDegradedMode;
|
|
};
|
|
redis = lib.optionalAttrs (cfg.cache.redis != null) {
|
|
addrs = cfg.cache.redis.addresses;
|
|
db = cfg.cache.redis.database;
|
|
username = cfg.cache.redis.username;
|
|
use-tls = cfg.cache.redis.useTLS;
|
|
pool-size = cfg.cache.redis.poolSize;
|
|
};
|
|
};
|
|
};
|
|
|
|
configFile = pkgs.writeText "ncps-config.json" (
|
|
builtins.toJSON (
|
|
lib.filterAttrsRecursive (_: v: v != null && v != { } && v != "" && v != [ ]) settings
|
|
)
|
|
);
|
|
|
|
isSqlite = cfg.cache.databaseURL != null && lib.strings.hasPrefix "sqlite:" cfg.cache.databaseURL;
|
|
|
|
dbPath = if isSqlite then lib.removePrefix "sqlite:" cfg.cache.databaseURL else null;
|
|
dbDir = if isSqlite then dirOf dbPath else null;
|
|
in
|
|
{
|
|
imports = [
|
|
(lib.mkRenamedOptionModule
|
|
[ "services" "ncps" "cache" "dataPath" ]
|
|
[ "services" "ncps" "cache" "storage" "local" ]
|
|
)
|
|
|
|
(lib.mkRenamedOptionModule
|
|
[ "services" "ncps" "upstream" "caches" ]
|
|
[ "services" "ncps" "cache" "upstream" "urls" ]
|
|
)
|
|
|
|
(lib.mkRenamedOptionModule
|
|
[ "services" "ncps" "upstream" "publicKeys" ]
|
|
[ "services" "ncps" "cache" "upstream" "publicKeys" ]
|
|
)
|
|
|
|
(lib.mkRemovedOptionModule [
|
|
"services"
|
|
"ncps"
|
|
"dbmatePackage"
|
|
] "dbmate is now wrapped within ncps package, you need to override ncps to change dbmate package")
|
|
|
|
(lib.mkRemovedOptionModule [
|
|
"services"
|
|
"ncps"
|
|
"cache"
|
|
"lock"
|
|
"postgresKeyPrefix"
|
|
] "PostgreSQL lock backend was removed upstream")
|
|
];
|
|
|
|
options = {
|
|
services.ncps = {
|
|
enable = lib.mkEnableOption "ncps: Nix binary cache proxy service implemented in Go";
|
|
|
|
analytics.reporting = {
|
|
enable = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = ''
|
|
Enable reporting anonymous usage statistics (DB type, Lock type, Total Size) to the project maintainers.
|
|
'';
|
|
};
|
|
|
|
samples = lib.mkEnableOption "Enable printing the analytics samples to stdout. This is useful for debugging and verification purposes only.";
|
|
};
|
|
|
|
package = lib.mkPackageOption pkgs "ncps" { };
|
|
|
|
openTelemetry = {
|
|
enable = lib.mkEnableOption "Enable OpenTelemetry logs, metrics, and tracing";
|
|
|
|
grpcURL = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = ''
|
|
Configure OpenTelemetry gRPC URL. Missing or "https" scheme enables
|
|
secure gRPC, "insecure" otherwise. Omit to emit telemetry to
|
|
stdout.
|
|
'';
|
|
};
|
|
};
|
|
|
|
prometheus.enable = lib.mkEnableOption "Enable Prometheus metrics endpoint at /metrics";
|
|
|
|
logLevel = lib.mkOption {
|
|
type = lib.types.enum logLevels;
|
|
default = "info";
|
|
description = ''
|
|
Set the level for logging. Refer to
|
|
<https://pkg.go.dev/github.com/rs/zerolog#readme-leveled-logging> for
|
|
more information.
|
|
'';
|
|
};
|
|
|
|
cache = {
|
|
allowDeleteVerb = lib.mkEnableOption ''
|
|
Whether to allow the DELETE verb to delete narinfo and nar files from
|
|
the cache.
|
|
'';
|
|
|
|
allowPutVerb = lib.mkEnableOption ''
|
|
Whether to allow the PUT verb to push narinfo and nar files directly
|
|
to the cache.
|
|
'';
|
|
|
|
cdc = {
|
|
enabled = lib.mkEnableOption ''
|
|
Whether to enable Content-Defined Chunking (CDC) for deduplication (experimental).
|
|
'';
|
|
|
|
min = lib.mkOption {
|
|
type = lib.types.ints.u32;
|
|
default = 16384;
|
|
description = ''
|
|
The minimum chunk size for CDC in bytes.
|
|
'';
|
|
};
|
|
|
|
avg = lib.mkOption {
|
|
type = lib.types.ints.u32;
|
|
default = 65536;
|
|
description = ''
|
|
The average chunk size for CDC in bytes.
|
|
'';
|
|
};
|
|
|
|
max = lib.mkOption {
|
|
type = lib.types.ints.u32;
|
|
default = 262144;
|
|
description = ''
|
|
The maximum chunk size for CDC in bytes.
|
|
'';
|
|
};
|
|
};
|
|
|
|
hostName = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = ''
|
|
The hostname of the cache server. **This is used to generate the
|
|
private key used for signing store paths (.narinfo)**
|
|
'';
|
|
};
|
|
|
|
databaseURL = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = "sqlite:${cfg.cache.storage.local}/db/db.sqlite";
|
|
defaultText = "sqlite:/var/lib/ncps/db/db.sqlite";
|
|
description = ''
|
|
The URL of the database (currently only SQLite is supported)
|
|
'';
|
|
};
|
|
|
|
databaseURLFile = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
description = ''
|
|
File containing the URL of the database.
|
|
'';
|
|
};
|
|
|
|
database = {
|
|
pool = {
|
|
maxOpenConns = lib.mkOption {
|
|
type = lib.types.int;
|
|
default = 0;
|
|
description = ''
|
|
Maximum number of open connections to the database (0 = use
|
|
database-specific defaults).
|
|
'';
|
|
};
|
|
|
|
maxIdleConns = lib.mkOption {
|
|
type = lib.types.int;
|
|
default = 0;
|
|
description = ''
|
|
Maximum number of idle connections in the pool (0 = use
|
|
database-specific defaults).
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
|
|
lru = {
|
|
schedule = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
example = "0 2 * * *";
|
|
description = ''
|
|
The cron spec for cleaning the store to keep it under
|
|
config.ncps.cache.maxSize. Refer to
|
|
https://pkg.go.dev/github.com/robfig/cron/v3#hdr-Usage for
|
|
documentation.
|
|
'';
|
|
};
|
|
|
|
scheduleTimeZone = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "Local";
|
|
example = "America/Los_Angeles";
|
|
description = ''
|
|
The name of the timezone to use for the cron schedule. See
|
|
<https://en.wikipedia.org/wiki/List_of_tz_database_time_zones>
|
|
for a comprehensive list of possible values for this setting.
|
|
'';
|
|
};
|
|
};
|
|
|
|
lock = {
|
|
backend = lib.mkOption {
|
|
type = lib.types.enum [
|
|
"local"
|
|
"redis"
|
|
];
|
|
default = "local";
|
|
description = ''
|
|
Lock backend to use: 'local' (single instance), 'redis'
|
|
(distributed).
|
|
'';
|
|
};
|
|
|
|
redisKeyPrefix = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "ncps:lock:";
|
|
description = ''
|
|
Prefix for all Redis lock keys (only used when Redis is
|
|
configured).
|
|
'';
|
|
};
|
|
|
|
downloadTTL = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "5m0s";
|
|
description = ''
|
|
TTL for download locks (per-hash locks).
|
|
'';
|
|
};
|
|
|
|
lruTTL = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "30m0s";
|
|
description = ''
|
|
TTL for LRU lock (global exclusive lock).
|
|
'';
|
|
};
|
|
|
|
retry = {
|
|
maxAttempts = lib.mkOption {
|
|
type = lib.types.int;
|
|
default = 3;
|
|
description = ''
|
|
Maximum number of retry attempts for distributed locks.
|
|
'';
|
|
};
|
|
|
|
initialDelay = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "100ms";
|
|
description = ''
|
|
Initial retry delay for distributed locks.
|
|
'';
|
|
};
|
|
|
|
maxDelay = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "2s";
|
|
description = ''
|
|
Maximum retry delay for distributed locks (exponential backoff
|
|
caps at this).
|
|
'';
|
|
};
|
|
|
|
jitter = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = ''
|
|
Enable jitter in retry delays to prevent thundering herd.
|
|
'';
|
|
};
|
|
};
|
|
|
|
allowDegradedMode = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = ''
|
|
Allow falling back to local locks if Redis is unavailable (WARNING:
|
|
breaks HA guarantees).
|
|
'';
|
|
};
|
|
};
|
|
|
|
maxSize = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
example = "100G";
|
|
description = ''
|
|
The maximum size of the store. It can be given with units such as
|
|
5K, 10G etc. Supported units: B, K, M, G, T.
|
|
'';
|
|
};
|
|
|
|
redis = lib.mkOption {
|
|
type = lib.types.nullOr (
|
|
lib.types.submodule {
|
|
options = {
|
|
addresses = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
example = ''
|
|
["redis0:6379" "redis1:6379"]
|
|
'';
|
|
description = ''
|
|
A list of host:port for the Redis servers that are part of a cluster.
|
|
To use a single Redis instance, just set this to its single address.
|
|
'';
|
|
};
|
|
|
|
database = lib.mkOption {
|
|
type = lib.types.int;
|
|
default = 0;
|
|
description = ''
|
|
Redis database number (0-15)
|
|
'';
|
|
};
|
|
|
|
username = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = ''
|
|
Redis username for authentication (for Redis ACL).
|
|
'';
|
|
};
|
|
|
|
password = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = ''
|
|
Redis password for authentication (for Redis ACL).
|
|
'';
|
|
};
|
|
|
|
passwordFile = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
description = ''
|
|
File containing the redis password for authentication (for Redis ACL).
|
|
'';
|
|
};
|
|
|
|
poolSize = lib.mkOption {
|
|
type = lib.types.int;
|
|
default = 10;
|
|
description = ''
|
|
Redis connection pool size.
|
|
'';
|
|
};
|
|
|
|
useTLS = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = ''
|
|
Use TLS for Redis connection.
|
|
'';
|
|
};
|
|
};
|
|
}
|
|
);
|
|
|
|
default = null;
|
|
|
|
description = ''
|
|
Configure Redis.
|
|
'';
|
|
};
|
|
|
|
secretKeyPath = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
description = ''
|
|
The path to load the secretKey for signing narinfos. Leave this
|
|
empty to automatically generate a private/public key.
|
|
'';
|
|
};
|
|
|
|
storage = {
|
|
local = lib.mkOption {
|
|
type = lib.types.path;
|
|
default = "/var/lib/ncps";
|
|
description = ''
|
|
The local directory for storing configuration and cached store
|
|
paths. This is ignored if services.ncps.cache.storage.s3 is not
|
|
null.
|
|
'';
|
|
};
|
|
|
|
s3 = lib.mkOption {
|
|
type = lib.types.nullOr (
|
|
lib.types.submodule {
|
|
options = {
|
|
bucket = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = ''
|
|
The name of the S3 bucket.
|
|
'';
|
|
};
|
|
|
|
endpoint = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = ''
|
|
S3-compatible endpoint URL with scheme.
|
|
'';
|
|
example = "https://s3.amazonaws.com";
|
|
};
|
|
|
|
forcePathStyle = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = ''
|
|
Force path-style S3 addressing (bucket/key vs key.bucket).
|
|
'';
|
|
};
|
|
|
|
region = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = ''
|
|
The S3 region.
|
|
'';
|
|
};
|
|
|
|
accessKeyIdPath = lib.mkOption {
|
|
type = lib.types.path;
|
|
description = ''
|
|
The path to a file containing only the access-key-id.
|
|
'';
|
|
};
|
|
|
|
secretAccessKeyPath = lib.mkOption {
|
|
type = lib.types.path;
|
|
description = ''
|
|
The path to a file containing only the secret-access-key.
|
|
'';
|
|
};
|
|
};
|
|
}
|
|
);
|
|
default = null;
|
|
description = ''
|
|
Use S3 for storage instead of local storage.
|
|
'';
|
|
};
|
|
};
|
|
|
|
tempPath = lib.mkOption {
|
|
type = lib.types.path;
|
|
default = "/tmp";
|
|
description = ''
|
|
The path to the temporary directory that is used by the cache to download NAR files
|
|
'';
|
|
};
|
|
|
|
signNarinfo = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
example = false;
|
|
description = ''
|
|
Whether to sign narInfo files or passthru as-is from upstream
|
|
'';
|
|
};
|
|
|
|
upstream = {
|
|
dialerTimeout = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = ''
|
|
Timeout for establishing TCP connections to upstream caches (e.g., 3s, 5s, 10s).
|
|
'';
|
|
};
|
|
|
|
responseHeaderTimeout = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
example = "5s";
|
|
description = ''
|
|
Timeout for waiting for upstream server's response headers.
|
|
'';
|
|
};
|
|
|
|
publicKeys = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ ];
|
|
example = [ "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" ];
|
|
description = ''
|
|
A list of public keys of upstream caches in the format
|
|
`host[-[0-9]*]:public-key`. This flag is used to verify the
|
|
signatures of store paths downloaded from upstream caches.
|
|
'';
|
|
};
|
|
|
|
urls = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
example = [ "https://cache.nixos.org" ];
|
|
description = ''
|
|
A list of URLs of upstream binary caches.
|
|
'';
|
|
};
|
|
};
|
|
|
|
};
|
|
|
|
server = {
|
|
addr = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = ":8501";
|
|
description = ''
|
|
The address and port the server listens on.
|
|
'';
|
|
};
|
|
};
|
|
|
|
netrcFile = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
example = "/etc/nix/netrc";
|
|
description = ''
|
|
The path to netrc file for upstream authentication.
|
|
When unspecified ncps will look for ``$HOME/.netrc`.
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
assertions = [
|
|
{
|
|
assertion = lib.xor (cfg.cache.databaseURL != null) (cfg.cache.databaseURLFile != null);
|
|
message = "You must specify exactly one of config.ncps.cache.databaseURL or config.ncps.cache.databaseURLFile";
|
|
}
|
|
{
|
|
assertion = cfg.cache.lru.schedule == null || cfg.cache.maxSize != null;
|
|
message = "You must specify config.ncps.cache.lru.schedule when config.ncps.cache.maxSize is set";
|
|
}
|
|
{
|
|
assertion =
|
|
cfg.cache.redis == null || cfg.cache.redis.password == null || cfg.cache.redis.passwordFile == null;
|
|
message = "You cannot specify both config.ncps.cache.redis.password and config.ncps.cache.redis.passwordFile";
|
|
}
|
|
{
|
|
assertion = cfg.cache.lock.backend == "redis" -> cfg.cache.redis != null;
|
|
message = "You must specify config.ncps.cache.redis when config.ncps.cache.lock.backend is set to 'redis'";
|
|
}
|
|
{
|
|
assertion = cfg.cache.redis != null -> cfg.cache.lock.backend == "redis";
|
|
message = "You must set config.ncps.cache.lock.backend to 'redis' when config.ncps.cache.redis is set";
|
|
}
|
|
];
|
|
|
|
users.users.ncps = {
|
|
isSystemUser = true;
|
|
group = "ncps";
|
|
};
|
|
users.groups.ncps = { };
|
|
|
|
systemd.tmpfiles.settings.ncps =
|
|
let
|
|
perms = {
|
|
group = "ncps";
|
|
mode = "0700";
|
|
user = "ncps";
|
|
};
|
|
in
|
|
lib.mkMerge [
|
|
(lib.mkIf (cfg.cache.storage.s3 == null && cfg.cache.storage.local != "/var/lib/ncps") {
|
|
"${cfg.cache.storage.local}".d = perms;
|
|
})
|
|
|
|
(lib.mkIf isSqlite { "${dbDir}".d = perms; })
|
|
|
|
(lib.mkIf (cfg.cache.tempPath != "/tmp") { "${cfg.cache.tempPath}".d = perms; })
|
|
];
|
|
|
|
systemd.services.ncps = {
|
|
description = "ncps binary cache proxy service";
|
|
|
|
after = [ "network-online.target" ];
|
|
wants = [ "network-online.target" ];
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
preStart = ''
|
|
${lib.optionalString (cfg.cache.databaseURLFile != null) ''
|
|
export DATABASE_URL="$(cat "$CREDENTIALS_DIRECTORY/databaseURL")"
|
|
''}
|
|
${lib.optionalString (cfg.cache.databaseURL != null) ''
|
|
export DATABASE_URL="${cfg.cache.databaseURL}"
|
|
''}
|
|
echo ${cfg.package}/bin/dbmate-ncps up
|
|
${cfg.package}/bin/dbmate-ncps up
|
|
'';
|
|
|
|
serviceConfig = lib.mkMerge [
|
|
{
|
|
ExecStart = "${ncpsWrapper} serve";
|
|
User = "ncps";
|
|
Group = "ncps";
|
|
Restart = "on-failure";
|
|
RuntimeDirectory = "ncps";
|
|
}
|
|
|
|
# credentials for cache.secretKeyPath
|
|
(lib.mkIf (cfg.cache.secretKeyPath != null) {
|
|
LoadCredential = lib.singleton "secretKey:${cfg.cache.secretKeyPath}";
|
|
})
|
|
|
|
# credentials for cache.storage.s3 accessKeyIdPath and secretAccessKeyPath
|
|
(lib.mkIf (cfg.cache.storage.s3 != null) {
|
|
LoadCredential = [
|
|
"s3AccessKeyId:${cfg.cache.storage.s3.accessKeyIdPath}"
|
|
"s3SecretAccessKey:${cfg.cache.storage.s3.secretAccessKeyPath}"
|
|
];
|
|
})
|
|
|
|
# credentials for Redis
|
|
(lib.mkIf (cfg.cache.redis != null && cfg.cache.redis.passwordFile != null) {
|
|
LoadCredential = lib.singleton "redisPassword:${cfg.cache.redis.passwordFile}";
|
|
})
|
|
|
|
(lib.mkIf (cfg.cache.databaseURLFile != null) {
|
|
LoadCredential = lib.singleton "databaseURL:${cfg.cache.databaseURLFile}";
|
|
})
|
|
|
|
# ensure permissions on required directories
|
|
(lib.mkIf (cfg.cache.storage.s3 == null && cfg.cache.storage.local != "/var/lib/ncps") {
|
|
ReadWritePaths = [ cfg.cache.storage.local ];
|
|
})
|
|
(lib.mkIf (cfg.cache.storage.s3 == null && cfg.cache.storage.local == "/var/lib/ncps") {
|
|
StateDirectory = "ncps";
|
|
StateDirectoryMode = "0700";
|
|
})
|
|
(lib.mkIf (cfg.cache.storage.s3 != null && isSqlite && lib.strings.hasPrefix "/var/lib/ncps" dbDir)
|
|
{
|
|
StateDirectory = "ncps";
|
|
StateDirectoryMode = "0700";
|
|
}
|
|
)
|
|
(lib.mkIf (isSqlite && !lib.strings.hasPrefix "/var/lib/ncps" dbDir) {
|
|
ReadWritePaths = [ dbDir ];
|
|
})
|
|
(lib.mkIf (cfg.cache.tempPath != "/tmp") {
|
|
ReadWritePaths = [ cfg.cache.tempPath ];
|
|
})
|
|
|
|
# Hardening
|
|
{
|
|
SystemCallFilter = [
|
|
"@system-service"
|
|
"~@privileged"
|
|
"~@resources"
|
|
];
|
|
CapabilityBoundingSet = "";
|
|
PrivateUsers = true;
|
|
DevicePolicy = "closed";
|
|
DeviceAllow = [ "" ];
|
|
ProtectKernelModules = true;
|
|
ProtectKernelTunables = true;
|
|
ProtectControlGroups = true;
|
|
ProtectKernelLogs = true;
|
|
ProtectHostname = true;
|
|
ProtectClock = true;
|
|
ProtectProc = "invisible";
|
|
ProtectSystem = "strict";
|
|
ProtectHome = true;
|
|
RestrictSUIDSGID = true;
|
|
RestrictRealtime = true;
|
|
MemoryDenyWriteExecute = true;
|
|
ProcSubset = "pid";
|
|
RestrictNamespaces = true;
|
|
SystemCallArchitectures = "native";
|
|
PrivateNetwork = false;
|
|
PrivateTmp = true;
|
|
PrivateDevices = true;
|
|
PrivateMounts = true;
|
|
NoNewPrivileges = true;
|
|
LockPersonality = true;
|
|
RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
|
|
LimitNOFILE = 65536;
|
|
UMask = "0066";
|
|
}
|
|
];
|
|
|
|
unitConfig.RequiresMountsFor = lib.concatStringsSep " " (
|
|
(lib.optional (cfg.cache.storage.s3 == null) "${cfg.cache.storage.local}")
|
|
++ (lib.optional isSqlite dbDir)
|
|
);
|
|
};
|
|
};
|
|
|
|
meta.maintainers = with lib.maintainers; [
|
|
kalbasit
|
|
aciceri
|
|
];
|
|
}
|