first commit

This commit is contained in:
Louis Chih-Ming Lee 2026-01-26 01:16:35 +01:00
commit 2a3ea31491
52 changed files with 2991 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
*.txt
*.key
*.age

10
.sops.yaml Normal file
View file

@ -0,0 +1,10 @@
keys:
- &local_T14p "age1y38cnfvt42ar4seqx8n2cw0ynwls2rdl6jyzhv9wuv6jcfqrwckqwlh9p0"
- &server_hetzner "age1sjq27nh4znq75fzzx6epwd6700wgg23v9xenzvmam4kmncvjdcesakv9e2"
creation_rules:
- path_regex: secrets.yaml$
key_groups:
- age:
- *local_T14p
- *server_hetzner

29
apps/fuzzel/default.nix Normal file
View file

@ -0,0 +1,29 @@
{ config, pkgs, lib, ... }:
let
cfg = config.my.apps.fuzzel;
in
{
options.my.apps.fuzzel = {
enable = lib.mkEnableOption "Fuzzel launcher";
package = lib.mkOption {
type = lib.types.package;
default = pkgs.fuzzel;
defaultText = "pkgs.fuzzel";
description = "Fuzzel package to install.";
};
};
config = lib.mkIf cfg.enable {
home.packages = [
cfg.package
pkgs.jetbrains-mono
];
xdg.enable = true;
# fuzzel uses ~/.config/fuzzel/fuzzel.ini
xdg.configFile."fuzzel/fuzzel.ini".source = ./fuzzel.ini;
};
}

41
apps/fuzzel/fuzzel.ini Normal file
View file

@ -0,0 +1,41 @@
# Fuzzel config
# Catppuccin Mocha-ish palette + Emacs navigation keys
[main]
font=JetBrains Mono:size=14
dpi-aware=yes
prompt=Search:
terminal=foot
layer=overlay
# Window sizing
width=45
lines=8
horizontal-pad=20
vertical-pad=16
inner-pad=10
[colors]
# Catppuccin Mocha palette
background=1e1e2eff
text=cdd6f4ff
match=cba6f7ff
selection=585b70ff
selection-text=f5e0dcff
border=cba6f7ff
[border]
width=2
radius=12
[key-bindings]
# List navigation
next=Control+n
prev=Control+p
# Optional extras
next-page=Page_Down
prev-page=Page_Up
accept=Return
cancel=Escape

View file

@ -0,0 +1 @@
require("nvim-autopairs").setup {}

View file

@ -0,0 +1,76 @@
require("catppuccin").setup({
flavour = "auto", -- latte, frappe, macchiato, mocha
background = { -- :h background
light = "latte",
dark = "mocha",
},
transparent_background = false, -- disables setting the background color.
float = {
transparent = false, -- enable transparent floating windows
solid = false, -- use solid styling for floating windows, see |winborder|
},
show_end_of_buffer = false, -- shows the '~' characters after the end of buffers
term_colors = false, -- sets terminal colors (e.g. `g:terminal_color_0`)
dim_inactive = {
enabled = false, -- dims the background color of inactive window
shade = "dark",
percentage = 0.15, -- percentage of the shade to apply to the inactive window
},
no_italic = false, -- Force no italic
no_bold = false, -- Force no bold
no_underline = false, -- Force no underline
styles = { -- Handles the styles of general hi groups (see `:h highlight-args`):
comments = { "italic" }, -- Change the style of comments
conditionals = { "italic" },
loops = {},
functions = {},
keywords = {},
strings = {},
variables = {},
numbers = {},
booleans = {},
properties = {},
types = {},
operators = {},
-- miscs = {}, -- Uncomment to turn off hard-coded styles
},
lsp_styles = { -- Handles the style of specific lsp hl groups (see `:h lsp-highlight`).
virtual_text = {
errors = { "italic" },
hints = { "italic" },
warnings = { "italic" },
information = { "italic" },
ok = { "italic" },
},
underlines = {
errors = { "underline" },
hints = { "underline" },
warnings = { "underline" },
information = { "underline" },
ok = { "underline" },
},
inlay_hints = {
background = true,
},
},
color_overrides = {},
custom_highlights = {},
default_integrations = true,
auto_integrations = false,
integrations = {
cmp = true,
gitsigns = true,
nvimtree = true,
notify = false,
mini = {
enabled = true,
indentscope_color = "",
},
telescope = {
enabled = true,
},
lsp_trouble = true,
},
})
vim.cmd.colorscheme "catppuccin"

86
apps/neovim/cmp.lua Normal file
View file

