Compare commits

..

25 Commits

Author SHA1 Message Date
daniil-berg 5e1b9332a4 docs(readme): show a realistic private-inventory layout and overrides
The consumer section had only a bare-bones flake snippet. Add an example
private layout (`system/` + `home/` split, `hosts/<host>/`), a fuller
`flake.nix` matching it, and an "Overriding the baseline" section covering
the three mechanisms (neutral default → real value, `local.*` options,
extra modules). Note that structurally only `flake.nix` is required.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 16:42:53 +02:00
daniil-berg c9b976f0ae refactor(nixos): default regional settings to neutral values
The public baseline hard-coded a German keymap and Europe/Berlin time
zone — personal values a consumer would have to override. Default them to
region-agnostic values instead (`us` keymap, `UTC`, `en_US` locale), all
`mkDefault`, so a private inventory sets the real regional values on top
without needing `mkForce`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 16:25:05 +02:00
daniil-berg 07a78b6012 feat(home): add declarative Neovim via nixvim
New `nixvim` flake input (release branch, follows nixpkgs) drives the whole
editor from `modules/nvim.nix`, wired into `homeModules.default`. Minimal LSP
baseline (`nixd`, `bashls`) exposed as `local.nvim.lspServers`; blink-cmp,
Telescope, neo-tree, lualine, treesitter, colorizer, gitsigns, toggleterm, and a
catppuccin theme. which-key groups plus `<leader>?`/`<leader>sk` give an
in-editor keybind cheatsheet; per-directory session persistence with
`sessionoptions` + a `PersistenceSavePre` neo-tree close for clean layouts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 11:51:58 +02:00
daniil-berg 94411c0abe feat(home): add file, ripgrep, fd, tree, unzip; remove dead ka alias 2026-07-22 12:39:01 +02:00
daniil-berg 7415ea0b54 feat(git): fetch submodules on-demand 2026-07-21 14:35:11 +02:00
daniil-berg 184a2f5545 feat(home): add htop 2026-07-21 13:56:11 +02:00
daniil-berg b413c8af81 feat(hyprland): enable numlock by default for any keyboard 2026-07-21 10:41:15 +02:00
daniil-berg 5c4045f4a6 feat(hyprland): add runtime monitor left/right swap bind
`$mod CTRL o` reflows the two connected monitors adjacent in reversed
order (gap-free regardless of differing widths) and re-inits the
layer-shell clients, which Hyprland doesn't reliably re-notify on a
runtime output move: waybar rebuilds on SIGUSR2, wallpaper is re-pushed.
`monitor` default becomes `mkDefault` so a host can pin its own layout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 10:38:16 +02:00
daniil-berg e4a8b07117 fix(wallpaper): repaint monitors hotplugged after login
awww-daemon paints only the outputs present when `awww img` last ran and does
not repaint a monitor connected later, so it comes up on the daemon's black
fill — which a transparent foot then shows instead of the wallpaper. Add
`wallpaper-hotplug`: it watches Hyprland's event socket and re-runs `setbg`
(which pushes to all outputs) on each `monitoradded`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 09:33:15 +02:00
daniil-berg f533bbeb05 feat(hyprland): add per-monitor workspace homing and monitor nav binds
New `local.hyprland.workspaceRules` option binds workspaces to monitor
connectors so each has a home screen; a consumer sets it from its own host
config (empty by default). Soft binding — an absent monitor's workspaces fall
back to an active one, so a laptop-only session still works.

`$mod+o` / `$mod SHIFT+o` focus and move the current workspace to the other
monitor. When rules are set, an `exec-once` nudges a single-monitor session to
workspace 1 rather than a support monitor's high default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 09:32:59 +02:00
daniil-berg 031bad3238 feat(waybar): add window title module and styled clock calendar
Center a `hyprland/window` title module and move the clock/date to the far
right of the bar. Give the clock a month-calendar tooltip with One Dark
Pango styling (yellow header, purple weekdays, blue underlined today, dim
ISO week-number gutter), 3-per-row year view via right-click.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 08:03:48 +02:00
daniil-berg dc676b6f8c refactor(i18n): make defaultLocale overridable via mkDefault
Template hard-set `en_US.UTF-8`, so a consumer setting `i18n.defaultLocale`
in the private inventory hit a conflicting-definition eval error. Wrap the
template default in `lib.mkDefault` per the overrides-are-options contract.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 08:03:43 +02:00
daniil-berg f64d68ac05 fix(mc): discard xdg-open stdout/stderr so app logs don't clobber mc pane 2026-07-13 07:03:03 +02:00
daniil-berg 4be1c4fb67 feat(mc): open files with their configured app via xdg-open
Ship `mc.ext.ini` as the `local.mc.extIni` option (like `local.mc.menu`):
Enter browses archives in-place as before and delegates every other type to
`xdg-open`. Add public `mime.nix` with `xdg.mimeApps` defaults (pdf ->
zathura, images -> imv, audio/video -> mpv, office -> libreoffice) -- public
because those handlers are; personal browser/mail defaults stay private.

