Compare commits

...

4 Commits

Author SHA1 Message Date
Olivier 6b8fa60540 docs: update README with feature overview and module structure 2026-07-22 18:02:21 -03:00
Olivier 86dc80eb3c feat(wisdom): enhance vscode module with java support and writable settings
Add Java extension support and configure multiple JDK runtimes.
Introduce a wrapped VS Code package that includes JDK 25 in the PATH
to support the Red Hat Java language server.

Switch from using `programs.vscode.userSettings` to a manually managed
`settings.json` file via `home.file`. This allows the settings file to
be writable by external tools like `kilo-code` at runtime while
preserving them across Nix configuration switches by using `force = false`.
2026-07-19 18:13:30 -03:00
Olivier f1bd5146a3 refactor(hosts): simplify streaming display configuration
Refactor the streaming display logic for the 14900k host to use the
`streamingDisplay` configuration object directly instead of separate
variables. This simplifies the monitor layout logic for both Niri and
Hyprland.

Additionally, remove the fragile `sunshineOutputIndex` option in favor
of using the stable connector name for Sunshine's `output_name`. This
prevents issues where numeric monitor indices drift after hotplugging.
The change also includes an explicit encoder list for Sunshine to avoid
probing for AV1 on hardware that does not support it.
2026-07-18 09:35:52 -03:00
Olivier 6f35d60aab refactor(system): simplify steamTinkerLaunch logic and package assignment
Refactor the steamExtraPkgs conditional logic into a single line and
introduce a launcherSet variable to reduce redundancy when assigning
packages to the system or specific users.
2026-07-17 17:37:38 -03:00
5 changed files with 97 additions and 91 deletions
+14 -4
View File
@@ -17,16 +17,26 @@ How to work on it: [conventions.md](./conventions.md).
Each machine: `modules/hosts/<name>/default.nix``nixosConfigurations.<name>`, real config in `*Configuration` + optional `_private/` and `_services/`. Each machine: `modules/hosts/<name>/default.nix``nixosConfigurations.<name>`, real config in `*Configuration` + optional `_private/` and `_services/`.
## Features at a glance
- **Users catalog** — one catalog in `modules/system/users.nix`, pick who exists on a host with `chiasson.users.enabled = [ "example" ]`, override per host. Home Manager wires itself from the same list.
- **SSH + Bitwarden (rbw) keys** — one ed25519 keypair per catalog user, private key in Bitwarden, public key in `modules/ssh/inventory.nix`. Outbound SSH uses a `.pub` filter against the rbw agent; inbound `authorized_keys` are generated from the same inventory. [Full wiring in conventions.md](./conventions.md#ssh-model).
- **Wisdom modules** — Home Manager slices in `modules/wisdom/` auto-catalog via `lib.wisdomCatalogExtraModules` (defined in `modules/desktop/options.nix`). The baseline `wisdom` module handles git identity + shared tools; all other `wisdom*` exports are pulled in once per user through `desktopHomeBase``chiasson.users.extraModules.olivier`. Hosts only flip `chiasson.home.<category>.<slice>.enable` — no manual imports in `home.nix`. Categories include `apps` (spotify, discord, localsend), `browsers` (chrome, edge, zen, …), `editors` (cursor, vscode, obsidian, …), `shells` (bash, fish, oh-my-posh, yazi), `desktop` (gtk-qt-theming, screenshot), and `terminals` (kitty). Trivial single-package slices can be written with the `lib.wisdomSlice` helper instead of repeating the `root`/`cfg` + `mkIf` boilerplate.
- **Wallpapers from a dedicated repo** — `modules/desktop/wallpapers.nix` pins `inputs.wallpapers` into the store and exposes it as `CHIASSON_NIX_WALLPAPERS` and `/etc/wallpapers`. Override `chiasson.desktop.wallpapers.source` if needed.
- **Private host data** — import-tree skips `_private/` globally, so machine-only files (firmware quirks, display configs, service tweaks) live next to the host module without leaking elsewhere.
## Deploy / rebuild ## Deploy / rebuild
Remote builds use the `builder` user (`systemDeployBuilder`, wired through `client-services` on desktops). Remote builds use the `builder` user (`systemDeployBuilder`, wired through `client-services` on desktops/laptops/tablets).
Fleet deploy is [Navi](https://github.com/cafkafk/navi) — config in `modules/deploy/navi.nix`, outputs `flake.navi` / `flake.naviHive`. Fleet deploy is [Navi](https://github.com/cafkafk/navi) — config in `modules/deploy/navi.nix`, outputs `flake.navi` / `flake.naviHive`.
```bash ```bash
nix develop # devShell has navi + hints nix develop # devShell has navi + just + hints
just # list local helpers like `sync-dms`
navi apply --on <host> navi apply --on <host>
navi apply-local --node 14900k --sudo # this machine, if hostname matches navi apply-local --node 14900k --sudo # this machine, if hostname matches
navi tui # interactive fleet dashboard
``` ```
Plain rebuild still works: `sudo nixos-rebuild switch --flake .#<host>`. Plain rebuild still works: `sudo nixos-rebuild switch --flake .#<host>`.
@@ -37,11 +47,11 @@ Plain rebuild still works: `sudo nixos-rebuild switch --flake .#<host>`.
modules/ modules/
hosts/<host>/ # per-machine composition hosts/<host>/ # per-machine composition
system/ # nixosModules.system aggregate system/ # nixosModules.system aggregate
desktop/ # GUI stack desktop/ # GUI stack (niri, hyprland, plasma, DMS)
wisdom/ # HM modules (exports still named wisdom*) wisdom/ # HM modules (exports still named wisdom*)
ssh/ # inbound NixOS + outbound HM ssh/ # inbound NixOS + outbound HM
deploy/ # navi hive deploy/ # navi hive
lib/ # pure helpers → flake.lib patches/ # one-off patches (yt-dlp, t2fanrd, …)
``` ```
Machine-only stuff lives in `hosts/<host>/_private/` — import-tree skips `_private/` globally, so those files only get pulled in where you import them. Machine-only stuff lives in `hosts/<host>/_private/` — import-tree skips `_private/` globally, so those files only get pulled in where you import them.
+22 -53
View File
@@ -1,25 +1,15 @@
# Monitor layout for 14900k. # Monitor layout for 14900k.
# NVIDIA (default): DP-3 ultrawide, HDMI-A-3 + DP-4 side/top stack; DP-2 virtual 4K when streamingDisplay enabled. # NVIDIA (default): DP-3 ultrawide, HDMI-A-3 + DP-4 stack; DP-2 virtual 4K when streamingDisplay is on.
# Intel iGPU (gpu passthru): DP-1 ultrawide, HDMI-A-2 Samsung to the left. # Intel iGPU (gpu passthru): DP-1 ultrawide, HDMI-A-2 to the left.
# Niri: `extraSettings` (KDL via `extraConfig`, other wrapper-modules keys) + `extraBinds` (merged keybinds). # Niri uses `extraSettings.extraConfig`; Hyprland uses `chiasson.desktop.hyprland.settings`.
# Hyprland: `chiasson.desktop.hyprland.settings` (merged in HM when `chiasson.desktop.hyprland.enable`).
#TODO[epic=Moderate] Clean this up, move to host's configuration.nix. #TODO[epic=Moderate] Clean this up, move to host's configuration.nix.
{ config, lib, ... }: { config, lib, ... }:
let let
streamingDisplay = config.chiasson.system.streamingDisplay;
gpuPassthru = config.chiasson.system.gpuPassthru.enable; gpuPassthru = config.chiasson.system.gpuPassthru.enable;
streamingDisplay = config.chiasson.system.streamingDisplay.enable or false;
streamingCfg = config.chiasson.system.streamingDisplay or { };
virtualOutputNiri =
lib.optionalString streamingDisplay ''
output "${streamingCfg.connector}" {
mode "${streamingCfg.mode}"
scale ${toString (streamingCfg.niriScale or 1.0)}
position ${streamingCfg.niriPosition}
}
'';
# Niri outputs: a static block per GPU layout, plus the optional virtual 4K panel.
niriOutputs = niriOutputs =
(if gpuPassthru then (if gpuPassthru then
'' ''
@@ -54,48 +44,30 @@ let
position x=0 y=-1080 position x=0 y=-1080
} }
'') '')
+ virtualOutputNiri; + lib.optionalString streamingDisplay.enable ''
output "${streamingDisplay.connector}" {
virtualOutputHyprland = mode "${streamingDisplay.mode}"
lib.optionalString streamingDisplay scale ${toString streamingDisplay.niriScale}
"${streamingCfg.connector}, ${streamingCfg.mode}, 7680x0, ${toString (streamingCfg.niriScale or 1.0)}"; position ${streamingDisplay.niriPosition}
}
'';
# Hyprland monitor strings, with the virtual panel appended when enabled.
hyprlandMonitors = hyprlandMonitors =
(if gpuPassthru then (if gpuPassthru then
[ [ "DP-1, 2560x1080@144, 0x0, 1" "HDMI-A-2, 1920x1080@60, -1920x0, 1" ]
"DP-1, 2560x1080@144, 0x0, 1"
"HDMI-A-2, 1920x1080@60, -1920x0, 1"
]
else else
[ [ "DP-3, 2560x1080@144, 0x0, 1" "DP-4, 1920x1080@144, 0x-1080, 1" "HDMI-A-3, 1920x1080@60, -1920x0, 1" ])
"DP-3, 2560x1080@144, 0x0, 1" ++ lib.optional streamingDisplay.enable "${streamingDisplay.connector}, ${streamingDisplay.mode}, 7680x0, ${toString streamingDisplay.niriScale}";
"DP-4, 1920x1080@144, 0x-1080, 1"
"HDMI-A-3, 1920x1080@60, -1920x0, 1"
])
++ lib.optional streamingDisplay virtualOutputHyprland;
hyprlandWorkspaces = hyprlandWorkspaces =
if gpuPassthru then if gpuPassthru then
[ [ "1, monitor:DP-1, default:true" "2, monitor:DP-1" "3, monitor:DP-1"
"1, monitor:DP-1, default:true" "4, monitor:HDMI-A-2, default:true" "5, monitor:HDMI-A-2" "6, monitor:HDMI-A-2" ]
"2, monitor:DP-1"
"3, monitor:DP-1"
"4, monitor:HDMI-A-2, default:true"
"5, monitor:HDMI-A-2"
"6, monitor:HDMI-A-2"
]
else else
[ [ "1, monitor:DP-3, default:true" "2, monitor:DP-3" "3, monitor:DP-3"
"1, monitor:DP-3, default:true" "4, monitor:HDMI-A-3, default:true" "5, monitor:HDMI-A-3" "6, monitor:HDMI-A-3"
"2, monitor:DP-3" "7, monitor:DP-4" "8, monitor:DP-4" "9, monitor:DP-4" ];
"3, monitor:DP-3"
"4, monitor:HDMI-A-3, default:true"
"5, monitor:HDMI-A-3"
"6, monitor:HDMI-A-3"
"7, monitor:DP-4"
"8, monitor:DP-4"
"9, monitor:DP-4"
];
in in
{ {
chiasson.desktop.niri.extraSettings = { chiasson.desktop.niri.extraSettings = {
@@ -103,10 +75,7 @@ in
}; };
chiasson.desktop.niri.extraBinds."XF86Tools".spawn = [ chiasson.desktop.niri.extraBinds."XF86Tools".spawn = [
"wpctl" "wpctl" "set-mute" "@DEFAULT_AUDIO_SOURCE@" "toggle"
"set-mute"
"@DEFAULT_AUDIO_SOURCE@"
"toggle"
]; ];
chiasson.desktop.hyprland.settings = lib.mkIf config.chiasson.desktop.hyprland.enable { chiasson.desktop.hyprland.settings = lib.mkIf config.chiasson.desktop.hyprland.enable {
@@ -1,14 +1,12 @@
# Headless 4K virtual output for Sunshine → Moonlight on a client TV. # Headless 4K virtual output for Sunshine → Moonlight on a client TV.
# #
# NVIDIA proprietary driver: both kernel params are required — # NVIDIA: force-enable a spare DRM connector with a custom EDID via kernel params:
# video=<connector>:e force-enables the connector # video=<connector>:e force-enables the connector
# drm.edid_firmware=<connector>:… loads modes from the EDID blob # drm.edid_firmware=<connector>:… loads modes from the EDID blob
# #
# Pick a connector that is disconnected on the host (check after boot): # Capture is pinned to the connector by name (Sunshine `output_name`), which is
# grep -H . /sys/class/drm/card*-*/status # stable across reboots/hotplug — unlike a numeric monitor index.
# # Reboot after changing connector or EDID. 4K@60 is realistic on RTX 2070.
# Reboot after changing connector or EDID. 4K@60 is realistic on RTX 2070;
# 4K@120 on a sink-less connector needs driver patches (see NVIDIA issue #1184).
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
let let
cfg = config.chiasson.system.streamingDisplay; cfg = config.chiasson.system.streamingDisplay;
@@ -16,14 +14,13 @@ let
edidFirmware = pkgs.runCommand "sunshine-virtual-4k-edid" { edidFirmware = pkgs.runCommand "sunshine-virtual-4k-edid" {
nativeBuildInputs = [ pkgs.python3 ]; nativeBuildInputs = [ pkgs.python3 ];
} '' } ''
${pkgs.python3}/bin/python3 ${./generate-virtual-edid.py} $TMPDIR/${cfg.edidFileName}
mkdir -p $out/lib/firmware/edid mkdir -p $out/lib/firmware/edid
cp $TMPDIR/${cfg.edidFileName} $out/lib/firmware/edid/${cfg.edidFileName} ${pkgs.python3}/bin/python3 ${./generate-virtual-edid.py} $out/lib/firmware/edid/${cfg.edidFileName}
''; '';
# Sunshine 2025.924 wlr capture only accepts a numeric monitor index (not "DP-2"). # On stream start: scale + focus the virtual 4K output so streamed windows
# If the index drifts after hotplug, check: # land there. Capture is pinned to the connector via Sunshine `output_name`,
# journalctl --user -u sunshine -b | rg 'Monitor [0-9]+ is' # so no fragile numeric-index detection is needed.
streamDisplayUp = pkgs.writeShellScriptBin "stream-display-up" '' streamDisplayUp = pkgs.writeShellScriptBin "stream-display-up" ''
${pkgs.niri}/bin/niri msg output ${cfg.connector} scale ${toString cfg.niriScale} ${pkgs.niri}/bin/niri msg output ${cfg.connector} scale ${toString cfg.niriScale}
${pkgs.niri}/bin/niri msg action focus-monitor ${cfg.connector} ${pkgs.niri}/bin/niri msg action focus-monitor ${cfg.connector}
@@ -87,18 +84,6 @@ in
default = "DP-3"; default = "DP-3";
description = "Niri monitor to refocus when a Moonlight session ends."; description = "Niri monitor to refocus when a Moonlight session ends.";
}; };
sunshineOutputIndex = lib.mkOption {
type = lib.types.str;
default = "3";
example = "3";
description = ''
Wayland/wlr monitor index for Sunshine `output_name`. Sunshine 2025.924 ignores
connector strings like `DP-2` and falls back to monitor 0 (your primary).
Find the index with:
`journalctl --user -u sunshine -b | rg "Monitor [0-9]+ is ${cfg.connector}"`
'';
};
}; };
config = lib.mkIf cfg.enable (lib.mkMerge [ config = lib.mkIf cfg.enable (lib.mkMerge [
@@ -118,7 +103,11 @@ in
(lib.mkIf config.chiasson.system.gaming.sunshine.enable { (lib.mkIf config.chiasson.system.gaming.sunshine.enable {
services.sunshine.settings = { services.sunshine.settings = {
capture = "wlr"; capture = "wlr";
output_name = cfg.sunshineOutputIndex; # Pin capture to the virtual display by connector name (stable across
# reboots/hotplug). A numeric index drifted to the ultrawide before.
output_name = cfg.connector;
# RTX 2070 has no AV1 NVENC; avoid probing av1_nvenc.
encoder = "hevc_nvenc,h264_nvenc";
global_prep_cmd = "[{\"do\":\"${streamDisplayUp}/bin/stream-display-up\",\"undo\":\"${streamDisplayDown}/bin/stream-display-down\"}]"; global_prep_cmd = "[{\"do\":\"${streamDisplayUp}/bin/stream-display-up\",\"undo\":\"${streamDisplayDown}/bin/stream-display-down\"}]";
}; };
}) })
+5 -6
View File
@@ -17,10 +17,9 @@
++ lib.optionals pkgs.stdenv.isx86_64 [ heroic ]; ++ lib.optionals pkgs.stdenv.isx86_64 [ heroic ];
steamExtraPkgs = steamExtraPkgs =
if !cfg.steam.steamTinkerLaunch.enable then lib.optional (cfg.steam.steamTinkerLaunch.enable && lib.meta.availableOn pkgs.stdenv.hostPlatform pkgs.steamtinkerlaunch) pkgs.steamtinkerlaunch;
[ ]
else launcherSet = launcherPkgs ++ steamExtraPkgs ++ cfg.launchers.extraPackages;
lib.optional (lib.meta.availableOn pkgs.stdenv.hostPlatform pkgs.steamtinkerlaunch) pkgs.steamtinkerlaunch;
in in
{ {
options.chiasson.system.gaming = { options.chiasson.system.gaming = {
@@ -194,11 +193,11 @@
]; ];
} }
(lib.mkIf (cfg.launchers.forUsers == null) { (lib.mkIf (cfg.launchers.forUsers == null) {
environment.systemPackages = launcherPkgs ++ steamExtraPkgs ++ cfg.launchers.extraPackages; environment.systemPackages = launcherSet;
}) })
(lib.mkIf (cfg.launchers.forUsers != null) { (lib.mkIf (cfg.launchers.forUsers != null) {
users.users = lib.genAttrs cfg.launchers.forUsers (_: { users.users = lib.genAttrs cfg.launchers.forUsers (_: {
packages = launcherPkgs ++ steamExtraPkgs ++ cfg.launchers.extraPackages; packages = launcherSet;
}); });
}) })
]); ]);
+43 -4
View File
@@ -12,8 +12,23 @@
jnoortheen.nix-ide jnoortheen.nix-ide
yzhang.markdown-all-in-one yzhang.markdown-all-in-one
esbenp.prettier-vscode esbenp.prettier-vscode
vscjava.vscode-java-pack
redhat.java
]; ];
# Wrap VS Code so a JDK is on PATH — the Red Hat Java language server (and
# other extension-bundled JVMs) need `java` to run, independent of the
# per-project `java.configuration.runtimes` setting.
vscodeWithJdk = pkgs.symlinkJoin {
name = "vscode-with-jdk";
paths = [ pkgs.vscode ];
nativeBuildInputs = [ pkgs.makeWrapper ];
postBuild = ''
wrapProgram "$out/bin/code" \
--prefix PATH : "${pkgs.jdk25}/bin"
'';
};
coreSettings = { coreSettings = {
"nix.enableLanguageServer" = true; "nix.enableLanguageServer" = true;
"nix.serverPath" = "nixd"; "nix.serverPath" = "nixd";
@@ -26,7 +41,26 @@
}; };
"diffEditor.ignoreTrimWhitespace" = false; "diffEditor.ignoreTrimWhitespace" = false;
"chat.disableAIFeatures" = true; "chat.disableAIFeatures" = true;
"java.configuration.runtimes" = [
{
name = "JavaSE-21";
path = "${pkgs.jdk21}/lib/openjdk";
}
{
name = "JavaSE-25";
path = "${pkgs.jdk25}/lib/openjdk";
default = true;
}
];
}; };
# Seed a *writable* settings.json (not a store symlink) so extensions like
# kilo-code can edit it at runtime. `force = false` in the config means HM
# only writes it when absent, preserving kilo's later edits across `switch`.
settingsJson = pkgs.writeText "vscode-user-settings" (
builtins.toJSON coreSettings
);
in in
{ {
options.chiasson.home.editors.vscode = { options.chiasson.home.editors.vscode = {
@@ -48,14 +82,19 @@
}; };
config = lib.mkIf (root.enable && cfg.enable) { config = lib.mkIf (root.enable && cfg.enable) {
home.packages = [ pkgs.vscode ]; home.packages = [ ];
# Writable, seed-once user settings (kilo-code can append to it).
home.file.".config/Code/User/settings.json" = {
source = settingsJson;
force = false;
};
programs.vscode = { programs.vscode = {
enable = true; enable = true;
package = pkgs.vscode; package = vscodeWithJdk;
extensions = defaultExtensions ++ cfg.extensions; extensions = defaultExtensions ++ cfg.extensions;
userSettings = coreSettings;
}; };
}; };
}; };
} }