@ -0,0 +1,86 @@
-- https://github.com/hrsh7th/nvim-cmp
local cmp = require'cmp'
cmp.setup({
snippet = {
-- REQUIRED - you must specify a snippet engine
expand = function(args)
vim.fn["vsnip#anonymous"](args.body) -- For `vsnip` users.
-- require('luasnip').lsp_expand(args.body) -- For `luasnip` users.
-- require('snippy').expand_snippet(args.body) -- For `snippy` users.
-- vim.fn["UltiSnips#Anon"](args.body) -- For `ultisnips` users.
-- vim.snippet.expand(args.body) -- For native neovim snippets (Neovim v0.10+)
-- For `mini.snippets` users:
-- local insert = MiniSnippets.config.expand.insert or MiniSnippets.default_insert
-- insert({ body = args.body }) -- Insert at cursor
-- cmp.resubscribe({ "TextChangedI", "TextChangedP" })
-- require("cmp.config").set_onetime({ sources = {} })
end,
},
window = {
completion = cmp.config.window.bordered(),
documentation = cmp.config.window.bordered(),
},
mapping = cmp.mapping.preset.insert({
['<C-b>'] = cmp.mapping.scroll_docs(-4),
['<C-f>'] = cmp.mapping.scroll_docs(4),
['<C-j>'] = cmp.mapping.complete(),
['<C-e>'] = cmp.mapping.abort(),
['<CR>'] = cmp.mapping.confirm({ select = true }), -- Accept currently selected item. Set `select` to `false` to only confirm explicitly selected items.
}),
sources = cmp.config.sources({
{ name = 'nvim_lsp' },
{ name = 'vsnip' }, -- For vsnip users.
-- { name = 'luasnip' }, -- For luasnip users.
-- { name = 'ultisnips' }, -- For ultisnips users.
-- { name = 'snippy' }, -- For snippy users.
}, {
{ name = 'buffer' },
})
})
-- To use git you need to install the plugin petertriho/cmp-git and uncomment lines below
-- Set configuration for specific filetype.
--[[ cmp.setup.filetype('gitcommit', {
sources = cmp.config.sources({
{ name = 'git' },
}, {
{ name = 'buffer' },
})
)
require("cmp_git").setup() ]]--
-- Use buffer source for `/` and `?` (if you enabled `native_menu`, this won't work anymore).
cmp.setup.cmdline({ '/', '?' }, {
mapping = cmp.mapping.preset.cmdline(),
sources = {
{ name = 'buffer' }
}
})
-- Use cmdline & path source for ':' (if you enabled `native_menu`, this won't work anymore).
cmp.setup.cmdline(':', {
mapping = cmp.mapping.preset.cmdline(),
sources = cmp.config.sources({
{ name = 'path' }
}, {
{ name = 'cmdline' }
}),
matching = { disallow_symbol_nonprefix_matching = false }
})
-- Set up lspconfig.
local capabilities = require('cmp_nvim_lsp').default_capabilities()
function config_and_enable_lsp(language, extra_config)
--lsp_config = { capabilities = capabilities }
--vim.lsp.config(language, lsp_config)
vim.lsp.enable(language)
end
vim.lsp.config('*', {
capabilities = capabilities
})

113
apps/neovim/default.nix Normal file
View file

@ -0,0 +1,113 @@
{ config, pkgs, inputs, my-r,... }:
let
nvim-config-store = builtins.filterSource (path: type: baseNameOf path != "default.nix") ./.;
nvim-config-files = builtins.attrNames (builtins.readDir nvim-config-store);
nvim-config-lis = map (path: builtins.readFile "${nvim-config-store}/${path}") (nvim-config-files);
nvim-config = builtins.concatStringsSep "\n" nvim-config-lis;
in
{
programs.neovim = {
enable = true;
defaultEditor = true;
plugins = with pkgs.vimPlugins; [
nvim-treesitter.withAllGrammars
nvim-lspconfig
telescope-nvim
nvim-cmp
cmp-nvim-lsp
nvim-tree-lua
which-key-nvim
trouble-nvim
kanagawa-nvim
lazygit-nvim
quarto-nvim
otter-nvim
otter-nvim
image-nvim
molten-nvim
toggleterm-nvim
markdown-preview-nvim
lualine-nvim
gitsigns-nvim
indent-blankline-nvim
guess-indent-nvim
nvim-autopairs
catppuccin-nvim
];
extraLuaConfig = nvim-config;
extraPackages = with pkgs; [
#quarto
pyright
ripgrep
lua-language-server
rust-analyzer
lazygit
tectonic
imagemagick
jupyter
pnglatex
nil
nixfmt
rustc
cargo
rustfmt
clippy
rust-analyzer
];
extraLuaPackages = p: with p; [
magick
];
extraPython3Packages = ps: with ps; [
pynvim
jupyter-client
cairosvg
pnglatex
plotly
pyperclip
ipython
nbformat
];
};
xdg.dataFile."jupyter/kernels/ir-nix/kernel.json".text = ''
{
"argv": [
"${my-r.env}/bin/R",
"--slave",
"-e",
"IRkernel::main()",
"--args",
"{connection_file}"
],
"display_name": "R (nix)",
"language": "R"
}
'';
home.file.".Rprofile".text = ''
graphics::par(bg = "white")
options(
jupyter.rich_display = TRUE,
#jupyter.display_mimetypes = c("text/plain", "image/png", "text/latex"),
jupyter.display_mimetypes = c("text/plain", "image/png"),
jupyter.plot_mimetypes = "image/png"
)
'';
#sessionVariables = {
# EDITOR = "nvim";
# VISUAL = "nvim";
#};
}

View file

@ -0,0 +1,21 @@
local sign = function(opts)
vim.fn.sign_define(opts.name, {
texthl = opts.name,
text = opts.text,
numhl = ''
})
end
sign({name = 'DiagnosticSignError', text = ''})
sign({name = 'DiagnosticSignWarn', text = ''})
sign({name = 'DiagnosticSignHint', text = ''})
sign({name = 'DiagnosticSignInfo', text = '»'})
vim.diagnostic.config({
virtual_text = false,
severity_sort = true,
float = {
border = 'rounded',
source = 'always',
},
})

66
apps/neovim/gitsigns.lua Normal file
View file

@ -0,0 +1,66 @@
require('gitsigns').setup{
on_attach = function(bufnr)
local gitsigns = require('gitsigns')
local function map(mode, l, r, opts)
opts = opts or {}
opts.buffer = bufnr
vim.keymap.set(mode, l, r, opts)
end
-- Navigation
map('n', ']c', function()
if vim.wo.diff then
vim.cmd.normal({']c', bang = true})
else
gitsigns.nav_hunk('next')
end
end)
map('n', '[c', function()
if vim.wo.diff then
vim.cmd.normal({'[c', bang = true})
else
gitsigns.nav_hunk('prev')
end
end)
-- Actions
map('n', '<leader>hs', gitsigns.stage_hunk)
map('n', '<leader>hr', gitsigns.reset_hunk)
map('v', '<leader>hs', function()
gitsigns.stage_hunk({ vim.fn.line('.'), vim.fn.line('v') })
end)
map('v', '<leader>hr', function()
gitsigns.reset_hunk({ vim.fn.line('.'), vim.fn.line('v') })
end)
map('n', '<leader>hS', gitsigns.stage_buffer)
map('n', '<leader>hR', gitsigns.reset_buffer)
map('n', '<leader>hp', gitsigns.preview_hunk)
map('n', '<leader>hi', gitsigns.preview_hunk_inline)
map('n', '<leader>hb', function()
gitsigns.blame_line({ full = true })
end)
map('n', '<leader>hd', gitsigns.diffthis)
map('n', '<leader>hD', function()
gitsigns.diffthis('~')
end)
map('n', '<leader>hQ', function() gitsigns.setqflist('all') end)
map('n', '<leader>hq', gitsigns.setqflist)
-- Toggles
map('n', '<leader>tb', gitsigns.toggle_current_line_blame)
map('n', '<leader>tw', gitsigns.toggle_word_diff)
-- Text object
map({'o', 'x'}, 'ih', gitsigns.select_hunk)
end
}

View file

@ -0,0 +1 @@
require("guess-indent").setup {}

10
apps/neovim/ibl.lua Normal file
View file

@ -0,0 +1,10 @@
require("ibl").setup {
indent = {
char = "",
highlight = "IblIndent",
}
}
--│, ┆, ⋅, ╎
-- Use very subtle color
vim.api.nvim_set_hl(0, "IblIndent", { fg = "#3a3a3a" })

61
apps/neovim/init.lua Normal file
View file

@ -0,0 +1,61 @@
vim.g.mapleader = ' '
vim.g.maplocalleader = ' '
vim.g.have_nerd_font = true
vim.o.number = true
vim.o.mouse = 'a'
--vim.o.showmode = false
vim.schedule(function()
vim.o.clipboard = 'unnamedplus'
end)
vim.o.breakindent = true
vim.o.undofile = true
vim.o.ignorecase = true
vim.o.smartcase = true
vim.o.signcolumn = 'yes'
vim.o.colorcolumn = "80"
vim.o.updatetime = 250
vim.o.timeoutlen = 300
vim.o.splitright = true
vim.o.splitbelow = true
vim.o.list = true
vim.opt.listchars = { tab = '» ', trail = '·', nbsp = '' }
vim.o.inccommand = 'split'
vim.o.cursorline = true
vim.o.scrolloff = 15
vim.o.confirm = true
vim.keymap.set('i', 'jk', '<Escape>')
vim.keymap.set('i', 'kj', '<Escape>')
--vim.keymap.set('i', 'jf', '<Escape>')
-- See `:help wincmd` for a list of all window commands
vim.keymap.set('n', '<C-h>', '<C-w><C-h>', { desc = 'Move focus to the left window' })
vim.keymap.set('n', '<C-l>', '<C-w><C-l>', { desc = 'Move focus to the right window' })
vim.keymap.set('n', '<C-j>', '<C-w><C-j>', { desc = 'Move focus to the lower window' })
vim.keymap.set('n', '<C-k>', '<C-w><C-k>', { desc = 'Move focus to the upper window' })
vim.o.tabstop = 2
vim.o.smartindent = true
vim.o.shiftwidth = 2
vim.o.expandtab = true

14
apps/neovim/lazygit.lua Normal file
View file

@ -0,0 +1,14 @@
vim.g.lazygit_floating_window_winblend = 0 -- transparency of floating window
vim.g.lazygit_floating_window_scaling_factor = 0.9 -- scaling factor for floating window
vim.g.lazygit_floating_window_border_chars = {'','', '', '', '','', '', ''} -- customize lazygit popup window border characters
vim.g.lazygit_floating_window_use_plenary = 0 -- use plenary.nvim to manage floating window if available
vim.g.lazygit_use_neovim_remote = 1 -- fallback to 0 if neovim-remote is not installed
vim.g.lazygit_use_custom_config_file_path = 0 -- config file path is evaluated if this value is 1
vim.g.lazygit_config_file_path = '' -- custom config file path
-- OR
vim.g.lazygit_config_file_path = {} -- table of custom config file paths
vim.g.lazygit_on_exit_callback = nil -- optional function callback when exiting lazygit (useful for example to refresh some UI elements after lazy git has made some changes)
vim.keymap.set("n", "<Leader>gg", ":LazyGit<CR>", { desc = "LazyGit" })

101
apps/neovim/lsp.lua Normal file
View file

@ -0,0 +1,101 @@
local lspconfig = require('lspconfig')
vim.lsp.config('lua_ls', { cmd = { 'lua-language-server' },
settings = {
Lua = {
runtime = {
version = 'LuaJIT',
path = vim.split(package.path, ';'),
},
diagnostics = {
globals = { 'vim' },
},
},
},
})
vim.lsp.config('rust_analyzer', {
settings = {
['rust-analyzer'] = {
diagnostics = {
enable = true;
};
};
}
})
local languages = {
'rust_analyzer',
'r_language_server',
'lua_ls',
'quarto',
'nil_ls',
'pyright'
}
for _, language in ipairs(languages) do
vim.lsp.enable(language)
end
vim.diagnostic.config {
severity_sort = true,
float = { border = 'rounded', source = 'if_many' },
underline = { severity = vim.diagnostic.severity.ERROR },
signs = vim.g.have_nerd_font and {
text = {
[vim.diagnostic.severity.ERROR] = '󰅚 ',
[vim.diagnostic.severity.WARN] = '󰀪 ',
[vim.diagnostic.severity.INFO] = '󰋽 ',
[vim.diagnostic.severity.HINT] = '󰌶 ',
},
} or {},
virtual_text = {
source = 'if_many',
spacing = 2,
format = function(diagnostic)
local diagnostic_message = {
[vim.diagnostic.severity.ERROR] = diagnostic.message,
[vim.diagnostic.severity.WARN] = diagnostic.message,
[vim.diagnostic.severity.INFO] = diagnostic.message,
[vim.diagnostic.severity.HINT] = diagnostic.message,
}
return diagnostic_message[diagnostic.severity]
end,
},
}
vim.api.nvim_create_autocmd("LspAttach", {
callback = function(event)
local buf = event.buf
local opts = function(desc)
return { buffer = buf, desc = desc }
end
-- Jumping + finding
vim.keymap.set("n", "gd", vim.lsp.buf.definition, opts("[G]oto Definition"))
vim.keymap.set("n", "gD", vim.lsp.buf.declaration, opts("Goto [D]eclaration"))
vim.keymap.set("n", "gi", vim.lsp.buf.implementation, opts("[G]oto [I]mplementation"))
vim.keymap.set("n", "gr", vim.lsp.buf.references, opts("[G]oto [R]eferences"))
-- Info + documentation
vim.keymap.set("n", "K", vim.lsp.buf.hover, opts("Hover Documentation"))
vim.keymap.set("n", "<C-k>", vim.lsp.buf.signature_help, opts("Signature Help"))
-- Actions + rename
vim.keymap.set("n", "<leader>rn", vim.lsp.buf.rename, opts("[R]e[n]ame"))
vim.keymap.set({ "n", "v" }, "<leader>ca", vim.lsp.buf.code_action, opts("[C]ode [A]ction"))
-- Diagnostics navigation
vim.keymap.set("n", "[d", vim.diagnostic.goto_prev, opts("Prev Diagnostic"))
vim.keymap.set("n", "]d", vim.diagnostic.goto_next, opts("Next Diagnostic"))
vim.keymap.set("n", "<leader>e", vim.diagnostic.open_float, opts("Show Error"))
vim.keymap.set("n", "<leader>q", vim.diagnostic.setloclist, opts("Diagnostic Quickfix"))
-- Formatting
vim.keymap.set("n", "<leader>f", function()
vim.lsp.buf.format({ async = true })
end, opts("[F]ormat Buffer"))
end,
})

54
apps/neovim/lua-line.lua Normal file
View file

@ -0,0 +1,54 @@
require('lualine').setup {
options = {
icons_enabled = true,
theme = 'auto',
component_separators = { left = '', right = ''},
section_separators = { left = '', right = ''},
disabled_filetypes = {
statusline = {},
winbar = {},
},
ignore_focus = {},
always_divide_middle = true,
always_show_tabline = true,
globalstatus = false,
refresh = {
statusline = 1000,
tabline = 1000,
winbar = 1000,
refresh_time = 16, -- ~60fps
events = {
'WinEnter',
'BufEnter',
'BufWritePost',
'SessionLoadPost',
'FileChangedShellPost',
'VimResized',
'Filetype',
'CursorMoved',
'CursorMovedI',
'ModeChanged',
},
}
},
sections = {
lualine_a = {'mode'},
lualine_b = {'branch', 'diff', 'diagnostics'},
lualine_c = {'filename'},
lualine_x = {'encoding', 'fileformat', 'filetype'},
lualine_y = {'progress'},
lualine_z = {'location'}
},
inactive_sections = {
lualine_a = {},
lualine_b = {},
lualine_c = {'filename'},
lualine_x = {'location'},
lualine_y = {},
lualine_z = {}
},
tabline = {},
winbar = {},
inactive_winbar = {},
extensions = {}
}

48
apps/neovim/molten.lua Normal file
View file

@ -0,0 +1,48 @@
vim.g.molten_auto_open_output = true
vim.g.molten_wrap_output = true
vim.g.molten_output_show_exec_time = true
vim.g.molten_output_win_hide_on_leave = false
vim.g.molten_use_border_highlights = true
vim.g.molten_virt_text_output = true
vim.g.molten_image_provider = "image.nvim"
vim.g.molten_image_location = "both"
vim.g.molten_output_virt_lines = true
vim.g.molten_cover_empty_lines = true
vim.g.molten_output_truncate = "bottom"
vim.g.molten_virt_text_max_lines = 20
--vim.g.molten_output = "pane"
vim.api.nvim_set_hl(0, "NormalFloat", { bg = "#1e1e2e" })
vim.api.nvim_set_hl(0, "FloatBorder", { bg = "#1e1e2e" })
-- vim.api.nvim_set_hl(0, "MoltenVirtualText", {
-- bg = "#ae0606",
-- fg = "#ffd787",
-- italic = true,
-- })
require("image").setup({
editor = {
-- force a visible background for plots
background = "white", -- or "#ffffff" or "black"
inline = {
enable = true,
};
},
})
vim.keymap.set("n", "<localleader>mi", ":MoltenInit<CR>",
{ silent = true, desc = "Initialize the plugin" })
vim.keymap.set("n", "<localleader>e", ":MoltenEvaluateOperator<CR>",
{ silent = true, desc = "run operator selection" })
vim.keymap.set("n", "<localleader>rl", ":MoltenEvaluateLine<CR>",
{ silent = true, desc = "evaluate line" })
vim.keymap.set("n", "<localleader>rr", ":MoltenReevaluateCell<CR>",
{ silent = true, desc = "re-evaluate cell" })
vim.keymap.set("v", "<localleader>r", ":<C-u>MoltenEvaluateVisual<CR>gv",
{ silent = true, desc = "evaluate visual selection" })
vim.keymap.set("n", "<localleader>rd", ":MoltenDelete<CR>",
{ silent = true, desc = "molten delete cell" })
vim.keymap.set("n", "<localleader>oh", ":MoltenHideOutput<CR>",
{ silent = true, desc = "hide output" })
vim.keymap.set("n", "<localleader>os", ":noautocmd MoltenEnterOutput<CR>",
{ silent = true, desc = "show/enter output" })

25
apps/neovim/nvim-tree.lua Normal file
View file

@ -0,0 +1,25 @@
-- disable netrw at the very start of your init.lua
vim.g.loaded_netrw = 1
vim.g.loaded_netrwPlugin = 1
-- optionally enable 24-bit colour
vim.opt.termguicolors = true
-- OR setup with some options
require("nvim-tree").setup({
sort = {
sorter = "case_sensitive",
},
view = {
width = 30,
},
renderer = {
group_empty = true,
},
filters = {
dotfiles = true,
},
})
vim.keymap.set('n', '<Leader>d', ':NvimTreeToggle<CR>', { desc = 'Open or close the tree' })

5
apps/neovim/otter.lua Normal file
View file

@ -0,0 +1,5 @@
require("otter").setup({
buffers = {
write_to_disk = true,
},
})

37
apps/neovim/quarto.lua Normal file
View file

@ -0,0 +1,37 @@
local quarto = require('quarto')
require('quarto').setup{
debug = true,
lspFeatures = {
enabled = true,
hover = true,
chunks = "curly",
languages = { "r", "python", "julia", "bash", "html" },
diagnostics = {
enabled = false,
lint_cache = false,
triggers = { "BufWritePost" },
},
completion = {
enabled = true,
},
},
codeRunner = {
enabled = true,
default_method = "molten", -- "molten", "slime", "iron" or <function>
ft_runners = { r = "molten", python = "molten" }, -- filetype to runner, ie. `{ python = "molten" }`.
-- Takes precedence over `default_method`
never_run = { 'yaml' }, -- filetypes which are never sent to a code runner
},
}
vim.keymap.set('n', '<leader>qp', quarto.quartoPreview, { silent = true, noremap = true })
local runner = require("quarto.runner")
vim.keymap.set("n", "<localleader>rr", runner.run_cell, { desc = "run cell", silent = true })
vim.keymap.set("n", "<localleader>ra", runner.run_above, { desc = "run cell and above", silent = true })
vim.keymap.set("n", "<localleader>rR", runner.run_all, { desc = "run all cells", silent = true })
vim.keymap.set("n", "<localleader>rl", runner.run_line, { desc = "run line", silent = true })
vim.keymap.set("v", "<localleader>r", runner.run_range, { desc = "run visual range", silent = true })
vim.keymap.set("n", "<localleader>RA", function()
runner.run_all(true)
end, { desc = "run all cells of all languages", silent = true })

36
apps/neovim/telescope.lua Normal file
View file

@ -0,0 +1,36 @@
local builtin = require('telescope.builtin')
vim.keymap.set('n', '<leader>ff', builtin.find_files, { desc = 'Telescope find files' })
vim.keymap.set('n', '<leader>fg', builtin.live_grep, { desc = 'Telescope live grep' })
vim.keymap.set('n', '<leader>fb', builtin.buffers, { desc = 'Telescope buffers' })
vim.keymap.set('n', '<leader>fh', builtin.help_tags, { desc = 'Telescope help tags' })
require('telescope').setup{
defaults = {
-- Default configuration for telescope goes here:
-- config_key = value,
mappings = {
i = {
-- map actions.which_key to <C-h> (default: <C-/>)
-- actions.which_key shows the mappings for your picker,
-- e.g. git_{create, delete, ...}_branch for the git_branches picker
["<C-h>"] = "which_key"
}
}
},
pickers = {
-- Default configuration for builtin pickers goes here:
-- picker_name = {
-- picker_config_key = value,
-- ...
-- }
-- Now the picker_config_key will be applied every time you call this
-- builtin picker
},
extensions = {
-- Your extension configuration goes here:
-- extension_name = {
-- extension_config_key = value,
-- }
-- please take a look at the readme of the extension you want to configure
}
}

View file

@ -0,0 +1,5 @@
require("toggleterm").setup()
vim.keymap.set("n", "<Leader>tt", '<cmd>ToggleTerm direction=float<CR>', { noremap = ture, silent = true })
vim.keymap.set("n", "<Leader>tv", '<cmd>ToggleTerm direction=vertical<CR>', { noremap = ture, silent = true })
vim.keymap.set("n", "<Leader>th", '<cmd>ToggleTerm direction=horizontal<CR>', { noremap = ture, silent = true })

View file

@ -0,0 +1,23 @@
vim.api.nvim_create_autocmd("FileType", {
pattern = "*",
callback = function()
local ft = vim.bo.filetype
local lang = vim.treesitter.language.get_lang(ft)
if not lang or not vim.treesitter.language.add(lang) then
return
end
vim.treesitter.start()
-- Set folding if available
if vim.treesitter.query.get(lang, "folds") then
vim.wo.foldexpr = "v:lua.vim.treesitter.foldexpr()"
end
-- Set indentation if available (overrides traditional indent)
if vim.treesitter.query.get(lang, "indents") then
vim.bo.indentexpr = "nvim_treesitter#indent()"
end
end
})

204
apps/neovim/trouble.lua Normal file
View file

@ -0,0 +1,204 @@
---@class trouble.Mode: trouble.Config,trouble.Section.spec
---@field desc? string
---@field sections? string[]
---@class trouble.Config
---@field mode? string
---@field config? fun(opts:trouble.Config)
---@field formatters? table<string,trouble.Formatter> custom formatters
---@field filters? table<string, trouble.FilterFn> custom filters
---@field sorters? table<string, trouble.SorterFn> custom sorters
local defaults = {
auto_close = false, -- auto close when there are no items
auto_open = false, -- auto open when there are items
auto_preview = true, -- automatically open preview when on an item
auto_refresh = true, -- auto refresh when open
auto_jump = false, -- auto jump to the item when there's only one
focus = false, -- Focus the window when opened
restore = true, -- restores the last location in the list when opening
follow = true, -- Follow the current item
indent_guides = true, -- show indent guides
max_items = 200, -- limit number of items that can be displayed per section
multiline = true, -- render multi-line messages
pinned = false, -- When pinned, the opened trouble window will be bound to the current buffer
warn_no_results = true, -- show a warning when there are no results
open_no_results = false, -- open the trouble window when there are no results
---@type trouble.Window.opts
win = {}, -- window options for the results window. Can be a split or a floating window.
-- Window options for the preview window. Can be a split, floating window,
-- or `main` to show the preview in the main editor window.
---@type trouble.Window.opts
preview = {
type = "main",
-- when a buffer is not yet loaded, the preview window will be created
-- in a scratch buffer with only syntax highlighting enabled.
-- Set to false, if you want the preview to always be a real loaded buffer.
scratch = true,
},
-- Throttle/Debounce settings. Should usually not be changed.
---@type table<string, number|{ms:number, debounce?:boolean}>
throttle = {
refresh = 20, -- fetches new data when needed
update = 10, -- updates the window
render = 10, -- renders the window
follow = 100, -- follows the current item
preview = { ms = 100, debounce = true }, -- shows the preview for the current item
},
-- Key mappings can be set to the name of a builtin action,
-- or you can define your own custom action.
---@type table<string, trouble.Action.spec|false>
keys = {
["?"] = "help",
r = "refresh",
R = "toggle_refresh",
q = "close",
o = "jump_close",
["<esc>"] = "cancel",
["<cr>"] = "jump",
["<2-leftmouse>"] = "jump",
["<c-s>"] = "jump_split",
["<c-v>"] = "jump_vsplit",
-- go down to next item (accepts count)
-- j = "next",
["}"] = "next",
["]]"] = "next",
-- go up to prev item (accepts count)
-- k = "prev",
["{"] = "prev",
["[["] = "prev",
dd = "delete",
d = { action = "delete", mode = "v" },
i = "inspect",
p = "preview",
P = "toggle_preview",
zo = "fold_open",
zO = "fold_open_recursive",
zc = "fold_close",
zC = "fold_close_recursive",
za = "fold_toggle",
zA = "fold_toggle_recursive",
zm = "fold_more",
zM = "fold_close_all",
zr = "fold_reduce",
zR = "fold_open_all",
zx = "fold_update",
zX = "fold_update_all",
zn = "fold_disable",
zN = "fold_enable",
zi = "fold_toggle_enable",
gb = { -- example of a custom action that toggles the active view filter
action = function(view)
view:filter({ buf = 0 }, { toggle = true })
end,
desc = "Toggle Current Buffer Filter",
},
s = { -- example of a custom action that toggles the severity
action = function(view)
local f = view:get_filter("severity")
local severity = ((f and f.filter.severity or 0) + 1) % 5
view:filter({ severity = severity }, {
id = "severity",
template = "{hl:Title}Filter:{hl} {severity}",
del = severity == 0,
})
end,
desc = "Toggle Severity Filter",
},
},
---@type table<string, trouble.Mode>
modes = {
-- sources define their own modes, which you can use directly,
-- or override like in the example below
lsp_references = {
-- some modes are configurable, see the source code for more details
params = {
include_declaration = true,
},
},
-- The LSP base mode for:
-- * lsp_definitions, lsp_references, lsp_implementations
-- * lsp_type_definitions, lsp_declarations, lsp_command
lsp_base = {
params = {
-- don't include the current location in the results
include_current = false,
},
},
-- more advanced example that extends the lsp_document_symbols
symbols = {
desc = "document symbols",
mode = "lsp_document_symbols",
focus = false,
win = { position = "right" },
filter = {
-- remove Package since luals uses it for control flow structures
["not"] = { ft = "lua", kind = "Package" },
any = {
-- all symbol kinds for help / markdown files
ft = { "help", "markdown" },
-- default set of symbol kinds
kind = {
"Class",
"Constructor",
"Enum",
"Field",
"Function",
"Interface",
"Method",
"Module",
"Namespace",
"Package",
"Property",
"Struct",
"Trait",
},
},
},
},
},
-- stylua: ignore
icons = {
---@type trouble.Indent.symbols
indent = {
top = "",
middle = "├╴",
last = "└╴",
-- last = "-╴",
-- last = "╰╴", -- rounded
fold_open = "",
fold_closed = "",
ws = " ",
},
folder_closed = "",
folder_open = "",
kinds = {
Array = "",
Boolean = "󰨙 ",
Class = "",
Constant = "󰏿 ",
Constructor = "",
Enum = "",
EnumMember = "",
Event = "",
Field = "",
File = "",
Function = "󰊕 ",
Interface = "",
Key = "",
Method = "󰊕 ",
Module = "",
Namespace = "󰦮 ",
Null = "",
Number = "󰎠 ",
Object = "",
Operator = "",
Package = "",
Property = "",
String = "",
Struct = "󰆼 ",
TypeParameter = "",
Variable = "󰀫 ",
},
},
}

View file

@ -0,0 +1 @@
local wk = require("which-key")

175
apps/sway/default.nix Normal file
View file

@ -0,0 +1,175 @@
{ config, pkgs, lib, ... }:
let
mod = "Mod4";
terminal = "ghostty";
menu = "fuzzel";
pactl = "${pkgs.pulseaudio}/bin/pactl";
light = "${pkgs.brightnessctl}/bin/brightnessctl";
grim = "${pkgs.grim}/bin/grim";
slurp = "${pkgs.slurp}/bin/slurp";
wlcopy = "${pkgs.wl-clipboard}/bin/wl-copy";
colors = {
base = "#1e1e2e";
surface0 = "#313244";
surface1 = "#45475a";
text = "#cdd6f4";
subtext = "#bac2de";
blue = "#89b4fa";
yellow = "#f9e2af";
peach = "#fab387";
red = "#f38ba8";
};
in
{
wayland.windowManager.sway = {
enable = true;
config = {
modifier = mod;
terminal = terminal;
menu = menu;
bars = [ ];
fonts = {
names = [ "sans-serif" ];
size = 10.0;
};
window = {
border = 3;
titlebar = false;
};
gaps = {
inner = 0;
outer = 0;
smartGaps = false;
smartBorders = "on";
};
colors = {
focused = {
border = colors.yellow;
background = colors.yellow;
text = colors.base;
indicator = colors.yellow;
childBorder = colors.yellow;
};
focusedInactive = {
border = colors.surface1;
background = colors.surface1;
text = colors.text;
indicator = colors.surface1;
childBorder = colors.surface1;
};
unfocused = {
border = colors.surface1;
background = colors.surface1;
text = colors.text;
indicator = colors.surface1;
childBorder = colors.surface1;
};
urgent = {
border = colors.red;
background = colors.red;
text = colors.base;
indicator = colors.red;
childBorder = colors.red;
};
};
keybindings = lib.mkOptionDefault {
# launcher
"${mod}+Return" = "exec ${terminal}";
"${mod}+d" = "exec ${menu}";
# basic
"${mod}+Shift+q" = "kill";
"${mod}+Shift+r" = "reload";
"${mod}+Shift+e" = "exec swaynag -t warning -m 'Exit sway?' -b 'Yes, exit' 'swaymsg exit'";
# focus movement
"${mod}+h" = "focus left";
"${mod}+j" = "focus down";
"${mod}+k" = "focus up";
"${mod}+l" = "focus right";
# move windows
"${mod}+Shift+h" = "move left";
"${mod}+Shift+j" = "move down";
"${mod}+Shift+k" = "move up";
"${mod}+Shift+l" = "move right";
# split/layout
"${mod}+b" = "splith";
"${mod}+v" = "splitv";
"${mod}+e" = "layout toggle split";
"${mod}+s" = "layout stacking";
"${mod}+w" = "layout tabbed";
"${mod}+f" = "fullscreen toggle";
"${mod}+space" = "floating toggle";
"${mod}+a" = "focus parent";
# workspaces 1..10
"${mod}+1" = "workspace number 1";
"${mod}+2" = "workspace number 2";
"${mod}+3" = "workspace number 3";
"${mod}+4" = "workspace number 4";
"${mod}+5" = "workspace number 5";
"${mod}+6" = "workspace number 6";
"${mod}+7" = "workspace number 7";
"${mod}+8" = "workspace number 8";
"${mod}+9" = "workspace number 9";
"${mod}+0" = "workspace number 10";
"${mod}+Shift+1" = "move container to workspace number 1";
"${mod}+Shift+2" = "move container to workspace number 2";
"${mod}+Shift+3" = "move container to workspace number 3";
"${mod}+Shift+4" = "move container to workspace number 4";
"${mod}+Shift+5" = "move container to workspace number 5";
"${mod}+Shift+6" = "move container to workspace number 6";
"${mod}+Shift+7" = "move container to workspace number 7";
"${mod}+Shift+8" = "move container to workspace number 8";
"${mod}+Shift+9" = "move container to workspace number 9";
"${mod}+Shift+0" = "move container to workspace number 10";
# screenshots
"Print" = "exec ${grim} - | ${wlcopy}";
"Shift+Print" = "exec ${grim} -g \"$(${slurp})\" - | ${wlcopy}";
# audio keys
"XF86AudioRaiseVolume" = "exec ${pactl} set-sink-volume @DEFAULT_SINK@ +5%";
"XF86AudioLowerVolume" = "exec ${pactl} set-sink-volume @DEFAULT_SINK@ -5%";
"XF86AudioMute" = "exec ${pactl} set-sink-mute @DEFAULT_SINK@ toggle";
"XF86AudioMicMute" = "exec ${pactl} set-source-mute @DEFAULT_SOURCE@ toggle";
# brightness keys
"XF86MonBrightnessUp" = "exec ${light} set +5%";
"XF86MonBrightnessDown" = "exec ${light} set 5%-";
};
startup = [
{
command = "mako";
always = true;
}
{
command = "fcitx5";
always = true;
}
#{ command = "waybar"; always = true; }
];
};
#extraConfig = '''';
};
}

91
apps/waybar/config.jsonc Normal file
View file

@ -0,0 +1,91 @@
{
"layer": "top",
"position": "top",
"height": 25,
"spacing": 1,
"modules-left": ["sway/workspaces", "sway/mode"],
"modules-right": ["cpu", "memory", "network", "pulseaudio","battery", "clock"],
"sway/workspaces": {
"disable-scroll": true,
},
"sway/window": {
"max-length": 60
},
"network": {
"interval": 5,
"format-alt": "{icon} ↓{bandwidthDownBits} ↑{bandwidthUpBits}",
"format-wifi": "{icon} {signalStrength}%",
"format-icons": ["󰤯 ", "󰤟 ", "󰤢 ", "󰤥 ", "󰤨 "],
"format-ethernet": "󰈀 ",
"format-disconnected": "󰤭 ",
"tooltip": true,
"tooltip-format": "{essid} {ipaddr}"
},
"pulseaudio": {
"format": "{icon} {volume}%",
"format-muted": " 0%",
"format-icons": {
"default": [" "],
},
"on-click": "pactl set-sink-mute @DEFAULT_SINK@ toggle",
},
"battery": {
"format": "{icon} {capacity}%",
"format-alt": "{icon} {time} 󱐋 {power}W",
"format-charging": "{icon} {capacity}%",
"states": {
"warning": 25,
"critical": 10
},
"format-icons": {
"default": ["󰂃", "󰁻", "󰁼", "󰁽", "󰁾", "󰁿", "󰂀", "󰂁", "󰂂", "󰁹"],
"charging": ["󰢜 ", "󰂆 ", "󰂇 ", "󰂈 ", "󰢝 ", "󰂉 ", "󰢞 ", "󰂊 ", "󰂋 ", "󰂅 "],
}
},
"clock": {
"format": "󰥔 {:%H:%M}",
"tooltip-format": "{:%a %d %b %Y}"
},
"cpu": {
"interval": 2,
"format": " {usage}%",
"format-alt":" {usage}% {icon0}{icon1}{icon2}{icon3}{icon4}{icon5}{icon6}{icon7}{icon8}{icon9}{icon10}{icon11}{icon12}{icon13}{icon14}{icon15}  {avg_frequency} GHz",
"states": {
"warning": 60,
"critical": 85,
},
"format-icons": [
"<span color='#b4befe'>▁</span>", // lavender (cool)
"<span color='#89b4fa'>▂</span>", // blue
"<span color='#cdd6f4'>▃</span>", // text
"<span color='#cdd6f4'>▄</span>", // text
"<span color='#f9e2af'>▅</span>", // yellow (warm)
"<span color='#f9e2af'>▆</span>", // yellow
"<span color='#fab387'>▇</span>", // peach (hot)
"<span color='#f38ba8'>█</span>" // red (very hot)
],
"tooltip": true
},
"memory": {
"interval": 5,
"format": " {percentage}%",
"format-alt": " {used}GiB / {avail}GiB",
"states": {
"warning": 70,
"critical": 90,
},
"tooltip": true
}
}

12
apps/waybar/default.nix Normal file
View file

@ -0,0 +1,12 @@
{ config, pkgs, ... }:
{
programs.waybar = {
enable = true;
systemd.enable = true;
};
xdg.configFile = {
"waybar/config.jsonc".source = ./config.jsonc;
"waybar/style.css".source = ./style.css;
};
}

150
apps/waybar/style.css Normal file
View file

@ -0,0 +1,150 @@
/* =========================================================
Waybar style.css Catppuccin Mocha (GTK-safe)
Unified palette + consistent workspaces + severity states
========================================================= */
/* ---------- Catppuccin Mocha palette ---------- */
@define-color base #1e1e2e;
@define-color mantle #181825;
@define-color crust #11111b;
@define-color text #cdd6f4;
@define-color subtext #bac2de;
@define-color muted #9399b2;
@define-color surface0 #313244;
@define-color surface1 #45475a;
/* Accents */
@define-color blue #89b4fa;
@define-color green #a6e3a1;
@define-color peach #fab387;
@define-color yellow #f9e2af;
@define-color red #f38ba8;
@define-color teal #94e2d5;
@define-color lavender #b4befe;
/* Bar + state backgrounds */
@define-color bar_bg rgba(30, 30, 46, 0.88);
@define-color warn_bg rgba(249, 226, 175, 0.28);
@define-color crit_bg rgba(243, 139, 168, 0.38);
/* Workspace backgrounds */
@define-color ws_hover rgba(137, 180, 250, 0.16);
@define-color ws_focus rgba(137, 180, 250, 0.26);
/* ---------- Global reset ---------- */
* {
border: none;
border-radius: 0;
min-height: 0;
box-shadow: none;
}
/* ---------- Bar ---------- */
window#waybar {
background: @bar_bg;
color: @text;
font-size: 12px;
}
/* =========================================================
Workspaces
========================================================= */
#workspaces {
padding: 0 5px;
}
#workspaces button {
padding: 0 5px;
margin: 0px 0px;
color: @subtext;
background: transparent;
}
#workspaces button.focused {
color: @text;
background: @ws_focus;
}
#workspaces button:hover {
background: @ws_hover;
}
#workspaces button.urgent {
color: @text;
background: @crit_bg;
}
#mode {
padding: 0 12px;
background: #f9e2af; /* fully opaque yellow */
color: #1e1e2e; /* dark text */
font-weight: 600;
}
/* =========================================================
Modules (shared)
========================================================= */
#network,
#pulseaudio,
#battery,
#clock,
#cpu,
#memory {
padding: 0 5px; /* literal spacing — GTK-safe */
margin: 0px 0px;
background: transparent;
color: @text;
}
/* Identity colors */
#network { color: @blue; }
#pulseaudio { color: @peach; }
#battery { color: @green; }
#clock { color: @text; font-weight: 500; }
#cpu { color: @lavender; }
#memory { color: @teal; }
/* =========================================================
Severity states (single, consistent approach)
========================================================= */
#battery.warning {
background: @warn_bg;
color: @text;
}
#battery.critical {
background: @crit_bg;
color: @text;
}
#network.disconnected {
background: @crit_bg;
color: @text;
}
#cpu.warning {
background: @warn_bg;
color: @text;
}
#cpu.critical {
background: @crit_bg;
color: @text;
}
#memory.warning {
background: @warn_bg;
color: @text;
}
#memory.critical {
background: @crit_bg;
color: @text;
}

