diff --git a/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml
index e7bfd27e249b..7f2f1dc6fe74 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml
@@ -37,6 +37,13 @@
programs.fzf.
+
+
+ atuin,
+ a sync server for shell history. Available as
+ services.atuin.
+
+
v2rayA, a Linux
diff --git a/nixos/doc/manual/release-notes/rl-2305.section.md b/nixos/doc/manual/release-notes/rl-2305.section.md
index cfebfd9a86c4..d52d54e0440f 100644
--- a/nixos/doc/manual/release-notes/rl-2305.section.md
+++ b/nixos/doc/manual/release-notes/rl-2305.section.md
@@ -18,6 +18,8 @@ In addition to numerous new and upgraded packages, this release has the followin
- [fzf](https://github.com/junegunn/fzf), a command line fuzzyfinder. Available as [programs.fzf](#opt-programs.fzf.fuzzyCompletion).
+- [atuin](https://github.com/ellie/atuin), a sync server for shell history. Available as [services.atuin](#opt-services.atuin.enable).
+
- [v2rayA](https://v2raya.org), a Linux web GUI client of Project V which supports V2Ray, Xray, SS, SSR, Trojan and Pingtunnel. Available as [services.v2raya](options.html#opt-services.v2raya.enable).
## Backward Incompatibilities {#sec-release-23.05-incompatibilities}
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 842797d24302..375059f6514d 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -559,6 +559,7 @@
./services/misc/airsonic.nix
./services/misc/ankisyncd.nix
./services/misc/apache-kafka.nix
+ ./services/misc/atuin.nix
./services/misc/autofs.nix
./services/misc/autorandr.nix
./services/misc/bazarr.nix
diff --git a/nixos/modules/services/misc/atuin.nix b/nixos/modules/services/misc/atuin.nix
new file mode 100644
index 000000000000..c94852e3aad9
--- /dev/null
+++ b/nixos/modules/services/misc/atuin.nix
@@ -0,0 +1,85 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.atuin;
+in
+{
+ options = {
+ services.atuin = {
+ enable = mkEnableOption (mdDoc "Enable server for shell history sync with atuin.");
+
+ openRegistration = mkOption {
+ type = types.bool;
+ default = false;
+ description = mdDoc "Allow new user registrations with the atuin server.";
+ };
+
+ path = mkOption {
+ type = types.str;
+ default = "";
+ description = mdDoc "A path to prepend to all the routes of the server.";
+ };
+
+ host = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = mdDoc "The host address the atuin server should listen on.";
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = 8888;
+ description = mdDoc "The port the atuin server should listen on.";
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = mdDoc "Open ports in the firewall for the atuin server.";
+ };
+
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ # enable postgres to host atuin db
+ services.postgresql = {
+ enable = true;
+ ensureUsers = [{
+ name = "atuin";
+ ensurePermissions = {
+ "DATABASE atuin" = "ALL PRIVILEGES";
+ };
+ }];
+ ensureDatabases = [ "atuin" ];
+ };
+
+ systemd.services.atuin = {
+ description = "atuin server";
+ after = [ "network.target" "postgresql.service" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ ExecStart = "${pkgs.atuin}/bin/atuin server start";
+ RuntimeDirectory = "atuin";
+ RuntimeDirectoryMode = "0700";
+ DynamicUser = true;
+ };
+
+ environment = {
+ ATUIN_HOST = cfg.host;
+ ATUIN_PORT = toString cfg.port;
+ ATUIN_OPEN_REGISTRATION = boolToString cfg.openRegistration;
+ ATUIN_DB_URI = "postgresql:///atuin";
+ ATUIN_PATH = cfg.path;
+ ATUIN_CONFIG_DIR = "/run/atuin"; # required to start, but not used as configuration is via environment variables
+ };
+ };
+
+ networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
+
+ };
+}
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index b372ae20480b..b94ef1e700c6 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -80,6 +80,7 @@ in {
apparmor = handleTest ./apparmor.nix {};
atd = handleTest ./atd.nix {};
atop = handleTest ./atop.nix {};
+ atuin = handleTest ./atuin.nix {};
auth-mysql = handleTest ./auth-mysql.nix {};
avahi = handleTest ./avahi.nix {};
avahi-with-resolved = handleTest ./avahi.nix { networkd = true; };
diff --git a/nixos/tests/atuin.nix b/nixos/tests/atuin.nix
new file mode 100644
index 000000000000..85213d1e53ea
--- /dev/null
+++ b/nixos/tests/atuin.nix
@@ -0,0 +1,65 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+ testPort = 8888;
+ testUser = "testerman";
+ testPass = "password";
+ testEmail = "test.testerman@test.com";
+in
+with lib;
+{
+ name = "atuin";
+ meta.maintainers = with pkgs.lib.maintainers; [ devusb ];
+
+ nodes = {
+ server =
+ { ... }:
+ {
+ services.atuin = {
+ enable = true;
+ port = testPort;
+ host = "0.0.0.0";
+ openFirewall = true;
+ openRegistration = true;
+ };
+ };
+
+ client =
+ { ... }:
+ { };
+
+ };
+
+ testScript = with pkgs; ''
+ start_all()
+
+ # wait for atuin server startup
+ server.wait_for_unit("atuin.service")
+ server.wait_for_open_port(${toString testPort})
+
+ # configure atuin client on server node
+ server.execute("mkdir -p ~/.config/atuin")
+ server.execute("echo 'sync_address = \"http://localhost:${toString testPort}\"' > ~/.config/atuin/config.toml")
+
+ # register with atuin server on server node
+ server.succeed("${atuin}/bin/atuin register -u ${testUser} -p ${testPass} -e ${testEmail}")
+ _, key = server.execute("${atuin}/bin/atuin key")
+
+ # store test record in atuin server and sync
+ server.succeed("ATUIN_SESSION=$(${atuin}/bin/atuin uuid) ${atuin}/bin/atuin history start 'shazbot'")
+ server.succeed("${atuin}/bin/atuin sync")
+
+ # configure atuin client on client node
+ client.execute("mkdir -p ~/.config/atuin")
+ client.execute("echo 'sync_address = \"http://server:${toString testPort}\"' > ~/.config/atuin/config.toml")
+
+ # log in to atuin server on client node
+ client.succeed(f"${atuin}/bin/atuin login -u ${testUser} -p ${testPass} -k {key}")
+
+ # pull records from atuin server
+ client.succeed("${atuin}/bin/atuin sync -f")
+
+ # check for test record
+ client.succeed("ATUIN_SESSION=$(${atuin}/bin/atuin uuid) ${atuin}/bin/atuin history list | grep shazbot")
+ '';
+})