about summary refs log tree commit diff stats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--modules/by-name/at/atuin-sync/module.nix45
-rw-r--r--pkgs/by-name/at/atuin-server-only/package.nix22
-rw-r--r--tests/by-name/at/atuin-sync/test.nix206
3 files changed, 273 insertions, 0 deletions
diff --git a/modules/by-name/at/atuin-sync/module.nix b/modules/by-name/at/atuin-sync/module.nix
new file mode 100644
index 0000000..0db2e29
--- /dev/null
+++ b/modules/by-name/at/atuin-sync/module.nix
@@ -0,0 +1,45 @@
+{
+  config,
+  lib,
+  vhackPackages,
+  ...
+}: let
+  cfg = config.vhack.atuin-sync;
+in {
+  options.vhack.atuin-sync = {
+    enable = lib.mkEnableOption "atuin sync server";
+
+    fqdn = lib.mkOption {
+      description = "The fully qualified domain name of this instance.";
+      type = lib.types.str;
+      example = "atuin-sync.atuin.sh";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    vhack.nginx.enable = true;
+
+    services = {
+      nginx.virtualHosts."${cfg.fqdn}" = {
+        locations."/" = {
+          proxyPass = "http://127.0.0.1:${toString config.services.atuin.port}";
+          recommendedProxySettings = true;
+        };
+
+        enableACME = true;
+        forceSSL = true;
+      };
+
+      atuin = {
+        enable = true;
+        package = vhackPackages.atuin-server-only;
+        host = "127.0.0.1";
+
+        # Nobody knows about the fqdn and even if, they can only upload encrypted blobs.
+        openRegistration = true;
+
+        database.createLocally = true;
+      };
+    };
+  };
+}
diff --git a/pkgs/by-name/at/atuin-server-only/package.nix b/pkgs/by-name/at/atuin-server-only/package.nix
new file mode 100644
index 0000000..ddad2ec
--- /dev/null
+++ b/pkgs/by-name/at/atuin-server-only/package.nix
@@ -0,0 +1,22 @@
+{atuin}:
+atuin.overrideAttrs (final: prev: {
+  pname = prev.pname + "-server-only";
+  inherit (prev) patches;
+
+  # atuin's default features include 'check-updates', which do not make sense
+  # for distribution builds. List all other default features.
+  buildNoDefaultFeatures = true;
+  buildFeatures = [
+    # All of this is client code
+    # "client"
+    # "sync"
+    # "check-update"
+    # "clipboard"
+    # "daemon" # Background daemon for the client
+
+    "server"
+  ];
+
+  # The checks don't compile without the `server` feature
+  doCheck = true;
+})
diff --git a/tests/by-name/at/atuin-sync/test.nix b/tests/by-name/at/atuin-sync/test.nix
new file mode 100644
index 0000000..3e01885
--- /dev/null
+++ b/tests/by-name/at/atuin-sync/test.nix
@@ -0,0 +1,206 @@
+{
+  nixos-lib,
+  pkgsUnstable,
+  nixpkgs-unstable,
+  vhackPackages,
+  pkgs,
+  extraModules,
+  nixLib,
+  ...
+}:
+nixos-lib.runTest {
+  hostPkgs = pkgs;
+  name = "atuin-sync";
+
+  node = {
+    specialArgs = {inherit pkgsUnstable vhackPackages nixpkgs-unstable nixLib;};
+
+    # Use the nixpkgs as constructed by the `nixpkgs.*` options
+    pkgs = null;
+  };
+
+  nodes = let
+    atuinSession = "01969ec6b8d07e30a9d2df0911fbfe2a";
+  in {
+    acme = {
+      imports = [
+        ../../../common/acme/server.nix
+        ../../../common/dns/client.nix
+      ];
+    };
+    name_server = {nodes, ...}: {
+      imports =
+        extraModules
+        ++ [
+          ../../../common/acme/client.nix
+          ../../../common/dns/server.nix
+        ];
+
+      vhack.dns.zones = {
+        "atuin-sync.server" = {
+          SOA = {
+            nameServer = "ns";
+            adminEmail = "admin@server.com";
+            serial = 2025012301;
+          };
+          useOrigin = false;
+
+          A = [
+            nodes.server.networking.primaryIPAddress
+          ];
+          AAAA = [
+            nodes.server.networking.primaryIPv6Address
+          ];
+        };
+      };
+    };
+
+    server = {config, ...}: {
+      imports =
+        extraModules
+        ++ [
+          ../../../../modules
+          ../../../common/acme/client.nix
+          ../../../common/dns/client.nix
+        ];
+
+      vhack = {
+        persist.enable = true;
+        nginx.enable = true;
+        atuin-sync = {
+          enable = true;
+          fqdn = "atuin-sync.server";
+        };
+      };
+    };
+
+    client1 = {config, ...}: {
+      imports = [
+        ../../../common/acme/client.nix
+        ../../../common/dns/client.nix
+      ];
+
+      environment.sessionVariables.ATUIN_SESSION = atuinSession;
+
+      environment.systemPackages = [
+        pkgs.atuin
+        pkgs.sqlite-interactive
+      ];
+    };
+    client2 = {config, ...}: {
+      imports = [
+        ../../../common/acme/client.nix
+        ../../../common/dns/client.nix
+      ];
+
+      environment.sessionVariables.ATUIN_SESSION = atuinSession;
+
+      environment.systemPackages = [
+        pkgs.atuin
+        pkgs.sqlite-interactive
+      ];
+    };
+  };
+
+  testScript = {nodes, ...}: let
+    syncLogin = pkgs.writeShellScript "login-atuin-sync-account" ''
+      atuin login --username syncy --password password1234 --key "$1"
+    '';
+
+    syncRegister = pkgs.writeShellScript "register-atuin-sync-account" ''
+      atuin register --username syncy --email syncy@email.com --password password1234
+    '';
+
+    mkSyncConfig = pkgs.writeShellScript "register-atuin-sync-account" ''
+      mkdir --parents ~/.config/atuin/
+      cat << EOF > ~/.config/atuin/config.toml
+      sync_address = "https://atuin-sync.server"
+
+      # Use the v2 sync
+      [sync]
+      records = true
+      EOF
+    '';
+
+    runCommandAndRecordInAtuin = pkgs.writeShellScript "run-command-and-record-in-atuin" ''
+      # SPDX-SnippetBegin
+      # SPDX-SnippetCopyrightText: 2023 mentalisttraceur (https://github.com/mentalisttraceur)
+      # Source: https://github.com/atuinsh/atuin/issues/1188#issuecomment-1698354107
+      run_and_record_in_atuin()
+      {
+          local id
+          local status
+          local escaped_command="$(printf '%q ' "$@")"
+          id="$(atuin history start -- "$escaped_command")"
+          "$@"
+          status=$?
+          atuin history end --exit $status "$id"
+          return $status
+      }
+      # SPDX-SnippetEnd
+
+      run_and_record_in_atuin "$@"
+    '';
+
+    acme_scripts = import ../../../common/acme/scripts.nix {inherit pkgs;};
+  in
+    /*
+    python
+    */
+    ''
+      # Start dependencies for the other services
+      acme.start()
+      acme.wait_for_unit("pebble.service")
+      name_server.start()
+      name_server.wait_for_unit("nsd.service")
+
+      # Start actual test
+      start_all()
+
+      with subtest("Add pebble ca key to all services"):
+        for node in [name_server, server, client1, client2]:
+          node.wait_for_unit("network-online.target")
+          node.succeed("${acme_scripts.add_pebble_acme_ca}")
+
+      server.wait_for_unit("atuin.service")
+      server.wait_for_open_port(443)
+
+      # Wait for the server to acquire the acme certificate
+      client1.wait_until_succeeds("curl https://atuin-sync.server")
+
+      with subtest("Setup client syncing"):
+          for client in [client1, client2]:
+            client.succeed("${mkSyncConfig}")
+
+          client1.succeed("${syncRegister}")
+
+          for client in [client1, client2]:
+            # See https://docs.atuin.sh/guide/sync/
+            client.succeed(f"${syncLogin} '{client1.succeed("atuin key")}'")
+
+      with subtest("Can import shell history"):
+          client1.succeed("${runCommandAndRecordInAtuin} echo hi - client 1")
+          client2.succeed("${runCommandAndRecordInAtuin} echo hi - client 2")
+
+      with subtest("Can sync tasks"):
+          for client in [client1, client2]:
+            client.succeed("atuin sync --force")
+          client1.succeed("atuin sync --force")
+
+
+      with subtest("Have correct tasks"):
+          hist1 = client1.succeed("atuin history list --session --format '{command}'").strip().split('\n')
+          hist2 = client2.succeed("atuin history list --session --format '{command}'").strip().split('\n')
+
+          hist1.sort()
+          hist2.sort()
+
+          canonicalHistory = [
+            "echo hi - client 1",
+            "echo hi - client 2"
+          ]
+
+          assert hist1 == hist2, f"The clients don't have the same amount of history items, client1: '{hist1}', client2: '{hist2}'"
+          assert hist1 == canonicalHistory, f"The history is not correct: '{hist1}' vs. '{canonicalHistory}'"
+    '';
+}