92
flake.lock generated Normal file
View file

@ -0,0 +1,92 @@
{
"nodes": {
"disko": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1746728054,
"narHash": "sha256-eDoSOhxGEm2PykZFa/x9QG5eTH0MJdiJ9aR00VAofXE=",
"owner": "nix-community",
"repo": "disko",
"rev": "ff442f5d1425feb86344c028298548024f21256d",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "latest",
"repo": "disko",
"type": "github"
}
},
"home-manager": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1758463745,
"narHash": "sha256-uhzsV0Q0I9j2y/rfweWeGif5AWe0MGrgZ/3TjpDYdGA=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "3b955f5f0a942f9f60cdc9cacb7844335d0f21c3",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "release-25.05",
"repo": "home-manager",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1765762245,
"narHash": "sha256-3iXM/zTqEskWtmZs3gqNiVtRTsEjYAedIaLL0mSBsrk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c8cfcd6ccd422e41cc631a0b73ed4d5a925c393d",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.11",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"disko": "disko",
"home-manager": "home-manager",
"nixpkgs": "nixpkgs",
"sops-nix": "sops-nix"
}
},
"sops-nix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1768863606,
"narHash": "sha256-1IHAeS8WtBiEo5XiyJBHOXMzECD6aaIOJmpQKzRRl64=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "c7067be8db2c09ab1884de67ef6c4f693973f4a2",
"type": "github"
},
"original": {
"owner": "Mic92",
"repo": "sops-nix",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

124
flake.nix Normal file
View file

@ -0,0 +1,124 @@
{
description = "My NixOS flake";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
disko = {
url = "github:nix-community/disko/latest";
inputs.nixpkgs.follows = "nixpkgs";
};
home-manager = {
url = "github:nix-community/home-manager/release-25.05";
inputs.nixpkgs.follows = "nixpkgs";
};
sops-nix = {
url = "github:Mic92/sops-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
{
self,
nixpkgs,
disko,
home-manager,
sops-nix,
...
}@inputs:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in
{
nixosConfigurations =
let
my-r = import ./languages/r.nix { inherit pkgs; };
my-python = import ./languages/python.nix { inherit pkgs; };
T14pModules = [
disko.nixosModules.disko
sops-nix.nixosModules.sops
home-manager.nixosModules.home-manager
./hosts/T14p/configuration.nix
{
home-manager.extraSpecialArgs = {
inherit inputs my-r my-python;
};
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.users.louis = import ./home/full.nix;
home-manager.users.root = import ./home/root.nix;
}
];
mkT14p = nixpkgs.lib.nixosSystem {
inherit system;
specialArgs = { inherit inputs; };
modules = T14pModules;
};
mkServer =
domain: isProd:
nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
specialArgs = { inherit inputs domain isProd; };
modules = [
inputs.disko.nixosModules.disko
sops-nix.nixosModules.sops
./hosts/hetzner/configuration.nix
];
};
in
{
T14p = mkT14p;
hetzner-prod = mkServer "louisclee.com" true;
hetzner-test = mkServer "localhost" false;
};
apps.${system} =
let
target = "root@louisclee.com";
deployScript = pkgs.writeShellScriptBin "deploy" ''
${pkgs.nixos-rebuild}/bin/nixos-rebuild switch \
--flake .#hetzner-prod \
--target-host ${target} \
--use-remote-sudo
'';
initScript = pkgs.writeShellScriptBin "init" ''
nix run github:nix-community/nixos-anywhere -- \
--flake .#hetzner-prod \
--extra-files ./hosts/hetzner/extra-files \
${target}
'';
in
{
deploy = {
type = "app";
program = "${deployScript}/bin/deploy";
};
init = {
type = "app";
program = "${initScript}/bin/init";
};
vm = {
type = "app";
program = "${self.nixosConfigurations.hetzner-test.config.system.build.vm}/bin/run-webserver-vm";
};
};
};
}

86
home/full.nix Normal file
View file

@ -0,0 +1,86 @@
{ config, pkgs, inputs, my-r, my-python,... }: {
home.username = "louis";
home.homeDirectory = "/home/louis";
home.enableNixpkgsReleaseCheck = false;
imports = [
../apps/neovim
../apps/sway
../apps/waybar
../apps/fuzzel
#../modules/model-downloads.nix
];
home.packages = with pkgs; [
fzf
chromium
poppler-utils
my-r.env
my-r.rstudio
my-python
which
ripgrep
kitty
gcc
gnumake
kdePackages.okular
quarto
julia
wechat
wemeet
zotero
sqlite
anki-bin
sqlitebrowser
dig
ghostty
inkscape
tig
wofi
grim
slurp
wl-clipboard
brightnessctl
pulseaudio
neofetch
llama-cpp
];
my.apps.fuzzel.enable = true;
programs.fish = {
enable = true;
interactiveShellInit = ''
export QUARTO_R="/home/louis/.nix-profile/bin/R";
export EDITOR="nvim";
'';
};
#programs.zsh = {
# enable = true;
# enableCompletion = true;
# autosuggestion.enable = true;
# syntaxHighlighting.enable = true;
# sessionVariables = {
# QUARTO_R = "/home/louis/.nix-profile/bin/R";
# EDITOR = "nvim";
# };
#};
programs.git = {
enable = true;
userName = "Louis Chih-Ming Lee";
userEmail = "louis@louisclee.com";
};
#modelDownloads = {
# enable = true;
# dir = "${config.home.homeDirectory}/data/ai/models";
# urls = [
# "https://huggingface.co/bartowski/Meta-Llama-3.1-8B-Instruct-GGUF/resolve/main/Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf"
# ];
#};
home.stateVersion = "25.05";
}

12
home/root.nix Normal file
View file

@ -0,0 +1,12 @@
{ config, pkgs, inputs, ... }:
{
home.username = "root";
home.enableNixpkgsReleaseCheck = false;
imports = [
./../apps/neovim
];
home.stateVersion = "25.05";
}

View file

@ -0,0 +1,275 @@
# Edit this configuration file to define what should be installed on
# your system. Help is available in the configuration.nix(5) man page, on
# https://search.nixos.org/options and in the NixOS manual (`nixos-help`).
{
inputs,
config,
lib,
pkgs,
...
}:
{
imports = [
# Include the results of the hardware scan.
./hardware-configuration.nix
#./impermanence.nix { inherit lib; }
./disko.nix
./network.nix
];
nix.settings.experimental-features = [
"nix-command"
"flakes"
];
# Use the systemd-boot EFI boot loader.
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
boot.kernelPackages = pkgs.linuxPackages_latest;
networking.hostName = "T14p"; # Define your hostname.
# Pick only one of the below networking options.
# networking.wireless.enable = true; # Enables wireless support via wpa_supplicant.
networking.networkmanager.enable = true; # Easiest to use and most distros use this by default.
services.mullvad-vpn.enable = true;
sops = {
defaultSopsFile = ./../../secrets.yaml;
defaultSopsFormat = "yaml";
age.keyFile = "/home/louis/.config/sops/age/keys.txt";
};
hardware.enableAllFirmware = true;
hardware.graphics = {
enable = true;
extraPackages = with pkgs; [
mesa
];
};
hardware.bluetooth = {
enable = true;
powerOnBoot = true;
#settings = {
# General = {
# Experimental = true;
# FastConnectable = true;
# };
# Policy = {
# AutoEnable = true;
# };
#};
};
# Set your time zone.
time.timeZone = "Europe/Amsterdam";
# Configure network proxy if necessary
# networking.proxy.default = "http://user:password@proxy:port/";
# networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain";
# Select internationalisation properties.
i18n = {
defaultLocale = "en_US.UTF-8";
extraLocales = [
"zh_TW.UTF-8/UTF-8"
"zh_CN.UTF-8/UTF-8"
"nl_NL.UTF-8/UTF-8"
];
};
i18n.inputMethod = {
type = "fcitx5";
enable = true;
fcitx5.addons = with pkgs; [
fcitx5-gtk
fcitx5-rime
fcitx5-chewing
qt6Packages.fcitx5-chinese-addons
rime-data
librime
];
fcitx5.waylandFrontend = true;
};
environment.variables = {
GTK_IM_MODULE = "fcitx";
QT_IM_MODULE = "fcitx";
XMODIFIERS = "@im=fcitx";
GLFW_IM_MODULE = "fcitx";
SDL_IM_MODULE = "fcitx";
MOZ_ENABLE_WAYLAND = 1;
QT_QPA_PLATFORM = "wayland";
};
# electron blurring in wayland
environment.sessionVariables = {
NIXOS_OZONE_WL = "1";
};
console = {
font = "Lat2-Terminus16";
#keyMap = "us";
useXkbConfig = true; # use xkb.options in tty.
};
# Enable the X11 windowing system.
services.xserver.enable = true;
#services.displayManager.cosmic-greeter.enable = true;
services.displayManager.gdm.enable = true;
services.desktopManager.cosmic.enable = true;
programs.sway = {
enable = true;
wrapperFeatures.gtk = true;
extraPackages = with pkgs; [
adwaita-icon-theme
gnome-themes-extra
];
};
fonts.packages = with pkgs; [
lxgw-fusionkai
noto-fonts
noto-fonts-cjk-sans
noto-fonts-cjk-serif
winePackages.fonts
noto-fonts-color-emoji
nerd-fonts.fira-code
];
services.keyd = {
enable = true;
keyboards = {
default = {
ids = [ "*" ];
settings = {
main = {
capslock = "layer(control)";
control = "capslock";
leftmeta = "layer(alt)";
leftalt = "layer(meta)";
rightalt = "esc";
};
};
};
hhkb = {
ids = [ "04fe:0021:f2a164d2" ];
settings = { };
};
};
};
# Configure keymap in X11
# services.xserver.xkb.layout = "us";
# services.xserver.xkb.options = "eurosign:e,caps:escape";
# Enable CUPS to print documents.
services.printing.enable = true;
# Enable sound.
# services.pulseaudio.enable = true;
# OR
services.pipewire = {
enable = true;
pulse.enable = true;
};
# Enable touchpad support (enabled default in most desktopManager).
services.libinput.enable = true;
# Define a user account. Don't forget to set a password with passwd.
users.users.louis = {
isNormalUser = true;
extraGroups = [
"wheel"
"networkmanager"
"video"
];
shell = pkgs.fish;
ignoreShellProgramCheck = true;
packages = with pkgs; [
keyd
tree
wl-clipboard
mako
killall
];
};
programs.firefox.enable = true;
# List packages installed in system profile.
# You can use https://search.nixos.org/ to find more packages (and options).
environment.systemPackages = with pkgs; [
vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default.
kdePackages.kdeconnect-kde
usbutils
wget
neovim
acpi
texliveFull
zathura
libreoffice
htop
ranger
teams-for-linux
gcc
gnumake
mullvad-vpn
mullvad
sops
age
ssh-to-age
];
# Some programs need SUID wrappers, can be configured further or are
# started in user sessions.
programs.mtr.enable = true;
programs.gnupg.agent = {
enable = true;
enableSSHSupport = true;
};
nixpkgs.config.allowUnfree = true;
# List services that you want to enable:
# Enable the OpenSSH daemon.
services.openssh.enable = true;
# Open ports in the firewall.
# networking.firewall.allowedTCPPorts = [ ... ];
# networking.firewall.allowedUDPPorts = [ ... ];
# Or disable the firewall altogether.
networking.firewall.enable = false;
# Copy the NixOS configuration file and link it from the resulting system
# (/run/current-system/configuration.nix). This is useful in case you
# accidentally delete configuration.nix.
#system.copySystemConfiguration = true;
# This option defines the first version of NixOS you have installed on this particular machine,
# and is used to maintain compatibility with application data (e.g. databases) created on older NixOS versions.
#
# Most users should NEVER change this value after the initial install, for any reason,
# even if you've upgraded your system to a new NixOS release.
#
# This value does NOT affect the Nixpkgs version your packages and OS are pulled from,
# so changing it will NOT upgrade your system - see https://nixos.org/manual/nixos/stable/#sec-upgrading for how
# to actually do that.
#
# This value being lower than the current NixOS release does NOT mean your system is
# out of date, out of support, or vulnerable.
#
# Do NOT change this value unless you have manually inspected all the changes it would make to your configuration,
# and migrated your data accordingly.
#
# For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion .
system.stateVersion = "25.05"; # Did you read the comment?
}

70
hosts/T14p/disko.nix Normal file
View file

@ -0,0 +1,70 @@
{
disko.devices = {
disk = {
main = {
type = "disk";
device = "/dev/nvme1n1";
content = {
type = "gpt";
partitions = {
ESP = {
size = "512M";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
mountOptions = [ "umask=0077" ];
};
};
luks = {
size = "100%";
content = {
type = "luks";
name = "crypted";
# disable settings.keyFile if you want to use interactive password entry
#passwordFile = "/tmp/secret.key"; # Interactive
settings = {
allowDiscards = true;
#keyFile = "/tmp/secret.key";
};
#additionalKeyFiles = [ "/tmp/additionalSecret.key" ];
content = {
type = "btrfs";
extraArgs = [ "-f" ];
subvolumes = {
"@root" = {
mountpoint = "/";
mountOptions = [
"compress=zstd"
"noatime"
];
};
"@persist" = {
mountpoint = "/persist";
mountOptions = [
"compress=zstd"
"noatime"
];
};
"@nix" = {
mountpoint = "/nix";
mountOptions = [
"compress=zstd"
"noatime"
];
};
"@swap" = {
mountpoint = "/.swapvol";
swap.swapfile.size = "20G";
};
};
};
};
};
};
};
};
};
};
}

