{ 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 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.masterKeyFile` 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 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 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 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; settings.env = 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; [ tmarkus ]; }