Compare commits
4 commits
d63aed1aa6
...
420cbd770f
| Author | SHA1 | Date | |
|---|---|---|---|
| 420cbd770f | |||
| ddca4e1d72 | |||
| d04b17fe52 | |||
| 1ac7506a87 |
5 changed files with 481 additions and 121 deletions
|
|
@ -2,6 +2,20 @@
|
||||||
# Monitor layout is host-specific; written by the flake per host.
|
# Monitor layout is host-specific; written by the flake per host.
|
||||||
source = ~/.config/hypr/monitors.conf
|
source = ~/.config/hypr/monitors.conf
|
||||||
|
|
||||||
|
# Virtual desktops plugin. A vdesk spans every monitor, so SUPER+1..5 switches
|
||||||
|
# all screens together instead of workspaces being pinned per monitor.
|
||||||
|
source = ~/.config/hypr/plugins.conf
|
||||||
|
|
||||||
|
plugin {
|
||||||
|
virtual-desktops {
|
||||||
|
# The startup toast would fire on every login now that mako exists.
|
||||||
|
notifyinit = 0
|
||||||
|
rememberlayout = size
|
||||||
|
# Deliberately NOT enabling cycleworkspaces: upstream states it does
|
||||||
|
# not work with more than 2 monitors, and pc has 3.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
device {
|
device {
|
||||||
name = apple-spi-keyboard
|
name = apple-spi-keyboard
|
||||||
kb_options = caps:ctrl_modifier,grp:win_space_toggle
|
kb_options = caps:ctrl_modifier,grp:win_space_toggle
|
||||||
|
|
@ -63,9 +77,15 @@ ecosystem {
|
||||||
env = QT_QPA_PLATFORMTHEME,qt6ct
|
env = QT_QPA_PLATFORMTHEME,qt6ct
|
||||||
env = QT_STYLE_OVERRIDE,Adwaita-Dark
|
env = QT_STYLE_OVERRIDE,Adwaita-Dark
|
||||||
env = XDG_SCREENSHOTS_DIR,$HOME/Pictures/Screenshots
|
env = XDG_SCREENSHOTS_DIR,$HOME/Pictures/Screenshots
|
||||||
|
# hyprshot ignores XDG_SCREENSHOTS_DIR: it reads HYPRSHOT_DIR, else XDG_PICTURES_DIR,
|
||||||
|
# else $HOME. Without this, screenshots land loose in ~/Pictures.
|
||||||
|
env = HYPRSHOT_DIR,$HOME/Pictures/Screenshots
|
||||||
|
|
||||||
# -- Autostart --
|
# -- Autostart --
|
||||||
exec-once = uwsm finalize
|
exec-once = uwsm finalize
|
||||||
|
# Boot lands on the lock screen rather than an unlocked desktop. Unlocking it
|
||||||
|
# also unlocks the keyring (see security.pam.services.hyprlock in flake.nix).
|
||||||
|
exec-once = hyprlock
|
||||||
exec-once = swaybg -c '#16181a'
|
exec-once = swaybg -c '#16181a'
|
||||||
exec-once = GDK_BACKEND=wayland eww daemon
|
exec-once = GDK_BACKEND=wayland eww daemon
|
||||||
exec-once = systemctl --user start hyprpolkitagent
|
exec-once = systemctl --user start hyprpolkitagent
|
||||||
|
|
@ -73,13 +93,27 @@ exec-once = systemctl --user start hyprpolkitagent
|
||||||
# -- Core --
|
# -- Core --
|
||||||
bind = SUPER, Return, exec, kitty
|
bind = SUPER, Return, exec, kitty
|
||||||
bind = SUPER, D, exec, fuzzel
|
bind = SUPER, D, exec, fuzzel
|
||||||
bind = SUPER, B, exec, eww open battery-popup
|
bind = SUPER, R, exec, eww open battery-popup
|
||||||
bindr = SUPER, B, exec, eww close battery-popup
|
bindr = SUPER, R, exec, eww close battery-popup
|
||||||
bind = SUPER, Q, killactive
|
bind = SUPER, Q, killactive
|
||||||
bind = SUPER, F, fullscreen, 1
|
bind = SUPER, F, fullscreen, 1
|
||||||
bind = SUPER SHIFT, Space, togglefloating
|
bind = SUPER SHIFT, Space, togglefloating
|
||||||
bind = SUPER, Escape, exec, hyprlock
|
bind = SUPER, Escape, exec, hyprlock
|
||||||
|
|
||||||
|
# Amphetamine-style stay-awake toggle: idle + sleep + lid. See flake.nix.
|
||||||
|
bind = SUPER, A, exec, caffeine
|
||||||
|
|
||||||
|
# -- Floating panels --
|
||||||
|
# Each gets its own app_id so the rule below floats only these and not every
|
||||||
|
# terminal. Quit with the TUI's own `q`; no toggle wrapper.
|
||||||
|
#
|
||||||
|
# The float/size rule is in monitors.conf, generated per host: Hyprland 0.56
|
||||||
|
# `size` takes ONLY absolute pixels -- percentages are silently ignored (they
|
||||||
|
# parse fine and then do nothing), so the value depends on the resolution.
|
||||||
|
bind = SUPER, W, exec, kitty --class panel-wifi impala
|
||||||
|
bind = SUPER, E, exec, kitty --class panel-bt bluetui
|
||||||
|
|
||||||
|
|
||||||
# -- Focus --
|
# -- Focus --
|
||||||
bind = SUPER, h, movefocus, l
|
bind = SUPER, h, movefocus, l
|
||||||
bind = SUPER, j, movefocus, d
|
bind = SUPER, j, movefocus, d
|
||||||
|
|
@ -92,18 +126,18 @@ bind = SUPER SHIFT, j, movewindow, d
|
||||||
bind = SUPER SHIFT, k, movewindow, u
|
bind = SUPER SHIFT, k, movewindow, u
|
||||||
bind = SUPER SHIFT, l, movewindow, r
|
bind = SUPER SHIFT, l, movewindow, r
|
||||||
|
|
||||||
# -- Workspaces --
|
# -- Virtual desktops (plugin dispatchers, not Hyprland workspaces) --
|
||||||
bind = SUPER, 1, workspace, 1
|
bind = SUPER, 1, vdesk, 1
|
||||||
bind = SUPER, 2, workspace, 2
|
bind = SUPER, 2, vdesk, 2
|
||||||
bind = SUPER, 3, workspace, 3
|
bind = SUPER, 3, vdesk, 3
|
||||||
bind = SUPER, 4, workspace, 4
|
bind = SUPER, 4, vdesk, 4
|
||||||
bind = SUPER, 5, workspace, 5
|
bind = SUPER, 5, vdesk, 5
|
||||||
|
|
||||||
bind = SUPER SHIFT, 1, movetoworkspace, 1
|
bind = SUPER SHIFT, 1, movetodesk, 1
|
||||||
bind = SUPER SHIFT, 2, movetoworkspace, 2
|
bind = SUPER SHIFT, 2, movetodesk, 2
|
||||||
bind = SUPER SHIFT, 3, movetoworkspace, 3
|
bind = SUPER SHIFT, 3, movetodesk, 3
|
||||||
bind = SUPER SHIFT, 4, movetoworkspace, 4
|
bind = SUPER SHIFT, 4, movetodesk, 4
|
||||||
bind = SUPER SHIFT, 5, movetoworkspace, 5
|
bind = SUPER SHIFT, 5, movetodesk, 5
|
||||||
|
|
||||||
# -- Move current workspace to another monitor --
|
# -- Move current workspace to another monitor --
|
||||||
bind = SUPER ALT, h, movecurrentworkspacetomonitor, l
|
bind = SUPER ALT, h, movecurrentworkspacetomonitor, l
|
||||||
|
|
@ -135,10 +169,8 @@ bindl = , XF86AudioNext, exec, playerctl next
|
||||||
bindl = , XF86AudioPrev, exec, playerctl previous
|
bindl = , XF86AudioPrev, exec, playerctl previous
|
||||||
|
|
||||||
# -- Screenshots --
|
# -- Screenshots --
|
||||||
# Super+S: region -> clipboard
|
# Super+Ctrl+S: focused monitor -> file + clipboard
|
||||||
bind = SUPER, S, exec, grimblast --notify copy area
|
bind = SUPER CTRL, S, exec, hyprshot -m output
|
||||||
# Super+Ctrl+S: full screen -> file + clipboard
|
# Super+Shift+S: region (screen frozen while selecting) -> file + clipboard
|
||||||
bind = SUPER CTRL, S, exec, grimblast --notify copysave screen
|
bind = SUPER SHIFT, S, exec, hyprshot -m region -z
|
||||||
# Super+Shift+S: region -> file + clipboard + open in GIMP
|
|
||||||
bind = SUPER SHIFT, S, exec, bash -c 'mkdir -p "$HOME/Pictures/Screenshots"; f="$HOME/Pictures/Screenshots/$(date +%Y-%m-%dT%H-%M-%S).png"; grimblast --notify --freeze save area "$f" && wl-copy --type image/png < "$f" && gimp "$f"'
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,114 +1,141 @@
|
||||||
vim.o.winborder = "rounded"
|
vim.o.winborder = "rounded" -- floating windows (hover docs, pickers) get rounded borders
|
||||||
vim.o.tabstop = 2
|
vim.o.tabstop = 2 -- a tab character is rendered 2 columns wide
|
||||||
vim.o.ignorecase = true
|
vim.o.ignorecase = true -- searching is case insensitive
|
||||||
vim.o.shiftwidth = 2
|
vim.o.shiftwidth = 2 -- >> and << indent by 2
|
||||||
vim.o.smartindent = true
|
vim.o.smartindent = true -- indent new lines based on the line above
|
||||||
vim.opt.wrap = false
|
vim.opt.wrap = false -- long lines run off the screen instead of wrapping
|
||||||
vim.opt.number = true
|
vim.opt.number = true -- show line numbers
|
||||||
vim.o.relativenumber = true
|
vim.o.relativenumber = true -- ...but relative to the cursor, so 5j / 5k are easy to aim
|
||||||
vim.o.termguicolors = true
|
vim.o.termguicolors = true -- 24 bit color, the colorscheme needs this
|
||||||
vim.o.undofile = true
|
vim.o.undofile = true -- keep undo history on disk, so you can undo after reopening a file
|
||||||
vim.o.incsearch = true
|
vim.o.incsearch = true -- jump to the match while you are still typing the search
|
||||||
vim.o.signcolumn = "yes"
|
vim.o.signcolumn = "yes" -- always show the left gutter (diagnostics live there) so text never jumps
|
||||||
vim.opt.clipboard = "unnamedplus"
|
vim.opt.clipboard = "unnamedplus" -- yank and paste use the system clipboard
|
||||||
vim.opt.expandtab = true
|
vim.opt.expandtab = true -- pressing tab inserts spaces, not a tab character
|
||||||
|
|
||||||
-- On NixOS the flake provides plugins (vim.g.nix_managed set in the flake);
|
-- vim.pack is neovim's built in plugin manager. it clones these repos into
|
||||||
-- on Arch, vim.pack fetches them at runtime. Both paths keep working.
|
-- stdpath("data")/site/pack the first time you start. `vim.pack.update()`
|
||||||
if not vim.g.nix_managed then
|
-- updates them.
|
||||||
vim.pack.add({
|
vim.pack.add({
|
||||||
{ src = "https://github.com/nvim-tree/nvim-web-devicons" },
|
{ src = "https://github.com/nvim-tree/nvim-web-devicons" }, -- filetype icons (needs a nerd font in your terminal)
|
||||||
{ src = "https://github.com/scottmckendry/cyberdream.nvim" },
|
{ src = "https://github.com/scottmckendry/cyberdream.nvim" }, -- the colorscheme
|
||||||
{ src = "https://github.com/stevearc/oil.nvim" },
|
{ src = "https://github.com/stevearc/oil.nvim" }, -- file manager that is just an editable buffer
|
||||||
{ src = "https://github.com/neovim/nvim-lspconfig" },
|
{ src = "https://github.com/neovim/nvim-lspconfig" }, -- ready made configs for language servers
|
||||||
{ src = "https://github.com/chomosuke/typst-preview.nvim" },
|
{ src = "https://github.com/saghen/blink.cmp" }, -- the autocomplete popup
|
||||||
{ src = "https://github.com/saghen/blink.cmp" },
|
{ src = "https://github.com/folke/snacks.nvim" }, -- grab bag of small tools. we use the picker, terminal and lazygit
|
||||||
{ src = "https://github.com/dynamotn/Navigator.nvim" },
|
{ src = "https://github.com/folke/todo-comments.nvim" }, -- highlights TODO/FIXME/HACK and can list them
|
||||||
{ src = "https://github.com/folke/snacks.nvim" },
|
{ src = "https://github.com/folke/trouble.nvim" }, -- a readable list of errors and warnings
|
||||||
{ src = "https://github.com/folke/todo-comments.nvim" },
|
{ src = "https://github.com/nvim-treesitter/nvim-treesitter" },-- real syntax parsing, better highlighting
|
||||||
{ src = "https://github.com/folke/trouble.nvim" },
|
{ src = "https://github.com/j-hui/fidget.nvim" }, -- little lsp progress spinner in the corner
|
||||||
{ src = "https://github.com/nvim-treesitter/nvim-treesitter" },
|
{ src = "https://github.com/nvim-lualine/lualine.nvim" }, -- the statusline at the bottom
|
||||||
{ src = "https://github.com/j-hui/fidget.nvim" },
|
})
|
||||||
{ src = "https://github.com/nvim-lualine/lualine.nvim" },
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
|
-- most plugins only need setup() called with no arguments to turn them on.
|
||||||
require("blink.cmp").setup()
|
require("blink.cmp").setup()
|
||||||
require("Navigator").setup()
|
|
||||||
require("todo-comments").setup()
|
require("todo-comments").setup()
|
||||||
require("trouble").setup()
|
require("trouble").setup()
|
||||||
require("fidget").setup()
|
require("fidget").setup()
|
||||||
require("lualine").setup({
|
require("lualine").setup({
|
||||||
options = {
|
options = {
|
||||||
|
-- plain | instead of the default powerline arrows, so it looks right in any font
|
||||||
component_separators = { left = "|", right = "|" },
|
component_separators = { left = "|", right = "|" },
|
||||||
section_separators = { left = "|", right = "|" },
|
section_separators = { left = "|", right = "|" },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
require("oil").setup({
|
require("oil").setup({
|
||||||
default_file_explorer = true,
|
default_file_explorer = true, -- opening a directory opens oil instead of netrw
|
||||||
delete_to_trash = true,
|
delete_to_trash = true, -- deleting a file in oil sends it to the trash, not the void
|
||||||
skip_confirm_for_simple_edits = true,
|
skip_confirm_for_simple_edits = true, -- don't ask "are you sure" for renames and single deletes
|
||||||
view_options = { show_hidden = true },
|
view_options = { show_hidden = true }, -- show dotfiles
|
||||||
|
-- oil's own keymaps, inside an oil buffer only.
|
||||||
keymaps = {
|
keymaps = {
|
||||||
["g?"] = { "actions.show_help", mode = "n" },
|
["g?"] = { "actions.show_help", mode = "n" }, -- everything oil can do
|
||||||
["<CR>"] = "actions.select",
|
["<CR>"] = "actions.select", -- open the file/directory under the cursor
|
||||||
["-"] = { "actions.parent", mode = "n" },
|
["-"] = { "actions.parent", mode = "n" }, -- go up one directory
|
||||||
["_"] = { "actions.open_cwd", mode = "n" },
|
["_"] = { "actions.open_cwd", mode = "n" }, -- jump to the directory nvim was started in
|
||||||
["`"] = { "actions.cd", mode = "n" },
|
["`"] = { "actions.cd", mode = "n" }, -- make this directory nvim's working directory
|
||||||
},
|
},
|
||||||
use_default_keymaps = false,
|
use_default_keymaps = false, -- only the five above, nothing else
|
||||||
})
|
})
|
||||||
require("snacks").setup({ picker = { ui_select = true } })
|
require("snacks").setup({ picker = { ui_select = true } }) -- any "pick one" prompt uses the snacks picker
|
||||||
|
|
||||||
vim.cmd("colorscheme cyberdream")
|
vim.cmd("colorscheme cyberdream")
|
||||||
|
|
||||||
|
-- briefly flash whatever you just yanked, so you can see what got copied
|
||||||
vim.api.nvim_create_autocmd("TextYankPost", {
|
vim.api.nvim_create_autocmd("TextYankPost", {
|
||||||
callback = function()
|
callback = function()
|
||||||
vim.highlight.on_yank()
|
vim.highlight.on_yank()
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
vim.api.nvim_create_autocmd("FileType", {
|
|
||||||
pattern = "typst",
|
|
||||||
callback = function()
|
|
||||||
vim.opt_local.wrap = true
|
|
||||||
vim.opt_local.linebreak = true
|
|
||||||
vim.opt_local.breakindent = true
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
|
|
||||||
vim.lsp.enable({ "lua_ls", "svelte", "tinymist", "ts_ls", "rust_analyzer", "yamlls", "html", "cssls", "jsonls", "nixd", "zls" })
|
-- These are the language servers we turn on. the server programs themselves are
|
||||||
|
-- installed by nix (see programs.neovim.extraPackages in flake.nix), this line
|
||||||
|
-- only tells neovim to start them for the right filetypes.
|
||||||
|
vim.lsp.enable({ "lua_ls", "ts_ls", "rust_analyzer", "html", "cssls", "jsonls", "nixd" })
|
||||||
|
|
||||||
|
-- This means that we should run the callback when the event LspAttach happens
|
||||||
|
-- aka. when a language server attaches to the buffer. that is when these
|
||||||
|
-- keymaps start doing something useful, so we only set them then.
|
||||||
vim.api.nvim_create_autocmd("LspAttach", {
|
vim.api.nvim_create_autocmd("LspAttach", {
|
||||||
callback = function(ev)
|
callback = function(ev)
|
||||||
vim.keymap.set('n', 'gd', vim.lsp.buf.definition)
|
vim.keymap.set('n', 'gd', vim.lsp.buf.definition) -- go to where the thing under the cursor is defined
|
||||||
vim.keymap.set('n', 'gD', vim.lsp.buf.declaration)
|
vim.keymap.set('n', 'gD', vim.lsp.buf.declaration) -- go to its declaration (mostly a C/C++ thing)
|
||||||
vim.keymap.set('n', 'gr', Snacks.picker.lsp_references)
|
vim.keymap.set('n', 'gr', Snacks.picker.lsp_references) -- list every place it is used
|
||||||
vim.keymap.set('n', 'gt', Snacks.picker.lsp_type_definitions)
|
vim.keymap.set('n', 'gt', Snacks.picker.lsp_type_definitions) -- go to the definition of its type
|
||||||
vim.keymap.set('n', '<leader>ss', Snacks.picker.lsp_symbols)
|
vim.keymap.set('n', '<leader>ss', Snacks.picker.lsp_symbols) -- search symbols in this file
|
||||||
vim.keymap.set('n', '<leader>sS', Snacks.picker.lsp_workspace_symbols)
|
vim.keymap.set('n', '<leader>sS', Snacks.picker.lsp_workspace_symbols) -- search symbols in the whole project
|
||||||
vim.keymap.set('n', '<leader>ca', vim.lsp.buf.code_action)
|
vim.keymap.set('n', '<leader>ca', vim.lsp.buf.code_action) -- offer the fixes/refactors the server suggests
|
||||||
vim.keymap.set('n', '<leader>cr', vim.lsp.buf.rename)
|
vim.keymap.set('n', '<leader>cr', vim.lsp.buf.rename) -- rename it everywhere at once
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
|
||||||
|
-- This is your leader key. set it to whatever you like. but space is nice i think
|
||||||
vim.g.mapleader = " "
|
vim.g.mapleader = " "
|
||||||
vim.keymap.set('n', '<leader>o', ':update<CR> :source<CR>')
|
-- space+o in normal mode saves this file and re-runs it, so config changes apply
|
||||||
|
-- without restarting neovim. (to update the plugins themselves: :lua vim.pack.update())
|
||||||
|
vim.keymap.set('n', '<leader>o', ':update<CR>:source<CR>')
|
||||||
|
-- space+w in normal mode writes (saves) the current buffer
|
||||||
vim.keymap.set('n', '<leader>w', ':write<CR>')
|
vim.keymap.set('n', '<leader>w', ':write<CR>')
|
||||||
|
-- space+q in normal mode quits
|
||||||
vim.keymap.set('n', '<leader>q', ':quit<CR>')
|
vim.keymap.set('n', '<leader>q', ':quit<CR>')
|
||||||
|
-- space+f in normal mode opens a file picker where you can fuzzy search for files by their name
|
||||||
vim.keymap.set('n', '<leader>f', function() Snacks.picker.files() end)
|
vim.keymap.set('n', '<leader>f', function() Snacks.picker.files() end)
|
||||||
|
-- space+sg in normal mode opens a fuzzy searcher that searches through the contents of all files
|
||||||
vim.keymap.set('n', '<leader>sg', function() Snacks.picker.grep() end)
|
vim.keymap.set('n', '<leader>sg', function() Snacks.picker.grep() end)
|
||||||
|
-- space+e in normal mode opens a new buffer with Oil. this is the file manager or what you would call it
|
||||||
|
-- The cool thing about oil is that you can edit all of your files like neovim buffers
|
||||||
vim.keymap.set('n', '<leader>e', ':Oil<CR>')
|
vim.keymap.set('n', '<leader>e', ':Oil<CR>')
|
||||||
vim.keymap.set({ 'n', 't' }, '<C-h>', ':NavigatorLeft<CR>')
|
|
||||||
vim.keymap.set({ 'n', 't' }, '<C-l>', ':NavigatorRight<CR>')
|
-- ctrl+h/j/k/l move to the split left/down/up/right. <C-w>h is neovim's own
|
||||||
vim.keymap.set({ 'n', 't' }, '<C-k>', ':NavigatorUp<CR>')
|
-- "go to the window on the left", these just put it on an easier key.
|
||||||
vim.keymap.set({ 'n', 't' }, '<C-j>', ':NavigatorDown<CR>')
|
-- normal mode only: inside the terminal these keys belong to the shell
|
||||||
vim.keymap.set({ "n", "t" }, "<C-t>", function() Snacks.terminal.toggle("zsh") end) -- Open Snacks picker
|
-- (ctrl+l clears it), and you leave that terminal with ctrl+t anyway.
|
||||||
|
vim.keymap.set('n', '<C-h>', '<C-w>h')
|
||||||
|
vim.keymap.set('n', '<C-l>', '<C-w>l')
|
||||||
|
vim.keymap.set('n', '<C-k>', '<C-w>k')
|
||||||
|
vim.keymap.set('n', '<C-j>', '<C-w>j')
|
||||||
|
|
||||||
|
-- ctrl+t toggles a floating zsh terminal. it works both in normal mode and from
|
||||||
|
-- inside the terminal itself, so the same key opens and closes it
|
||||||
|
vim.keymap.set({ "n", "t" }, "<C-t>", function() Snacks.terminal.toggle("zsh") end)
|
||||||
|
-- space+g in normal mode opens lazygit in floating window (tui for git)
|
||||||
vim.keymap.set('n', '<leader>g', function() Snacks.lazygit() end)
|
vim.keymap.set('n', '<leader>g', function() Snacks.lazygit() end)
|
||||||
|
-- space+t in normal mode opens a picker of todo comments where you can search through them
|
||||||
vim.keymap.set('n', '<leader>t', function() Snacks.picker.todo_comments() end)
|
vim.keymap.set('n', '<leader>t', function() Snacks.picker.todo_comments() end)
|
||||||
|
-- space+x in normal mode opens a bottom bar that shows warning and errors from your LSP in the current buffer
|
||||||
vim.keymap.set('n', '<leader>x', ":Trouble diagnostics toggle filter.buf=0<CR>")
|
vim.keymap.set('n', '<leader>x', ":Trouble diagnostics toggle filter.buf=0<CR>")
|
||||||
|
-- space+shift+X does the same but for every file in the project
|
||||||
vim.keymap.set('n', '<leader>X', ":Trouble diagnostics<CR>")
|
vim.keymap.set('n', '<leader>X', ":Trouble diagnostics<CR>")
|
||||||
|
-- space+| in normal mode vertically splits the current buffer
|
||||||
vim.keymap.set('n', '<leader>|', ":vsplit<CR>")
|
vim.keymap.set('n', '<leader>|', ":vsplit<CR>")
|
||||||
|
-- space+_ in normal mode horizontally splits the current buffer
|
||||||
vim.keymap.set('n', '<leader>_', ":split<CR>")
|
vim.keymap.set('n', '<leader>_', ":split<CR>")
|
||||||
|
-- ESC in normal mode clears the highlighting left over from your last search
|
||||||
vim.keymap.set('n', '<ESC>', ":noh<CR>")
|
vim.keymap.set('n', '<ESC>', ":noh<CR>")
|
||||||
|
-- > in visual mode indents the selected lines (gv reselects them so you can keep pressing >)
|
||||||
vim.keymap.set("v", ">", ">gv", { silent = true, desc = "Indent and reselect" })
|
vim.keymap.set("v", ">", ">gv", { silent = true, desc = "Indent and reselect" })
|
||||||
|
-- < in visual mode unindents the selected lines
|
||||||
vim.keymap.set("v", "<", "<gv", { silent = true, desc = "Unindent and reselect" })
|
vim.keymap.set("v", "<", "<gv", { silent = true, desc = "Unindent and reselect" })
|
||||||
|
-- space+lf in normal mode calls the lsp's formatter on the current buffer
|
||||||
vim.keymap.set('n', '<leader>lf', vim.lsp.buf.format)
|
vim.keymap.set('n', '<leader>lf', vim.lsp.buf.format)
|
||||||
|
|
|
||||||
45
flake.lock
45
flake.lock
|
|
@ -78,13 +78,50 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"hyprland-virtual-desktops": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1790338297,
|
||||||
|
"narHash": "sha256-iJP+CyrS/fw/dqysRN9GtVB8GlJZcja4rkjBeJsXFsg=",
|
||||||
|
"owner": "levnikmyskin",
|
||||||
|
"repo": "hyprland-virtual-desktops",
|
||||||
|
"rev": "74b88ee5209c4dcea74eedc4646e185b02a8f946",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "levnikmyskin",
|
||||||
|
"repo": "hyprland-virtual-desktops",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"linear-tui-src": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1790503843,
|
||||||
|
"narHash": "sha256-FS0w8a53N+XN0tnxUzpu4FNG0rYVycv/ZlnB7bPgd+I=",
|
||||||
|
"owner": "k1-c",
|
||||||
|
"repo": "linear-tui",
|
||||||
|
"rev": "fa79ffed365c2deff1916022abe8652989682216",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "k1-c",
|
||||||
|
"ref": "v0.13.0",
|
||||||
|
"repo": "linear-tui",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1786106723,
|
"lastModified": 1791179034,
|
||||||
"narHash": "sha256-zDSUbpoeo/9ZmD2+wXnzxoo1+uhL8vxc0b8yuYMKYq0=",
|
"narHash": "sha256-Ni0LBydzaCi8oek12r4mVreuFHqYlM1eTD6SfiENKfw=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "f13ff45afd1bb73e640eaa08a7066dbed07e3238",
|
"rev": "494ce7fd23ff6a5dff39e1fb11e9b6f2ac74bf25",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -99,6 +136,8 @@
|
||||||
"apple-silicon": "apple-silicon",
|
"apple-silicon": "apple-silicon",
|
||||||
"firefox-addons": "firefox-addons",
|
"firefox-addons": "firefox-addons",
|
||||||
"home-manager": "home-manager",
|
"home-manager": "home-manager",
|
||||||
|
"hyprland-virtual-desktops": "hyprland-virtual-desktops",
|
||||||
|
"linear-tui-src": "linear-tui-src",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
"zen-browser": "zen-browser"
|
"zen-browser": "zen-browser"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
316
flake.nix
316
flake.nix
|
|
@ -24,10 +24,46 @@
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
inputs.home-manager.follows = "home-manager";
|
inputs.home-manager.follows = "home-manager";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# linear-tui has no flake of its own, so take the source and build it below.
|
||||||
|
# Pinned to a tag; `nix flake update` moves it and records the hash in
|
||||||
|
# flake.lock, so there is no sha256 to maintain by hand.
|
||||||
|
linear-tui-src = {
|
||||||
|
url = "github:k1-c/linear-tui/v0.13.0";
|
||||||
|
flake = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Virtual desktops: one desktop spans all monitors, replacing Hyprland's
|
||||||
|
# workspace-pinned-to-monitor model. Its own nixpkgs pin is older than
|
||||||
|
# ours, and Hyprland plugins are ABI-locked to the compositor, so it must
|
||||||
|
# follow ours to compile against the hyprland we actually run.
|
||||||
|
hyprland-virtual-desktops = {
|
||||||
|
url = "github:levnikmyskin/hyprland-virtual-desktops";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, apple-silicon, home-manager, firefox-addons, zen-browser, ... }:
|
outputs = { self, nixpkgs, apple-silicon, home-manager, firefox-addons, zen-browser, linear-tui-src, hyprland-virtual-desktops, ... }:
|
||||||
let
|
let
|
||||||
|
# linear-tui, built from the pinned source input above. Upstream ships no
|
||||||
|
# flake and it is not in nixpkgs, but it has a committed Cargo.lock, so
|
||||||
|
# cargoLock.lockFile covers the dependency hashes with nothing to vendor.
|
||||||
|
#
|
||||||
|
# No OpenSSL/pkg-config needed: the crate selects reqwest's rustls
|
||||||
|
# backend on purpose, and takes its trust roots from the OS store.
|
||||||
|
linearTui = pkgs: pkgs.rustPlatform.buildRustPackage {
|
||||||
|
pname = "linear-tui";
|
||||||
|
version = "0.13.0";
|
||||||
|
src = linear-tui-src;
|
||||||
|
cargoLock.lockFile = "${linear-tui-src}/Cargo.lock";
|
||||||
|
# One test (a_worktree_belongs_to_its_repository) shells out to `git`,
|
||||||
|
# which the build sandbox lacks -- it panics on NotFound unwrapping
|
||||||
|
# Command::new("git"). Supplying git lets all 512 tests run.
|
||||||
|
nativeCheckInputs = [ pkgs.git ];
|
||||||
|
|
||||||
|
meta.mainProgram = "linear-tui";
|
||||||
|
};
|
||||||
|
|
||||||
# ───────────────────── shared home-manager user ─────────────────────
|
# ───────────────────── shared home-manager user ─────────────────────
|
||||||
home = { pkgs, osConfig, ... }: {
|
home = { pkgs, osConfig, ... }: {
|
||||||
imports = [ zen-browser.homeModules.beta ];
|
imports = [ zen-browser.homeModules.beta ];
|
||||||
|
|
@ -47,19 +83,76 @@
|
||||||
if osConfig.networking.hostName == "mbp"
|
if osConfig.networking.hostName == "mbp"
|
||||||
then ''
|
then ''
|
||||||
monitor = , preferred, auto, 2.0
|
monitor = , preferred, auto, 2.0
|
||||||
|
|
||||||
|
# Floating TUI panels: 70% wide, full height. Absolute px because
|
||||||
|
# Hyprland 0.56 ignores percentages in `size`. 1728x1080 logical.
|
||||||
|
windowrule = match:class ^(panel-wifi|panel-bt)$, float on, size 1210 1080, center on
|
||||||
''
|
''
|
||||||
else ''
|
else ''
|
||||||
monitor = desc:Lenovo Group Limited P27q-20 V909LRFR, 2560x1440@60, 0x0, 1.0
|
monitor = desc:Lenovo Group Limited P27q-20 V909LRFR, 2560x1440@60, 0x0, 1.0
|
||||||
monitor = desc:Lenovo Group Limited P27q-20 V909LPNY, 2560x1440@60, 2560x0, 1.0
|
monitor = desc:Lenovo Group Limited P27q-20 V909LPNY, 2560x1440@60, 2560x0, 1.0
|
||||||
monitor = desc:Lenovo Group Limited P27q-20 V909LRDD, 2560x1440@60, 5120x0, 1.0
|
monitor = desc:Lenovo Group Limited P27q-20 V909LRDD, 2560x1440@60, 5120x0, 1.0
|
||||||
workspace = 1, monitor:desc:Lenovo Group Limited P27q-20 V909LRFR, default:true
|
|
||||||
workspace = 2, monitor:desc:Lenovo Group Limited P27q-20 V909LPNY, default:true
|
# Same panels, sized for 2560x1440 logical.
|
||||||
workspace = 3, monitor:desc:Lenovo Group Limited P27q-20 V909LRDD, default:true
|
windowrule = match:class ^(panel-wifi|panel-bt)$, float on, size 1792 1440, center on
|
||||||
'';
|
'';
|
||||||
|
# Plugin load line, as a store path. Sourced from hyprland.conf, the
|
||||||
|
# same pattern as monitors.conf. Upstream installs the library as
|
||||||
|
# libvirtual-desktops.so (not lib<pname>.so).
|
||||||
|
"hypr/plugins.conf".text = ''
|
||||||
|
plugin = ${hyprland-virtual-desktops.packages.${pkgs.stdenv.hostPlatform.system}.virtual-desktops}/lib/libvirtual-desktops.so
|
||||||
|
'';
|
||||||
"eww".source = ./config/eww;
|
"eww".source = ./config/eww;
|
||||||
"fuzzel".source = ./config/fuzzel;
|
"fuzzel".source = ./config/fuzzel;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
# Launcher hygiene. These .desktop entries ship with packages we do want,
|
||||||
|
# but they are noise in fuzzel. A NoDisplay entry in XDG_DATA_HOME
|
||||||
|
# shadows the package's own copy, which is the only way to hide one
|
||||||
|
# without patching the package.
|
||||||
|
#
|
||||||
|
# Deliberately home.file and NOT xdg.desktopEntries: that option only
|
||||||
|
# emits anything when xdg.enable = true, which we don't set, so it
|
||||||
|
# silently produces no files at all.
|
||||||
|
# nvim - neovim's "Neovim wrapper" entry; we only launch it from a terminal
|
||||||
|
# uuctl - uwsm's systemd-user-unit picker, pulled in by withUWSM
|
||||||
|
home.file = {
|
||||||
|
".local/share/applications/nvim.desktop".text = ''
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=Neovim wrapper
|
||||||
|
NoDisplay=true
|
||||||
|
'';
|
||||||
|
".local/share/applications/uuctl.desktop".text = ''
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=uuctl
|
||||||
|
NoDisplay=true
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Notification daemon. Nothing owned org.freedesktop.Notifications before
|
||||||
|
# this, so notify-send failed outright: hyprshot's "Screenshot saved"
|
||||||
|
# toasts went nowhere, and kitty logged a DBus ServiceUnknown error on
|
||||||
|
# every launch. Colours match the cyberdream kitty theme above.
|
||||||
|
services.mako = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
font = "JetBrainsMono Nerd Font 11";
|
||||||
|
background-color = "#16181a";
|
||||||
|
text-color = "#ffffff";
|
||||||
|
border-color = "#5ea1ff";
|
||||||
|
border-size = 2;
|
||||||
|
border-radius = 8;
|
||||||
|
default-timeout = 5000;
|
||||||
|
anchor = "top-right";
|
||||||
|
margin = "12";
|
||||||
|
padding = "12";
|
||||||
|
width = 380;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
programs.kitty = {
|
programs.kitty = {
|
||||||
enable = true;
|
enable = true;
|
||||||
settings = {
|
settings = {
|
||||||
|
|
@ -105,7 +198,7 @@
|
||||||
hyprcursor.enable = true;
|
hyprcursor.enable = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
# Dark GTK apps (gimp, file dialogs).
|
# Dark GTK apps (file dialogs, etc).
|
||||||
gtk = {
|
gtk = {
|
||||||
enable = true;
|
enable = true;
|
||||||
theme = { name = "Adwaita-dark"; package = pkgs.gnome-themes-extra; };
|
theme = { name = "Adwaita-dark"; package = pkgs.gnome-themes-extra; };
|
||||||
|
|
@ -208,40 +301,83 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
home.packages = with pkgs; [ ripgrep fd tridactyl-native lazygit rustc cargo clippy rustfmt rust-analyzer claude-code gcc bluetui impala];
|
home.packages = with pkgs; [ ripgrep fd tridactyl-native lazygit rustc cargo clippy rustfmt rust-analyzer claude-code gcc bluetui impala postgresql fastfetch (linearTui pkgs) ];
|
||||||
};
|
};
|
||||||
|
|
||||||
# ───────────────────── shared system (both hosts) ─────────────────────
|
# ───────────────────── shared system (both hosts) ─────────────────────
|
||||||
common = { pkgs, ... }: {
|
common = { pkgs, ... }: {
|
||||||
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
||||||
# Trusted so client-side nix config takes effect — the deepvis repo's
|
# Trusted so client-side nix config takes effect — substituters and
|
||||||
# flake nixConfig advertises the team dev cache (attic on the builder,
|
# remote builders are trusted settings, silently ignored for anyone
|
||||||
# over the tailnet), and the daemon ignores it for untrusted users.
|
# else. A cache that appears to do nothing is the usual symptom.
|
||||||
nix.settings.trusted-users = [ "mads" ];
|
nix.settings.trusted-users = [ "mads" ];
|
||||||
# x86_64 derivations build on the build host instead of under local
|
|
||||||
# qemu emulation — anything the dev cache doesn't already have ships
|
# The deepvis binary cache: the embedded flakes in models/ort-nix
|
||||||
# there as a remote build (tailnet; the builder's `builder` user
|
# (onnxruntime built from source, a CUDA training env) are hours of
|
||||||
# exists for exactly this).
|
# work to build and seconds to substitute.
|
||||||
|
nix.settings.extra-substituters = [ "https://deepvis.cachix.org" ];
|
||||||
|
nix.settings.extra-trusted-public-keys = [
|
||||||
|
"deepvis.cachix.org-1:UE/H6ERLwqhSqubzh8+pzl9eTm0kpMExGe0ym6fENp8="
|
||||||
|
];
|
||||||
|
# Private cache, so reads need a token. nix refuses `netrc-file` on
|
||||||
|
# the command line under the daemon, and a token in the substituter
|
||||||
|
# URL would land in the process table — a root-owned 0600 netrc is
|
||||||
|
# the only form the calling process cannot read.
|
||||||
|
nix.settings.netrc-file = "/etc/nix/netrc";
|
||||||
|
|
||||||
|
# x86_64 derivations build off this machine rather than under local
|
||||||
|
# qemu emulation, and aarch64 ones go out too when the local
|
||||||
|
# machine is busy.
|
||||||
#
|
#
|
||||||
# The host key is pinned system-wide because the ssh'ing party is the
|
# The host keys are pinned system-wide because the ssh'ing party is
|
||||||
# root daemon, which has no known_hosts and never gets a tty to
|
# the root daemon, which has no known_hosts and never gets a tty to
|
||||||
# answer a trust prompt.
|
# answer a trust prompt.
|
||||||
programs.ssh.knownHosts."builder" = {
|
programs.ssh.knownHosts."builder" = {
|
||||||
extraHostNames = [ "5.9.71.104" "builder.tailf5eaab.ts.net" ];
|
extraHostNames = [ "5.9.71.104" "builder.tailf5eaab.ts.net" ];
|
||||||
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHnKIrxYtlovvYGgkpEZXY8wG/CuSLMGOM/84eIlyeVW";
|
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHnKIrxYtlovvYGgkpEZXY8wG/CuSLMGOM/84eIlyeVW";
|
||||||
};
|
};
|
||||||
|
programs.ssh.knownHosts."eu.nixbuild.net" = {
|
||||||
|
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPIQCZc54poJ8vqawd8TraNryQeJnvH1eLpIDgbiqymM";
|
||||||
|
};
|
||||||
nix.distributedBuilds = true;
|
nix.distributedBuilds = true;
|
||||||
nix.buildMachines = [{
|
nix.buildMachines = [
|
||||||
hostName = "builder";
|
{
|
||||||
sshUser = "builder";
|
hostName = "builder";
|
||||||
# The daemon (root) does the ssh'ing, so it needs an explicit
|
sshUser = "builder";
|
||||||
# on-disk key — it has no access to a user's agent.
|
# The daemon (root) does the ssh'ing, so it needs an explicit
|
||||||
sshKey = "/home/mads/.ssh/id_ed25519";
|
# on-disk key — it has no access to a user's agent.
|
||||||
protocol = "ssh-ng";
|
sshKey = "/home/mads/.ssh/id_ed25519";
|
||||||
system = "x86_64-linux";
|
protocol = "ssh-ng";
|
||||||
maxJobs = 16;
|
system = "x86_64-linux";
|
||||||
supportedFeatures = [ "big-parallel" "kvm" "nixos-test" ];
|
maxJobs = 16;
|
||||||
}];
|
supportedFeatures = [ "big-parallel" "kvm" "nixos-test" ];
|
||||||
|
}
|
||||||
|
# nixbuild.net, for both architectures. Listed after `builder` so
|
||||||
|
# the machine we already pay for is preferred.
|
||||||
|
#
|
||||||
|
# Every feature it supports is advertised, not just the two in the
|
||||||
|
# quickstart: nix only sends a derivation to a builder claiming
|
||||||
|
# *every* feature that derivation requires, so omitting `kvm` or
|
||||||
|
# `nixos-test` would silently route those back to local builds.
|
||||||
|
{
|
||||||
|
hostName = "eu.nixbuild.net";
|
||||||
|
sshUser = "root";
|
||||||
|
sshKey = "/home/mads/.ssh/id_ed25519";
|
||||||
|
protocol = "ssh-ng";
|
||||||
|
system = "x86_64-linux";
|
||||||
|
maxJobs = 100;
|
||||||
|
supportedFeatures = [ "benchmark" "big-parallel" "ca-derivations" "kvm" "nixos-test" ];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
hostName = "eu.nixbuild.net";
|
||||||
|
sshUser = "root";
|
||||||
|
sshKey = "/home/mads/.ssh/id_ed25519";
|
||||||
|
protocol = "ssh-ng";
|
||||||
|
system = "aarch64-linux";
|
||||||
|
maxJobs = 100;
|
||||||
|
supportedFeatures = [ "benchmark" "big-parallel" "ca-derivations" "kvm" "nixos-test" ];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
networking.networkmanager.enable = true;
|
networking.networkmanager.enable = true;
|
||||||
networking.networkmanager.wifi.backend = "iwd";
|
networking.networkmanager.wifi.backend = "iwd";
|
||||||
|
|
@ -273,7 +409,44 @@
|
||||||
|
|
||||||
programs.hyprland = { enable = true; withUWSM = true; };
|
programs.hyprland = { enable = true; withUWSM = true; };
|
||||||
programs.hyprlock.enable = true;
|
programs.hyprlock.enable = true;
|
||||||
|
|
||||||
|
# Every GUI app lives in the one wayland-wm@hyprland.service cgroup, so
|
||||||
|
# with systemd's default OOMPolicy=stop a kernel OOM-kill of ANY process
|
||||||
|
# in it stops the whole unit -- compositor included. 1Password was always
|
||||||
|
# the victim (highest oom_score_adj), but losing it killed the entire
|
||||||
|
# desktop, and uwsm sets Restart=no, so nothing came back. It read as
|
||||||
|
# "Hyprland crashed" when the compositor was never at fault.
|
||||||
|
#
|
||||||
|
# continue: log the kill, lose that one app, keep the session. Merges
|
||||||
|
# into the drop-in the uwsm module already generates for this unit.
|
||||||
|
systemd.user.services."wayland-wm@".serviceConfig.OOMPolicy = "continue";
|
||||||
|
|
||||||
|
# Autologin means no password reaches PAM at login, so the gnome-keyring
|
||||||
|
# login keyring is never created or unlocked -- which is why 1Password
|
||||||
|
# could not persist its 2FA device token and re-prompted the YubiKey
|
||||||
|
# every unlock. Putting pam_gnome_keyring in hyprlock's stack instead
|
||||||
|
# makes the lock screen we already have do that job, so there is still
|
||||||
|
# exactly one password prompt and no second login screen.
|
||||||
|
security.pam.services.hyprlock.enableGnomeKeyring = true;
|
||||||
services.hypridle.enable = true;
|
services.hypridle.enable = true;
|
||||||
|
|
||||||
|
# ── Amphetamine-style "stay awake" ──
|
||||||
|
# caffeine.service holds ONE logind block-inhibitor covering all three
|
||||||
|
# ways this machine can go dark:
|
||||||
|
# idle -> hypridle stops counting (no blank, no hyprlock)
|
||||||
|
# sleep -> suspend requests are refused
|
||||||
|
# handle-lid-switch -> closing the lid does nothing
|
||||||
|
# systemd owns the process, so "is it on?" is just "is the unit
|
||||||
|
# active?" -- no pidfile, and it can't be orphaned. Deliberately no
|
||||||
|
# wantedBy: it only ever starts via SUPER+A or the `caffeine` command.
|
||||||
|
systemd.user.services.caffeine = {
|
||||||
|
description = "Caffeine - inhibit idle, sleep and lid-switch";
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "simple";
|
||||||
|
ExecStart = "${pkgs.systemd}/bin/systemd-inhibit --what=idle:sleep:handle-lid-switch --who=caffeine --why=stay-awake --mode=block ${pkgs.coreutils}/bin/sleep infinity";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
xdg.portal = {
|
xdg.portal = {
|
||||||
enable = true;
|
enable = true;
|
||||||
extraPortals = [ pkgs.xdg-desktop-portal-hyprland ];
|
extraPortals = [ pkgs.xdg-desktop-portal-hyprland ];
|
||||||
|
|
@ -289,19 +462,60 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
virtualisation.docker.enable = true;
|
||||||
|
|
||||||
hardware.bluetooth.enable = true;
|
hardware.bluetooth.enable = true;
|
||||||
programs.zsh.enable = true;
|
programs.zsh.enable = true;
|
||||||
|
# No NixOS manual: it puts a "NixOS Manual" entry in the launcher and we
|
||||||
|
# read the docs on the web. man pages are unaffected.
|
||||||
|
documentation.nixos.enable = false;
|
||||||
|
|
||||||
fonts.packages = [ pkgs.nerd-fonts.jetbrains-mono ];
|
fonts.packages = [ pkgs.nerd-fonts.jetbrains-mono ];
|
||||||
|
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
git kitty fuzzel gimp
|
git kitty fuzzel
|
||||||
wl-clipboard grim slurp swaybg brightnessctl playerctl grimblast
|
wl-clipboard grim slurp swaybg brightnessctl playerctl hyprshot
|
||||||
hyprpolkitagent xdg-utils
|
hyprpolkitagent xdg-utils
|
||||||
|
|
||||||
|
# Front-end for caffeine.service (above). Bound to SUPER+A.
|
||||||
|
(writeShellApplication {
|
||||||
|
name = "caffeine";
|
||||||
|
runtimeInputs = [ systemd ];
|
||||||
|
text = ''
|
||||||
|
unit=caffeine.service
|
||||||
|
|
||||||
|
is_on() { systemctl --user is-active --quiet "$unit"; }
|
||||||
|
|
||||||
|
# Guarded so `caffeine` still works over SSH / with no compositor.
|
||||||
|
toast() {
|
||||||
|
command -v hyprctl >/dev/null 2>&1 || return 0
|
||||||
|
hyprctl notify "$1" 2000 "$2" "$3" >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
wake_on() {
|
||||||
|
systemctl --user start "$unit"
|
||||||
|
toast 5 "rgb(5eff6c)" "Caffeine ON - idle, sleep and lid disabled"
|
||||||
|
}
|
||||||
|
|
||||||
|
wake_off() {
|
||||||
|
systemctl --user stop "$unit"
|
||||||
|
toast 1 "rgb(ff6e5e)" "Caffeine OFF - normal sleep restored"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "''${1:-toggle}" in
|
||||||
|
toggle) if is_on; then wake_off; else wake_on; fi ;;
|
||||||
|
on) wake_on ;;
|
||||||
|
off) wake_off ;;
|
||||||
|
status) if is_on; then echo on; else echo off; fi ;;
|
||||||
|
*) echo "usage: caffeine [toggle|on|off|status]" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
'';
|
||||||
|
})
|
||||||
];
|
];
|
||||||
|
|
||||||
users.users.mads = {
|
users.users.mads = {
|
||||||
isNormalUser = true;
|
isNormalUser = true;
|
||||||
extraGroups = [ "wheel" "networkmanager" "video" ];
|
extraGroups = [ "wheel" "networkmanager" "video" "docker" ];
|
||||||
shell = pkgs.zsh;
|
shell = pkgs.zsh;
|
||||||
initialPassword = "changeme"; # CHANGE after first boot (`passwd`)
|
initialPassword = "changeme"; # CHANGE after first boot (`passwd`)
|
||||||
};
|
};
|
||||||
|
|
@ -347,6 +561,38 @@
|
||||||
boot.extraModprobeConfig = "options hid_apple fnmode=1";
|
boot.extraModprobeConfig = "options hid_apple fnmode=1";
|
||||||
boot.blacklistedKernelModules = [ "pcspkr" ];
|
boot.blacklistedKernelModules = [ "pcspkr" ];
|
||||||
|
|
||||||
|
# Root is btrfs (p8). A scrub re-reads every block and verifies its
|
||||||
|
# checksum, which is the only way bit rot on this SSD surfaces before
|
||||||
|
# something tries to read the damaged file. Metadata is DUP, so a bad
|
||||||
|
# metadata block gets repaired from its copy automatically.
|
||||||
|
services.btrfs.autoScrub = {
|
||||||
|
enable = true;
|
||||||
|
interval = "monthly";
|
||||||
|
fileSystems = [ "/" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# 15G of RAM and no swap at all: a `nixos-rebuild` forks ~60 nix
|
||||||
|
# evaluators, and stacked on a browser and a few editor sessions
|
||||||
|
# that was enough to exhaust it. The kernel then OOM-killed a
|
||||||
|
# process inside wayland-wm@hyprland.service, and systemd's default
|
||||||
|
# OOMPolicy=stop tore the whole graphical session down with it.
|
||||||
|
#
|
||||||
|
# zram rather than a swapfile, because root is compress=zstd and a
|
||||||
|
# btrfs swapfile needs its own nodatacow, uncompressed subvolume.
|
||||||
|
zramSwap = {
|
||||||
|
enable = true;
|
||||||
|
memoryPercent = 50; # 7.6G of backing store, ~a third of that in real RAM
|
||||||
|
};
|
||||||
|
|
||||||
|
# Swapping to RAM is cheap, so let the kernel compress anon pages
|
||||||
|
# rather than evict page cache. At the OOM above, cache had already
|
||||||
|
# been squeezed to 40M while 13G sat in anon and nothing was
|
||||||
|
# reclaimable — the default swappiness of 60 gets that backwards here.
|
||||||
|
boot.kernel.sysctl = {
|
||||||
|
"vm.swappiness" = 180;
|
||||||
|
"vm.page-cluster" = 0; # zram has no seek penalty, so no readahead
|
||||||
|
};
|
||||||
|
|
||||||
# Build x86_64 derivations (packing-station iso/deploys) via qemu.
|
# Build x86_64 derivations (packing-station iso/deploys) via qemu.
|
||||||
boot.binfmt.emulatedSystems = [ "x86_64-linux" ];
|
boot.binfmt.emulatedSystems = [ "x86_64-linux" ];
|
||||||
|
|
||||||
|
|
@ -383,6 +629,18 @@
|
||||||
boot.loader.systemd-boot.enable = true;
|
boot.loader.systemd-boot.enable = true;
|
||||||
boot.loader.efi.canTouchEfiVariables = true; # normal x86 UEFI
|
boot.loader.efi.canTouchEfiVariables = true; # normal x86 UEFI
|
||||||
|
|
||||||
|
# Desktop: never suspend or hibernate, even if something asks.
|
||||||
|
systemd.sleep.settings.Sleep = {
|
||||||
|
AllowSuspend = "no";
|
||||||
|
AllowHibernation = "no";
|
||||||
|
AllowHybridSleep = "no";
|
||||||
|
AllowSuspendThenHibernate = "no";
|
||||||
|
};
|
||||||
|
# Ignore the keys that would normally trigger sleep.
|
||||||
|
services.logind.suspendKey = "ignore";
|
||||||
|
services.logind.hibernateKey = "ignore";
|
||||||
|
services.logind.lidSwitch = "ignore";
|
||||||
|
|
||||||
# NVIDIA (RTX 20-series or newer → open kernel module).
|
# NVIDIA (RTX 20-series or newer → open kernel module).
|
||||||
services.xserver.videoDrivers = [ "nvidia" ];
|
services.xserver.videoDrivers = [ "nvidia" ];
|
||||||
hardware.graphics.enable = true; # OpenGL/Vulkan userspace (was hardware.opengl)
|
hardware.graphics.enable = true; # OpenGL/Vulkan userspace (was hardware.opengl)
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,13 @@
|
||||||
boot.kernelModules = [ ];
|
boot.kernelModules = [ ];
|
||||||
boot.extraModulePackages = [ ];
|
boot.extraModulePackages = [ ];
|
||||||
|
|
||||||
|
# Root lives on nvme0n1p8 (btrfs, subvol @) — the 256G reclaimed from
|
||||||
|
# the shrunk macOS container. The old ext4 p5 is kept as a rollback until
|
||||||
|
# it is added to this pool as a second device.
|
||||||
fileSystems."/" =
|
fileSystems."/" =
|
||||||
{ device = "/dev/disk/by-uuid/2664fdc1-4309-4682-a02e-4e27eb1e14f6";
|
{ device = "/dev/disk/by-uuid/c828c1a0-ce7b-4e27-b8e4-bce032bb30bf";
|
||||||
fsType = "ext4";
|
fsType = "btrfs";
|
||||||
|
options = [ "subvol=@" "compress=zstd" "noatime" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
fileSystems."/boot" =
|
fileSystems."/boot" =
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue