2025-07-07 01:01:39 +10:00

301 lines
9.3 KiB
Nix

{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.sharkey;
settingsFormat = pkgs.formats.yaml { };
configFile = settingsFormat.generate "config.yml" cfg.settings;
in
{
options.services.sharkey =
let
inherit (lib)
mkEnableOption
mkOption
mkPackageOption
types
;
in
{
enable = mkEnableOption "Sharkey, a Sharkish microblogging platform";
package = mkPackageOption pkgs "sharkey" { };
environmentFiles = mkOption {
type = types.listOf types.path;
default = [ ];
example = [ "/run/secrets/sharkey-env" ];
description = ''
List of paths to files containing environment variables for Sharkey to use at runtime.
This is useful for keeping secrets out of the Nix store. See
https://docs.joinsharkey.org/docs/install/configuration/ for how to configure Sharkey using environment
variables.
'';
};
openFirewall = mkOption {
type = types.bool;
default = false;
example = true;
description = ''
Whether to open ports in the NixOS firewall for Sharkey.
'';
};
setupMeilisearch = mkOption {
type = types.bool;
default = false;
example = true;
description = ''
Whether to automatically set up a local Meilisearch instance and configure Sharkey to use it.
You need to ensure `services.meilisearch.masterKeyEnvironmentFile` is correctly configured for a working
Meilisearch setup. You also need to configure Sharkey to use an API key obtained from Meilisearch with the
`MK_CONFIG_MEILISEARCH_APIKEY` environment variable, and set `services.sharkey.settings.meilisearch.index` to
the created index. See https://docs.joinsharkey.org/docs/customisation/search/meilisearch/ for how to create
an API key and index.
'';
};
setupPostgresql = mkOption {
type = types.bool;
default = true;
example = false;
description = ''
Whether to automatically set up a local PostgreSQL database and configure Sharkey to use it.
'';
};
setupRedis = mkOption {
type = types.bool;
default = true;
example = false;
description = ''
Whether to automatically set up a local Redis cache and configure Sharkey to use it.
'';
};
settings = mkOption {
type = types.submodule {
freeformType = settingsFormat.type;
options = {
url = mkOption {
type = types.str;
example = "https://blahaj.social/";
description = ''
The full URL that the Sharkey instance will be publically accessible on.
Do NOT change this after initial setup!
'';
};
port = mkOption {
type = types.port;
default = 3000;
description = ''
The port that Sharkey will listen on.
'';
};
address = mkOption {
type = types.str;
default = "0.0.0.0";
example = "127.0.0.1";
description = ''
The address that Sharkey binds to.
'';
};
socket = mkOption {
type = types.nullOr types.path;
default = null;
example = "/run/sharkey/sharkey.sock";
description = ''
If specified, creates a UNIX socket at the given path that Sharkey listens on.
'';
};
mediaDirectory = mkOption {
type = types.path;
default = "/var/lib/sharkey";
description = ''
Path to the folder where Sharkey stores uploaded media such as images and attachments.
'';
};
fulltextSearch.provider = mkOption {
type = types.enum [
"sqlLike"
"sqlPgroonga"
"sqlTsvector"
"meilisearch"
];
default = "sqlLike";
example = "sqlPgroonga";
description = ''
Which provider to use for full text search.
All options other than `sqlLike` require extra setup - see the comments in
https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/.config/example.yml for details.
If `sqlPgroonga` is set, and `services.sharkey.setupPostgres` is `true`, the pgroonga extension will
automatically be setup. You still need to create an index manually.
If using Meilisearch, consider setting `services.sharkey.setupMeilisearch` instead, which will
configure Meilisearch for you.
'';
};
id = mkOption {
type = types.enum [
"aid"
"aidx"
"meid"
"ulid"
"objectid"
];
default = "aidx";
description = ''
The ID generation method for Sharkey to use.
Do NOT change this after initial setup!
'';
};
};
};
default = { };
description = ''
Configuration options for Sharkey.
See https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/.config/example.yml for a list of all
available configuration options.
'';
};
};
config =
let
inherit (lib) mkDefault mkIf mkMerge;
in
mkIf cfg.enable (mkMerge [
{
systemd.services.sharkey = {
description = "Sharkey";
documentation = [ "https://docs.joinsharkey.org/" ];
wantedBy = [ "multi-user.target" ];
startLimitBurst = 5;
startLimitIntervalSec = 60;
environment.MISSKEY_CONFIG_DIR = "/etc/sharkey";
serviceConfig = {
Type = "simple";
ExecStart = "${lib.getExe cfg.package} migrateandstart";
EnvironmentFile = cfg.environmentFiles;
DynamicUser = true;
TimeoutSec = 60;
Restart = "always";
SyslogIdentifier = "sharkey";
ConfigurationDirectory = "sharkey";
RuntimeDirectory = "sharkey";
StateDirectory = "sharkey";
CapabilityBoundingSet = "";
LockPersonality = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateUsers = true;
PrivateTmp = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
ReadWritePaths = [ cfg.settings.mediaDirectory ];
RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX AF_NETLINK";
RestrictNamespaces = true;
RestrictRealtime = true;
SystemCallArchitectures = "native";
SystemCallFilter = [
"~@cpu-emulation @debug @mount @obsolete @privileged @resources"
"@chown"
];
UMask = "0077";
};
};
environment.etc."sharkey/default.yml".source = configFile;
}
(mkIf cfg.openFirewall {
networking.firewall.allowedTCPPorts = [ cfg.settings.port ];
})
(mkIf cfg.setupMeilisearch {
services.meilisearch = {
enable = mkDefault true;
environment = mkDefault "production";
};
services.sharkey.settings = {
fulltextSearch.provider = "meilisearch";
meilisearch = {
host = config.services.meilisearch.listenAddress;
port = config.services.meilisearch.listenPort;
};
};
systemd.services.sharkey = {
after = [ "meilisearch.service" ];
wants = [ "meilisearch.service" ];
};
})
(mkIf cfg.setupPostgresql {
services.postgresql = {
enable = mkDefault true;
ensureDatabases = [ "sharkey" ];
ensureUsers = [
{
name = "sharkey";
ensureDBOwnership = true;
}
];
extensions = mkIf (cfg.settings.fulltextSearch.provider == "sqlPgroonga") (ps: [ ps.pgroonga ]);
};
services.sharkey.settings.db = {
host = "/run/postgresql";
db = "sharkey";
};
systemd.services.sharkey = {
after = [ "postgresql.target" ];
bindsTo = [ "postgresql.target" ];
};
})
(mkIf cfg.setupRedis {
services.redis.servers.sharkey.enable = mkDefault true;
services.sharkey.settings.redis.path = config.services.redis.servers.sharkey.unixSocket;
systemd.services.sharkey = {
after = [ "redis-sharkey.service" ];
bindsTo = [ "redis-sharkey.service" ];
serviceConfig.SupplementaryGroups = [
config.services.redis.servers.sharkey.group
];
};
})
]);
meta.maintainers = with lib.maintainers; [ srxl ];
}