View file

@ -0,0 +1,26 @@
# Do not modify this file! It was generated by nixos-generate-config
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{ config, lib, pkgs, modulesPath, ... }:
{
imports =
[ (modulesPath + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = [ "xhci_pci" "thunderbolt" "nvme" "usb_storage" "sd_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
# (the default) this is the recommended approach. When using systemd-networkd it's
# still possible to use this option, but it's recommended to use it in conjunction
# with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
networking.useDHCP = lib.mkDefault true;
# networking.interfaces.enp0s31f6.useDHCP = lib.mkDefault true;
# networking.interfaces.wlp0s20f3.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}

View file

@ -0,0 +1,56 @@
{ lib, ... }:
let
impermanence = builtins.fetchTarball "https://github.com/nix-community/impermanence/archive/master.tar.gz";
in
{
boot.initrd.postResumeCommands = lib.mkAfter ''
mkdir /btrfs_tmp
mount /dev/mapper/crypted /btrfs_tmp
if [[ -e /btrfs_tmp/@root ]]; then
mkdir -p /btrfs_tmp/@old_roots
timestamp=$(date --date="@$(stat -c %Y /btrfs_tmp/root)" "+%Y-%m-%-d_%H:%M:%S")
mv /btrfs_tmp/@root "/btrfs_tmp/@old_roots/$timestamp"
fi
delete_subvolume_recursively() {
IFS=$'\n'
for i in $(btrfs subvolume list -o "$1" | cut -f 9- -d ' '); do
delete_subvolume_recursively "/btrfs_tmp/$i"
done
btrfs subvolume delete "$1"
}
for i in $(find /btrfs_tmp/@old_roots/ -maxdepth 1 -mtime +10); do
delete_subvolume_recursively "$i"
done
btrfs subvolume create /btrfs_tmp/@root
umount /btrfs_tmp
'';
imports = [ "${impermanence}/nixos.nix" ];
environment.persistence."/persist" = {
enable = true;
hideMounts = true;
directories = [
"/var/log"
"/var/lib/bluetooth"
"/var/lib/nixox"
"/var/lib/systemd/coredump"
"/etc/NetworkManager/system-connections"
];
files = [
"/etc/machine-id"
];
users.louis = {
directories = [
"Documents"
"Downloads"
];
};
};
fileSystems."/persist".neededForBoot = true;
}

34
hosts/T14p/leiden.crt Normal file
View file

@ -0,0 +1,34 @@
-----BEGIN CERTIFICATE-----
MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB
iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV
BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw
MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV
BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy
dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B
3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY
tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/
Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2
VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT
79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6
c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT
Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l
c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee
UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE
Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd
BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G
A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF
Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO
VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3
ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs
8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR
iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze
Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ
XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/
qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB
VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB
L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG
jjxDah2nGN59PRbxYvnKkKj9
-----END CERTIFICATE-----

83
hosts/T14p/network.nix Normal file
View file

@ -0,0 +1,83 @@
{
pkgs,
lib,
config,
...
}:
let
wifi = ssid: alias: priority: {
sops.secrets."wifi_${alias}" = { };
sops.templates."wifi_${alias}.nmconnection" = {
path = "/etc/NetworkManager/system-connections/wifi_${ssid}.nmconnection";
owner = "root";
group = "root";
mode = "0600";
content = ''
[connection]
id=${ssid}
type=wifi
autoconnect=true
autoconnect-priority=${toString priority}
[wifi]
ssid=${ssid}
mode=infrastructure
[wifi-security]
key-mgmt=wpa-psk
psk=${config.sops.placeholder."wifi_${alias}"}
[ipv4]
method=auto
[ipv6]
addr-gen-mode=default
method=auto
'';
};
};
in
lib.mkMerge [
(wifi "Ziggo966936B" "home" 100)
{
sops.secrets.wifi_leiden_identity = { };
sops.secrets.wifi_leiden_password = { };
sops.templates."wifi_eduroam.nmconnection" = {
path = "/etc/NetworkManager/system-connections/eduroam.nmconnection";
owner = "root";
group = "root";
mode = "0600";
content = ''
[connection]
id=eduroam
type=wifi
[wifi]
ssid=eduroam
mode=infrastructure
[wifi-security]
key-mgmt=wpa-eap
[802-1x]
eap=peap
identity=${config.sops.placeholder.wifi_leiden_identity}
password=${config.sops.placeholder.wifi_leiden_password}
anonymous-identity=anonymous@leidenuniv.nl
phase2-auth=mschapv2
ca-cert=${./leiden.crt}
'';
};
}
{
# Run nmcli reload after every switch
system.activationScripts.nm-reload = {
text = ''
${pkgs.networkmanager}/bin/nmcli connection reload || true
'';
deps = [ ];
};
}
]

View file

@ -0,0 +1,64 @@
{
pkgs,
lib,
domain,
isProd,
inputs,
config,
modulesPath,
...
}:
{
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
(modulesPath + "/profiles/qemu-guest.nix")
./disko-config.nix
./secret.nix
./media.nix
./nginx.nix
./immich.nix
./forego.nix
# ./vm.nix
];
system.stateVersion = "25.11";
environment.systemPackages = with pkgs; [
vim
cifs-utils
btrfs-progs
forgejo
];
nix.settings.experimental-features = [
"nix-command"
"flakes"
];
boot.loader.grub = {
# enable = true;
efiSupport = true;
efiInstallAsRemovable = true;
};
services.openssh.enable = true;
users.users.root.openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDBUxBUar3CyZCZTet3s8s28Pu1d0viuDe6YoMQBVdFB louis@T14p"
];
networking.hostName = "webserver";
networking.firewall.allowedTCPPorts = [
80
443
22
];
security.acme = lib.mkIf isProd {
acceptTerms = true;
defaults.email = "admin@${domain}";
};
}

View file

@ -0,0 +1,86 @@
{ lib, ... }:
let
disk-id = "scsi-0QEMU_QEMU_HARDDISK_110162268";
data-id = "scsi-0HC_Volume_104473479";
in
{
disko.devices = {
# --- DISK 1: MAIN OS (38GB) ---
disk.main = {
# You were right! This IS the correct ID for the 38GB drive.
device = "/dev/disk/by-id/${disk-id}";
type = "disk";
content = {
type = "gpt";
partitions = {
boot = {
size = "1M";
type = "EF02";
priority = 1;
};
ESP = {
size = "512M";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
};
};
root = {
size = "100%";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
};
};
};
};
disk.volume = {
# This is the ID for your volume (from your ls output)
device = "/dev/disk/by-id/${disk-id}";
type = "disk";
content = {
type = "gpt";
partitions = {
data = {
size = "100%";
content = {
type = "btrfs";
extraArgs = [ "-f" ];
mountpoint = "/mnt/data";
subvolumes = {
"@postgresql" = {
mountpoint = "/mnt/data/postgresql";
mountOptions = [
"nodatacow"
"noatime"
];
};
"@forgejo" = {
mountpoint = "/mnt/data/forgejo";
mountOptions = [
"compress=zstd"
"noatime"
];
};
"@immich" = {
mountpoint = "/mnt/data/immich";
mountOptions = [
"compress=zstd"
"noatime"
];
};
};
};
};
};
};
};
};
}

View file

@ -0,0 +1,71 @@
{ lib, config, ... }:
let
volume-id = "scsi-0HC_Volume_104473479";
in
{
systemd.tmpfiles.rules = [
"d /mnt/ 0755 root root -"
"d /mnt/box 0770 root root -"
"d /mnt/box/immich 0770 immich immich -"
"d /mnt/box/immich 0770 immich immich -"
"d /mnt/box/immich/library 0770 immich immich -"
"d /mnt/box/immich/upload 0770 immich immich -"
"d /mnt/box/immich/thumbs 0770 immich immich -"
"d /mnt/box/immich/encoded-video 0770 immich immich -"
"d /mnt/box/immich/profile 0770 immich immich -"
"d /mnt/box/immich/backups 0770 immich immich -"
"d /mnt/volume 0777 root root -"
"d /mnt/volume/postgresql 0700 postgres postgres -"
"d /mnt/volume/forgejo 0750 forgejo forgejo -"
"d /mnt/volume/immich 0750 immich immich -"
];
fileSystems."/mnt/data/postgres" = {
device = "/dev/disk/by-id/${volume-id}";
fsType = "btrfs";
options = [
"subvol=@postgres"
"nodatacow" # <--- Disables Copy-on-Write for performance
"noatime"
];
};
fileSystems."/mnt/data/immich" = {
device = "/dev/disk/by-id/${volume-id}";
fsType = "btrfs";
options = [
"subvol=@immich"
"compress=zstd"
"noatime"
];
};
fileSystems."/mnt/data/forgejo" = {
device = "/dev/disk/by-id/${volume-id}";
fsType = "btrfs";
options = [
"subvol=@forgejo"
"compress=zstd"
"noatime"
];
};
#fileSystems."/mnt/box" = {
# device = "//u536222.your-storagebox.de/backup";
# fsType = "cifs";
# options = [
# "x-systemd.automount"
# "noauto"
# "rw"
# "credentials=${config.sops.secrets.storage_box_credentials.path}"
# "uid=900"
# "gid=100"
# "file_mode=0660"
# "dir_mode=0770"
# ];
#};
}

74
hosts/hetzner/forego.nix Normal file
View file

@ -0,0 +1,74 @@
{
domain,
isProd,
config,
pkgs,
...
}:
{
# 1. Access the Secret
sops.secrets.forgejo_db_password = {
owner = "forgejo";
# Restart forgejo if the password changes
restartUnits = [ "forgejo.service" ];
};
services.forgejo = {
enable = true;
# 2. STORAGE (SSD)
stateDir = "/mnt/data/forgejo";
# 3. DATABASE (Shared Postgres)
database = {
type = "postgres";
name = "forgejo";
user = "forgejo";
createDatabase = false; # We let NixOS manage this below
socket = "/run/postgresql"; # Ultra-fast socket connection
passwordFile = config.sops.secrets.forgejo_db_password.path;
};
# 4. SETTINGS
settings = {
server = {
DOMAIN = "git.${domain}";
ROOT_URL = "https://git.${domain}/";
HTTP_PORT = 3000;
# Run internal SSH on 2222 so it doesn't block your Admin SSH (22)
SSH_PORT = 2222;
START_SSH_SERVER = true;
};
# Disable registration to prevent random internet people from joining
service.DISABLE_REGISTRATION = true;
# Optional: Metrics for Grafana later
metrics.ENABLED = true;
};
};
# 5. POSTGRESQL PROVISIONING
# This automatically creates the DB and User when you deploy
services.postgresql = {
ensureDatabases = [ "forgejo" ];
ensureUsers = [
{
name = "forgejo";
ensureDBOwnership = true;
}
];
};
# 6. REVERSE PROXY
services.nginx.virtualHosts."git.${domain}" = {
forceSSL = isProd;
enableACME = isProd;
locations."/" = {
proxyPass = "http://127.0.0.1:3000";
};
};
# 7. FIREWALL
# Allow Git-over-SSH on the custom port
networking.firewall.allowedTCPPorts = [ 2222 ];
}

79
hosts/hetzner/immich.nix Normal file
View file

@ -0,0 +1,79 @@
{
domain,
lib,
isProd,
config,
pkgs,
...
}:
{
sops.secrets.immich_db_password = { };
sops.secrets.immich_jwt_secret = { };
sops.templates."immich.env".content = ''
DB_PASSWORD=${config.sops.placeholder.immich_db_password}
JWT_SECRET=${config.sops.placeholder.immich_jwt_secret}
'';
#users.users.immich.extraGroups = [ "users" ];
users.users.immich.uid = 900;
users.groups.immich.gid = 900;
services.immich = {
enable = true;
host = "127.0.0.1";
port = 2283;
mediaLocation = "/mnt/media/immich";
secretsFile = config.sops.templates."immich.env".path;
redis.enable = true;
database = {
enable = true;
createDB = true;
user = "immich";
name = "immich";
host = "/run/postgresql";
};
machine-learning.enable = true;
};
systemd.services.immich-server = {
requires = [ "mnt-media.mount" ];
after = [ "mnt-media.mount" ];
serviceConfig = {
DynamicUser = lib.mkForce false;
ReadWritePaths = [ "/mnt/media/immich" ];
BindPaths = [ "/mnt/media/immich" ];
};
};
services.postgresql = {
enable = true;
dataDir = "/mnt/data/postgresql";
ensureDatabases = [ "immich" ];
ensureUsers = [
{
name = "immich";
ensureDBOwnership = true;
}
];
};
services.nginx.virtualHosts."photo.${domain}" = {
forceSSL = isProd;
enableACME = isProd;
locations."/" = {
proxyPass = "http://127.0.0.1:2283";
proxyWebsockets = true;
extraConfig = "client_max_body_size 50G;";
};
};
}

34
hosts/hetzner/media.nix Normal file
View file

@ -0,0 +1,34 @@
{ config, ... }:
{
systemd.tmpfiles.rules = [
# 1. THE PARENT DIRS
# Change 0770 -> 0755 so users like 'postgres' can walk through the door.
"d /mnt/data 0755 root root -"
"d /mnt/media 0755 root root -"
# 2. THE SERVICES (SSD / Data)
# IMPORTANT: These MUST match where your services.postgresql.dataDir points
"d /mnt/data/postgresql 0700 postgres postgres -"
"d /mnt/data/forgejo 0750 forgejo forgejo -"
# 3. THE STORAGE (HDD / Media)
"d /mnt/media/immich 0750 immich immich -"
];
fileSystems."/mnt/media" = {
device = "//u536222.your-storagebox.de/backup";
fsType = "cifs";
options = [
"nofail"
"noperm"
"rw"
"credentials=${config.sops.secrets.storage_box_credentials.path}"
"uid=900"
"gid=900"
"forceuid"
"forcegid"
"file_mode=0660"
"dir_mode=0770"
];
};
}

18
hosts/hetzner/nginx.nix Normal file
View file

@ -0,0 +1,18 @@
{ domain, isProd, ... }:
{
services.nginx = {
enable = true;
recommendedProxySettings = true;
recommendedTlsSettings = isProd;
virtualHosts."${domain}" = {
forceSSL = isProd;
enableACME = isProd;
locations."/test" = {
return = "200 'Hello! You are accessing: ${domain}/test'";
extraConfig = "default_type text/plain;";
};
};
};
}

10
hosts/hetzner/secret.nix Normal file
View file

@ -0,0 +1,10 @@
{ ... }:
{
sops = {
defaultSopsFile = ./../../secrets.yaml;
defaultSopsFormat = "yaml";
age.keyFile = "/var/lib/sops-nix/key.txt";
};
sops.secrets.storage_box_credentials = { };
}

55
hosts/hetzner/vm.nix Normal file
View file

@ -0,0 +1,55 @@
{ lib, ... }:
{
virtualisation.vmVariant = {
virtualisation = {
memorySize = 4096;
cores = 2;
graphics = false;
sharedDirectories = {
sops-keys = {
source = "/home/louis/.config/sops/age";
target = "/var/lib/sops-nix";
};
};
forwardPorts = [
{
from = "host";
host.port = 8080;
guest.port = 80;
}
];
};
fileSystems."/mnt/volume" = lib.mkForce {
device = "none";
fsType = "tmpfs";
options = [
"size=2G"
"mode=777"
];
};
fileSystems."/mnt/box" = lib.mkForce {
device = "none";
fsType = "tmpfs";
options = [
"size=2G"
"mode=777"
];
};
sops.age.keyFile = lib.mkForce "/var/lib/sops-nix/keys.txt";
users.users.root.password = "root";
services.openssh.settings.PermitRootLogin = "yes";
services.openssh.settings.PasswordAuthentication = true;
documentation.enable = false;
systemd.services.NetworkManager-wait-online.enable = false;
networking.useDHCP = lib.mkDefault true;
services.qemuGuest.enable = true;
networking.enableIPv6 = false;
};
}

9
languages/python.nix Normal file
View file

@ -0,0 +1,9 @@
{ pkgs, ... }:
pkgs.python3.withPackages (ps: with ps; [
pynvim
cairosvg
pnglatex
plotly
pyperclip
ipython
])

52
languages/r.nix Normal file
View file

@ -0,0 +1,52 @@
{ pkgs, ... }:
let
r-packages = with pkgs.rPackages; [
ggplot2
BiocManager
ptmixed
reshape2
opencv
dplyr
stringi
stringr
tidyverse
waffle
languageserver
knitr
rmarkdown
IRkernel
matlib
lme4
emmeans
multcomp
lobstr
sloop
vioplot
corrplot
brolgar
mvtnorm
doBy
car
rbenchmark
testthat
MASS
faraway
EnvStats
leaps
ISLR
MuMIn
corrplot
caret
skimr
];
in
{
packages = r-packages;
env = pkgs.rWrapper.override {
packages = r-packages;
};
rstudio = pkgs.rstudioWrapper.override {
packages = r-packages;
};
}

View file

@ -0,0 +1,49 @@
{ config, lib, pkgs, ... }:
let
cfg = config.modelDownloads;
script = pkgs.writeShellScript "download-models" ''
set -eu
mkdir -p "${cfg.dir}"
for url in ${lib.concatStringsSep " " (map lib.escapeShellArg cfg.urls)}; do
name="$(basename "$url")"
out="${cfg.dir}/$name"
if [ -f "$out" ]; then
echo "OK: $name already exists"
continue
fi
echo "Downloading $name"
tmp="$out.part"
${pkgs.curl}/bin/curl -L --fail --retry 3 --retry-delay 2 -o "$tmp" "$url"
mv -f "$tmp" "$out"
done
'';
in
{
options.modelDownloads = {
enable = lib.mkEnableOption "Download model files into a persistent directory";
dir = lib.mkOption {
type = lib.types.str;
default = "/data/ai/models";
};
urls = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "List of direct download URLs (filenames are taken from the URL).";
};
};
config = lib.mkIf cfg.enable {
home.packages = [ pkgs.curl ];
home.activation.modelDownloads = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
${script}
'';
};
}

