Compare commits
25 Commits
c1ec7401d1
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
5e1b9332a4
|
|||
|
c9b976f0ae
|
|||
|
07a78b6012
|
|||
|
94411c0abe
|
|||
|
7415ea0b54
|
|||
|
184a2f5545
|
|||
|
b413c8af81
|
|||
|
5c4045f4a6
|
|||
|
e4a8b07117
|
|||
|
f533bbeb05
|
|||
|
031bad3238
|
|||
|
dc676b6f8c
|
|||
|
f64d68ac05
|
|||
|
4be1c4fb67
|
|||
|
7205841c4d
|
|||
|
0851515de0
|
|||
|
de6c3993cc
|
|||
|
98eb0f87ba
|
|||
|
d8d86aadcb
|
|||
|
c375a15561
|
|||
|
08a14d52f2
|
|||
|
eab578e699
|
|||
|
8f9eff1640
|
|||
|
3e093b02d1
|
|||
|
b907a39c41
|
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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,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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
# ];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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 &
|
||||||
@@ -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
@@ -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;
|
||||||
|
}
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'";
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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
@@ -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,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
|
||||||
|
|||||||
@@ -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
@@ -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.
|
||||||
|
|||||||
@@ -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'';
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user