diff --git a/flake.nix b/flake.nix index a84ead5..3a02e3e 100644 --- a/flake.nix +++ b/flake.nix @@ -46,31 +46,48 @@ }; utils = import ./utils.nix {inherit inputs system sources;}; - - # Discover hosts: all subdirectories of hosts/ - hosts = builtins.attrNames (nixpkgs.lib.filterAttrs - (_: type: type == "directory") - (builtins.readDir ./hosts)); - - nixosConfigs = builtins.listToAttrs (map (host: { - name = host; - value = utils.mkHost ./hosts/${host}/configuration.nix; - }) hosts); in { - nixosConfigurations = nixosConfigs; + nixosConfigurations = { + # desktop + muon = utils.mkHost ./hosts/muon/configuration.nix; + + # laptop + muop = utils.mkHost ./hosts/muop/configuration.nix; + + # server + muho = utils.mkHost ./hosts/muho/configuration.nix; + + # vps + mups = utils.mkHost ./hosts/mups/configuration.nix; + + # vm + muvm = utils.mkHost ./hosts/muvm/configuration.nix; + + # work + murk = utils.mkHost ./hosts/murk/configuration.nix; + + # work desktop + musk = utils.mkHost ./hosts/musk/configuration.nix; + + # lenovo + muvo = utils.mkHost ./hosts/muvo/configuration.nix; + + # installer + muin = utils.mkHost ./hosts/muin/configuration.nix; + }; homeManagerModules.default = ./modules/home; - # Standalone HM configurations — one per host. - # osConfig is injected so all modules using it continue to work. - # Use: home-manager switch --flake '.#muon@' - homeConfigurations = builtins.listToAttrs (map (host: { - name = "muon@${host}"; - value = utils.mkHome { - hostConfig = nixosConfigs.${host}; - homeFile = ./hosts/${host}/home.nix; - }; - }) hosts); + # Expose each host's HM activation package so `home-manager switch --flake .#muon@` + # works without a full NixOS rebuild. Extracted from the already-evaluated + # nixosConfiguration, so osConfig remains fully populated. + homeConfigurations = nixpkgs.lib.mapAttrs' (host: nixos: + nixpkgs.lib.nameValuePair "muon@${host}" { + activationPackage = nixos.config.home-manager.users.muon.home.activationPackage; + } + ) (nixpkgs.lib.filterAttrs + (_: nixos: nixos.config.home-manager.users ? muon) + inputs.self.outputs.nixosConfigurations); colmena = { meta = { diff --git a/hosts/muho/configuration.nix b/hosts/muho/configuration.nix index 3e41fbb..8e3315d 100644 --- a/hosts/muho/configuration.nix +++ b/hosts/muho/configuration.nix @@ -50,7 +50,6 @@ in { mods.server.ntfy.enable = true; mods.server.lemmy.enable = true; mods.server.audio.enable = true; - mods.server.murmur.enable = true; mods.server.atuin.enable = true; mods.server.seedbox.enable = true; diff --git a/hosts/muho/home.nix b/hosts/muho/home.nix index 19bfdad..84075d4 100644 --- a/hosts/muho/home.nix +++ b/hosts/muho/home.nix @@ -10,8 +10,6 @@ in { mods.xdg.enable = true; mods.social.enable = false; mods.i3.enable = false; - mods.terminal.wezterm.enable = true; - mods.terminal.nushell.enable = true; mods.terminal.zsh.enable = true; mods.terminal.emulator.enable = false; mods.terminal.development.enable = true; diff --git a/modules/home/desktop/i3.nix b/modules/home/desktop/i3.nix index a3d7d3c..524aaa8 100644 --- a/modules/home/desktop/i3.nix +++ b/modules/home/desktop/i3.nix @@ -5,97 +5,22 @@ osConfig, ... }: let - # Bootstrap script that runs as the first program inside a new wezterm window. - # It sets up the three tabs synchronously before the shell prompt appears, - # so by the time the window is visible the layout is already complete. - # Receives the project path as $1. - wezterm-zmenu-init = pkgs.writeShellApplication { - name = "wezterm-zmenu-init"; - runtimeInputs = [pkgs.wezterm pkgs.zsh]; - text = '' - ZPATH=$1 - WIN=$(wezterm cli list --format json 2>/dev/null \ - | tr ',' '\n' \ - | awk -v pane="$WEZTERM_PANE" ' - /window_id/ { gsub(/.*: */,""); w=int($0) } - /pane_id/ { gsub(/.*: */,""); if (int($0)==pane) { print w; exit } }') - P1=$(wezterm cli spawn --window-id "$WIN" --cwd "$ZPATH" -- direnv exec . nvim) - wezterm cli spawn --window-id "$WIN" --cwd "$ZPATH" > /dev/null - wezterm cli spawn --window-id "$WIN" --cwd "$ZPATH" -- lazygit > /dev/null - wezterm cli activate-tab --tab-index 0 --pane-id "$P1" 2>/dev/null - ''; - }; - zmenu = with pkgs; writeShellApplication { name = "zmenu"; - runtimeInputs = - [zoxide wmctrl i3 rofi zsh] - ++ lib.optionals config.mods.terminal.wezterm.enable [wezterm] - ++ lib.optionals (!config.mods.terminal.wezterm.enable) [zellij alacritty]; - text = - if config.mods.terminal.wezterm.enable - then '' - ZPATH=$(zoxide query -l | sed -e "s|$HOME/||g" | rofi -dmenu) - [[ -z "$ZPATH" ]] && exit - ZSESH=$(echo "$ZPATH" | tr / -) - - # Per-workspace socket registry so we can focus existing workspaces. - SOCKDIR="$XDG_RUNTIME_DIR/zmenu-wez" - mkdir -p "$SOCKDIR" - SOCKFILE="$SOCKDIR/$ZSESH" - - SOCK="" - if [[ -f "$SOCKFILE" ]]; then - SOCK=$(cat "$SOCKFILE") - # Extract the PID from the socket path (gui-sock-) and check - # if the process is still alive. Dead sockets linger on disk. - SOCK_PID=''${SOCK##*-} - if ! kill -0 "$SOCK_PID" 2>/dev/null; then - rm -f "$SOCKFILE" - SOCK="" - fi - fi - - if [[ -n "$SOCK" ]]; then - # Workspace open: raise its window by matching the wezterm PID in - # wmctrl's process list, then activate a pane to ensure focus. - SOCK_PID=''${SOCK##*-} - WIN_HEX=$(wmctrl -lp | awk -v pid="$SOCK_PID" '$3==pid {print $1; exit}') - wmctrl -i -a "$WIN_HEX" - PANE=$(WEZTERM_UNIX_SOCKET="$SOCK" wezterm cli list --format json 2>/dev/null \ - | tr ',' '\n' \ - | awk '/pane_id/{gsub(/.*: */,""); print int($0); exit}') - WEZTERM_UNIX_SOCKET="$SOCK" wezterm cli activate-pane --pane-id "$PANE" 2>/dev/null - else - # Not open: the init script runs as the first program inside the new - # window. It sets up all tabs synchronously before the shell prompt - # appears, so the layout is complete before anything is visible. - wezterm start --always-new-process \ - --workspace "$ZSESH" --cwd "$HOME/$ZPATH" \ - -- ${lib.getExe wezterm-zmenu-init} "$HOME/$ZPATH" & - WEZ_PID=$! - - # Record the socket for future focus calls. - for _ in $(seq 50); do - sleep 0.1 - [[ -S /run/user/$UID/wezterm/gui-sock-$WEZ_PID ]] && break - done - echo "/run/user/$UID/wezterm/gui-sock-$WEZ_PID" > "$SOCKFILE" - fi - '' - else '' - ZPATH=$(zoxide query -l | sed -e "s|$HOME/||g" | rofi -dmenu) - [[ -z "$ZPATH" ]] && exit - ZSESH=$(echo "$ZPATH" | tr / -) - ZWIND=$(wmctrl -l | grep "$ZSESH$" || echo "") - cd "$HOME/$ZPATH" - if [[ -z "$ZWIND" ]]; then - alacritty -T "$ZSESH" -e zsh -c "zellij -s $ZSESH -n dev || zellij a $ZSESH" - else - wmctrl -a "$ZSESH" - fi - ''; + runtimeInputs = [zellij zoxide wmctrl i3 rofi alacritty zsh]; + text = '' + ZPATH=$(zoxide query -l | sed -e "s|$HOME/||g" | rofi -dmenu) + [[ -z "$ZPATH" ]] && exit + ZSESH=$(echo "$ZPATH" | tr / -) + ZWIND=$(wmctrl -l | grep "$ZSESH$" || echo "") + cd "$ZPATH" + if [[ -z "$ZWIND" ]]; then + alacritty -T "$ZSESH" -e zsh -c "zellij -s $ZSESH -n dev || zellij a $ZSESH" + else + wmctrl -a "$ZSESH" + fi + ''; }; in with lib; { @@ -169,10 +94,7 @@ in enable = true; config = { modifier = modifier; - terminal = - if config.mods.terminal.wezterm.enable - then "wezterm" - else "alacritty"; + terminal = "alacritty"; menu = "rofi -show drun"; window = { diff --git a/modules/home/terminal/nushell.nix b/modules/home/terminal/nushell.nix index dbdab6d..b7e38d1 100644 --- a/modules/home/terminal/nushell.nix +++ b/modules/home/terminal/nushell.nix @@ -23,43 +23,35 @@ in { shellAliases = aliases; - # vi mode + sensible defaults via flat assignments (avoids clobbering other modules) - settings = { - show_banner = false; - edit_mode = "vi"; - cursor_shape = { - vi_insert = "line"; - vi_normal = "block"; - }; - history = { - max_size = 2097152; - sync_on_enter = true; - file_format = "sqlite"; - isolation = false; - }; - completions = { - case_sensitive = false; - quick = true; - partial = true; - algorithm = "fuzzy"; - }; - table.mode = "rounded"; - }; + # vi mode + sensible defaults + extraConfig = '' + $env.config = { + show_banner: false + edit_mode: vi - # Append the / keybinding after all integrations (including atuin) are sourced, - # so _atuin_search_cmd is defined when this runs. - extraConfig = lib.mkAfter '' - $env.config = ( - $env.config | upsert keybindings ( - $env.config.keybindings | append { - name: atuin_search_vi_normal - modifier: none - keycode: char_/ - mode: vi_normal - event: { send: executehostcommand cmd: (_atuin_search_cmd) } - } - ) - ) + cursor_shape: { + vi_insert: line + vi_normal: block + } + + history: { + max_size: 2097152 + sync_on_enter: true + file_format: "sqlite" + isolation: false + } + + completions: { + case_sensitive: false + quick: true + partial: true + algorithm: "fuzzy" + } + + table: { + mode: rounded + } + } ''; # Carry over zsh session variables diff --git a/modules/home/terminal/tools.nix b/modules/home/terminal/tools.nix index 88b4745..b3c00dd 100644 --- a/modules/home/terminal/tools.nix +++ b/modules/home/terminal/tools.nix @@ -42,7 +42,6 @@ in atuin = { enable = true; enableZshIntegration = true; - enableNushellIntegration = true; flags = ["--disable-up-arrow"]; settings = { sync_frequency = "5m"; diff --git a/modules/home/terminal/wezterm.nix b/modules/home/terminal/wezterm.nix index 8ec3555..0314889 100644 --- a/modules/home/terminal/wezterm.nix +++ b/modules/home/terminal/wezterm.nix @@ -28,155 +28,70 @@ # vi directions: key, arrow alias, wezterm direction name viDirs = [ - { - key = "h"; - arrow = "LeftArrow"; - dir = "Left"; - } - { - key = "j"; - arrow = "DownArrow"; - dir = "Down"; - } - { - key = "k"; - arrow = "UpArrow"; - dir = "Up"; - } - { - key = "l"; - arrow = "RightArrow"; - dir = "Right"; - } + {key = "h"; arrow = "LeftArrow"; dir = "Left";} + {key = "j"; arrow = "DownArrow"; dir = "Down";} + {key = "k"; arrow = "UpArrow"; dir = "Up";} + {key = "l"; arrow = "RightArrow"; dir = "Right";} ]; # hjkl + arrow equivalents; mkAction :: dir -> lua-action-string - viDirKeys = { - mods ? null, - indent ? " ", - mkAction, - }: + viDirKeys = {mods ? null, indent ? " ", mkAction}: lib.concatMapStrings (d: - luaKey { - inherit mods indent; - key = d.key; - action = mkAction d.dir; - } - + luaKey { - inherit mods indent; - key = d.arrow; - action = mkAction d.dir; - }) + luaKey {inherit mods indent; key = d.key; action = mkAction d.dir;} + + luaKey {inherit mods indent; key = d.arrow; action = mkAction d.dir;}) viDirs; # 1-9 tab-jump bindings - tabJumpKeys = { - mods ? null, - indent ? " ", - }: + tabJumpKeys = {mods ? null, indent ? " "}: lib.concatStrings (builtins.genList (i: luaKey { inherit mods indent; - key = toString (i + 1); + key = toString (i + 1); action = "act.ActivateTab(${toString i})"; }) 9); # hjkl + arrows for AdjustPaneSize at a given step - resizeDirKeys = { - step, - indent ? " ", - }: + resizeDirKeys = {step, indent ? " "}: lib.concatMapStrings (d: - luaKey { - inherit indent; - key = d.key; - action = "act.AdjustPaneSize({ \"${d.dir}\", ${toString step} })"; - } - + luaKey { - inherit indent; - key = d.arrow; - action = "act.AdjustPaneSize({ \"${d.dir}\", ${toString step} })"; - }) + luaKey {inherit indent; key = d.key; action = "act.AdjustPaneSize({ \"${d.dir}\", ${toString step} })";} + + luaKey {inherit indent; key = d.arrow; action = "act.AdjustPaneSize({ \"${d.dir}\", ${toString step} })";}) viDirs; # Uppercase HJKL fine-tune resize - resizeShiftKeys = { - step, - indent ? " ", - }: + resizeShiftKeys = {step, indent ? " "}: lib.concatMapStrings (d: - luaKey { - inherit indent; - key = lib.toUpper d.key; - action = "act.AdjustPaneSize({ \"${d.dir}\", ${toString step} })"; - }) + luaKey {inherit indent; key = lib.toUpper d.key; action = "act.AdjustPaneSize({ \"${d.dir}\", ${toString step} })";}) viDirs; # Wrap a lua action string so it also pops the key table (return to locked) andPop = action: "act.Multiple({ ${action}, act.PopKeyTable })"; # Standard exit bindings, given the mode's own pop key - exitKeys = { - selfKey, - indent ? " ", - }: + exitKeys = {selfKey, indent ? " "}: lib.concatStrings [ - (luaKey { - inherit indent; - key = selfKey; - action = "act.PopKeyTable"; - }) - (luaKey { - inherit indent; - key = "Escape"; - action = "act.PopKeyTable"; - }) - (luaKey { - inherit indent; - key = "Enter"; - action = "act.PopKeyTable"; - }) - (luaKey { - inherit indent; - key = "Space"; - mods = "ALT"; - action = "act.PopKeyTable"; - }) + (luaKey {inherit indent; key = selfKey; action = "act.PopKeyTable";}) + (luaKey {inherit indent; key = "Escape"; action = "act.PopKeyTable";}) + (luaKey {inherit indent; key = "Enter"; action = "act.PopKeyTable";}) + (luaKey {inherit indent; key = "Space"; mods = "ALT"; action = "act.PopKeyTable";}) ]; # ── Status bar ─────────────────────────────────────────────────────────────── - # Per-mode accent colour (base16) and hint string shown in the status bar - # Hints use nerd font glyphs: separators, icons, key labels + # Per-mode accent colour (base16) and hint string shown on the left status bar modes = { - locked = { - fg = color.base03; - hint = "⌥󱁐:󰌌 ⌥hl:󰹳 ^ud:󰹹 ⌥1-9:󰓩 ⌥n:󰤻 ⌥s:󱇳 ⌥w:󰅙 ⌥t:󰓩 ⌥p:󱃔"; - }; - normal = { - fg = color.base0D; - hint = "t:󰐕 x:󰅙 r:󰩨 c:󰆏 s:󰍉 esc:󱊷"; - }; - resize = { - fg = color.base0A; - hint = "hjkl:󰁌 HJKL:󰁎 esc:󱊷"; - }; - copy_mode = { - fg = color.base0E; - hint = "hjkl:󰹳 v:󰒆 V:󰒇 ^v:󰒈 y:󰆏 /:󰍉 n/p:󰁹 esc:󱊷"; - }; - search_mode = { - fg = color.base08; - hint = "type:󰈙 ^n/^p:󰁹 ^r:󰑓 ↵/esc:󰆐"; - }; + locked = {fg = color.base03; hint = "Alt+Space:enter Alt+hl:focus/tab Alt+1-9:tab Alt+n:split C-ud:scroll";}; + normal = {fg = color.base0D; hint = "p:pane t:tab r:resize s:scroll n:split Esc:exit";}; + pane = {fg = color.base0B; hint = "hjkl:focus n/r:split→ d:split↓ f:zoom x:close Esc:exit";}; + tab = {fg = color.base0C; hint = "hjkl:prev/next 1-9:jump n:new x:close r:rename Esc:exit";}; + resize = {fg = color.base0A; hint = "hjkl:+5 HJKL:+1 Esc:exit";}; + scroll = {fg = color.base0E; hint = "jk:line ud:half hl:page f:search Esc:exit";}; }; # Lua table literal mapping mode name -> { fg, hint } modeTableEntries = lib.concatStringsSep "\n " (lib.mapAttrsToList (name: m: '' - ["${name}"] = { fg = "${m.fg}", hint = "${m.hint}" }, - '') - modes); + ["${name}"] = { fg = "${m.fg}", hint = "${m.hint}" }, + '') modes); in { options.mods.terminal.wezterm.enable = lib.mkEnableOption "enables wezterm"; @@ -207,51 +122,6 @@ in { end) end - -- scroll_or_passthrough: Ctrl+key scrolls at the shell prompt but passes - -- the key through when a program has taken the alt screen (vim, less, fzf…). - local function scroll_or_passthrough(key, pages) - return wezterm.action_callback(function(window, pane) - if pane:is_alt_screen_active() then - window:perform_action(act.SendKey({ key = key, mods = "CTRL" }), pane) - else - window:perform_action(act.ScrollByPage(pages), pane) - end - end) - end - - -- tab_is_zoomed: true if any pane in the current tab is zoomed. - -- Used by move_vertical to decide whether to carry zoom forward. - local function tab_is_zoomed(pane) - for _, info in ipairs(pane:tab():panes_with_info()) do - if info.is_zoomed then return true end - end - return false - end - - -- move_vertical: move focus Up/Down. - -- If any pane in the tab is currently zoomed (zoom-mode is "on" for this - -- tab), zoom the destination pane too — stacked-panel behaviour. - -- Otherwise just move focus plainly. - local function move_vertical(direction) - return wezterm.action_callback(function(window, pane) - local neighbour = pane:tab():get_pane_direction(direction) - if neighbour == nil then return end - if tab_is_zoomed(pane) then - -- unzoom_on_switch_pane unzooms the current pane when we move; - -- then SetPaneZoomState(true) zooms the newly active pane. - window:perform_action( - act.Multiple({ - act.ActivatePaneDirection(direction), - act.SetPaneZoomState(true), - }), - pane - ) - else - window:perform_action(act.ActivatePaneDirection(direction), pane) - end - end) - end - -- ─── Status bar ──────────────────────────────────────────────────────────── -- Per-mode accent colour and hint text, generated from Nix/stylix palette @@ -261,107 +131,27 @@ in { local bg_bar = "${color.base01}" local fg_text = "${color.base05}" - local fg_dim = "${color.base03}" local bg_main = "${color.base00}" - -- Render hints with the key in the mode accent colour and the icon dimmed. - -- Each hint token is "key:icon"; we colour them individually. - local function render_hints(hint, accent) - local cells = {} - for token in hint:gmatch("%S+") do - local key, icon = token:match("^(.-):(.)$") - if key and icon then - table.insert(cells, { Background = { Color = bg_bar } }) - table.insert(cells, { Foreground = { Color = accent } }) - table.insert(cells, { Text = key }) - table.insert(cells, { Foreground = { Color = fg_text } }) - table.insert(cells, { Text = ":" .. icon .. " " }) - else - table.insert(cells, { Background = { Color = bg_bar } }) - table.insert(cells, { Foreground = { Color = fg_dim } }) - table.insert(cells, { Text = token .. " " }) - end - end - return cells - end - - wezterm.on("format-window-title", function(tab, _pane, _tabs, _panes, _config) - local ok, win = pcall(function() return wezterm.mux.get_window(tab.window_id) end) - if ok and win then - local ws = win:get_workspace() - if ws ~= "default" then - return ws .. " — " .. tab.active_pane.title - end - end - return tab.active_pane.title - end) - - -- format-tab-title: show "rank/total title" when the active tab is in - -- stacked (zoom) mode; otherwise show plain " title ". - -- Returning a cell table gives us explicit padding and colours; - -- returning a plain string loses the retro tab bar's built-in spacing. - local tab_bg_active = "${color.base0D}" - local tab_fg_active = "${color.base00}" - local tab_bg_inactive = "${color.base01}" - local tab_fg_inactive = "${color.base03}" - - wezterm.on("format-tab-title", function(tab, _tabs, panes, _config, _hover, _max_width) - local title = (tab.tab_title and tab.tab_title ~= "") and tab.tab_title or tab.active_pane.title - local bg = tab.is_active and tab_bg_active or tab_bg_inactive - local fg = tab.is_active and tab_fg_active or tab_fg_inactive - - -- Only compute stack position for the active tab. - -- The `panes` event parameter only contains the active pane snapshot - -- and lacks `top`, so we use wezterm.mux.get_tab():panes_with_info() - -- which returns full PaneInformation including top/is_zoomed/is_active. - if tab.is_active then - local mux_tab = wezterm.mux.get_tab(tab.tab_id) - if mux_tab then - local infos = mux_tab:panes_with_info() - table.sort(infos, function(a, b) return a.top < b.top end) - - local any_zoomed = false - local active_rank, total = 0, 0 - for _, p in ipairs(infos) do - total = total + 1 - if p.is_active then active_rank = total end - if p.is_zoomed then any_zoomed = true end - end - - if any_zoomed and total > 1 and active_rank > 0 then - title = active_rank .. "/" .. total .. " " .. title - end - end - end - - return { - { Background = { Color = bg } }, - { Foreground = { Color = fg } }, - { Text = " " .. title .. " " }, - } - end) - wezterm.on("update-status", function(window, _pane) local kt = window:active_key_table() local mode = kt or "locked" local info = mode_info[mode] or mode_info["locked"] - window:set_left_status("") + -- Left: coloured mode badge + hint line + window:set_left_status(wezterm.format({ + { Background = { Color = info.fg } }, + { Foreground = { Color = bg_main } }, + { Attribute = { Intensity = "Bold" } }, + { Text = " " .. mode:upper() .. " " }, + "ResetAttributes", + { Background = { Color = bg_bar } }, + { Foreground = { Color = fg_text } }, + { Text = " " .. info.hint .. " " }, + "ResetAttributes", + })) - -- Right: coloured hints then mode badge - local cells = { { Text = " " } } - for _, c in ipairs(render_hints(info.hint, info.fg)) do - table.insert(cells, c) - end - table.insert(cells, "ResetAttributes") - table.insert(cells, { Background = { Color = info.fg } }) - table.insert(cells, { Foreground = { Color = bg_bar } }) - table.insert(cells, { Text = "" }) - table.insert(cells, { Foreground = { Color = bg_main } }) - table.insert(cells, { Attribute = { Intensity = "Bold" } }) - table.insert(cells, { Text = " " .. mode:upper() .. " " }) - table.insert(cells, "ResetAttributes") - window:set_right_status(wezterm.format(cells)) + window:set_right_status("") end) -- ─── Appearance ──────────────────────────────────────────────────────────── @@ -380,29 +170,7 @@ in { config.default_cursor_style = "BlinkingBar" config.hide_tab_bar_if_only_one_tab = false config.use_fancy_tab_bar = false - config.tab_max_width = 24 - - config.colors = { - tab_bar = { - background = "${color.base01}", - active_tab = { bg_color = "${color.base0D}", fg_color = "${color.base00}", intensity = "Bold" }, - inactive_tab = { bg_color = "${color.base01}", fg_color = "${color.base03}" }, - inactive_tab_hover = { bg_color = "${color.base02}", fg_color = "${color.base04}" }, - new_tab = { bg_color = "${color.base01}", fg_color = "${color.base03}" }, - new_tab_hover = { bg_color = "${color.base02}", fg_color = "${color.base04}" }, - }, - } - config.tab_bar_at_bottom = false - - -- ─── SSH Domains ─────────────────────────────────────────────────────────── - - config.ssh_domains = { - { - name = "muho", - remote_address = "muho", - username = "muon", - }, - } + config.tab_bar_at_bottom = true -- ─── Shell ───────────────────────────────────────────────────────────────── @@ -414,53 +182,30 @@ in { config.scrollback_lines = 10000 - -- ─── Stacked panes ───────────────────────────────────────────────────────── - -- When switching away from a zoomed pane, unzoom it automatically. - -- Paired with stack_focus (Alt+j/k) this gives Zellij-style stacked panels. - config.unzoom_on_switch_pane = true - -- ─── Key bindings ────────────────────────────────────────────────────────── -- -- Default mode = "locked" (all keys pass through to the shell). - -- Alt+Space enters "normal" mode. - -- From normal: t→new tab x→close tab p/n→split r→resize mode + -- Alt+Space enters "normal" mode (like Zellij). + -- From normal: p→pane t→tab r→resize s→scroll -- Esc / Enter / Alt+Space → back to locked from any mode. config.disable_default_key_bindings = true config.keys = { -- Alt+h/l: move focus or switch tab at edge (Zellij MoveFocusOrTab) - -- Alt+j/k: initial bindings (overridden below by stacked-focus variants) + -- Alt+j/k: move focus between panes ${viDirKeys { - mods = "ALT"; - indent = " "; - mkAction = d: - if d == "Left" || d == "Right" - then "move_focus_or_tab(\"${d}\")" - else "act.ActivatePaneDirection(\"${d}\")"; - }} + mods = "ALT"; + indent = " "; + mkAction = d: + if d == "Left" || d == "Right" + then "move_focus_or_tab(\"${d}\")" + else "act.ActivatePaneDirection(\"${d}\")"; + }} -- Alt+1-9: jump to tab by number - ${tabJumpKeys { - mods = "ALT"; - indent = " "; - }} + ${tabJumpKeys {mods = "ALT"; indent = " ";}} -- Alt+n: new pane (split right) { key = "n", mods = "ALT", action = act.SplitHorizontal({ domain = "CurrentPaneDomain" }) }, - -- Alt+s: split below, then zoom the new pane so stacked mode starts. - -- Two separate perform_action calls: the split runs first and focuses - -- the new pane, then the second call zooms window:active_pane() which - -- is now the new pane (not the original `pane` argument). - { key = "s", mods = "ALT", action = wezterm.action_callback(function(window, pane) - window:perform_action(act.SplitVertical({ domain = "CurrentPaneDomain" }), pane) - window:perform_action(act.SetPaneZoomState(true), window:active_pane()) - end) - }, - -- Alt+j/k: move_vertical — plain focus move normally, stacked zoom - -- when any pane in the tab is already zoomed (mirrors Alt+f state). - { key = "j", mods = "ALT", action = move_vertical("Down") }, - { key = "k", mods = "ALT", action = move_vertical("Up") }, - { key = "DownArrow", mods = "ALT", action = move_vertical("Down") }, - { key = "UpArrow", mods = "ALT", action = move_vertical("Up") }, -- Alt+f: toggle zoom { key = "f", mods = "ALT", action = act.TogglePaneZoomState }, -- Alt+[/]: rotate panes @@ -473,31 +218,11 @@ in { -- Alt+i/o: reorder tabs { key = "i", mods = "ALT", action = act.MoveTabRelative(-1) }, { key = "o", mods = "ALT", action = act.MoveTabRelative(1) }, - -- Alt+w: close current pane; if tab was in stacked (zoom) mode, re-zoom - -- the new active pane so zoom mode persists across close. - -- confirm=false because the callback must run synchronously after close; - -- a confirm dialog would make the zoom fire before the user answers. - -- Guard: only re-zoom when there will still be panes left after close. - { key = "w", mods = "ALT", action = wezterm.action_callback(function(window, pane) - local panes = pane:tab():panes() - local was_zoomed = tab_is_zoomed(pane) - window:perform_action(act.CloseCurrentPane({ confirm = false }), pane) - if was_zoomed and #panes > 1 then - window:perform_action(act.SetPaneZoomState(true), window:active_pane()) - end - end) - }, - -- Alt+p: command palette - { key = "p", mods = "ALT", action = act.ActivateCommandPalette }, - -- Alt+t: tab navigator (searchable tab list) - { key = "t", mods = "ALT", action = act.ShowTabNavigator }, -- Alt+q: quit { key = "q", mods = "ALT", action = act.QuitApplication }, - -- Ctrl+U/D: scroll half page when at the shell prompt; pass through - -- to the running program when it is using the alt screen (vim, less, - -- fzf, etc. switch to the alt screen and need the raw key). - { key = "u", mods = "CTRL", action = scroll_or_passthrough("u", -0.5) }, - { key = "d", mods = "CTRL", action = scroll_or_passthrough("d", 0.5) }, + -- Ctrl+U/D: scroll half page (matches Alacritty) + { key = "u", mods = "CTRL", action = act.ScrollByPage(-0.5) }, + { key = "d", mods = "CTRL", action = act.ScrollByPage(0.5) }, -- Copy / paste { key = "c", mods = "CTRL|SHIFT", action = act.CopyTo("Clipboard") }, { key = "v", mods = "CTRL|SHIFT", action = act.PasteFrom("Clipboard") }, @@ -509,86 +234,77 @@ in { -- ── NORMAL ───────────────────────────────────────────────────────────── normal = { - { key = "t", action = ${andPop "act.SpawnTab(\"CurrentPaneDomain\")"} }, - { key = "x", action = ${andPop "act.CloseCurrentTab({ confirm = true })"} }, + { key = "p", action = act.ActivateKeyTable({ name = "pane", one_shot = false }) }, + { key = "t", action = act.ActivateKeyTable({ name = "tab", one_shot = false }) }, { key = "r", action = act.ActivateKeyTable({ name = "resize", one_shot = false }) }, - { key = "c", action = ${andPop "act.ActivateCopyMode"} }, - { key = "s", action = ${andPop "act.Search({ CaseSensitiveString = \"\" })"} }, + { key = "s", action = act.ActivateKeyTable({ name = "scroll", one_shot = false }) }, + { key = "n", action = ${andPop "act.SplitHorizontal({ domain = \"CurrentPaneDomain\" })"} }, ${exitKeys {selfKey = "Escape";}} }, + -- ── PANE ─────────────────────────────────────────────────────────────── + pane = { + ${viDirKeys { + mkAction = d: + if d == "Left" || d == "Right" + then "move_focus_or_tab(\"${d}\")" + else "act.ActivatePaneDirection(\"${d}\")"; + }} + { key = "n", action = ${andPop "act.SplitHorizontal({ domain = \"CurrentPaneDomain\" })"} }, + { key = "r", action = ${andPop "act.SplitHorizontal({ domain = \"CurrentPaneDomain\" })"} }, + { key = "d", action = ${andPop "act.SplitVertical({ domain = \"CurrentPaneDomain\" })"} }, + { key = "f", action = ${andPop "act.TogglePaneZoomState"} }, + { key = "z", action = ${andPop "act.TogglePaneZoomState"} }, + { key = "x", action = ${andPop "act.CloseCurrentPane({ confirm = true })"} }, + { key = "Tab", action = act.ActivatePaneDirection("Next") }, + ${exitKeys {selfKey = "p";}} }, + + -- ── TAB ──────────────────────────────────────────────────────────────── + tab = { + { key = "h", action = act.ActivateTabRelative(-1) }, + { key = "k", action = act.ActivateTabRelative(-1) }, + { key = "j", action = act.ActivateTabRelative(1) }, + { key = "l", action = act.ActivateTabRelative(1) }, + { key = "LeftArrow", action = act.ActivateTabRelative(-1) }, + { key = "UpArrow", action = act.ActivateTabRelative(-1) }, + { key = "RightArrow", action = act.ActivateTabRelative(1) }, + { key = "DownArrow", action = act.ActivateTabRelative(1) }, + { key = "Tab", action = act.ActivateTabRelative(1) }, + ${tabJumpKeys {}} + { key = "n", action = ${andPop "act.SpawnTab(\"CurrentPaneDomain\")"} }, + { key = "x", action = ${andPop "act.CloseCurrentTab({ confirm = true })"} }, + { key = "r", action = act.PromptInputLine({ + description = "Rename tab", + action = wezterm.action_callback(function(window, _, line) + if line then window:active_tab():set_title(line) end + window:perform_action(act.PopKeyTable, window:active_pane()) + end), + }) + }, + ${exitKeys {selfKey = "t";}} }, + -- ── RESIZE ───────────────────────────────────────────────────────────── resize = { - ${resizeDirKeys {step = 5;}}${resizeShiftKeys {step = 1;}} + ${resizeDirKeys {step = 5;}}${resizeShiftKeys {step = 1;}} { key = "+", action = act.AdjustPaneSize({ "Right", 5 }) }, { key = "-", action = act.AdjustPaneSize({ "Left", 5 }) }, { key = "=", action = act.AdjustPaneSize({ "Right", 5 }) }, ${exitKeys {selfKey = "r";}} }, - -- ── COPY MODE ────────────────────────────────────────────────────────── - copy_mode = { - -- movement - { key = "h", action = act.CopyMode("MoveLeft") }, - { key = "j", action = act.CopyMode("MoveDown") }, - { key = "k", action = act.CopyMode("MoveUp") }, - { key = "l", action = act.CopyMode("MoveRight") }, - { key = "LeftArrow", action = act.CopyMode("MoveLeft") }, - { key = "DownArrow", action = act.CopyMode("MoveDown") }, - { key = "UpArrow", action = act.CopyMode("MoveUp") }, - { key = "RightArrow", action = act.CopyMode("MoveRight") }, - { key = "w", action = act.CopyMode("MoveForwardWord") }, - { key = "b", action = act.CopyMode("MoveBackwardWord") }, - { key = "e", action = act.CopyMode("MoveForwardWordEnd") }, - { key = "0", action = act.CopyMode("MoveToStartOfLine") }, - { key = "^", action = act.CopyMode("MoveToStartOfLineContent") }, - { key = "$", action = act.CopyMode("MoveToEndOfLineContent") }, - { key = "g", action = act.CopyMode("MoveToScrollbackTop") }, - { key = "G", action = act.CopyMode("MoveToScrollbackBottom") }, - { key = "Enter", action = act.CopyMode("MoveToStartOfNextLine") }, - { key = "u", mods = "CTRL", action = act.CopyMode({ MoveByPage = -0.5 }) }, - { key = "d", mods = "CTRL", action = act.CopyMode({ MoveByPage = 0.5 }) }, - { key = "b", mods = "CTRL", action = act.CopyMode("PageUp") }, - { key = "f", mods = "CTRL", action = act.CopyMode("PageDown") }, - { key = "PageUp", action = act.CopyMode("PageUp") }, - { key = "PageDown", action = act.CopyMode("PageDown") }, - -- selection - { key = "v", action = act.CopyMode({ SetSelectionMode = "Cell" }) }, - { key = "V", action = act.CopyMode({ SetSelectionMode = "Line" }) }, - { key = "v", mods = "CTRL", action = act.CopyMode({ SetSelectionMode = "Block" }) }, - { key = "o", action = act.CopyMode("MoveToSelectionOtherEnd") }, - { key = "O", action = act.CopyMode("MoveToSelectionOtherEndHoriz") }, - -- yank and exit - { key = "y", action = act.Multiple({ - act.CopyTo("ClipboardAndPrimarySelection"), - act.Multiple({ act.ScrollToBottom, act.CopyMode("Close") }), - }) - }, - -- search within copy mode - { key = "/", action = act.Search({ CaseSensitiveString = "" }) }, - { key = "n", action = act.CopyMode("NextMatch") }, - { key = "p", action = act.CopyMode("PriorMatch") }, - -- exit - { key = "q", action = act.Multiple({ act.ScrollToBottom, act.CopyMode("Close") }) }, - { key = "Escape", action = act.Multiple({ act.ScrollToBottom, act.CopyMode("Close") }) }, - { key = "c", mods = "CTRL", action = act.Multiple({ act.ScrollToBottom, act.CopyMode("Close") }) }, - }, - - -- ── SEARCH MODE ──────────────────────────────────────────────────────── - search_mode = { - -- navigate matches - { key = "Enter", action = act.CopyMode("AcceptPattern") }, - { key = "n", mods = "CTRL", action = act.CopyMode("NextMatch") }, - { key = "p", mods = "CTRL", action = act.CopyMode("PriorMatch") }, - { key = "PageUp", action = act.CopyMode("PriorMatchPage") }, - { key = "PageDown", action = act.CopyMode("NextMatchPage") }, - { key = "UpArrow", action = act.CopyMode("PriorMatch") }, - { key = "DownArrow", action = act.CopyMode("NextMatch") }, - -- cycle match type (case-sensitive / insensitive / regex) - { key = "r", mods = "CTRL", action = act.CopyMode("CycleMatchType") }, - -- clear search input - { key = "u", mods = "CTRL", action = act.CopyMode("ClearPattern") }, - -- Escape: dismiss search bar, return to copy mode at current match - { key = "Escape", action = act.CopyMode("AcceptPattern") }, - }, + -- ── SCROLL ───────────────────────────────────────────────────────────── + scroll = { + { key = "j", action = act.ScrollByLine(1) }, + { key = "k", action = act.ScrollByLine(-1) }, + { key = "d", action = act.ScrollByPage(0.5) }, + { key = "u", action = act.ScrollByPage(-0.5) }, + { key = "h", action = act.ScrollByPage(-1) }, + { key = "l", action = act.ScrollByPage(1) }, + { key = "DownArrow", action = act.ScrollByLine(1) }, + { key = "UpArrow", action = act.ScrollByLine(-1) }, + { key = "PageDown", action = act.ScrollByPage(1) }, + { key = "PageUp", action = act.ScrollByPage(-1) }, + { key = "f", action = act.Search({ CaseSensitiveString = "" }) }, + { key = "c", mods = "CTRL", action = act.ScrollToBottom }, + ${exitKeys {selfKey = "s";}} }, } return config diff --git a/modules/nixos/server/default.nix b/modules/nixos/server/default.nix index a291e7d..26912c8 100644 --- a/modules/nixos/server/default.nix +++ b/modules/nixos/server/default.nix @@ -1,8 +1,4 @@ -{ - pkgs, - lib, - ... -}: { +{ pkgs, lib, ... }: { imports = [ ./containers ./gaming @@ -29,6 +25,5 @@ ./lemmy.nix ./audio.nix ./atuin.nix - ./murmur.nix ]; } diff --git a/modules/nixos/server/murmur.nix b/modules/nixos/server/murmur.nix deleted file mode 100644 index cc5302e..0000000 --- a/modules/nixos/server/murmur.nix +++ /dev/null @@ -1,25 +0,0 @@ -{ - pkgs, - lib, - config, - ... -}: let - cfg = config.mods.server.murmur; -in - with lib; { - options.mods.server = { - murmur = { - enable = mkEnableOption { - default = false; - description = "enables murmur server"; - }; - }; - }; - - config = mkIf cfg.enable { - services.murmur = { - enable = true; - openFirewall = true; - }; - }; - } diff --git a/utils.nix b/utils.nix index b3c1b7d..fdb6c8b 100644 --- a/utils.nix +++ b/utils.nix @@ -1,7 +1,4 @@ -{ inputs, system, sources, ... }: -let - pkgs = import inputs.nixpkgs { inherit system; }; -in { +{ inputs, system, sources, ... }: { mkHost = host: inputs.nixpkgs.lib.nixosSystem { specialArgs = { inherit inputs system sources; }; @@ -14,41 +11,4 @@ in { inputs.impermanence.nixosModules.impermanence ]; }; - - # Build a standalone HM configuration for a given host NixOS config and - # home.nix file. osConfig is injected as a specialArg so all modules that - # reference it continue to work. - mkHome = { hostConfig, homeFile, username ? "muon" }: - inputs.home-manager.lib.homeManagerConfiguration { - inherit pkgs; - extraSpecialArgs = { - inherit inputs system sources; - osConfig = hostConfig.config; - }; - modules = [ - homeFile - inputs.self.outputs.homeManagerModules.default - inputs.stylix.homeManagerModules.stylix - ({ osConfig, ... }: { - home.username = username; - home.homeDirectory = "/home/${username}"; - # Mirror the stylix settings from the NixOS config so the standalone - # HM build has the same theme without re-declaring it. - stylix = { - enable = osConfig.stylix.enable; - autoEnable = osConfig.stylix.autoEnable; - base16Scheme = osConfig.stylix.base16Scheme; - image = osConfig.stylix.image; - cursor = osConfig.stylix.cursor; - # Forward individual font options; stylix derives fonts.packages - # internally as read-only so we must not forward that attr. - fonts.monospace = osConfig.stylix.fonts.monospace; - fonts.sansSerif = osConfig.stylix.fonts.sansSerif; - fonts.serif = osConfig.stylix.fonts.serif; - fonts.emoji = osConfig.stylix.fonts.emoji; - fonts.sizes = osConfig.stylix.fonts.sizes; - }; - }) - ]; - }; }