Also document, at the Hyprland swallow config, how swallowing differs from an
app opening fullscreen/maximized (LibreOffice restores its own maximized state
and looks swallowed but isn't).
2026-07-12 22:09:01 +02:00
daniil-berg 7205841c4d fix(ssh): spawn askpass pinentry off-tree so push doesn't swallow terminal 2026-07-12 16:45:18 +02:00
daniil-berg 0851515de0 fix(hyprland): exempt pinentry dialog from terminal swallowing 2026-07-12 16:22:59 +02:00
daniil-berg de6c3993cc feat(systemd): add shutdown/boot hang diagnostics module
Shutdown stalls are hard to debug because journald is torn down mid-way,
so late hangs never reach the journal. `modules/shutdown-debug.nix`
exposes `local.shutdownDebug.*`: verbose systemd logging routed to kmsg
(recoverable from pstore after a forced power-off), an opt-in tty9 debug
shell, and a shortened `DefaultTimeoutStopSec` preset (system + user) so
a hung unit is killed and logged in seconds instead of spinning at 90s.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 14:47:30 +02:00
daniil-berg 98eb0f87ba feat(waybar): highlight active workspace and flag urgent ones
Style the `hyprland/workspaces` buttons so the focused workspace is
distinguishable when several are occupied: white text, darker
background, blue underline; urgent workspaces turn red.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 08:01:41 +02:00
daniil-berg d8d86aadcb fix(hyprland): repair master-width, fullscreen, and brightness binds
- `$mod+h`/`l` used dwindle's `splitratio`, a no-op in the master layout;
  switch to `mfact` so they actually resize the master area.
- `$mod+f` ran `fullscreen 1` (maximize, bar/gaps kept); swap so `$mod+f` is
  true fullscreen and `$mod SHIFT+f` maximizes.
- add `$mod+space` `swapwithmaster` (dwm-style zoom).
- the `XF86MonBrightness` binds called `brightnessctl`, which was never
  installed; add the package. It writes via the active logind session.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 07:56:59 +02:00
daniil-berg c375a15561 feat(hyprland): swallow GUI windows spawned from foot into the terminal
A viewer opened from a foot shell (zathura/imv, incl. from mc) now hides
the terminal and takes its place, restoring it on close. Requires
standalone foot: server/client mode shares one process across all
windows, so PID-based swallow cannot tell them apart. `$mod+Return` runs
`foot`, not `footclient`, and the `foot --server` exec-once is dropped.
2026-07-11 14:01:04 +02:00
daniil-berg 08a14d52f2 feat(mc): make the F2 menu overridable via local.mc.menu
Expose the user menu as a `types.lines` option instead of a baked source
file. The shipped menu is a `mkBefore` definition (not a `default`, which a
consumer definition would drop), so `local.mc.menu = "…"` appends entries
after the baseline and `lib.mkForce "…"` replaces it wholesale.

`ini` stays a baked source file: it is section/key=value, so append-to-extend
risks duplicate keys — not a safe override path.
2026-07-10 23:07:16 +02:00
daniil-berg eab578e699 feat(mc): add midnight commander with declarative config
Public baseline module: installs `mc` plus `ouch` (backs the F2 pack/unpack
entries, archive format inferred from the extension), and ships `ini`, `menu`,
and a generated `panels.ini` into `$XDG_CONFIG_HOME/mc`.

`local.mc.otherDir` exposes the other-panel start dir as an option defaulting
to `config.home.homeDirectory`, so the one personal value isn't baked in. The
F2 user menu is trimmed to the entries that match this config (imv/zathura/mpv
openers, ouch pack/unpack, man view); the stock rcp/uudecode/news/latex cruft
is dropped. `auto_save_setup=false` keeps mc from rewriting the read-only store
symlinks on exit.
2026-07-10 22:46:32 +02:00
daniil-berg 8f9eff1640 feat(librewolf): expose package as an overridable option
Move librewolf out of the bare package list into its own module declaring
`local.librewolf.package` (default pkgs.librewolf), mirroring texlive. Lets a
consumer swap the wrapped package — extraPolicies/extraPrefs — to share
extensions and an about:config baseline across profiles without editing the
module.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:19:40 +02:00
daniil-berg 3e093b02d1 docs: explain how consumers override public module defaults
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 09:00:50 +02:00
daniil-berg b907a39c41 feat(home): add pandoc with texlive for PDF output
texlive scheme is the `local.texlive.package` option (default scheme-medium)
so consumers can swap schemes without editing the module; TEXMF* vars keep
its cache/config out of $HOME. pandoc PDF defaults to xelatex — pdflatex
fails on combining Unicode (e.g. U+0308 in decomposed ä).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 09:00:33 +02:00
18 changed files with 1312 additions and 34 deletions
+14
View File
@@ -15,6 +15,20 @@ one as a flake input. Deploy happens from the private repo, not here.
- secrets (keys, tokens, passwords) → **neither** repo; runtime files or a secrets manager - secrets (keys, tokens, passwords) → **neither** repo; runtime files or a secrets manager
decrypting to a path. See the SSH section. decrypting to a path. See the SSH section.
## Overrides = options (the consumer contract)
Private imports these modules **read-only** (flake input) — it can't patch a package list
or a hard-coded value in place. So anything a consumer might reasonably want to change is
exposed as an **option with a default**, never baked into config. That is what makes this
repo a *baseline others extend* rather than a fork-and-patch.
Pattern (see `modules/texlive.nix`): option-bearing config lives in its own sub-module
under the `local.*` namespace, `imports`-ed by `home.nix`. A module that declares `options`
must use the split `{ options = …; config = …; }` form, so isolating it per file keeps
`home.nix` bare. Override is then one line, e.g.
`local.texlive.package = pkgs.texlive.withPackages (ps: [ ps.scheme-full ]);`. A plain value
with nothing to vary goes straight in the bare package list — no option, don't over-abstract.
## Maxim: keep $HOME clean (XDG) ## Maxim: keep $HOME clean (XDG)
Standing principle for every addition: minimise dotfiles/dirs in `$HOME`. Route a tool's Standing principle for every addition: minimise dotfiles/dirs in `$HOME`. Route a tool's
+79 -7
View File
@@ -24,19 +24,63 @@ you can tweak.
## Import the modules into your own flake ## Import the modules into your own flake
Add this repo as a flake input and import its modules from your own configuration: Add this repo as an input and assemble your machines in a private flake. Below is a
**realistic example layout**. Structurally only `flake.nix` is required — every other path
is just one way to organize your config; rename, merge, inline, or drop them freely. The one
bit of *content* a bootable machine can't skip is its `hardware-configuration.nix`
(`nixos-generate-config` output); the `hosts/<host>/` directory is only where this example
keeps it.
```
your-inventory/
├── flake.nix # inputs (pub + follows) + nixosConfigurations.<host>
├── flake.lock
├── system/ # system-level modules
│ ├── regional.nix # time zone / keymap / locale (see Overriding)
│ ├── packages.nix # extra system packages
│ └── tailscale.nix # VPN / network topology
├── home/ # home-manager modules, one file per concern
│ ├── identity.nix # git identity, name/email
│ ├── ssh.nix # SSH hosts (topology — stays private)
│ ├── syncthing.nix # Syncthing peers
│ └── … # personal apps: browser, editor, chat, …
└── hosts/
└── myhost/
├── default.nix # hostname, user account, host-specific system bits
└── hardware-configuration.nix # `nixos-generate-config` output
```
The `flake.nix` that wires it together:
```nix ```nix
{ {
inputs.config.url = "github:<owner>/<repo>"; inputs = {
# … pub.url = "github:<owner>/<repo>";
outputs = { nixpkgs, config, home-manager, ... }: { # Reuse the public flake's pins so versions never drift between the two.
nixpkgs.follows = "pub/nixpkgs";
home-manager.follows = "pub/home-manager";
};
outputs = inputs@{ self, nixpkgs, pub, home-manager, ... }: {
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem { nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [ modules = [
config.nixosModules.default pub.nixosModules.default # generic system baseline
./hosts/myhost # your hardware + hostname ./system/regional.nix # your regional values (see below)
./hosts/myhost # hardware + hostname
home-manager.nixosModules.home-manager home-manager.nixosModules.home-manager
{ home-manager.users.me.imports = [ config.homeModules.default ]; } {
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
# Inject your own inputs into your home modules; the public ones ignore them.
home-manager.extraSpecialArgs = { inherit inputs; };
home-manager.users.me.imports = [
pub.homeModules.default # generic home baseline
./home/identity.nix # your git identity, name/email, …
./home/ssh.nix # your SSH hosts (topology stays private)
# … more personal home modules
];
}
]; ];
}; };
}; };
@@ -46,6 +90,30 @@ Add this repo as a flake input and import its modules from your own configuratio
Your machine list, hostnames, and any private specifics stay in *your* flake — you pull Your machine list, hostnames, and any private specifics stay in *your* flake — you pull
only the generic modules from here. only the generic modules from here.
## Overriding the baseline
The public modules are a baseline you extend without forking. Three ways to shape them
from your private flake, in rough order of how often you reach for them:
1. **Neutral default → your real value.** Region-agnostic settings ship as `mkDefault`
placeholders (`console.keyMap = "us"`, `time.timeZone = "UTC"`, `en_US` locale). Set the
real ones in a private module — no `mkForce` needed:
```nix
# system/regional.nix
{ time.timeZone = "Europe/Berlin"; console.keyMap = "de"; }
```
2. **`local.*` options.** A module that exposes a knob puts it under the `local.*`
namespace with a default. Override in one line, e.g. a fuller TeX Live:
```nix
local.texlive.package = pkgs.texlive.withPackages (ps: [ ps.scheme-full ]);
```
3. **Extra modules.** Anything with no public counterpart is just another module in the
import lists above — personal apps, SSH/Syncthing topology, secrets wiring.
## Architecture: three layers ## Architecture: three layers
Kept apart on purpose. When adding config, decide which layer it belongs to: Kept apart on purpose. When adding config, decide which layer it belongs to:
@@ -71,3 +139,7 @@ hosts/example/ # the template host (placeholder hardware-configuration.nix
``` ```
Shared `*.nix` / `*.lock` live at the top level; per-host files under `hosts/<host>/`. Shared `*.nix` / `*.lock` live at the top level; per-host files under `hosts/<host>/`.
Chasing a shutdown/boot hang? `modules/shutdown-debug.nix` exposes `local.shutdownDebug.*`
(verbose systemd logging to kmsg, a tty9 debug shell, a shortened stop-timeout) — see its
header comment for how to read the evidence.
Generated
+60
View File
@@ -1,5 +1,26 @@
{ {
"nodes": { "nodes": {
"flake-parts": {
"inputs": {
"nixpkgs-lib": [
"nixvim",
"nixpkgs"
]
},
"locked": {
"lastModified": 1778716662,
"narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"home-manager": { "home-manager": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
@@ -37,10 +58,34 @@
"type": "github" "type": "github"
} }
}, },
"nixvim": {
"inputs": {
"flake-parts": "flake-parts",
"nixpkgs": [
"nixpkgs"
],
"systems": "systems"
},
"locked": {
"lastModified": 1782919967,
"narHash": "sha256-pRwjfB5HQJ3m8J8bOR43pPHtHI7VUJSqwLA3P06cOY0=",
"owner": "nix-community",
"repo": "nixvim",
"rev": "667c8471f4a0fb24d702d1a61af8609f1a5f1ba6",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "nixos-26.05",
"repo": "nixvim",
"type": "github"
}
},
"root": { "root": {
"inputs": { "inputs": {
"home-manager": "home-manager", "home-manager": "home-manager",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"nixvim": "nixvim",
"rust-overlay": "rust-overlay" "rust-overlay": "rust-overlay"
} }
}, },
@@ -63,6 +108,21 @@
"repo": "rust-overlay", "repo": "rust-overlay",
"type": "github" "type": "github"
} }
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",
+17 -2
View File
@@ -17,9 +17,20 @@
url = "github:oxalica/rust-overlay"; url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
# nixvim: declarative Neovim as home-manager options — the whole editor
# (plugins, LSP, keymaps) lives in modules/nvim.nix, pinned by this flake's
# lock. Track the release branch matching nixpkgs (a mismatched branch fails
# with `vimPlugins.<name> not found`). `follows` keeps one nixpkgs: both are
# on the same stable channel, so the version skew nixvim warns about (only an
# issue against unstable) does not arise here.
nixvim = {
url = "github:nix-community/nixvim/nixos-26.05";
inputs.nixpkgs.follows = "nixpkgs";
};
}; };
outputs = inputs@{ self, nixpkgs, home-manager, rust-overlay, ... }: outputs = inputs@{ self, nixpkgs, home-manager, rust-overlay, nixvim, ... }:
let let
system = "x86_64-linux"; system = "x86_64-linux";
in { in {
@@ -28,7 +39,11 @@
# imports = [ config.nixosModules.default ]; # imports = [ config.nixosModules.default ];
# home-manager.users.<you>.imports = [ config.homeModules.default ]; # home-manager.users.<you>.imports = [ config.homeModules.default ];
nixosModules.default = import ./modules/nixos.nix; nixosModules.default = import ./modules/nixos.nix;
homeModules.default = import ./modules/home.nix; # nixvim's home-manager module rides along so `modules/nvim.nix` can set
# `programs.nixvim.*`; consumers get the editor by importing this one module.
homeModules.default = {
imports = [ nixvim.homeModules.nixvim ./modules/home.nix ];
};
# ---- The example / template host ----------------------------------- # ---- The example / template host -----------------------------------
# Real and buildable; dogfoods the modules above. Clone, replace # Real and buildable; dogfoods the modules above. Clone, replace
+13
View File
@@ -14,4 +14,17 @@
name = "Example User"; name = "Example User";
email = "user@example.com"; email = "user@example.com";
}; };
# Optional: give workspaces a home monitor (see modules/hyprland-workspaces.nix).
# Bind by connector name (`hyprctl monitors`), not panel identity, so any
# monitor on that port inherits the rules. `default:true` sets what a screen
# shows on connect. Absent monitors fall back, so a single-screen session still
# works. Left unset here — uncomment and adjust to your outputs.
#
# local.hyprland.workspaceRules = [
# "1, monitor:HDMI-A-1, default:true" # external, workspaces 1-5
# "2, monitor:HDMI-A-1"
# "6, monitor:LVDS-1, default:true" # laptop, workspaces 6-9
# "7, monitor:LVDS-1"
# ];
} }
+160
View File
@@ -0,0 +1,160 @@
[Midnight-Commander]
verbose=true
shell_patterns=true
auto_save_setup=false
preallocate_space=false
auto_menu=false
use_internal_view=true
use_internal_edit=false
clear_before_exec=true
confirm_delete=true
confirm_overwrite=true
confirm_execute=false
confirm_history_cleanup=true
confirm_exit=false
confirm_directory_hotlist_delete=false
confirm_view_dir=false
safe_delete=false
safe_overwrite=false
use_8th_bit_as_meta=false
mouse_move_pages_viewer=true
mouse_close_dialog=false
fast_refresh=false
drop_menus=false
wrap_mode=false
old_esc_mode=true
cd_symlinks=true
show_all_if_ambiguous=true
use_file_to_guess_type=true
alternate_plus_minus=false
only_leading_plus_minus=true
show_output_starts_shell=false
xtree_mode=false
file_op_compute_totals=true
classic_progressbar=true
use_netrc=true
ftpfs_always_use_proxy=false
ftpfs_use_passive_connections=true
ftpfs_use_passive_connections_over_proxy=false
ftpfs_use_unix_list_options=true
ftpfs_first_cd_then_ls=true
ignore_ftp_chattr_errors=true
editor_fill_tabs_with_spaces=false
editor_return_does_auto_indent=true
editor_backspace_through_tabs=false
editor_fake_half_tabs=true
editor_option_save_position=true
editor_option_auto_para_formatting=false
editor_option_typewriter_wrap=false
editor_edit_confirm_save=true
editor_syntax_highlighting=true
editor_persistent_selections=true
editor_drop_selection_on_copy=true
editor_cursor_beyond_eol=false
editor_cursor_after_inserted_block=false
editor_visible_tabs=true
editor_visible_spaces=true
editor_line_state=false
editor_simple_statusbar=false
editor_check_new_line=false
editor_show_right_margin=false
editor_group_undo=false
editor_state_full_filename=false
editor_ask_filename_before_edit=false
nice_rotating_dash=true
mcview_remember_file_position=false
auto_fill_mkdir_name=true
copymove_persistent_attr=true
pause_after_run=1
mouse_repeat_rate=100
double_click_speed=250
old_esc_mode_timeout=1000000
max_dirt_limit=10
num_history_items_recorded=60
vfs_timeout=60
ftpfs_directory_timeout=900
ftpfs_retry_seconds=30
fish_directory_timeout=900
editor_tab_spacing=8
editor_word_wrap_line_length=72
editor_option_save_mode=0
editor_backup_extension=~
editor_filesize_threshold=64M
editor_stop_format_chars=-+*\\,.;:&>
mcview_eof=
skin=yadt256-defbg
shadows=true
[Layout]
output_lines=0
left_panel_size=115
top_panel_size=0
message_visible=true
keybar_visible=true
xterm_title=true
command_prompt=true
menubar_visible=true
free_space=true
horizontal_split=false
vertical_equal=true
horizontal_equal=true
[Misc]
timeformat_recent=%d. %b %H:%M
timeformat_old=%d. %b %Y
ftp_proxy_host=gate
ftpfs_password=anonymous@
display_codepage=UTF-8
source_codepage=Other_8_bit
autodetect_codeset=
clipboard_store=
clipboard_paste=
[Colors]
base_color=
st-256color=
color_terminals=
[Panels]
show_mini_info=true
kilobyte_si=true
mix_all_files=false
show_backups=true
show_dot_files=true
fast_reload=false
fast_reload_msg_shown=false
mark_moves_down=true
reverse_files_only=true
auto_save_setup_panels=false
navigate_with_arrows=true
panel_scroll_pages=true
panel_scroll_center=false
mouse_move_pages=true
filetype_mode=true
permission_mode=false
torben_fj_mode=false
quick_search_mode=2
select_flags=6
simple_swap=false
[FindFile]
file_case_sens=true
file_shell_pattern=true
file_find_recurs=true
file_skip_hidden=false
file_all_charsets=false
content_case_sens=true
content_regexp=false
content_first_hit=false
content_whole_words=false
content_all_charsets=false
ignore_dirs_enable=true
ignore_dirs=
[Panelize]
Geänderte Git-Dateien=git ls-files --modified
Nach dem Patchen nach Ablehnungen suchen=find . -name \\*.rej -print
Suche SUID- und SGID-Programme=find . \\( \\( -perm -04000 -a -perm /011 \\) -o \\( -perm -02000 -a -perm /01 \\) \\) -print
Suche nach *.orig nach dem Patchen=find . -name \\*.orig -print
+86
View File
@@ -0,0 +1,86 @@
# Midnight Commander file associations (public baseline).
#
# mc reads a SINGLE mc.ext.ini; this user copy fully replaces the system one
# (they are not merged), so it must also carry the archive entries we keep.
#
# Policy: Enter (Open) on an archive mounts it as a virtual filesystem (mc
# built-in, no external helper). Every other file falls through to [Default],
# which runs `xdg-open` -- so the app is whatever the XDG mimeapps default
# resolves to (set those in the private inventory's xdg.mimeApps). One source of
# truth, shared with every other launcher; nothing app-specific baked in here.
#
# Note: office documents (.docx/.odt/.xlsx/...) are ZIP containers, so they are
# deliberately absent from the [zip] entry below -- otherwise Enter would mount
# them as archives instead of handing them to LibreOffice via xdg-open.
#
# Override contract (this is a types.lines option shipped as mkBefore): append a
# same-named section to override one key -- mc merges same-named sections and
# the last key wins -- e.g. add a [Default] with a different Open to change the
# fallback, or add [pdf] Type=^PDF / Open=mupdf %f & to force one type to a
# specific app. lib.mkForce "..." replaces this baseline wholesale.
[mc.ext.ini]
Version=4.0
### Archives: Enter mounts them as a virtual filesystem (F3/F4 unaffected) ###
[tar]
Regex=\\.(tar(\\.(gz|bz2|xz|zst|lz|lz4|lzo|Z))?|t(gz|az|bz2?|xz|zst|lz))$
Open=%cd %p/utar://
[zip]
Regex=\\.(zip|jar|xpi|whl|egg|apk|war|ear)$
Open=%cd %p/uzip://
[7z]
Shell=.7z
Open=%cd %p/u7z://
[rar]
Regex=\\.r(ar|[0-9][0-9])$
Open=%cd %p/urar://
[arj]
Regex=\\.a(rj|[0-9][0-9])$
Open=%cd %p/uarj://
[cab]
Shell=.cab
Open=%cd %p/ucab://
[cpio]
Regex=\\.cpio(\\.(gz|xz|bz2|zst|lz|lz4|lzo|Z))?$
Open=%cd %p/ucpio://
[deb]
Regex=\\.u?deb$
Open=%cd %p/deb://
[rpm]
Regex=\\.(rpm|src\\.rpm|spm)$
Open=%cd %p/rpm://
[iso]
Shell=.iso
Open=%cd %p/iso9660://
[patch]
Regex=\\.(diff|patch)(\\.(gz|bz2|xz|zst|Z))?$
Open=%cd %p/patchfs://
### Everything else: hand off to the desktop's configured default app ###
# `&` backgrounds xdg-open so mc's shell returns and the launched GUI is orphaned
# to init. Hyprland only swallows the terminal when the new window's parent-PID
# chain still has a foot ancestor (getSwallower in Hyprland's Window.cpp), so an
# orphaned app opens as its own tile instead of swallowing mc (zathura, imv, ...).
# %f MUST stay unquoted: mc already backslash-escapes the path, so quoting turns
# those backslashes literal and breaks filenames with spaces.
# Redirect stdout/stderr to /dev/null: `&` backgrounds but leaves the app's fds on
# mc's terminal, so a chatty app paints over the mc pane and looks like a broken
# swallow. Tradeoff: this also discards any real diagnostics when an app fails to
# open a file. To keep a breadcrumb, swap /dev/null for a log file, e.g.
# `>>$XDG_STATE_HOME/mc-open.log 2>&1`.
[Default]
Open=xdg-open %f >/dev/null 2>&1 &
+30
View File
@@ -0,0 +1,30 @@
shell_patterns=0
# F2 user menu. Format macros: %f current file · %d current dir · %s tagged
# files else current · %t tagged files · %D other panel's dir. Full list: mc(1).
c Pack selected into an archive (ouch — format from the name's extension)
NAME=%{Archive name, e.g. stuff.tar.zst}
ouch compress %s "$NAME"
x Unpack archive here (ouch)
ouch decompress %f
i Open file with image viewer (imv)
imv %f
I Open current directory with image viewer (imv)
imv %d
p Open file with document viewer (zathura)
zathura %f
v Play file with mpv
mpv %f
V Play current directory with mpv
mpv %d
m View a man page
MAN=%{Enter manual name}
%view{ascii,nroff} MANROFFOPT='-c -Tlatin1' MAN_KEEP_FORMATTING=1 man -P cat "$MAN"
+171 -18
View File
@@ -18,11 +18,21 @@ let
# confirm/none informational, e.g. "Confirm user presence" (touch request) # confirm/none informational, e.g. "Confirm user presence" (touch request)
# A FIDO touch can't be faked by software, so those messages need no dialog: # A FIDO touch can't be faked by software, so those messages need no dialog:
# succeed silently instead of popping a spurious PIN box (the token blinks). # succeed silently instead of popping a spurious PIN box (the token blinks).
# pinentry runs under `systemd-run --user`, not directly, so it is reparented
# onto the user service manager instead of this askpass shell. ssh spawns the
# askpass from inside the foot terminal, and Hyprland's window swallowing walks
# a new window's parent-PID chain for a `foot` ancestor (swallow_regex) — a
# direct pinentry would match and hide the terminal behind the PIN dialog.
# Detaching breaks that chain (parent becomes systemd), exactly like the
# commit-signing pinentry that gpg-agent already spawns off-tree. --pipe wires
# stdin/stdout through so the Assuan exchange still works; --wait propagates
# the exit status; --collect reaps the transient unit.
sshAskpass = pkgs.writeShellScript "ssh-askpass-pinentry" '' sshAskpass = pkgs.writeShellScript "ssh-askpass-pinentry" ''
[ -n "''${SSH_ASKPASS_PROMPT:-}" ] && exit 0 [ -n "''${SSH_ASKPASS_PROMPT:-}" ] && exit 0
desc=$(printf '%s' "''${1:-Passphrase:}" | sed 's/%/%25/g') desc=$(printf '%s' "''${1:-Passphrase:}" | sed 's/%/%25/g')
printf 'SETTITLE ssh\nSETPROMPT PIN:\nSETDESC %s\nGETPIN\nBYE\n' "$desc" \ printf 'SETTITLE ssh\nSETPROMPT PIN:\nSETDESC %s\nGETPIN\nBYE\n' "$desc" \
| ${pkgs.pinentry-qt}/bin/pinentry-qt \ | ${pkgs.systemd}/bin/systemd-run --user --pipe --wait --collect --quiet \
${pkgs.pinentry-qt}/bin/pinentry-qt \
| sed -n 's/^D //p' | sed -n 's/^D //p'
''; '';
@@ -69,9 +79,50 @@ let
2) ${pkgs.systemd}/bin/systemctl poweroff ;; 2) ${pkgs.systemd}/bin/systemctl poweroff ;;
esac esac
''; '';
# Swap the physical left/right placement of a two-monitor setup at runtime.
# Reflows the pair adjacent from x=0 (left one at origin, right one butted
# against its logical width) after reversing their order — so it stays
# gap-free regardless of differing widths, unlike a naive x-origin exchange
# (equal widths only). Machine-agnostic: reads whichever two connectors are
# live from `hyprctl -j`, no connector names. Ephemeral (hyprctl keyword) —
# the per-host default layout is restored on the next reload/relogin. No-op
# unless exactly two monitors are connected.
#
# Hyprland doesn't reliably re-notify layer-shell clients when an output is
# reconfigured at runtime (a known limitation — same class as the mirror/unplug
# cases where waybar needs a restart). Observed here: after the move, waybar and
# the wallpaper keep drawing at the pre-swap offsets, so the moved monitor's
# bar/bg land off-screen. Re-init both against the new geometry: waybar rebuilds
# its bars on SIGUSR2, and awww only paints outputs present when `awww img` last
# ran, so re-push the wallpaper symlink. Both are idempotent — harmless when a
# surface happened not to lag (which surface stales seems to race).
swapMonitors = pkgs.writeShellScript "hypr-swap-monitors" ''
set -eu
hc=${config.wayland.windowManager.hyprland.finalPackage}/bin/hyprctl
jq=${pkgs.jq}/bin/jq
data=$("$hc" monitors -j)
n=$(printf '%s' "$data" | "$jq" length)
if [ "$n" -ne 2 ]; then
${pkgs.libnotify}/bin/notify-send "Monitor swap" "need exactly 2 monitors ($n connected)"
exit 0
fi
# sort by current x [left, right]; the new left is the old right.
cmd=$(printf '%s' "$data" | "$jq" -r '
sort_by(.x)
| .[1] as $l | .[0] as $r
| (($l.width / $l.scale) | floor) as $off
| "keyword monitor \($l.name),preferred,0x0,\($l.scale)"
+ " ; keyword monitor \($r.name),preferred,\($off)x0,\($r.scale)"')
"$hc" --batch "$cmd"
# re-anchor the layer-shell clients onto the new geometry (see comment above)
${pkgs.procps}/bin/pkill -USR2 -x .waybar-wrapped || true
bg="$HOME/.local/share/bg"
[ -e "$bg" ] && ${pkgs.awww}/bin/awww img "$bg" || true
'';
in in
{ {
imports = [ ./gpg.nix ./media.nix ]; imports = [ ./gpg.nix ./media.nix ./texlive.nix ./librewolf.nix ./mc.nix ./mime.nix ./hyprland-workspaces.nix ./nvim.nix ];
# Put ~/.local/bin (the XDG user-bin dir) on PATH so hand-dropped scripts run # Put ~/.local/bin (the XDG user-bin dir) on PATH so hand-dropped scripts run
# by name without wiring each through nix. # by name without wiring each through nix.
@@ -119,7 +170,8 @@ in
xdg.enable = true; xdg.enable = true;
home.packages = with pkgs; [ home.packages = with pkgs; [
librewolf # librewolf is installed by ./librewolf.nix (via local.librewolf.package) so
# the wrapped package stays overridable — do NOT also list it here.
keepassxc # config in ~/.config/keepassxc keepassxc # config in ~/.config/keepassxc
papirus-icon-theme # fuzzel icon-theme; ships non-symbolic power icons papirus-icon-theme # fuzzel icon-theme; ships non-symbolic power icons
# (Adwaita only has them as symbolic, which fuzzel skips) # (Adwaita only has them as symbolic, which fuzzel skips)
@@ -129,12 +181,20 @@ in
nodejs nodejs
uv # Python: versions, venvs, deps, lockfile. Runs uv-managed uv # Python: versions, venvs, deps, lockfile. Runs uv-managed
# standalone CPython via nix-ld (see modules/nixos.nix). # standalone CPython via nix-ld (see modules/nixos.nix).
htop # process viewer; config in ~/.config/htop (XDG-native)
highlight # `ccat` alias: syntax-highlighted cat highlight # `ccat` alias: syntax-highlighted cat
file # identify file type by content
ripgrep # `rg`: fast recursive grep
fd # fast, ergonomic find
tree # recursive directory listing
unzip # extract .zip (tools that shell out to it expect the binary)
jq # JSON on the command line (scripts, ad-hoc inspection) jq # JSON on the command line (scripts, ad-hoc inspection)
pv # pipe progress meter (throughput, ETA on streamed data) pv # pipe progress meter (throughput, ETA on streamed data)
libreoffice libreoffice
grim # screenshot capture (also used by the Print-key binds) grim # screenshot capture (also used by the Print-key binds)
slurp # interactive region selector for grim slurp # interactive region selector for grim
brightnessctl # backlight control for the XF86MonBrightness binds; writes
# via the active logind session, so no udev rule needed
]; ];
# Canonical XDG user dirs. Only Pictures is actually used (screenshot target, # Canonical XDG user dirs. Only Pictures is actually used (screenshot target,
@@ -185,6 +245,14 @@ in
programs.home-manager.enable = true; programs.home-manager.enable = true;
# Document converter (md/html/docx/…). PDF output shells out to a LaTeX engine
# (texlive.nix): default it to xelatex, which is Unicode-native — pdflatex
# (pandoc's own default) fails on combining marks, e.g. U+0308 in decomposed ä.
programs.pandoc = {
enable = true;
defaults.pdf-engine = "xelatex";
};
programs.git = { programs.git = {
enable = true; enable = true;
settings = { settings = {
@@ -202,6 +270,7 @@ in
merge.log = true; merge.log = true;
push = { default = "simple"; followTags = true; }; push = { default = "simple"; followTags = true; };
submodule.recurse = true; submodule.recurse = true;
fetch.recurseSubmodules = "on-demand";
alias = { alias = {
l = "log --pretty=oneline -n 20 --graph --abbrev-commit"; l = "log --pretty=oneline -n 20 --graph --abbrev-commit";
@@ -335,7 +404,6 @@ in
grep = "grep --color=auto"; grep = "grep --color=auto";
diff = "diff --color=auto"; diff = "diff --color=auto";
ccat = "highlight --out-format=ansi"; ccat = "highlight --out-format=ansi";
ka = "killall";
g = "git"; g = "git";
e = editor; e = editor;
v = editor; v = editor;
@@ -458,7 +526,10 @@ in
configType = "hyprlang"; configType = "hyprlang";
settings = { settings = {
monitor = ",preferred,auto,1"; # Fallback for hosts that don't pin a layout; a two-monitor host overrides
# this with explicit positions (see the private inventory). mkDefault so
# that override is a plain value, not a mkForce fight.
monitor = lib.mkDefault ",preferred,auto,1";
# The qt module sets QT_QPA_PLATFORMTHEME=gtk3 (→ Qt apps read the GTK # The qt module sets QT_QPA_PLATFORMTHEME=gtk3 (→ Qt apps read the GTK
# Adwaita-dark theme), but only into hm-session-vars.sh + the systemd user # Adwaita-dark theme), but only into hm-session-vars.sh + the systemd user
@@ -495,11 +566,38 @@ in
misc = { misc = {
force_default_wallpaper = 0; force_default_wallpaper = 0;
disable_hyprland_logo = true; disable_hyprland_logo = true;
# Window swallowing: a GUI spawned from a foot shell (e.g. zathura/imv
# from mc) hides the terminal and takes its place, restoring it on close.
# Matches on the *parent* terminal's class. Needs standalone foot: in
# server/client mode all windows share one process, so PID ancestry can't
# tell them apart and swallowing breaks — that's why $mod+Return runs
# `foot`, not `footclient`. Swallow is decided by getSwallower() walking
# the new window's parent-PID chain (up to 25 hops) for a window whose
# class matches swallow_regex; swallow_exception_regex, if set, matches the
# *terminal's title*, not the new window's class (a common misreading).
#
# Do NOT confuse swallowing with an app opening fullscreen/maximized — they
# look identical (the launching terminal vanishes) but are different things:
# - real swallow removes the terminal from the layout and restores it only
# when the swallower closes; you cannot un-swallow a live window.
# - a fullscreen/maximized app merely covers the workspace; the launcher
# is still a tile underneath and $mod+f / $mod SHIFT f toggles it back.
# `hyprctl clients` is decisive: a real swallow shows the terminal
# hidden=true and the new window swallowing!=0x0. LibreOffice trips this —
# it restores its own maximized state on open (from mc or a shell), so a
# document appears to "swallow" mc when it is only fullscreen. And being
# single-instance (one soffice.bin owns every window via IPC handoff), when
# it genuinely does swallow, that is fixed by whatever launched its daemon,
# not per-open — so it can't do the tile-from-mc / swallow-from-shell split
# that single-process handlers like zathura/imv get for free.
enable_swallow = true;
swallow_regex = "^(foot)$";
}; };
input = { input = {
kb_layout = "de"; kb_layout = "de";
follow_mouse = 1; follow_mouse = 1;
numlock_by_default = true;
}; };
"$mod" = "SUPER"; "$mod" = "SUPER";
@@ -513,11 +611,12 @@ in
]; ];
bind = [ bind = [
"$mod, Return, exec, footclient" "$mod, Return, exec, foot"
"$mod, q, killactive" "$mod, q, killactive"
"$mod SHIFT, q, exit" "$mod SHIFT, q, exit"
"$mod, f, fullscreen, 1" # fullscreen arg: 0 = true fullscreen, 1 = maximize (keeps bar/gaps).
"$mod SHIFT, f, fullscreen, 0" "$mod, f, fullscreen, 0"
"$mod SHIFT, f, fullscreen, 1"
"$mod, w, exec, ${browser}" "$mod, w, exec, ${browser}"
"$mod, d, exec, fuzzel" "$mod, d, exec, fuzzel"
"$mod, Backspace, exec, ${powerMenu}" "$mod, Backspace, exec, ${powerMenu}"
@@ -525,8 +624,17 @@ in
"$mod SHIFT, j, layoutmsg, swapnext" "$mod SHIFT, j, layoutmsg, swapnext"
"$mod, k, layoutmsg, cycleprev" "$mod, k, layoutmsg, cycleprev"
"$mod SHIFT, k, layoutmsg, swapprev" "$mod SHIFT, k, layoutmsg, swapprev"
"$mod, h, layoutmsg, splitratio -0.05" # swap focused window with the master (dwm-style "zoom").
"$mod, l, layoutmsg, splitratio +0.05" "$mod, space, layoutmsg, swapwithmaster"
# master area width. Master layout uses `mfact`, not dwindle's `splitratio`.
"$mod, h, layoutmsg, mfact -0.05"
"$mod, l, layoutmsg, mfact +0.05"
# Move focus / the current workspace to the other monitor ("o" = other).
# With two monitors the relative +1 just toggles between them.
"$mod, o, focusmonitor, +1"
"$mod SHIFT, o, movecurrentworkspacetomonitor, +1"
# Swap the two monitors' left/right placement (see swapMonitors above).
"$mod CTRL, o, exec, ${swapMonitors}"
] ++ ( ] ++ (
# Dynamic definitions for viewing workspaces 1 to 9 and moving windows between them. # Dynamic definitions for viewing workspaces 1 to 9 and moving windows between them.
# References physical key codes for robustness with the SHIFT key, e.g. `code:10` instead of `1`. # References physical key codes for robustness with the SHIFT key, e.g. `code:10` instead of `1`.
@@ -554,16 +662,13 @@ in
exec-once = [ exec-once = [
"waybar" "waybar"
# foot in server/daemon mode: $mod+Return runs `footclient`, which
# attaches to this one process → faster new-window startup, shared RAM.
# Uses foot's default socket, which footclient also defaults to.
# Tradeoff: if the server dies, all foot windows die with it.
"foot --server"
# wallpaper (see modules/media.nix): start the awww daemon, then restore # wallpaper (see modules/media.nix): start the awww daemon, then restore
# the persisted choice. awww keeps no state across restarts, so re-push # the persisted choice. awww keeps no state across restarts, so re-push
# the ~/.local/share/bg symlink if it exists. # the ~/.local/share/bg symlink if it exists.
"awww-daemon" "awww-daemon"
"sh -c '[ -e \"$HOME/.local/share/bg\" ] && awww img \"$HOME/.local/share/bg\"'" "sh -c '[ -e \"$HOME/.local/share/bg\" ] && awww img \"$HOME/.local/share/bg\"'"
# re-push the wallpaper onto monitors connected after login (media.nix)
"wallpaper-hotplug"
]; ];
}; };
}; };
@@ -609,15 +714,37 @@ in
position = "top"; position = "top";
height = 28; height = 28;
modules-left = [ "hyprland/workspaces" ]; modules-left = [ "hyprland/workspaces" ];
modules-center = [ "clock" ]; modules-center = [ "hyprland/window" ];
modules-right = [ "pulseaudio" "network" "battery" ]; modules-right = [ "pulseaudio" "network" "battery" "clock" ];
"hyprland/workspaces" = { "hyprland/workspaces" = {
format = "{id}"; format = "{id}";
}; };
"hyprland/window" = {
format = "{title}";
max-length = 100;
separate-outputs = true;
};
clock = { clock = {
format = "{:%H:%M %a %d %b}"; format = "{:%H:%M %a %d %b}";
tooltip-format = "<tt>{calendar}</tt>";
calendar = {
mode = "month";
mode-mon-col = 3;
weeks-pos = "left";
format = {
months = "<span color='#e5c07b'><b>{}</b></span>";
weekdays = "<span color='#c678dd'><b>{}</b></span>";
days = "<span color='#bbbbbb'>{}</span>";
today = "<span color='#61afef'><b><u>{}</u></b></span>";
weeks = "<span color='#5c6370'>W{:%V}</span>";
};
};
actions = {
on-click-right = "mode";
};
}; };
pulseaudio = { pulseaudio = {
@@ -657,9 +784,23 @@ in
background: #222222; background: #222222;
color: #bbbbbb; color: #bbbbbb;
} }
#pulseaudio, #network, #battery { #pulseaudio, #network, #battery, #clock {
padding: 0 12px; padding: 0 12px;
} }
#window {
color: #ffffff;
}
tooltip {
background: #222222;
border: 1px solid #333333;
}
tooltip label {
font-size: 15px;
padding: 4px;
}
window#waybar.empty #window {
background: transparent;
}
#battery.warning { #battery.warning {
color: #e5c07b; color: #e5c07b;
} }
@@ -669,6 +810,18 @@ in
#battery.charging { #battery.charging {
color: #98c379; color: #98c379;
} }
#workspaces button {
padding: 0 8px;
color: #bbbbbb;
}
#workspaces button.active {
color: #ffffff;
background: #333333;
box-shadow: inset 0 -2px #61afef;
}
#workspaces button.urgent {
color: #e06c75;
}
''; '';
}; };
} }
+42
View File
@@ -0,0 +1,42 @@
# Per-monitor "home" for workspaces. Each rule pins a workspace to a monitor
# connector, so it opens there and Hyprland moves it back when that monitor
# reappears after an unplug. The connector names are host hardware, so the rule
# list is an option a consumer sets from its own host config (see the example in
# `hosts/example/home.nix`), not baked in here; empty by default, so the example
# host binds nothing.
#
# Soft binding: when a bound monitor is absent the workspace just falls back to
# an active one — every workspace stays reachable, so a laptop-only (no external)
# session works unchanged. The `default` keyword marks the workspace a monitor
# shows on connect.
{ config, lib, pkgs, ... }:
{
options.local.hyprland.workspaceRules = lib.mkOption {
type = with lib.types; listOf str;
default = [ ];
example = [
"1, monitor:HDMI-A-1, default:true"
"6, monitor:LVDS-1, default:true"
];
description = ''
Hyprland `workspace` rules binding workspaces to monitor connectors, so
each workspace has a home screen. Connector names are host-specific set
this from the inventory rather than here.
'';
};
config.wayland.windowManager.hyprland.settings = {
workspace = config.local.hyprland.workspaceRules;
# With a support monitor whose `default` workspace is high (e.g. a laptop
# holding 6-9), a solo session with that monitor as the only screen opens on
# that high workspace instead of 1. When rules are in effect, nudge a
# single-monitor startup back to workspace 1. No-op with no rules (1 is
# already the default). jq is pinned by store path so a consumer needn't
# have it installed; hyprctl is bare (a Hyprland session always has it, like
# the wallpaper exec-once assumes awww).
exec-once = lib.optional (config.local.hyprland.workspaceRules != [ ])
"sh -c '[ \"$(hyprctl monitors -j | ${pkgs.jq}/bin/jq length)\" -eq 1 ] && hyprctl dispatch workspace 1'";
};
}
+35
View File
@@ -0,0 +1,35 @@
# LibreWolf browser. The package is an option so a consumer can swap the wrapped
# package without editing this module: the base is plain pkgs.librewolf, but the
# wrapper (wrapFirefox) accepts `extraPolicies` and `extraPrefs`, and overriding
# those is the clean way to manage LibreWolf declaratively.
#
# The useful case is sharing state across LibreWolf profiles that otherwise stay
# separate (each `-P <name>` has its own history/logins/cookies). Enterprise
# policy `ExtensionSettings` force- or normal-installs the same add-ons into
# every profile, and `extraPrefs` (autoconfig `defaultPref`/`lockPref`) seeds a
# shared about:config baseline — both applied per-launch, neither touching
# per-profile user data. Override, e.g.:
#
# local.librewolf.package = pkgs.librewolf.override {
# extraPolicies.ExtensionSettings."uBlock0@raymondhill.net" = {
# installation_mode = "normal_installed";
# install_url = "https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi";
# };
# extraPrefs = ''defaultPref("browser.startup.page", 3);'';
# };
{ config, lib, pkgs, ... }:
{
options.local.librewolf.package = lib.mkOption {
type = lib.types.package;
default = pkgs.librewolf;
defaultText = lib.literalExpression "pkgs.librewolf";
description = ''
LibreWolf package. Override with
`pkgs.librewolf.override { extraPolicies = ; extraPrefs = ; }`
to share extensions/settings across profiles.
'';
};
config.home.packages = [ config.local.librewolf.package ];
}
+108
View File
@@ -0,0 +1,108 @@
# Midnight Commander: TUI file manager. Its config (ini, panels, F2 user menu,
# mc.ext.ini file associations) is opinionated but generic, so it lives in the
# public baseline alongside the other desktop tooling. The only personal value
# is the right-panel start dir, exposed as an option defaulting to the
# consumer's own home. mc.ext.ini keeps archive browsing but routes every other
# type to xdg-open, so the app choices live in xdg.mimeApps (baseline: mime.nix).
#
# ouch backs the F2 pack/unpack entries (one tool, archive format inferred from
# the extension), so it ships with mc to keep the menu self-contained.
#
# mc honours XDG natively: config in $XDG_CONFIG_HOME/mc (the files below),
# mutable runtime state (history, cursor positions) in $XDG_DATA_HOME/mc — no
# env vars needed. `ini` sets auto_save_setup=false so mc never rewrites these
# read-only store symlinks on exit.
#
# The F2 menu is a `types.lines` option, not a baked source file, so consumers
# can extend or replace it. The shipped menu is a `mkBefore` definition (stays
# first, ahead of appended entries), NOT a `default` — a `default` is dropped
# the moment a consumer adds a definition, which would lose the base. As a real
# definition it merges: `local.mc.menu = "…";` appends after the base entries;
# `lib.mkForce "…"` replaces it wholesale.
{ config, lib, pkgs, ... }:
{
options.local.mc = {
otherDir = lib.mkOption {
type = lib.types.str;
default = config.home.homeDirectory;
defaultText = lib.literalExpression "config.home.homeDirectory";
description = "Startup directory for mc's other (right) panel.";
};
menu = lib.mkOption {
type = lib.types.lines;
description = ''
Contents of mc's F2 user menu. The shipped baseline is a mkBefore
definition, so a plain assignment appends entries after it; use
lib.mkForce to replace it entirely.
'';
};
extIni = lib.mkOption {
type = lib.types.lines;
description = ''
Contents of mc's mc.ext.ini (file-type associations driving Enter/F3/F4).
The shipped baseline keeps mc's archive browsing and delegates every
other type to xdg-open, so the actual app is the XDG mimeapps default.
Like `menu`, it is a mkBefore definition: append a same-named section to
override one key (mc merges same-named sections, last key wins), or use
lib.mkForce to replace it entirely.
'';
};
};
config = {
home.packages = [ pkgs.mc pkgs.ouch ];
local.mc.menu = lib.mkBefore (builtins.readFile ./assets/mc/menu);
local.mc.extIni = lib.mkBefore (builtins.readFile ./assets/mc/mc.ext.ini);
xdg.configFile = {
"mc/ini".source = ./assets/mc/ini;
"mc/menu".text = config.local.mc.menu;
"mc/mc.ext.ini".text = config.local.mc.extIni;
# Generated (not a static asset) so other_dir tracks the option instead of
# baking in a personal path.
"mc/panels.ini".text = ''
[New Left Panel]
display=listing
reverse=false
case_sensitive=true
exec_first=false
sort_order=name
list_mode=full
brief_cols=2
user_format=half type name | size | perm | mtime
user_status0=half type name | size | perm
user_status1=half type name | size | perm
user_status2=half type name | size | perm
user_status3=half type name | owner | group | ctime
user_mini_status=true
list_format=user
[New Right Panel]
display=listing
reverse=false
case_sensitive=true
exec_first=false
sort_order=name
list_mode=full
brief_cols=2
user_format=half type name | size | perm | mtime
user_status0=half type name | size | perm
user_status1=half type name | size | perm
user_status2=half type name | size | perm
user_status3=half type name | owner | group | ctime
user_mini_status=true
list_format=user
[Dirs]
current_is_left=true
other_dir=${config.local.mc.otherDir}
'';
};
};
}
+17
View File
@@ -17,6 +17,22 @@ let
[ -n "$1" ] && ln -sf "$(readlink -f "$1")" "$bgloc" [ -n "$1" ] && ln -sf "$(readlink -f "$1")" "$bgloc"
${pkgs.awww}/bin/awww img "$bgloc" ${pkgs.awww}/bin/awww img "$bgloc"
''; '';
# awww-daemon paints only the outputs present when `awww img` last ran and does
# not repaint a monitor hotplugged afterwards — it comes up on the fill color
# (black), and a transparent foot on it then shows that black, not the
# wallpaper. Watch Hyprland's event socket and re-push the wallpaper (setbg
# with no arg → all current outputs) on each monitor-add. Hyprland emits both
# `monitoradded` and `monitoraddedv2`; match only the former so setbg fires
# once. Runs as a Hyprland exec-once (see home.nix).
wallpaperHotplug = pkgs.writeShellScriptBin "wallpaper-hotplug" ''
sock="''${XDG_RUNTIME_DIR}/hypr/''${HYPRLAND_INSTANCE_SIGNATURE}/.socket2.sock"
${pkgs.socat}/bin/socat -u UNIX-CONNECT:"$sock" - | while IFS= read -r line; do
case "$line" in
"monitoradded>>"*) setbg ;;
esac
done
'';
in in
{ {
programs.mpv = { programs.mpv = {
@@ -50,6 +66,7 @@ in
home.packages = with pkgs; [ home.packages = with pkgs; [
setbg setbg
wallpaperHotplug # re-push wallpaper to a hotplugged monitor (see comment above)
awww # wayland wallpaper daemon (setbg backend) awww # wayland wallpaper daemon (setbg backend)
wl-clipboard # wl-copy, for the yank binds wl-clipboard # wl-copy, for the yank binds
imagemagick # `magick`: rotate/flip binds imagemagick # `magick`: rotate/flip binds
+75
View File
@@ -0,0 +1,75 @@
# Default applications per file type -- the single source of truth for "open
# with" across the desktop: xdg-open, file pickers, and Enter in mc (whose
# mc.ext.ini delegates every non-archive type to xdg-open; see mc.nix).
#
# These belong in the public baseline because the handlers do too: zathura, imv
# and mpv come from media.nix, libreoffice from home.nix. So "PDF opens in
# zathura", "images in imv", etc. are the natural defaults of this baseline, not
# personal choices -- every entry is mkDefault, so a consumer overrides one type
# by setting the same key (their definition wins), or flips enable off entirely.
#
# Personal handlers -- web browser, mail -- are NOT here; they map to a specific
# browser profile / mail client and live in the private inventory, merging into
# the same defaultApplications attrset.
{ lib, ... }:
let
# Map every mime in the list to one .desktop id, at mkDefault priority.
forAll = desktop: mimes: lib.genAttrs mimes (_: lib.mkDefault desktop);
zathura = "org.pwmt.zathura.desktop";
imv = "imv.desktop";
mpv = "mpv.desktop";
writer = "writer.desktop";
calc = "calc.desktop";
impress = "impress.desktop";
in {
xdg.mimeApps.enable = lib.mkDefault true;
xdg.mimeApps.defaultApplications =
(forAll zathura [
"application/pdf"
"application/epub+zip"
"application/oxps"
"application/postscript"
])
// (forAll imv [
"image/png"
"image/jpeg"
"image/gif"
"image/webp"
"image/bmp"
"image/tiff"
])
// (forAll mpv [
"video/mp4"
"video/x-matroska"
"video/webm"
"video/x-msvideo"
"video/quicktime"
"video/mpeg"
"audio/mpeg"
"audio/flac"
"audio/ogg"
"audio/x-wav"
"audio/mp4"
"audio/x-opus+ogg"
])
// (forAll writer [
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
"application/msword"
"application/vnd.oasis.opendocument.text"
"application/rtf"
])
// (forAll calc [
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
"application/vnd.ms-excel"
"application/vnd.oasis.opendocument.spreadsheet"
])
// (forAll impress [
"application/vnd.openxmlformats-officedocument.presentationml.presentation"
"application/vnd.ms-powerpoint"
"application/vnd.oasis.opendocument.presentation"
]);
}
+9 -7
View File
@@ -5,6 +5,8 @@
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
{ {
imports = [ ./shutdown-debug.nix ];
nix.settings.experimental-features = [ "nix-command" "flakes" ]; nix.settings.experimental-features = [ "nix-command" "flakes" ];
# Deduplicate identical store paths via hardlinks. # Deduplicate identical store paths via hardlinks.
nix.settings.auto-optimise-store = true; nix.settings.auto-optimise-store = true;
@@ -39,14 +41,14 @@
]; ];
}; };
# Time zone. Template default — override per host/region. # Neutral template defaults for regional/localized settings. All are
time.timeZone = "Europe/Berlin"; # mkDefault: set the real values (time zone, keymap, locale categories) in your
# own config so this public baseline stays region-agnostic.
# Internationalisation properties. Template defaults — override per region. time.timeZone = lib.mkDefault "UTC";
i18n.defaultLocale = "en_US.UTF-8"; i18n.defaultLocale = lib.mkDefault "en_US.UTF-8";
console = { console = {
font = "Lat2-Terminus16"; font = lib.mkDefault "Lat2-Terminus16";
keyMap = "de"; keyMap = lib.mkDefault "us";
}; };
# Programs. # Programs.
+272
View File
@@ -0,0 +1,272 @@
# Neovim, configured declaratively via nixvim. The editor — plugins, LSP,
# completion, keymaps — is defined here and pinned by the flake lock; nothing is
# downloaded at runtime, unlike an imperative distro (NvChad/LazyVim). The nixvim
# home-manager module that provides `programs.nixvim` is imported for consumers by
# `homeModules.default` in flake.nix, so this file only sets options.
#
# Consumer knobs (the option-with-default contract): `local.nvim.enable` to opt
# out wholesale, `local.nvim.colorscheme` to swap theme in one line, and
# `local.nvim.lspServers` to reshape the language set. Everything else is
# opinionated — but since nixvim exposes every plugin as an option, a consumer can
# still override any `programs.nixvim.plugins.*` directly from their own config.
{ config, lib, pkgs, ... }:
let
cfg = config.local.nvim;
in
{
options.local.nvim = {
enable = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Whether to enable the nixvim-based Neovim setup.";
};
colorscheme = lib.mkOption {
type = lib.types.str;
default = "catppuccin";
example = "tokyonight";
description = ''
nixvim colorscheme module to enable (the attr under `colorschemes.*`,
e.g. "catppuccin", "tokyonight", "gruvbox"). Catppuccin is tuned below
(mocha flavour); for another scheme set its `colorschemes.<name>.settings`
from your own config if you want to tweak it.
'';
};
lspServers = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [
"nixd" # Nix — this repo's own language, so a sensible public baseline
"bashls" # Shell — likewise generic enough to default on
];
example = [ "nixd" "bashls" "rust_analyzer" "basedpyright" ];
description = ''
lspconfig server names to enable under `plugins.lsp.servers.<name>`.
Each must exist in nixvim's server list and its binary in nixpkgs. The
public default is deliberately minimal (Nix + shell); a consumer sets its
own language set here and any per-server tuning a pick needs (e.g.
rust_analyzer's `installRustc`) via `programs.nixvim.plugins.lsp.servers`.
Note: standalone Lua wants "lua_ls", but Lua embedded in Nix strings is
invisible to any LSP, so it is not needed just to edit this config.
'';
};
};
config = lib.mkIf cfg.enable {
programs.nixvim = {
enable = true;
# Point nixvim at our (followed) nixpkgs instead of the rev it pins. Both
# track nixos-26.05, so the version skew nixvim warns about does not arise,
# and this keeps a single nixpkgs in the closure. Setting it explicitly is
# also what silences that warning.
nixpkgs.source = pkgs.path;
# System clipboard on Wayland: yanks go to / puts read from wl-clipboard,
# so copy/paste crosses the nvim boundary. Enabling the provider pulls the
# wl-clipboard binaries in as a dependency.
clipboard = {
register = "unnamedplus";
providers.wl-copy.enable = true;
};
globals = {
mapleader = " ";
maplocalleader = " ";
};
opts = {
number = true;
relativenumber = true; # relative line numbers for quick j/k motions
signcolumn = "yes"; # always show the gutter so text doesn't jump
cursorline = true;
termguicolors = true; # 24-bit colour, required by catppuccin & co.
scrolloff = 8; # keep 8 lines of context around the cursor
expandtab = true; # spaces, not tabs
shiftwidth = 2;
tabstop = 2;
smartindent = true;
ignorecase = true; # case-insensitive search…
smartcase = true; # …unless the query has a capital
undofile = true; # persistent undo across sessions
splitright = true;
splitbelow = true;
# What :mksession stores (persistence.nvim). Drops "blank" and "terminal"
# from nvim's default so empty/plugin windows (neo-tree, toggleterm) aren't
# serialised — they restore as broken empty splits otherwise. Adds
# "globals", which some plugins rely on. Pairs with the PersistenceSavePre
# neo-tree close below.
sessionoptions = "buffers,curdir,folds,globals,tabpages,winsize";
};
# Theme. Swap via local.nvim.colorscheme; catppuccin gets the mocha flavour.
# mkMerge keeps the computed `${cfg.colorscheme}` key from colliding with the
# literal `catppuccin` one when they happen to be the same attr.
colorschemes = lib.mkMerge [
{ ${cfg.colorscheme}.enable = true; }
(lib.mkIf (cfg.colorscheme == "catppuccin") {
catppuccin.settings.flavour = "mocha";
})
];
plugins = {
# Shared icon set for the tree, statusline, completion menu.
web-devicons.enable = true;
# Syntax via tree-sitter. The default grammar set is broad, so most
# languages get highlighting without an LSP or any per-language wiring.
treesitter = {
enable = true;
settings = {
highlight.enable = true;
indent.enable = true;
};
};
# LSP. Servers come from local.nvim.lspServers; blink-cmp feeds its
# completion capabilities in automatically (setupLspCapabilities).
lsp = {
enable = true;
servers = lib.genAttrs cfg.lspServers (_: { enable = true; });
keymaps = {
silent = true;
lspBuf = {
gd = "definition";
gD = "declaration";
K = "hover";
"<leader>rn" = "rename";
"<leader>ca" = "code_action";
};
# gr/gi (references, implementations) are wired to Telescope pickers in
# the global keymaps below instead — fuzzy + preview browsing beats the
# quickfix list that vim.lsp.buf.references would populate. gd stays a
# direct jump here (usually one target; Ctrl-o returns).
diagnostic = {
# Idiomatic vim bracket pair for stepping a list, kept as the public
# default. A consumer whose layout makes brackets awkward (they need
# AltGr on QWERTZ) can add comfortable aliases from their own config:
# this option is attrsOf, so extra keys merge in without conflict.
"[d" = "goto_prev";
"]d" = "goto_next";
"<leader>ds" = "open_float"; # show the diagnostic under the cursor
};
};
};
# Completion. blink.cmp — faster and lighter to configure than nvim-cmp.
blink-cmp = {
enable = true;
settings = {
keymap.preset = "default"; # C-space open, C-y confirm, C-n/C-p move
appearance.nerd_font_variant = "mono";
sources.default = [ "lsp" "path" "snippets" "buffer" ];
completion.documentation.auto_show = true;
};
};
# Fuzzy finder — files, live grep, open buffers (buffer switching lives
# here rather than a bufferline; see the S-h/S-l binds below too).
telescope.enable = true;
# File tree.
neo-tree.enable = true;
# Statusline, NvChad-ish blocks: powerline separators, one global bar,
# theme following the colorscheme.
lualine = {
enable = true;
settings.options = {
theme = "auto";
globalstatus = true;
section_separators = { left = ""; right = ""; };
component_separators = { left = ""; right = ""; };
};
};
# Inline colour swatches for #rrggbb / colour names.
colorizer.enable = true;
# Git signs in the gutter.
gitsigns.enable = true;
# Session persistence: auto-saves a session per project directory into XDG
# state, restorable later. <leader>S restores the current dir's session; a
# consumer wanting auto-restore on a bare `nvim` adds a VimEnter autocmd.
persistence.enable = true;
# Floating terminal toggled with Ctrl-\ (shares nvim's cwd). Try it; if
# a tiled foot next to nvim serves you better, drop this block.
toggleterm = {
enable = true;
settings = {
open_mapping = { __raw = "[[<c-\\>]]"; };
direction = "float";
};
};
# Popup of the pending keybinds after leader — a live, always-accurate
# cheatsheet. The group labels name each leader subtree so the popup reads
# by category; <leader>? opens the full list on demand (keymaps below).
which-key = {
enable = true;
settings.spec = [
{ __unkeyed-1 = "<leader>f"; group = "Find (Telescope)"; }
{ __unkeyed-1 = "<leader>d"; group = "Diagnostics"; }
{ __unkeyed-1 = "<leader>b"; group = "Buffer"; }
{ __unkeyed-1 = "<leader>c"; group = "Code"; }
{ __unkeyed-1 = "<leader>r"; group = "Refactor"; }
{ __unkeyed-1 = "<leader>s"; group = "Search"; }
];
};
};
# Global (non-LSP) keymaps. Deliberately small — a starting point to grow.
keymaps = [
{ mode = "n"; key = "<leader>w"; action = "<cmd>w<cr>"; options.desc = "Save"; }
{ mode = "n"; key = "<leader>q"; action = "<cmd>q<cr>"; options.desc = "Quit window"; }
{ mode = "n"; key = "<leader>Q"; action = "<cmd>qa<cr>"; options.desc = "Quit all (exit nvim)"; }
# Restore the saved session for the current directory (persistence.nvim).
{ mode = "n"; key = "<leader>S"; action.__raw = "function() require('persistence').load() end"; options.desc = "Restore session"; }
{ mode = "n"; key = "<leader>e"; action = "<cmd>Neotree toggle<cr>"; options.desc = "File tree"; }
{ mode = "n"; key = "<Esc>"; action = "<cmd>nohlsearch<cr>"; }
# Telescope
{ mode = "n"; key = "<leader>ff"; action = "<cmd>Telescope find_files<cr>"; options.desc = "Find files"; }
{ mode = "n"; key = "<leader>fg"; action = "<cmd>Telescope live_grep<cr>"; options.desc = "Live grep"; }
{ mode = "n"; key = "<leader>fb"; action = "<cmd>Telescope buffers<cr>"; options.desc = "Buffers"; }
{ mode = "n"; key = "<leader>fh"; action = "<cmd>Telescope help_tags<cr>"; options.desc = "Help tags"; }
# LSP navigation via Telescope pickers (fuzzy + preview) for the
# many-result lookups; gd/gD stay direct jumps (plugins.lsp.keymaps above).
{ mode = "n"; key = "gr"; action = "<cmd>Telescope lsp_references<cr>"; options.desc = "References"; }
{ mode = "n"; key = "gi"; action = "<cmd>Telescope lsp_implementations<cr>"; options.desc = "Implementations"; }
# Buffer navigation (no bufferline: cycle with Shift-h/l, list via <leader>fb)
{ mode = "n"; key = "<S-l>"; action = "<cmd>bnext<cr>"; options.desc = "Next buffer"; }
{ mode = "n"; key = "<S-h>"; action = "<cmd>bprevious<cr>"; options.desc = "Prev buffer"; }
{ mode = "n"; key = "<leader>bd"; action = "<cmd>bdelete<cr>"; options.desc = "Close buffer"; }
# Cheatsheet: <leader>? pops the full which-key list; <leader>sk fuzzy-
# searches every active mapping via Telescope (catches non-leader binds too).
{ mode = "n"; key = "<leader>?"; action = "<cmd>WhichKey<cr>"; options.desc = "All keybinds (which-key)"; }
{ mode = "n"; key = "<leader>sk"; action = "<cmd>Telescope keymaps<cr>"; options.desc = "Search keymaps"; }
];
# persistence saves the window layout via :mksession, but neo-tree's special
# buffer doesn't serialise — a saved tree window restores as a blank split.
# Close neo-tree just before a session is written so the layout stays clean.
autoCmd = [
{
event = "User";
pattern = "PersistenceSavePre";
callback.__raw = ''function() pcall(vim.cmd, "Neotree close") end'';
}
];
};
};
}
+92
View File
@@ -0,0 +1,92 @@
# Shutdown/boot hang diagnostics.
#
# Shutdown stalls are awkward to debug because journald is torn down mid-way,
# so anything that wedges *after* it dies never reaches /var/log/journal — the
# tail just stops with no explanation. These knobs make the next hang leave a
# trail instead. All are opt-in except the shortened stop-timeout, which is a
# baseline preset (overridable / disable with null).
#
# Reading the evidence after a hang:
# - On screen while it hangs: watch for "A stop job is running for <unit>
# (Xs / 1min 30s)". That line names the culprit; note whether its counter
# advances (named unit = easy) or the screen is frozen with no counter
# (late systemd-shutdown phase = needs pstore, below).
# - After a *clean* next boot: `journalctl -b -1 -e` shows the previous
# boot's shutdown tail (NixOS journald is persistent by default).
# - After a *forced* power-off: journald never flushed, so check the kernel
# ring buffer preserved across reboot in `/sys/fs/pstore/` (needs pstore
# backing — ACPI ERST on most laptops, else ramoops). `verboseLogging`
# below routes systemd's own messages to kmsg so they land there too.
# - With `debugShell` on: switch to tty9 (Ctrl+Alt+F9) while hung and run
# `systemctl list-jobs` to see which job is still pending.
{ config, lib, pkgs, ... }:
let
cfg = config.local.shutdownDebug;
in
{
options.local.shutdownDebug = {
stopTimeout = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = "20s";
example = null;
description = ''
DefaultTimeoutStopSec for both the system and per-user managers. The
default shortens systemd's 90s so a hung unit is killed and logged as
`timed out` in seconds rather than spinning at a black screen. Set to
null to leave systemd's default untouched. Any systemd time span, e.g.
"20s", "45s", "1min".
'';
};
verboseLogging = lib.mkEnableOption ''
verbose systemd shutdown logging. Adds the kernel params
`systemd.log_level=debug systemd.log_target=kmsg`: bumps systemd to debug
verbosity and routes its messages into the kernel ring buffer, which
outlives journald and can be recovered from /sys/fs/pstore after a forced
power-off. Noisy on every boot and mildly slows it enable only while
actively hunting a hang'';
debugShell = lib.mkEnableOption ''
an unauthenticated root shell on tty9 (Ctrl+Alt+F9), available through
shutdown, for running `systemctl list-jobs` while the system is wedged.
SECURITY: anyone with physical console access gets root leave off
except while investigating'';
};
config = lib.mkMerge [
(lib.mkIf (cfg.stopTimeout != null) {
systemd.settings.Manager.DefaultTimeoutStopSec = cfg.stopTimeout;
systemd.user.extraConfig = "DefaultTimeoutStopSec=${cfg.stopTimeout}";
})
(lib.mkIf cfg.verboseLogging {
boot.kernelParams = [ "systemd.log_level=debug" "systemd.log_target=kmsg" ];
})
# Uniquely named rather than enabling the upstream (static) debug-shell.service,
# whose enablement via systemd.services would generate a shadowing unit.
(lib.mkIf cfg.debugShell {
systemd.services.shutdown-debug-shell = {
description = "Root shell on tty9 for boot/shutdown debugging";
unitConfig = {
DefaultDependencies = false;
ConditionPathExists = "/dev/tty9";
};
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Environment = "TERM=linux";
ExecStart = "${pkgs.bashInteractive}/bin/bash";
Restart = "always";
RestartSec = 0;
StandardInput = "tty";
TTYPath = "/dev/tty9";
TTYReset = true;
TTYVHangup = true;
KillMode = "process";
};
};
})
];
}
+32
View File
@@ -0,0 +1,32 @@
# TeX Live for pandoc PDF output: pandoc shells out to a LaTeX engine, so PDF
# conversion needs a TeX distribution (other formats — html, docx, md — do not).
# The package set is an option so a consumer can swap the scheme without editing
# this module: scheme-medium covers pandoc's default LaTeX template; override
# for a larger set (missing .sty files) or a smaller one.
#
# TeX Live's font cache and config trees default to ~/.texlive<year> and ~/texmf
# (written on the first PDF build). The TEXMF* vars route them to XDG dirs.
{ config, lib, pkgs, ... }:
{
options.local.texlive.package = lib.mkOption {
type = lib.types.package;
default = pkgs.texlive.withPackages (ps: [ ps.scheme-medium ]);
defaultText = lib.literalExpression
"pkgs.texlive.withPackages (ps: [ ps.scheme-medium ])";
description = "TeX Live package set. Override for a larger/smaller scheme.";
};
config = {
home.packages = [ config.local.texlive.package ];
# texmf-var is regenerable font/format cache → cacheHome; the other two hold
# user config/data trees.
home.sessionVariables = {
TEXMFHOME = "${config.xdg.dataHome}/texmf";
TEXMFVAR = "${config.xdg.cacheHome}/texlive/texmf-var";
TEXMFCONFIG = "${config.xdg.configHome}/texlive/texmf-config";
};
};
}