From 8dea37e1d510ee3687af092e6d08db385001551d Mon Sep 17 00:00:00 2001 From: Michael Ross Date: Tue, 19 May 2026 22:15:56 +0100 Subject: [PATCH] feat(wrapperModules.ghostty): init Writes settings to a Nix store config file and loads it via --config-default-files=false --config-file=, isolating the wrapper from the host config in both installed and ephemeral use. The host config path varies by platform: ~/.config/ghostty/config on Linux, and ~/Library/Application Support/com.mitchellh.ghostty/config on macOS. Ghostty +actions and --help use minimal option parsers that reject unknown flags and exit silently, so argv0type bypasses flag injection for these. +show-config and +validate-config are subject to the same parser limitation and will show the host config rather than the generated one. On macOS, ghostty-bin is used by default (the pre-built binary distribution recommended for nix-darwin by the Ghostty installation docs). --- maintainers/default.nix | 5 ++ wrapperModules/g/ghostty/check.nix | 48 ++++++++++++ wrapperModules/g/ghostty/module.nix | 109 ++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+) create mode 100644 wrapperModules/g/ghostty/check.nix create mode 100644 wrapperModules/g/ghostty/module.nix diff --git a/maintainers/default.nix b/maintainers/default.nix index 7fb2f569..a8762dc5 100644 --- a/maintainers/default.nix +++ b/maintainers/default.nix @@ -111,4 +111,9 @@ github = "allen-liaoo"; githubId = 16383622; }; + trustworthyadult = { + name = "Michael Ross"; + github = "TrustworthyAdult"; + githubId = 104172948; + }; } diff --git a/wrapperModules/g/ghostty/check.nix b/wrapperModules/g/ghostty/check.nix new file mode 100644 index 00000000..ea3c5639 --- /dev/null +++ b/wrapperModules/g/ghostty/check.nix @@ -0,0 +1,48 @@ +{ + pkgs, + self, + tlib, + ... +}: +let + inherit (tlib) isFile fileContains test; + wrapper = self.wrappers.ghostty.wrap { + inherit pkgs; + settings = { + font-size = 14; + theme = "Catppuccin Mocha"; + window-decoration = false; + keybind = [ + "ctrl+a>-=new_split:down" + "ctrl+a>==new_split:right" + ]; + }; + }; + configFile = "${wrapper}/ghostty-config"; +in +test { wrapper = "ghostty"; } { + "ghostty wrapper binary should exist" = [ (isFile "${wrapper}/bin/ghostty") ]; + + "ghostty wrapper should disable default config and point to generated file" = [ + (fileContains "${wrapper}/bin/ghostty" "--config-default-files=false") + (fileContains "${wrapper}/bin/ghostty" "--config-file=") + ]; + + "ghostty wrapper should bypass config flags for +actions and --help" = [ + (fileContains "${wrapper}/bin/ghostty" "_ghostty_arg") + (fileContains "${wrapper}/bin/ghostty" "--help") + ]; + + "ghostty config file should exist" = [ (isFile configFile) ]; + + "ghostty settings should appear in generated config" = [ + (fileContains configFile "font-size = 14") + (fileContains configFile "theme = Catppuccin Mocha") + (fileContains configFile "window-decoration = false") + ]; + + "ghostty list settings should serialize as duplicate keys" = [ + (fileContains configFile "keybind = ctrl\\+a>-=new_split:down") + (fileContains configFile "keybind = ctrl\\+a>==new_split:right") + ]; +} diff --git a/wrapperModules/g/ghostty/module.nix b/wrapperModules/g/ghostty/module.nix new file mode 100644 index 00000000..c435a355 --- /dev/null +++ b/wrapperModules/g/ghostty/module.nix @@ -0,0 +1,109 @@ +{ + config, + wlib, + lib, + pkgs, + ... +}: +let + toGhosttyConf = lib.generators.toKeyValue { + listsAsDuplicateKeys = true; + mkKeyValue = lib.generators.mkKeyValueDefault { + mkValueString = v: if builtins.isBool v then lib.boolToString v else toString v; + } " = "; + }; +in +{ + imports = [ wlib.modules.default ]; + + options.settings = lib.mkOption { + type = + let + atom = lib.types.oneOf [ + lib.types.bool + lib.types.float + lib.types.int + lib.types.str + ]; + in + lib.types.attrsOf (lib.types.either (lib.types.listOf atom) atom); + default = { }; + example = lib.literalExpression '' + { + font-size = 14; + theme = "Catppuccin Mocha"; + window-decoration = false; + keybind = [ + "ctrl+a>-=new_split:down" + "ctrl+a>==new_split:right" + ]; + } + ''; + description = '' + Ghostty configuration written to a generated config file. The wrapper + passes {option}`--config-default-files=false` and + {option}`--config-file=`, so the host config is never loaded + -- including when the wrapper is used ephemerally on a machine that has + its own Ghostty config. The host config path varies by platform: + {file}`~/.config/ghostty/config` on Linux, and + {file}`~/Library/Application Support/com.mitchellh.ghostty/config` on + macOS. + See for all available options. + + Note: if you pass an additional {option}`--config-file` flag at runtime, + Ghostty will merge it on top of the generated config (later files take + precedence for conflicting keys). + + Note: {command}`ghostty +show-config` and {command}`ghostty + +validate-config` bypass the generated config entirely (their option + parsers reject {option}`--config-file`), so they will show the host + config if one exists -- this affects both installed and ephemeral use. + + On macOS, `ghostty-bin` is used by default, which is the pre-built + binary distribution [recommended for nix-darwin](https://ghostty.org/docs/install/binary) + by the Ghostty installation docs. + ''; + }; + + config = { + package = lib.mkDefault (if pkgs.stdenv.isDarwin then pkgs.ghostty-bin else pkgs.ghostty); + constructFiles.ghosttyConfig = { + content = toGhosttyConf config.settings; + relPath = "${config.binName}-config"; + }; + addFlag = [ + "--config-default-files=false" + "--config-file=${config.constructFiles.ghosttyConfig.path}" + ]; + # Ghostty has no environment variable for specifying a config file, so CLI + # flags are the only option: --config-default-files=false prevents loading + # the host's ~/.config/ghostty/config (including on machines where one + # exists), and --config-file points at the generated one. + # + # However, Ghostty +actions and --help have their own minimal Options + # struct with no _diagnostics field. Any flag unrecognised by the action + # parser causes error.InvalidField and a silent exit with no output. + # argv0type detects these and execs Ghostty directly without injecting any + # flags. + # + # Consequence: +show-config and +validate-config will show the host's + # ~/.config/ghostty/config if present, not the generated config, because + # their option parsers also reject --config-file. This applies equally to + # installed and ephemeral use. + argv0type = + let + binPath = lib.escapeShellArg config.wrapperPaths.input; + in + cmd: '' + for _ghostty_arg in "$@"; do + case "$_ghostty_arg" in + +*|--help) exec -a "$0" ${binPath} "$@";; + esac + done + exec -a "$0" ${cmd} + ''; + meta.description = "Ghostty terminal emulator"; + meta.maintainers = [ wlib.maintainers.trustworthyadult ]; + meta.platforms = lib.platforms.linux ++ lib.platforms.darwin; + }; +}