{ config, pkgs, lib, ... }: let inherit (lib) mkOption types mkIf; cfg = config.services.turtle; in { options = { services.turtle = { enable = lib.mkEnableOption "turtle server for shell history sync"; package = lib.mkPackageOption pkgs "turtle" {}; host = mkOption { type = types.str; default = "127.0.0.1"; description = "The host address the turtle server should listen on."; }; port = mkOption { type = types.port; default = 8888; description = "The port the turtle server should listen on."; }; database = { createLocally = mkOption { type = types.bool; default = true; description = "Create the database and database user locally."; }; uri = mkOption { type = types.nullOr types.str; default = "postgresql:///turtle?host=/run/postgresql"; example = "postgresql://turtle@localhost:5432/turtle"; description = '' URI to the database. Can be set to null in which case ATUIN_DB_URI should be set through an EnvironmentFile ''; }; }; environmentFile = lib.mkOption { type = lib.types.nullOr lib.types.externalPath; default = null; description = '' Environment file, used to set any secret ATUIN_* environment variables, such as ATUIN_DB_URI containing a password. See https://docs.atuin.sh/cli/self-hosting/server-setup/#configuration for available environment variables. ''; }; }; }; config = mkIf cfg.enable { assertions = [ { assertion = cfg.database.createLocally -> config.services.postgresql.enable; message = "Postgresql must be enabled to create a local database"; } ]; services.postgresql = mkIf cfg.database.createLocally { enable = true; ensureUsers = [ { name = "turtle"; ensureDBOwnership = true; } ]; ensureDatabases = ["turtle"]; }; systemd.services.turtle = { description = "turtle server"; requires = lib.optionals cfg.database.createLocally ["postgresql.target"]; after = [ "network-online.target" ] ++ lib.optionals cfg.database.createLocally ["postgresql.target"]; wants = [ "network-online.target" ] ++ lib.optionals cfg.database.createLocally ["postgresql.target"]; wantedBy = ["multi-user.target"]; serviceConfig = { ExecStart = "${lib.getExe' cfg.package "atuin"} server start"; EnvironmentFile = lib.mkIf (cfg.environmentFile != null) [cfg.environmentFile]; RuntimeDirectory = "turtle"; RuntimeDirectoryMode = "0700"; DynamicUser = true; # Hardening CapabilityBoundingSet = ""; LockPersonality = true; NoNewPrivileges = true; MemoryDenyWriteExecute = true; PrivateDevices = true; PrivateMounts = true; PrivateTmp = true; PrivateUsers = true; ProcSubset = "pid"; ProtectClock = true; ProtectControlGroups = true; ProtectHome = true; ProtectHostname = true; ProtectKernelLogs = true; ProtectKernelModules = true; ProtectKernelTunables = true; ProtectProc = "invisible"; ProtectSystem = "full"; RemoveIPC = true; RestrictAddressFamilies = [ "AF_INET" "AF_INET6" # Required for connecting to database sockets, "AF_UNIX" ]; RestrictNamespaces = true; RestrictRealtime = true; RestrictSUIDSGID = true; SystemCallArchitectures = "native"; SystemCallFilter = [ "@system-service" "~@privileged" ]; UMask = "0077"; }; environment = { ATUIN_HOST = cfg.host; ATUIN_PORT = toString cfg.port; ATUIN_CONFIG_DIR = "/run/turtle"; # required to start, but not used as configuration is via environment variables } // lib.optionalAttrs (cfg.database.uri != null) {ATUIN_DB_URI = cfg.database.uri;}; }; }; }