diff --git a/nixos/doc/manual/release-notes/rl-2605.section.md b/nixos/doc/manual/release-notes/rl-2605.section.md index 7166945d0b1e..89c971d54e38 100644 --- a/nixos/doc/manual/release-notes/rl-2605.section.md +++ b/nixos/doc/manual/release-notes/rl-2605.section.md @@ -57,6 +57,8 @@ - [OpenThread Border Router](https://openthread.io/), a Thread border router for POSIX-based platforms that bridges Thread mesh networks to IP networks. Available as [services.openthread-border-router](#opt-services.openthread-border-router.enable). +- [Atuin](https://atuin.sh), magical shell history — sync, search and backup your terminal history. Available as [programs.atuin](#opt-programs.atuin.enable). + - [Meshtastic](https://meshtastic.org), an open-source, off-grid, decentralised mesh network designed to run on affordable, low-power devices. Available as [services.meshtasticd] (#opt-services.meshtasticd.enable). diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 4a9d5b7695bb..dcba86a33b07 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -168,6 +168,7 @@ ./programs/appimage.nix ./programs/arp-scan.nix ./programs/atop.nix + ./programs/atuin.nix ./programs/ausweisapp.nix ./programs/autoenv.nix ./programs/autojump.nix diff --git a/nixos/modules/programs/atuin.nix b/nixos/modules/programs/atuin.nix new file mode 100644 index 000000000000..51541959606a --- /dev/null +++ b/nixos/modules/programs/atuin.nix @@ -0,0 +1,195 @@ +{ + config, + lib, + pkgs, + ... +}: +let + inherit (lib) escapeShellArgs; + + cfg = config.programs.atuin; + + tomlFormat = pkgs.formats.toml { }; + + settingsFile = tomlFormat.generate "atuin-config" cfg.settings; +in +{ + options.programs.atuin = { + enable = lib.mkEnableOption "atuin"; + + package = lib.mkPackageOption pkgs "atuin" { }; + + enableBashIntegration = lib.mkEnableOption "Bash integration" // { + default = config.programs.bash.enable; + defaultText = lib.literalExpression "config.programs.bash.enable"; + }; + + enableZshIntegration = lib.mkEnableOption "Zsh integration" // { + default = config.programs.zsh.enable; + defaultText = lib.literalExpression "config.programs.zsh.enable"; + }; + + enableFishIntegration = lib.mkEnableOption "Fish integration" // { + default = config.programs.fish.enable; + defaultText = lib.literalExpression "config.programs.fish.enable"; + }; + + flags = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ + "--disable-up-arrow" + "--disable-ctrl-r" + ]; + description = '' + Flags to append to the shell hook. + ''; + }; + + settings = lib.mkOption { + type = tomlFormat.type; + default = { }; + example = lib.literalExpression '' + { + auto_sync = true; + sync_frequency = "5m"; + sync_address = "https://api.atuin.sh"; + search_mode = "prefix"; + } + ''; + description = '' + Configuration written to {file}`/etc/atuin/config.toml`. + + See for the full list + of options. + ''; + }; + + daemon = { + enable = lib.mkEnableOption "the Atuin daemon" // { + default = pkgs.stdenv.hostPlatform.isLinux; + defaultText = lib.literalExpression "pkgs.stdenv.hostPlatform.isLinux"; + }; + + logLevel = lib.mkOption { + type = lib.types.enum [ + "trace" + "debug" + "info" + "warn" + "error" + ]; + default = "info"; + description = '' + Log level for the Atuin daemon. + ''; + }; + }; + + themes = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.oneOf [ + tomlFormat.type + lib.types.path + lib.types.lines + ] + ); + description = '' + Each theme is written to + {file}`/etc/atuin/themes/theme-name.toml` + where the name of each attribute is the theme-name + + See for the full list + of options. + ''; + default = { }; + example = lib.literalExpression '' + { + "my-theme" = { + theme.name = "My Theme"; + colors = { + Base = "#000000"; + Title = "#FFFFFF"; + }; + }; + } + ''; + }; + }; + + config = lib.mkIf cfg.enable { + environment.systemPackages = [ cfg.package ]; + + # Atuin only reads from ATUIN_CONFIG_DIR or XDG_CONFIG_HOME, not XDG_CONFIG_DIRS, + # so we must set ATUIN_CONFIG_DIR to point to the system-wide config location. + environment.variables.ATUIN_CONFIG_DIR = "/etc/atuin"; + + environment.etc = lib.mkMerge [ + (lib.mkIf (cfg.settings != { }) { + "atuin/config.toml".source = settingsFile; + }) + + (lib.mkIf (cfg.themes != { }) ( + builtins.mapAttrs' ( + name: theme: + lib.nameValuePair "atuin/themes/${name}.toml" { + source = + if builtins.isString theme then + pkgs.writeText "atuin-theme-${name}" theme + else if builtins.isPath theme || lib.isStorePath theme then + theme + else + tomlFormat.generate "atuin-theme-${name}" theme; + } + ) cfg.themes + )) + ]; + + programs.bash.interactiveShellInit = lib.mkIf cfg.enableBashIntegration '' + if [[ :$SHELLOPTS: =~ :(vi|emacs): ]]; then + eval "$(${lib.getExe cfg.package} init bash ${escapeShellArgs cfg.flags})" + fi + ''; + + programs.zsh.interactiveShellInit = lib.mkIf cfg.enableZshIntegration '' + if [[ $options[zle] = on ]]; then + eval "$(${lib.getExe cfg.package} init zsh ${escapeShellArgs cfg.flags})" + fi + ''; + + programs.fish.interactiveShellInit = lib.mkIf cfg.enableFishIntegration '' + ${lib.getExe cfg.package} init fish ${escapeShellArgs cfg.flags} | source + ''; + + systemd = lib.mkIf (cfg.daemon.enable && pkgs.stdenv.hostPlatform.isLinux) { + user.services.atuin-daemon = { + unitConfig = { + Description = "Atuin daemon"; + Requires = [ "atuin-daemon.socket" ]; + }; + serviceConfig = { + ExecStart = "${lib.getExe cfg.package} daemon start"; + Environment = [ "ATUIN_LOG=${cfg.daemon.logLevel}" ]; + Restart = "on-failure"; + RestartSteps = 3; + RestartMaxDelaySec = 6; + }; + }; + + user.sockets.atuin-daemon = { + unitConfig = { + Description = "Atuin daemon socket"; + }; + socketConfig = { + ListenStream = "%t/atuin/atuin.sock"; + SocketMode = "0640"; + DirectoryMode = "0740"; + RemoveOnStop = true; + }; + wantedBy = [ "sockets.target" ]; + }; + }; + }; + + meta.maintainers = cfg.package.meta.maintainers; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 8a1173f52cd5..11f3cd49ea44 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -236,6 +236,7 @@ in atticd = runTest ./atticd.nix; attr = pkgs.callPackage ./attr.nix { }; atuin = runTest ./atuin.nix; + atuin-programs = runTest ./atuin-programs.nix; audiobookshelf = runTest ./audiobookshelf.nix; audit = runTest ./audit.nix; audit-testsuite = runTest ./audit-testsuite.nix; diff --git a/nixos/tests/atuin-programs.nix b/nixos/tests/atuin-programs.nix new file mode 100644 index 000000000000..14477bae9b08 --- /dev/null +++ b/nixos/tests/atuin-programs.nix @@ -0,0 +1,39 @@ +{ pkgs, ... }: +{ + name = "atuin"; + meta.maintainers = pkgs.atuin.meta.maintainers; + + nodes.machine = { + programs = { + bash.enable = true; + fish.enable = true; + zsh.enable = true; + + atuin = { + enable = true; + settings = { + auto_sync = false; + }; + }; + }; + }; + + testScript = '' + start_all() + machine.wait_for_unit("default.target") + + # Check atuin is installed + machine.succeed("atuin --version") + + # Check shell integration - verify the init scripts can be sourced without error + machine.succeed("bash -c 'eval \"$(atuin init bash)\"'") + machine.succeed("zsh -c 'eval \"$(atuin init zsh)\"'") + machine.succeed("fish -c 'atuin init fish | source'") + + # Verify config file was created + machine.succeed("grep -q 'auto_sync = false' /etc/atuin/config.toml") + + # Verify daemon socket unit is enabled + machine.succeed("systemctl --user --machine=root@ is-enabled atuin-daemon.socket") + ''; +} diff --git a/pkgs/by-name/at/atuin/package.nix b/pkgs/by-name/at/atuin/package.nix index ebb7e47cf50d..dac5a4961653 100644 --- a/pkgs/by-name/at/atuin/package.nix +++ b/pkgs/by-name/at/atuin/package.nix @@ -58,7 +58,7 @@ rustPlatform.buildRustPackage (finalAttrs: { passthru = { tests = { - inherit (nixosTests) atuin; + inherit (nixosTests) atuin atuin-programs; }; updateScript = nix-update-script { }; };