33
secrets.yaml Normal file
View file

@ -0,0 +1,33 @@
storage_box_credentials: ENC[AES256_GCM,data:REgBpguvSUAQOrobf0ywPPKcegsclEvqMcplNPh8NrRUVXhviJa6aVeUpzw2L+jelcGM8A==,iv:8nHaRMKCE6YxX0wV85roucRZKjzX7ims1fyLUObNui4=,tag:Xq9hqI6l6v5e58urdTf4rA==,type:str]
immich_db_password: ENC[AES256_GCM,data:+5py95Kqa3RU+jitZbbe82xndsRJ5cZPHravO8xxwzFf,iv:4PJP8itiBFWJ9OsrIHFs3qBK/69LENEtjm73KsqECC8=,tag:W0BD/d3Ebls0LJ5dpYrKVA==,type:str]
immich_jwt_secret: ENC[AES256_GCM,data:ir2gXv515OSGaMmtDGwAbJ+SPVoKf07rBbGhBkmd2zI=,iv:YbFpuFBb2RQDUx0FUXUF+rjfLo0lA1eQAUGnMi0u784=,tag:tAyFUz0hPLf5UmkFkJSmZQ==,type:str]
forgejo_db_password: ENC[AES256_GCM,data:HeOaEGGxIx8ZabhzgzMttxt72gJ3fLaPU+zdXI77t5W81b7Tsbsm/SpelZnnyWTiyQU=,iv:mxg4m9am5c6J5hTcCe7LxLWFLHWJZBpsoN++0eudgXY=,tag:/cFNWRm0xNB/+0pE7Ga39A==,type:str]
wifi_leiden_identity: ENC[AES256_GCM,data:nj9LEiHNwubm2iCc4PnBk/Q79KXEsg==,iv:PLq235mWh0vKUutvZN4+vQu+7dUfrqq76LzuHvaWLg4=,tag:K9pMdUnqMR62QeFRHmNoog==,type:str]
wifi_leiden_password: ENC[AES256_GCM,data:p2eAI6Fe66fhqCS4iz17xkBIpc1i,iv:Yf4ZbmxGAw70aiG06sr3o7m0EtJyDfwKspIiyux9hrA=,tag:3VlUcreKWpTnbmzPJhdxOw==,type:str]
wifi_home: ENC[AES256_GCM,data:yXcc8VGG+WFFEByr,iv:yTAAYCJueAUajrLd9AThypIhTggtEIFJYl9uCpPpXQs=,tag:vJ6T5N0MB8sFrESJ/0Pwtw==,type:str]
git_name: ENC[AES256_GCM,data:iIlG0/JuyJgh8Z4zE8K9X1PGPQ==,iv:po4WCA3p5xEwQdNh5cYycCh1D6mWIxUmIDIUg13aQs0=,tag:SCED1jmBTaH/WRll0anpYg==,type:str]
git_email: ENC[AES256_GCM,data:pCrxo99MHEue1kat6tVFGsaXrA==,iv:itQgTc9HW/tweKylcMHzqwm2yNiNtxYjc0BmGM+b1mY=,tag:Ri+euCueyv+xwEOeswidpg==,type:str]
sops:
age:
- recipient: age1y38cnfvt42ar4seqx8n2cw0ynwls2rdl6jyzhv9wuv6jcfqrwckqwlh9p0
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsRTBPRjJIaHZtOGpDaWJY
SnhOSE5BeGkrOUQwQ3dQRG5SSDRVdXk2ZGdnCjg1Yk5NT01sOElyNyswR0ZzK0JG
cXhHVHhLR2cwRjRJb1A4OWQ4N3VTekkKLS0tIHcwWG9ORlR3TjVpbTZhcm5yYXpj
OG5xRVIvWFJFRjlTR2hRVFpVbFI2em8KdwnBU+SgjLoujtFT7m58JwWJBuRcL+cr
QLtDXAWRjrop1l+cjE1le5nwwGFUYVgUzHWRi5V6tMYrkUyazj76gg==
-----END AGE ENCRYPTED FILE-----
- recipient: age1sjq27nh4znq75fzzx6epwd6700wgg23v9xenzvmam4kmncvjdcesakv9e2
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2YmdRYVFOeGdWR01DbGxk
YUg3UXZXcVpPcXUzL0hQaUd4bW9BOXg2U3pjClB2R1M2UEpiSWVyR0ZTZk5yQXor
eXBKYWVIbzdEQ1dUZFlyWk5tZjZFa1UKLS0tIDkvN0FsaGZLMkNPa1ZxWkk0SlNM
emZCZDM4djFEdDIya3NISElqRU9QUFkKJG1REQS0/HmY1jbOUlt/ynhlLwmrVbkK
mNdhF8BdfP0rCwjWLvWnmikxdLIBHeeeodipZFr5QDpBZqzFHUxBaA==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-01-26T00:05:27Z"
mac: ENC[AES256_GCM,data:A0M7dnSZlO3A+vahwjtbSSLwjzTMF/GZjnw385siHeTrT3ICX9nfnh9MzgmT3k7AOsaJkbUuq5dl67pfNEaDxbLdYc6k2rFfyniVFq3t5JVPUr7+F1poijdKMSGrFMv/8uLDc0NlKDDa6VOEX4le7akqBRhAKMCqTZ5atmskwhg=,iv:sPJT9watBgpusk3lGPXLxNWNrMTLITxv1vo11a92uIc=,tag:YPhTUghRjYDZZo3VsYwAFQ==,type:str]
unencrypted_suffix: _unencrypted
version: 3.11.0