# Source: https://github.com/sodiboo/system/blob/b63c7b27f49043e8701b3ff5e1441cd27d5a2fff/sharkey/module.nix { config, lib, pkgs, vhackPackages, ... }: let cfg = config.vhack.sharkey; createDB = cfg.database.host == "127.0.0.1" && cfg.database.createLocally; settingsFormat = pkgs.formats.yaml {}; configFile = settingsFormat.generate "sharkey-config.yml" cfg.settings; in { options.vhack.sharkey = { enable = lib.mkEnableOption "sharkey"; fqdn = lib.mkOption { description = "The fully qualified domain name of this instance."; type = lib.types.str; example = "sharkey.shonk.social"; }; package = lib.mkOption { type = lib.types.package; default = vhackPackages.sharkey; defaultText = lib.literalExpression "vhackPackages.sharkey"; description = "Sharkey package to use."; }; dataDirectory = lib.mkOption { type = lib.types.path; default = "/var/lib/sharkey"; description = "The directory where sharkey stores it's data."; # This is already set in the package. readOnly = true; }; database = { createLocally = lib.mkOption { description = "Whether to enable local db creation."; type = lib.types.bool; default = true; }; host = lib.mkOption { type = lib.types.str; default = "127.0.0.1"; description = "The database host."; }; port = lib.mkOption { type = lib.types.port; default = 5432; description = "The database port."; }; name = lib.mkOption { type = lib.types.str; default = "sharkey"; description = "The database name in postgresql."; }; }; settings = lib.mkOption { inherit (settingsFormat) type; default = {}; description = '' Configuration for Sharkey, see for supported settings. ''; }; }; config = lib.mkIf cfg.enable { environment.systemPackages = [cfg.package]; vhack = { nginx.enable = true; sharkey.settings = { id = "aidx"; url = "https://${cfg.fqdn}/"; port = 5312; db = { inherit (cfg.database) host port; db = cfg.database.name; user = cfg.database.name; pass = "sharkey-password"; }; redis = { path = config.services.redis.servers."sharkey".unixSocket; }; }; persist.directories = [ { directory = "${config.services.redis.servers."sharkey".settings.dir}"; user = "sharkey"; group = "redis-sharey"; mode = "0770"; } { directory = "${cfg.dataDirectory}"; user = "sharkey"; group = "sharkey"; mode = "0770"; } ]; }; services = { nginx.virtualHosts."${cfg.fqdn}" = { locations."/" = { proxyPass = "http://127.0.0.1:${toString cfg.settings.port}"; proxyWebsockets = true; }; # proxy_set_header Host $host; # proxy_http_version 1.1; # proxy_redirect off; # # # If it's behind another reverse proxy or CDN, remove the following. # proxy_set_header X-Real-IP $remote_addr; # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # proxy_set_header X-Forwarded-Proto https; # # # For WebSocket # proxy_set_header Upgrade $http_upgrade; # proxy_set_header Connection $connection_upgrade; # # # Cache settings # proxy_cache cache1; # proxy_cache_lock on; # proxy_cache_use_stale updating; # proxy_force_ranges on; # add_header X-Cache $upstream_cache_status; enableACME = true; forceSSL = true; }; postgresql = lib.mkIf createDB { enable = true; settings.port = cfg.database.port; ensureUsers = [ { inherit (cfg.database) name; ensureDBOwnership = true; } ]; ensureDatabases = [cfg.database.name]; }; redis = { servers."sharkey" = { enable = true; user = "sharkey"; # Disable TCP listening. (We have a UNIX socket) port = 0; bind = null; settings = { protected-mode = true; enable-protected-configs = false; enable-debug-command = false; enable-module-command = false; supervised = "systemd"; stop-writes-on-bgsave-error = true; sanitize-dump-payload = "clients"; }; }; }; }; systemd.services.postgresql.postStart = '' $PSQL -tAc "ALTER ROLE ${cfg.database.name} WITH ENCRYPTED PASSWORD 'sharkey-password';" ''; systemd.services.sharkey = { requires = [ "redis-sharkey.service" "network-online.target" ] ++ lib.optionals createDB ["postgresql.service"]; after = [ "redis-sharkey.service" "network-online.target" ] ++ lib.optionals createDB ["postgresql.service"]; wantedBy = ["multi-user.target"]; environment = { MISSKEY_CONFIG_YML = "${configFile}"; NODE_ENV = "production"; }; serviceConfig = { Type = "simple"; StateDirectory = "sharkey"; StateDirectoryMode = "0700"; CacheDirectory = "sharkey"; RuntimeDirectory = "sharkey"; RuntimeDirectoryMode = "0700"; ExecStart = "${lib.getExe cfg.package} migrateandstart"; TimeoutSec = 60; Restart = "no"; StandardOutput = "journal"; StandardError = "journal"; SyslogIdentifier = "sharkey"; User = "sharkey"; Group = "sharkey"; # Bind standard privileged ports AmbientCapabilities = []; CapabilityBoundingSet = []; ReadWritePaths = [ "${cfg.dataDirectory}" ]; # Hardening DeviceAllow = [""]; LockPersonality = true; # Probably needed for v8's JIT (crashes with it on). MemoryDenyWriteExecute = false; PrivateDevices = true; PrivateUsers = true; ProcSubset = "pid"; PrivateTmp = true; ProtectClock = true; ProtectControlGroups = true; ProtectHome = true; ProtectHostname = true; ProtectKernelLogs = true; ProtectKernelModules = true; ProtectKernelTunables = true; ProtectProc = "invisible"; ProtectSystem = "strict"; RestrictAddressFamilies = [ "AF_UNIX" # Local communication unix(7) "AF_INET" # IPv4 Internet protocols ip(7) "AF_INET6" # IPv6 Internet protocols ipv6(7) # Needed for nodes `os.networkInterfaces()` function. "AF_NETLINK" # Kernel user interface device netlink(7) ]; RestrictNamespaces = true; RestrictRealtime = true; RestrictSUIDSGID = true; SystemCallArchitectures = "native"; SystemCallFilter = [ "@system-service" "~@privileged" "~@mount" ]; UMask = "0077"; }; }; users = { groups.sharkey = { gid = config.vhack.constants.ids.gids.sharkey; }; users.sharkey = { isSystemUser = true; group = "sharkey"; uid = config.vhack.constants.ids.uids.sharkey; home = cfg.package; packages = [cfg.package]; }; groups.redis-sharkey = { gid = config.vhack.constants.ids.gids.redis-sharkey; }; users.redis-sharkey = { group = "redis-sharkey"; uid = config.vhack.constants.ids.uids.redis-sharkey; }; }; }; }