Compare commits
20 Commits
yolo
...
noctalia-pin
| Author | SHA1 | Date | |
|---|---|---|---|
| 189196fb88 | |||
| 849cd3a5ef | |||
| 013d41aa72 | |||
| 0a4dd8a192 | |||
| 8efd0a1139 | |||
| de527df7c4 | |||
| c7eb5796ea | |||
| 3ee2f4db10 | |||
| 886af193a2 | |||
| 4a8f8d330c | |||
| 92a7273ff3 | |||
| 05037e40d8 | |||
| d417a27f2d | |||
| c79436d340 | |||
| 6a08ec3805 | |||
| 51f6cb013f | |||
| fbf5ce2616 | |||
| df319322b9 | |||
| 65b95bb1fa | |||
| 352700fdab |
Generated
+10
-9
@@ -927,23 +927,24 @@
|
||||
"original": {
|
||||
"owner": "noctalia-dev",
|
||||
"repo": "noctalia-shell",
|
||||
"rev": "aded31799eb0dffdbcf436243083f1ebce26f95d",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nvchad-starter": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1782738765,
|
||||
"narHash": "sha256-/jxBHJ74FYVYm8JGO3ay2J3B2/Rf3/KvroivtJFFJRM=",
|
||||
"owner": "naps62",
|
||||
"repo": "nvchad-starter",
|
||||
"rev": "020bf7716ea1b7d233cbdaef63c8585791f896b7",
|
||||
"type": "github"
|
||||
"lastModified": 1786971647,
|
||||
"narHash": "sha256-RsRq+BRIrpSzDUU6hF+ITwFcjtiIPsFpk220+Civ010=",
|
||||
"ref": "refs/heads/main",
|
||||
"rev": "4db90cb097f2358b8adb8c11f116e4201b81d0f6",
|
||||
"revCount": 178,
|
||||
"type": "git",
|
||||
"url": "https://git.naps.pt/naps62/nvim-config.git"
|
||||
},
|
||||
"original": {
|
||||
"owner": "naps62",
|
||||
"repo": "nvchad-starter",
|
||||
"type": "github"
|
||||
"type": "git",
|
||||
"url": "https://git.naps.pt/naps62/nvim-config.git"
|
||||
}
|
||||
},
|
||||
"nvchad4nix": {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
inputs.nvchad-starter.follows = "nvchad-starter";
|
||||
};
|
||||
nvchad-starter = {
|
||||
url = "github:naps62/nvchad-starter";
|
||||
url = "git+https://git.naps.pt/naps62/nvim-config.git";
|
||||
flake = false;
|
||||
};
|
||||
foundry = {
|
||||
@@ -49,8 +49,11 @@
|
||||
url = "https://github.com/hyprwm/Hyprland";
|
||||
submodules = true;
|
||||
};
|
||||
# Pinned to a rev: noctalia publishes no binary cache, so every bump means
|
||||
# building the Qt/QML app locally. quickshell itself comes from nixpkgs and
|
||||
# is cached, so the follows stays. Bump deliberately, not via `flake update`.
|
||||
noctalia = {
|
||||
url = "github:noctalia-dev/noctalia-shell";
|
||||
url = "github:noctalia-dev/noctalia-shell/aded31799eb0dffdbcf436243083f1ebce26f95d";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
spicetify-nix = {
|
||||
|
||||
@@ -19,7 +19,11 @@ let
|
||||
let
|
||||
target = "${config.home.homeDirectory}/${name}";
|
||||
storePath = value.source;
|
||||
originalPath = toRepoPath value.source;
|
||||
# toRepoPath only works for sources that live in the flake tree. A
|
||||
# generated source is not under flakePrefix, so removePrefix is a no-op
|
||||
# and the hint would print a /nix/store path to copy back onto.
|
||||
originalPath =
|
||||
if value.upstreamPath != null then "${repoPath}/${value.upstreamPath}" else toRepoPath value.source;
|
||||
in
|
||||
''
|
||||
if [ -f "${target}" ] && ! ${lib.getExe' pkgs.diffutils "diff"} -q "${storePath}" "${target}" > /dev/null 2>&1; then
|
||||
@@ -67,6 +71,17 @@ in
|
||||
default = false;
|
||||
description = "Whether the file should be executable.";
|
||||
};
|
||||
|
||||
upstreamPath = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "home/yolo/claude-settings.json";
|
||||
description = ''
|
||||
Repo-relative file to name in the "bring changes upstream" hint.
|
||||
Required when `source` is generated rather than a file in the
|
||||
flake tree, since the store path cannot be mapped back.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -30,7 +30,9 @@
|
||||
};
|
||||
|
||||
home = {
|
||||
mutableFilesRepoPath = "${config.home.homeDirectory}/projects/nixos-config";
|
||||
# mkDefault: hosts whose clone lives elsewhere (yolo, under ~/tea) override
|
||||
# this with a plain assignment.
|
||||
mutableFilesRepoPath = lib.mkDefault "${config.home.homeDirectory}/projects/nixos-config";
|
||||
|
||||
packages = with pkgs; [
|
||||
impala
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
{ pkgs, ... }:
|
||||
{ pkgs, config, ... }:
|
||||
let
|
||||
inherit (config.custom.hyprland) cursorTheme cursorSize;
|
||||
in
|
||||
{
|
||||
home.packages = with pkgs; [
|
||||
dconf
|
||||
@@ -15,14 +18,14 @@
|
||||
'';
|
||||
cursor-theme = ''
|
||||
${pkgs.dconf}/bin/dconf write \
|
||||
/org/gnome/desktop/interface/cursor-theme "'Nordzy-cursors'"
|
||||
/org/gnome/desktop/interface/cursor-theme "'${cursorTheme.dark}'"
|
||||
|
||||
# Set environment variables for current session
|
||||
export HYPRCURSOR_THEME="Nordzy-cursors"
|
||||
export XCURSOR_THEME="Nordzy-cursors"
|
||||
export HYPRCURSOR_THEME="${cursorTheme.dark}"
|
||||
export XCURSOR_THEME="${cursorTheme.dark}"
|
||||
|
||||
# Update Hyprland cursor theme
|
||||
${pkgs.hyprland}/bin/hyprctl setcursor Nordzy-cursors 24
|
||||
${pkgs.hyprland}/bin/hyprctl setcursor ${cursorTheme.dark} ${toString cursorSize}
|
||||
'';
|
||||
noctalia-theme = ''
|
||||
noctalia msg theme-mode-set dark
|
||||
@@ -37,14 +40,14 @@
|
||||
'';
|
||||
cursor-theme = ''
|
||||
${pkgs.dconf}/bin/dconf write \
|
||||
/org/gnome/desktop/interface/cursor-theme "'Nordzy-white'"
|
||||
/org/gnome/desktop/interface/cursor-theme "'${cursorTheme.light}'"
|
||||
|
||||
# Set environment variables for current session
|
||||
export HYPRCURSOR_THEME="Nordzy-white"
|
||||
export XCURSOR_THEME="Nordzy-white"
|
||||
export HYPRCURSOR_THEME="${cursorTheme.light}"
|
||||
export XCURSOR_THEME="${cursorTheme.light}"
|
||||
|
||||
# Update Hyprland cursor theme
|
||||
${pkgs.hyprland}/bin/hyprctl setcursor Nordzy-white 24
|
||||
${pkgs.hyprland}/bin/hyprctl setcursor ${cursorTheme.light} ${toString cursorSize}
|
||||
'';
|
||||
noctalia-theme = ''
|
||||
noctalia msg theme-mode-set light
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
{
|
||||
imports = [
|
||||
./codex.nix
|
||||
./pi.nix
|
||||
./ralph-claude-code.nix
|
||||
./t3-code.nix
|
||||
];
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
{ pkgs, config, ... }:
|
||||
let
|
||||
cfg = config.custom.hyprland;
|
||||
nordzy-cursors = pkgs.callPackage ../../../../pkgs/nordzy-cursors/package.nix { };
|
||||
cursorSize = config.custom.hyprland.cursorSize;
|
||||
cursorPackage = if cfg.cursorPackage != null then cfg.cursorPackage else nordzy-cursors;
|
||||
cursorSize = cfg.cursorSize;
|
||||
in
|
||||
{
|
||||
home.packages = [
|
||||
nordzy-cursors
|
||||
cursorPackage
|
||||
];
|
||||
|
||||
home.sessionVariables = {
|
||||
HYPRCURSOR_THEME = "Nordzy-cursors"; # Dark variant (default)
|
||||
HYPRCURSOR_THEME = cfg.cursorTheme.dark;
|
||||
HYPRCURSOR_SIZE = cursorSize;
|
||||
XCURSOR_THEME = "Nordzy-cursors";
|
||||
XCURSOR_THEME = cfg.cursorTheme.dark;
|
||||
XCURSOR_SIZE = cursorSize;
|
||||
};
|
||||
|
||||
|
||||
@@ -70,6 +70,26 @@ in
|
||||
default = 24;
|
||||
description = "Cursor size for XCURSOR_SIZE and HYPRCURSOR_SIZE";
|
||||
};
|
||||
cursorPackage = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.package;
|
||||
default = null;
|
||||
description = ''
|
||||
Package providing cursorTheme.dark/light under share/icons. null uses
|
||||
the nordzy-cursors package built in ./cursor.nix.
|
||||
'';
|
||||
};
|
||||
cursorTheme = {
|
||||
dark = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "Nordzy-cursors";
|
||||
description = "Cursor theme darkman selects in dark mode.";
|
||||
};
|
||||
light = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "Nordzy-white";
|
||||
description = "Cursor theme darkman selects in light mode.";
|
||||
};
|
||||
};
|
||||
shareOutput = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
@@ -204,7 +224,7 @@ in
|
||||
-- environment
|
||||
hl.env("GDK_SCALE", "2.0")
|
||||
hl.env("XCURSOR_SIZE", "${toString cfg.cursorSize}")
|
||||
hl.env("HYPRCURSOR_THEME", "rose-pine-hyprcursor")
|
||||
hl.env("HYPRCURSOR_THEME", "${cfg.cursorTheme.dark}")
|
||||
hl.env("HYPRCURSOR_SIZE", "${toString cfg.cursorSize}")
|
||||
-- Route Qt6 apps (incl. the xdph screen-share picker) through qt6ct so
|
||||
-- the noctalia-generated color scheme actually applies. Without this
|
||||
@@ -216,9 +236,12 @@ in
|
||||
-- Import the Qt theme var too, so the dbus/systemd-activated
|
||||
-- xdg-desktop-portal-hyprland (and its share-picker) inherit it.
|
||||
hl.exec_cmd("dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP QT_QPA_PLATFORMTHEME")
|
||||
hl.exec_cmd("hyprctl setcursor Nordzy-cursors ${toString cfg.cursorSize}")
|
||||
hl.exec_cmd("hyprctl setcursor ${cfg.cursorTheme.dark} ${toString cfg.cursorSize}")
|
||||
hl.exec_cmd("hyprsunset")
|
||||
hl.exec_cmd("noctalia")
|
||||
-- kdeconnectd is only DBus-activated; nothing starts it at login,
|
||||
-- so clipboard sync stays dead until the indicator runs.
|
||||
hl.exec_cmd("kdeconnect-indicator")
|
||||
end)
|
||||
|
||||
-- layer rules
|
||||
|
||||
@@ -1,27 +1,108 @@
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
clip2path = pkgs.writeShellScript "clip2path" ''
|
||||
set -e
|
||||
set -euo pipefail
|
||||
|
||||
if [ -n "$WAYLAND_DISPLAY" ]; then
|
||||
types=$(wl-paste --list-types)
|
||||
if grep -q '^image/' <<<"$types"; then
|
||||
ext=$(grep -m1 '^image/' <<<"$types" | cut -d/ -f2 | cut -d';' -f1)
|
||||
file="/tmp/clip_$(date +%s).''${ext}"
|
||||
wl-paste --type "image/''${ext}" > "$file"
|
||||
printf '%q' "$file" | kitty @ send-text --stdin
|
||||
jq=${pkgs.jq}/bin/jq
|
||||
scp=${pkgs.openssh}/bin/scp
|
||||
wlpaste=${pkgs.wl-clipboard}/bin/wl-paste
|
||||
xclip=${pkgs.xclip}/bin/xclip
|
||||
find=${pkgs.findutils}/bin/find
|
||||
|
||||
# kitty/kitten stay unpinned: remote control has to reach the running
|
||||
# instance, whose build may differ from pkgs.kitty.
|
||||
|
||||
"$find" /tmp -maxdepth 1 -name 'clip_*' -mtime +1 -delete 2>/dev/null || true
|
||||
|
||||
# Some apps advertise a lossy format before image/png.
|
||||
pick_type() {
|
||||
if grep -qx 'image/png' <<<"$1"; then
|
||||
echo image/png
|
||||
else
|
||||
wl-paste --no-newline | kitty @ send-text --stdin
|
||||
grep -m1 '^image/' <<<"$1" | cut -d';' -f1
|
||||
fi
|
||||
elif [ -n "$DISPLAY" ]; then
|
||||
types=$(xclip -selection clipboard -t TARGETS -o)
|
||||
if grep -q '^image/' <<<"$types"; then
|
||||
ext=$(grep -m1 '^image/' <<<"$types" | cut -d/ -f2 | cut -d';' -f1)
|
||||
file="/tmp/clip_$(date +%s).''${ext}"
|
||||
xclip -selection clipboard -t "image/''${ext}" -o > "$file"
|
||||
printf '%q' "$file" | kitty @ send-text --stdin
|
||||
}
|
||||
|
||||
ext_of() {
|
||||
local e=''${1#image/}
|
||||
e=''${e%%+*}
|
||||
e=''${e#x-}
|
||||
tr -cd 'a-zA-Z0-9' <<<"$e"
|
||||
}
|
||||
|
||||
# Destination of the ssh client running in the focused window, empty if
|
||||
# that window is local. Prefers the real ssh process over the `kitten ssh`
|
||||
# wrapper, whose own long options would confuse the parser below.
|
||||
ssh_dest() {
|
||||
local -a argv
|
||||
mapfile -t argv < <(kitty @ ls | "$jq" -r '
|
||||
[ .[] | select(.is_focused)
|
||||
| .tabs[] | select(.is_focused)
|
||||
| .windows[] | select(.is_focused)
|
||||
| .foreground_processes[]?
|
||||
| { b: ((.cmdline[0] // "") | split("/") | last), c: .cmdline }
|
||||
] as $p
|
||||
| ( [ $p[] | select(.b == "ssh") ] + [ $p[] | select(.b == "kitten") ] )
|
||||
| (.[0].c // [])[]')
|
||||
|
||||
[ ''${#argv[@]} -gt 0 ] || return 0
|
||||
if [ "''${argv[0]##*/}" = kitten ]; then
|
||||
[ "''${argv[1]:-}" = ssh ] || return 0
|
||||
argv=("''${argv[@]:2}")
|
||||
else
|
||||
xclip -selection clipboard -o | kitty @ send-text --stdin
|
||||
argv=("''${argv[@]:1}")
|
||||
fi
|
||||
|
||||
local valueflags=bcDEeFIiJLlmOopQRSWw a
|
||||
while [ ''${#argv[@]} -gt 0 ]; do
|
||||
a=''${argv[0]}
|
||||
case "$a" in
|
||||
--) echo "''${argv[1]:-}"; return 0 ;;
|
||||
-?)
|
||||
case "$valueflags" in *"''${a#-}"*) argv=("''${argv[@]:1}") ;; esac
|
||||
argv=("''${argv[@]:1}")
|
||||
;;
|
||||
-*) argv=("''${argv[@]:1}") ;;
|
||||
*) echo "$a"; return 0 ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
send_path() {
|
||||
local file=$1 dest remote
|
||||
dest=$(ssh_dest)
|
||||
if [ -n "$dest" ]; then
|
||||
remote=/tmp/''${file##*/}
|
||||
if ! "$scp" -q -o BatchMode=yes -o ConnectTimeout=5 "$file" "$dest:$remote"; then
|
||||
kitten notify --app-name clip2path clip2path "copy to $dest failed, nothing pasted"
|
||||
return 1
|
||||
fi
|
||||
file=$remote
|
||||
fi
|
||||
printf '%q' "$file" | kitty @ send-text --stdin
|
||||
}
|
||||
|
||||
# Text goes through the real paste action. send-text has no bracketed
|
||||
# paste, so multi-line text would run line by line in a shell.
|
||||
if [ -n "''${WAYLAND_DISPLAY:-}" ]; then
|
||||
types=$("$wlpaste" --list-types)
|
||||
if grep -q '^image/' <<<"$types"; then
|
||||
type=$(pick_type "$types")
|
||||
file=$(mktemp --suffix=".$(ext_of "$type")" /tmp/clip_XXXXXXXX)
|
||||
"$wlpaste" --type "$type" > "$file"
|
||||
send_path "$file"
|
||||
else
|
||||
kitty @ action paste_from_clipboard
|
||||
fi
|
||||
elif [ -n "''${DISPLAY:-}" ]; then
|
||||
types=$("$xclip" -selection clipboard -t TARGETS -o)
|
||||
if grep -q '^image/' <<<"$types"; then
|
||||
type=$(pick_type "$types")
|
||||
file=$(mktemp --suffix=".$(ext_of "$type")" /tmp/clip_XXXXXXXX)
|
||||
"$xclip" -selection clipboard -t "$type" -o > "$file"
|
||||
send_path "$file"
|
||||
else
|
||||
kitty @ action paste_from_clipboard
|
||||
fi
|
||||
fi
|
||||
'';
|
||||
|
||||
@@ -12,5 +12,14 @@
|
||||
|
||||
programs.nvchad = {
|
||||
enable = true;
|
||||
|
||||
# Neovim only auto-picks OSC 52 when it finds no clipboard tool, but
|
||||
# wl-clipboard is on every host — so over SSH it writes the *remote*
|
||||
# clipboard instead. Locally wl-copy stays: kitty refuses OSC 52 reads.
|
||||
extraConfig = ''
|
||||
if vim.env.SSH_TTY and vim.env.SSH_TTY ~= "" then
|
||||
vim.g.clipboard = "osc52"
|
||||
end
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
{ pkgs, inputs, ... }:
|
||||
{
|
||||
lib,
|
||||
pkgs,
|
||||
inputs,
|
||||
...
|
||||
}:
|
||||
{
|
||||
imports = [
|
||||
# Prebuilt, weekly-updated nix-index database (backs `comma`).
|
||||
@@ -13,7 +18,9 @@
|
||||
# `flake` sets NH_FLAKE so `nh home switch` finds this repo without --flake.
|
||||
programs.nh = {
|
||||
enable = true;
|
||||
flake = "/home/naps62/projects/nixos-config";
|
||||
# mkDefault: hosts whose clone lives elsewhere (yolo, under ~/tea) override
|
||||
# this with a plain assignment.
|
||||
flake = lib.mkDefault "/home/naps62/projects/nixos-config";
|
||||
};
|
||||
|
||||
home.packages = with pkgs; [
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
inputs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
pi = pkgs.callPackage ../../../pkgs/pi/package.nix { };
|
||||
|
||||
skills = inputs.agent-skills;
|
||||
|
||||
# pi has no `@file` imports in context files, so fragments get concatenated
|
||||
# into one AGENTS.md. Skills need no wiring: pi reads ~/.agents/skills, which
|
||||
# the agent-skills module already links.
|
||||
agentsMd = pkgs.writeText "pi-AGENTS.md" (
|
||||
lib.concatMapStringsSep "\n" builtins.readFile [
|
||||
"${skills}/claude-md/machines/${config.programs.agentSkills.machine}.md"
|
||||
"${skills}/claude-md/operating.md"
|
||||
"${skills}/claude-md/writing.md"
|
||||
"${skills}/claude-md/code-comments.md"
|
||||
"${skills}/claude-md/RTK.md"
|
||||
]
|
||||
);
|
||||
in
|
||||
{
|
||||
home.packages = [ pi ];
|
||||
|
||||
# Settings stay unmanaged: pi writes ~/.pi/agent/settings.json itself from
|
||||
# /settings, /trust and `pi config`.
|
||||
home.file.".pi/agent/AGENTS.md".source = agentsMd;
|
||||
}
|
||||
@@ -22,6 +22,15 @@ set -s escape-time 25
|
||||
# Enable 256 colors
|
||||
set -g default-terminal "tmux-256color"
|
||||
set -ag terminal-overrides ",xterm-256color:RGB"
|
||||
set -ag terminal-overrides ",*256col*:Tc"
|
||||
|
||||
# smcup@/rmcup@ drops the alternate screen, so what tmux drew stays in the
|
||||
# outer terminal's scrollback instead of vanishing when tmux exits.
|
||||
set -ag terminal-overrides ",xterm*:Tc:smcup@:rmcup@"
|
||||
|
||||
# Forward Shift+Enter and other modified keys through to applications
|
||||
set -s extended-keys on
|
||||
set -as terminal-features "tmux-256color:extkeys"
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# Status bar theme
|
||||
@@ -101,6 +110,37 @@ setw -g mode-keys vi
|
||||
bind -T copy-mode-vi v send -X begin-selection
|
||||
bind -T copy-mode-vi y send -X copy-selection-and-cancel
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# Clipboard (OSC 52)
|
||||
# -------------------------------------------------------------------
|
||||
set -g set-clipboard on
|
||||
|
||||
# tmux silently skips OSC 52 unless the outer terminal's terminfo claims the
|
||||
# capability. kitty's does; SSH sessions usually land on a bare
|
||||
# xterm-256color that does not, so declare both.
|
||||
set -ag terminal-features ",xterm-kitty:clipboard"
|
||||
set -ag terminal-features ",xterm-256color:clipboard"
|
||||
|
||||
# Let nvim's OSC 52 provider reach the outer terminal through tmux.
|
||||
set -g allow-passthrough on
|
||||
|
||||
# Drag-to-copy. No external command: set-clipboard routes this through
|
||||
# OSC 52, which works on Wayland and over SSH. xclip did neither.
|
||||
bind-key -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel
|
||||
bind-key -T copy-mode MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# Mouse scrolling
|
||||
# -------------------------------------------------------------------
|
||||
# Scrolling up enters copy-mode, unless the pane's application wants the
|
||||
# mouse itself or we are already in copy-mode.
|
||||
bind-key -n WheelUpPane if-shell -F -t = "#{mouse_any_flag}" "send-keys -M" "if -Ft= '#{pane_in_mode}' 'send-keys -M' 'copy-mode -e'"
|
||||
|
||||
bind-key -T copy-mode-vi WheelUpPane send-keys -X scroll-up
|
||||
bind-key -T copy-mode-vi WheelDownPane send-keys -X scroll-down
|
||||
bind-key -T copy-mode WheelUpPane send-keys -X scroll-up
|
||||
bind-key -T copy-mode WheelDownPane send-keys -X scroll-down
|
||||
|
||||
|
||||
# tmux-resurrect settings
|
||||
set -g @resurrect-capture-pane-contents 'on'
|
||||
@@ -109,40 +149,3 @@ set -g @resurrect-strategy-nvim 'session'
|
||||
# tmux-continuum settings (auto-save and auto-restore)
|
||||
set -g @continuum-restore 'on'
|
||||
set -g @continuum-save-interval '10'
|
||||
|
||||
|
||||
# agent-deck configuration
|
||||
# agent-deck-tmux-config-version: 3
|
||||
# Added by agent-deck installer - 2026-03-18
|
||||
# https://github.com/asheshgoplani/agent-deck
|
||||
|
||||
# Terminal with true color support
|
||||
set -g default-terminal "tmux-256color"
|
||||
set -ag terminal-overrides ",xterm*:Tc:smcup@:rmcup@"
|
||||
set -ag terminal-overrides ",*256col*:Tc"
|
||||
|
||||
# Performance
|
||||
set -sg escape-time 0
|
||||
set -g history-limit 50000
|
||||
|
||||
# Extended keys: forward Shift+Enter and other modified keys to apps (tmux 3.2+)
|
||||
set -s extended-keys on
|
||||
set -as terminal-features 'tmux-256color:extkeys'
|
||||
|
||||
# Mouse support (scroll + drag-to-copy)
|
||||
set -g mouse on
|
||||
|
||||
# Auto-enter copy-mode when scrolling up (critical for WSL compatibility)
|
||||
# This handles: 1) apps with mouse support, 2) already in copy-mode, 3) normal pane
|
||||
bind-key -n WheelUpPane if-shell -F -t = "#{mouse_any_flag}" "send-keys -M" "if -Ft= '#{pane_in_mode}' 'send-keys -M' 'copy-mode -e'"
|
||||
|
||||
# Scroll bindings in copy-mode (both vi and emacs modes)
|
||||
bind-key -T copy-mode-vi WheelUpPane send-keys -X scroll-up
|
||||
bind-key -T copy-mode-vi WheelDownPane send-keys -X scroll-down
|
||||
bind-key -T copy-mode WheelUpPane send-keys -X scroll-up
|
||||
bind-key -T copy-mode WheelDownPane send-keys -X scroll-down
|
||||
|
||||
# Clipboard integration (drag-to-copy)
|
||||
bind-key -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "xclip -in -selection clipboard"
|
||||
bind-key -T copy-mode MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "xclip -in -selection clipboard"
|
||||
# End agent-deck configuration
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
enable = true;
|
||||
nvidia = true;
|
||||
retroarch = true;
|
||||
moonlight = true;
|
||||
};
|
||||
|
||||
# 4K@1x monitors render the Electron AI apps tiny; scale their UI up. Tune to taste.
|
||||
|
||||
@@ -9,7 +9,7 @@ compaction_reminder_percent = 75
|
||||
default_agent = "claude"
|
||||
max_concurrent_workers = 5
|
||||
node_path = ""
|
||||
offer_structured_in_new_session = false
|
||||
offer_structured_in_new_session = true
|
||||
rate_limit_auto_resume = false
|
||||
replay_events = 0
|
||||
restrict_agents = false
|
||||
@@ -79,7 +79,7 @@ prevent_sleep_when_active = false
|
||||
restart_wake_message = "wake up: pick up what you were doing"
|
||||
row_tag = "none"
|
||||
show_session_colors = true
|
||||
show_tips = true
|
||||
show_tips = false
|
||||
smart_rename = true
|
||||
smart_rename_agent = ""
|
||||
snooze_duration_minutes = 30
|
||||
@@ -112,7 +112,7 @@ enabled = false
|
||||
[theme]
|
||||
color_mode = "truecolor"
|
||||
idle_decay_minutes = 0
|
||||
name = ""
|
||||
name = "zinc"
|
||||
|
||||
[tmux]
|
||||
clipboard = "auto"
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
],
|
||||
"defaultMode": "auto"
|
||||
},
|
||||
"model": "claude-fable-5[1m]",
|
||||
"hooks": {
|
||||
"PostToolUse": [
|
||||
{
|
||||
@@ -200,10 +199,6 @@
|
||||
"worktree": {
|
||||
"baseRef": "fresh"
|
||||
},
|
||||
"statusLine": {
|
||||
"type": "command",
|
||||
"command": "~/.claude/statusline.sh"
|
||||
},
|
||||
"enabledPlugins": {
|
||||
"building@subvisual": true,
|
||||
"rust-analyzer-lsp@claude-plugins-official": false,
|
||||
@@ -229,7 +224,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tui": "fullscreen",
|
||||
"skipDangerousModePermissionPrompt": true,
|
||||
"theme": "dark",
|
||||
"editorMode": "vim",
|
||||
@@ -237,7 +231,6 @@
|
||||
"autoCompactEnabled": true,
|
||||
"remoteControlAtStartup": false,
|
||||
"inputNeededNotifEnabled": true,
|
||||
"agentPushNotifEnabled": true,
|
||||
"mcpServers": {
|
||||
"playwright": {
|
||||
"args": [
|
||||
@@ -247,5 +240,8 @@
|
||||
"command": "npx",
|
||||
"type": "stdio"
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
|
||||
+46
-14
@@ -3,6 +3,18 @@
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
claudeSettings = "home/yolo/claude-settings.json";
|
||||
|
||||
# ./claude-settings.json holds only what yolo overrides; everything else is
|
||||
# inherited so common changes reach this host. Attrsets merge key-by-key,
|
||||
# lists are replaced whole (permissions.allow is yolo's, not a union).
|
||||
mergedClaudeSettings = (pkgs.formats.json { }).generate "claude-settings.json" (
|
||||
lib.recursiveUpdate (lib.importJSON ../common/programs/claude/settings.json) (
|
||||
lib.importJSON ./claude-settings.json
|
||||
)
|
||||
);
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
../common/programs/default.nix
|
||||
@@ -19,25 +31,45 @@
|
||||
|
||||
custom.hyprland.cursorSize = 32;
|
||||
|
||||
# Headless browser driver the agent tooling shells out to. Was a global npm
|
||||
# install on the ubuntu box.
|
||||
home.packages = [ pkgs.agent-browser ];
|
||||
# Amber, and a different silhouette to Nordzy — this desktop is only ever seen
|
||||
# inside a Moonlight window, so the cursor has to be tellable at a glance from
|
||||
# the client's own. Same theme in both modes; darkman would otherwise swap it.
|
||||
custom.hyprland.cursorPackage = pkgs.bibata-cursors;
|
||||
custom.hyprland.cursorTheme = {
|
||||
dark = "Bibata-Modern-Amber";
|
||||
light = "Bibata-Modern-Amber";
|
||||
};
|
||||
|
||||
# Both default to ~/projects/nixos-config in common/programs; this clone lives
|
||||
# under ~/tea. nh.flake sets NH_FLAKE, so without it `nh home switch` with no
|
||||
# argument resolves to a path that does not exist.
|
||||
home.mutableFilesRepoPath = lib.mkForce "/home/naps62/tea/nixos-config";
|
||||
home = {
|
||||
# Headless browser driver the agent tooling shells out to. Was a global npm
|
||||
# install on the ubuntu box.
|
||||
packages = [ pkgs.agent-browser ];
|
||||
|
||||
# Host-local, not shared: this sets yolo_mode_default = true, which starts aoe
|
||||
# sessions with permission checks skipped. Only correct on this box.
|
||||
home.mutableFiles.".config/agent-of-empires/config.toml".source = ./aoe-config.toml;
|
||||
# Both default to ~/projects/nixos-config in common/programs; this clone
|
||||
# lives under ~/tea. nh.flake sets NH_FLAKE, so without it `nh home switch`
|
||||
# with no argument resolves to a path that does not exist.
|
||||
mutableFilesRepoPath = "/home/naps62/tea/nixos-config";
|
||||
|
||||
# Likewise host-local: carries skipDangerousModePermissionPrompt and the rev
|
||||
# hook paths, neither of which belong on a workstation.
|
||||
home.mutableFiles.".claude/settings.json".source = lib.mkForce ./claude-settings.json;
|
||||
mutableFiles = {
|
||||
# Host-local, not shared: this sets yolo_mode_default = true, which starts
|
||||
# aoe sessions with permission checks skipped. Only correct on this box.
|
||||
".config/agent-of-empires/config.toml".source = ./aoe-config.toml;
|
||||
|
||||
# Likewise host-local: carries skipDangerousModePermissionPrompt and the
|
||||
# rev hook paths, neither of which belong on a workstation.
|
||||
".claude/settings.json" = {
|
||||
source = lib.mkForce mergedClaudeSettings;
|
||||
upstreamPath = claudeSettings;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
programs.agentSkills.machine = "yolo";
|
||||
programs.nh.flake = lib.mkForce "/home/naps62/tea/nixos-config";
|
||||
programs.nh.flake = "/home/naps62/tea/nixos-config";
|
||||
|
||||
# Idle lock and dpms-off blank the virtual output: Sunshine then captures a
|
||||
# flat frame and Moonlight goes black, with no console to unlock from.
|
||||
services.hypridle.enable = lib.mkForce false;
|
||||
|
||||
# Blur and shadow cost a fullscreen pass per frame, and every frame here is
|
||||
# also x264-encoded for the stream — on a virtio-gpu with no VirGL, in software.
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
_:
|
||||
{
|
||||
_: {
|
||||
# Hyprland 0.55+ is Lua-only (see home/common/programs/hyprland).
|
||||
#
|
||||
# Matches every output rather than naming one: the virtio-gpu connector name
|
||||
# varies by qemu display backend (Virtual-1 vs Virtual-0). Explicit mode, not
|
||||
# `preferred` — this is the resolution Sunshine streams.
|
||||
#
|
||||
# 2160p is near the ceiling: virtio-gpu has no NVENC/VAAPI, so Sunshine falls
|
||||
# back to x264, which measured 73fps here against 135fps at 1440p — a 60fps
|
||||
# client has only ~20% margin.
|
||||
wayland.windowManager.hyprland.extraConfig = ''
|
||||
hl.monitor({ output = "", mode = "2560x1440@60", position = "0x0", scale = 1 })
|
||||
hl.monitor({ output = "", mode = "3840x2160@60", position = "0x0", scale = 1 })
|
||||
'';
|
||||
}
|
||||
|
||||
+29
-2
@@ -15,6 +15,8 @@ let
|
||||
toolPath = "%h/.local/bin:%h/.nix-profile/bin:/etc/profiles/per-user/naps62/bin:/run/current-system/sw/bin";
|
||||
|
||||
sem = inputs.sem.packages.${pkgs.system}.default;
|
||||
|
||||
aoe = inputs.agent-of-empires.packages.${pkgs.system}.aoe-with-web;
|
||||
in
|
||||
{
|
||||
home.packages = [
|
||||
@@ -27,6 +29,10 @@ in
|
||||
Unit = {
|
||||
Description = "rev — always-on local code review server";
|
||||
After = [ "network.target" ];
|
||||
# MUST stay 0: at RestartSec=2 a fast-crashing rev burns the default
|
||||
# 5-starts-per-10s budget, and systemd parks the unit in `failed` until
|
||||
# a manual `systemctl --user reset-failed`.
|
||||
StartLimitIntervalSec = 0;
|
||||
};
|
||||
Service = {
|
||||
Type = "simple";
|
||||
@@ -51,6 +57,8 @@ in
|
||||
Unit = {
|
||||
Description = "rev-deploy — Gitea webhook listener that deploys rev on push to main";
|
||||
After = [ "network.target" ];
|
||||
# Same restart-budget trap as `rev` above.
|
||||
StartLimitIntervalSec = 0;
|
||||
};
|
||||
Service = {
|
||||
Type = "simple";
|
||||
@@ -64,6 +72,27 @@ in
|
||||
Install.WantedBy = [ "default.target" ];
|
||||
};
|
||||
|
||||
aoe-web = {
|
||||
Unit = {
|
||||
Description = "aoe serve — Agent of Empires web dashboard";
|
||||
After = [ "network.target" ];
|
||||
# Same restart-budget trap as `rev` above.
|
||||
StartLimitIntervalSec = 0;
|
||||
};
|
||||
Service = {
|
||||
Type = "simple";
|
||||
WorkingDirectory = "%h";
|
||||
# aoe refuses `--auth none` on a non-loopback bind unless --behind-proxy
|
||||
# is set; --allowed-host is what makes the rebinding gate accept a
|
||||
# hostname under a wildcard bind (an IP literal needs no flag).
|
||||
ExecStart = "${aoe}/bin/aoe serve --host 0.0.0.0 --port 8080 --auth none --behind-proxy --allowed-host aoe.n62.casa";
|
||||
Environment = [ "PATH=${toolPath}" ];
|
||||
Restart = "always";
|
||||
RestartSec = 2;
|
||||
};
|
||||
Install.WantedBy = [ "default.target" ];
|
||||
};
|
||||
|
||||
hourlog = {
|
||||
Unit = {
|
||||
Description = "Start the Friday hour log in a tmux session";
|
||||
@@ -119,6 +148,4 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,16 @@ _:
|
||||
# on the Ubuntu box.
|
||||
{
|
||||
programs.ssh.settings = {
|
||||
# gpg-agent serves as the ssh agent here, and signing blocks forever on a
|
||||
# pinentry prompt that has no TTY in headless/agent sessions — `ssh-add -l`
|
||||
# works, but git push hangs right after "Server accepts key". The key file
|
||||
# has no passphrase, so read it directly and skip the agent.
|
||||
"github.com" = {
|
||||
IdentityAgent = "none";
|
||||
IdentitiesOnly = true;
|
||||
IdentityFile = "~/.ssh/id_ed25519";
|
||||
};
|
||||
|
||||
"grafana" = {
|
||||
HostName = "10.6.10.30";
|
||||
User = "root";
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
../common/features/docker.nix
|
||||
../common/features/appimage.nix
|
||||
../common/features/fonts.nix
|
||||
../common/features/kdeconnect.nix
|
||||
../common/features/nix-ld.nix
|
||||
../common/features/bluetooth.nix
|
||||
../common/features/ledger.nix
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
# the module's own systemd.user.services.sunshine definition.
|
||||
systemd.user.services.sunshine.environment.LD_LIBRARY_PATH = "/run/opengl-driver/lib";
|
||||
|
||||
# hardware.uinput (pulled in by services.sunshine) makes /dev/uinput
|
||||
# root:uinput 0660 and adds nobody to the group, so sunshine's virtual mouse
|
||||
# and keyboard die with "Permission denied" and the stream takes no input.
|
||||
users.groups.uinput.members = [ "naps62" ];
|
||||
|
||||
# DualSense/DualShock emulation. Sunshine presents a *real* PS5 HID (touchpad,
|
||||
# motion, LEDs, rumble) via /dev/uhid, not plain uinput — a uinput pad can't
|
||||
# carry those features. The kernel uhid node is root-only (0600) with no udev
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
# Clipboard (and file) sync between hosts over the LAN. Moonlight/Sunshine
|
||||
# carry no clipboard channel at all, so this is the only path between the
|
||||
# streaming client and its host.
|
||||
programs.kdeconnect = {
|
||||
enable = true;
|
||||
package = pkgs.kdePackages.kdeconnect-kde;
|
||||
};
|
||||
}
|
||||
@@ -11,11 +11,16 @@ _:
|
||||
# moonlight shows a black, artefacted picture rather than any error.
|
||||
systemd.user.services.sunshine.serviceConfig.LimitNOFILE = 65536;
|
||||
|
||||
# hardware.uinput (pulled in by services.sunshine) makes /dev/uinput
|
||||
# root:uinput 0660 and adds nobody to the group, so sunshine's virtual mouse
|
||||
# and keyboard die with "Permission denied" and the stream takes no input.
|
||||
users.groups.uinput.members = [ "naps62" ];
|
||||
|
||||
services.sunshine = {
|
||||
enable = true;
|
||||
autoStart = true;
|
||||
openFirewall = true;
|
||||
capSysAdmin = false; # wlr-screencopy needs no CAP_SYS_ADMIN
|
||||
capSysAdmin = true; # kms capture reads the scanout framebuffer via DRM
|
||||
|
||||
settings = {
|
||||
# MUST be set. Auto-probe tries portalgrab first, and
|
||||
@@ -23,11 +28,17 @@ _:
|
||||
# probe then hangs forever instead of falling back, so sunshine never
|
||||
# binds its ports and the unit sits "active" doing nothing.
|
||||
#
|
||||
# "wlr", not "wlgrab": wlgrab is the name this backend logs under, but it
|
||||
# "kms", not "wlr": the wlr backend takes wlr-screencopy's dmabuf path,
|
||||
# which on this virtio-gpu returns empty buffers — sunshine encodes a flat
|
||||
# frame (~50-byte P-frames) and every client shows black, with no error on
|
||||
# either side. grim still works because it uses the shm path, and sunshine
|
||||
# exposes no way to force shm.
|
||||
#
|
||||
# "wlr", not "wlgrab": wlgrab is the name that backend logs under, but it
|
||||
# is not accepted as a value — sunshine rejects it with "Unable to
|
||||
# initialize capture method", then still starts and binds its ports with
|
||||
# no encoder, so the failure only shows up as a 503 in moonlight.
|
||||
capture = "wlr";
|
||||
capture = "kms";
|
||||
|
||||
# The web UI is only reachable over the network here — there is no local
|
||||
# browser — and sunshine CSRF-rejects any origin but localhost unless it
|
||||
@@ -37,6 +48,12 @@ _:
|
||||
# and never strips brackets or quotes, so "[...]" makes every entry fail
|
||||
# its starts_with("https://") check.
|
||||
csrf_allowed_origins = "https://10.7.250.1:47990,https://10.7.10.2:47990";
|
||||
|
||||
# At the default 20, sunshine's FEC mangles large frames and moonlight
|
||||
# draws horizontal bands over an otherwise-fine stream — no error on
|
||||
# either side, and the network shows 0% loss. konishi carries the same
|
||||
# workaround in its hand-written ~/.config/sunshine/sunshine.conf.
|
||||
fec_percentage = 1;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,6 +33,12 @@
|
||||
|
||||
services.dbus.packages = with pkgs; [ gcr ];
|
||||
|
||||
# NixOS provides /bin/sh but not /bin/bash. Some third-party scripts
|
||||
# hardcode #!/bin/bash, so provide it too.
|
||||
system.activationScripts.binbash = ''
|
||||
ln -sf ${pkgs.bash}/bin/bash /bin/bash
|
||||
'';
|
||||
|
||||
# Compressed RAM swap — better behaviour under memory pressure.
|
||||
zramSwap.enable = true;
|
||||
|
||||
|
||||
@@ -23,6 +23,14 @@
|
||||
# GC is handled by `nh clean` below (keep-N / keep-since semantics).
|
||||
# The two are mutually exclusive — nh asserts if nix.gc.automatic is on.
|
||||
gc.automatic = false;
|
||||
|
||||
# Unauthenticated GitHub API calls (flake input fetches) are capped at
|
||||
# 60/hr and 429 quickly. The token itself must not land in /nix/store
|
||||
# (world-readable), so it lives in a plain file outside Nix's management —
|
||||
# !include reads it at nix.conf parse time instead of embedding it.
|
||||
extraOptions = ''
|
||||
!include /etc/nix/github-token.conf
|
||||
'';
|
||||
};
|
||||
|
||||
# nh: ergonomic nixos-rebuild wrapper. Auto-detects the target from the
|
||||
@@ -30,7 +38,9 @@
|
||||
# no host arg. Runs as root, so its `clean` prunes system + user profiles.
|
||||
programs.nh = {
|
||||
enable = true;
|
||||
flake = "/home/naps62/projects/nixos-config";
|
||||
# mkDefault: hosts whose clone lives elsewhere (yolo, under ~/tea) override
|
||||
# this with a plain assignment.
|
||||
flake = lib.mkDefault "/home/naps62/projects/nixos-config";
|
||||
clean = {
|
||||
enable = true;
|
||||
extraArgs = "--keep 10 --keep-since 7d";
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
../common/features/pipewire.nix
|
||||
../common/features/docker.nix
|
||||
../common/features/fonts.nix
|
||||
../common/features/kdeconnect.nix
|
||||
../common/features/nix-ld.nix
|
||||
../common/features/bluetooth.nix
|
||||
../common/features/ledger.nix
|
||||
|
||||
+32
-36
@@ -17,7 +17,8 @@ qm create 132 --name yolo \
|
||||
--vga virtio-gl \
|
||||
--serial0 socket \
|
||||
--agent 1 \
|
||||
--onboot 1
|
||||
--onboot 1 \
|
||||
--args '-display egl-headless,gl=core,rendernode=/dev/dri/renderD128'
|
||||
```
|
||||
|
||||
After creating the disk, drop its ZFS reservation — the `data` pool has no
|
||||
@@ -27,12 +28,15 @@ After creating the disk, drop its ZFS reservation — the `data` pool has no
|
||||
zfs set refreservation=none data/vm-132-disk-1
|
||||
```
|
||||
|
||||
## The three that matter
|
||||
## The four that matter
|
||||
|
||||
**`--vga virtio-gl`.** Not for speed — the host's virglrenderer falls back to
|
||||
llvmpipe either way. It is what lets mesa allocate GBM buffers. With plain
|
||||
`virtio` every capture fails with `Failed to create GBM buffer`, and sunshine
|
||||
streams a black picture while reporting no error at all.
|
||||
**`--vga virtio-gl`.** Lets mesa allocate GBM buffers. With plain `virtio`
|
||||
every capture fails with `Failed to create GBM buffer`, and sunshine streams a
|
||||
black picture while reporting no error at all.
|
||||
|
||||
**`--args '-display egl-headless,...,rendernode=/dev/dri/renderD128'`.** Pins
|
||||
host-side rendering to the UHD 770. Without it the guest renders on llvmpipe —
|
||||
see below.
|
||||
|
||||
**`--serial0 socket`.** `virtio-gl` renders through a GL context with no QEMU
|
||||
console surface, so the Proxmox noVNC console and `qm screendump` both go dark.
|
||||
@@ -42,44 +46,36 @@ This plus `boot.kernelParams = [ "console=ttyS0,115200" ]` keeps
|
||||
**`--bios ovmf`.** `hosts/common/features/user.nix` uses systemd-boot, which
|
||||
needs UEFI. SeaBIOS gives an unbootable disk.
|
||||
|
||||
## Known limit: no GPU acceleration
|
||||
## Why the render node has to be pinned
|
||||
|
||||
The guest reports `virgl (LLVMPIPE)`, so compositing and capture readback are
|
||||
both on CPU. Hyprland delivers ~2fps at 2560x1440, sunshine asks for 60, and
|
||||
moonlight disconnects. Capture and encoding are otherwise fine — x264 manages
|
||||
1440p at 3x realtime here.
|
||||
pve 8.4.1 hardcodes `-display egl-headless,gl=core` in
|
||||
`/usr/share/perl5/PVE/QemuServer.pm` with no `rendernode=`. qemu then picks a
|
||||
node itself by scanning `/dev/dri` in `readdir` order and taking the first
|
||||
`renderD*` that opens. Here that lands on renderD129 — the nvidia card, which
|
||||
is reserved for LXCs. mesa cannot drive it, so virglrenderer falls back to
|
||||
software and the guest reports `virgl (LLVMPIPE)`: Hyprland manages ~2fps at
|
||||
2560x1440 while sunshine asks for 60, and moonlight disconnects.
|
||||
|
||||
The host can do better: `iris_dri.so` is present and `i915` is loaded on
|
||||
renderD128 (the UHD 770; renderD129 is the nvidia card, reserved for LXCs and
|
||||
not available to VMs). Two things stop it being used:
|
||||
|
||||
- pve 8.4.1 hardcodes `-display egl-headless,gl=core` in
|
||||
`/usr/share/perl5/PVE/QemuServer.pm` with no `rendernode=`, so qemu picks the
|
||||
EGL device itself.
|
||||
- `/usr/share/glvnd/egl_vendor.d/` lists `10_nvidia.json` ahead of
|
||||
`50_mesa.json`, and lower wins — so EGL initialises against nvidia and falls
|
||||
back to llvmpipe.
|
||||
|
||||
To test the diagnosis without changing anything, start the VM with the mesa
|
||||
vendor forced:
|
||||
The `--args` line overrides this. PVE appends `args` after its own `-display`,
|
||||
and qemu's last `-display` wins, so rendering is pinned to renderD128 (the UHD
|
||||
770, `i915`) without touching the packaged perl — which any pve-manager upgrade
|
||||
would revert. Confirm from the guest:
|
||||
|
||||
```
|
||||
__EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.json qm start 132
|
||||
grep "Renderer:" /run/user/1000/hypr/*/hyprland.log
|
||||
```
|
||||
|
||||
If the guest then reports `iris` rather than `LLVMPIPE`, persist it with a
|
||||
drop-in rather than by editing the packaged perl — drop-ins survive upgrades,
|
||||
and the nvidia LXCs are unaffected because they start via pve-container:
|
||||
`virgl (Mesa Intel(R) Graphics (RPL-S))` is correct; `LLVMPIPE` means the
|
||||
override is not taking. On the host, `ls -l /proc/$(cat
|
||||
/var/run/qemu-server/132.pid)/fd | grep dri` shows which node qemu actually
|
||||
holds.
|
||||
|
||||
```
|
||||
systemctl edit pvedaemon
|
||||
[Service]
|
||||
Environment=__EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.json
|
||||
```
|
||||
Forcing the EGL vendor instead (`__EGL_VENDOR_LIBRARY_FILENAMES=.../50_mesa.json`)
|
||||
does not help and was tried: the vendor was never the problem, the node was.
|
||||
|
||||
Last resort only, if EGL still picks wrong: append
|
||||
`,rendernode=/dev/dri/renderD128` to the QemuServer.pm line above. That edits a
|
||||
packaged file and is reverted by any pve-manager upgrade.
|
||||
Encoding stays on x264. virgl accelerates GL only — the guest sees a
|
||||
virtio-gpu, not the Intel device, so there is no VAAPI/QuickSync encode path.
|
||||
Not a bottleneck: x264 manages 1440p at 3x realtime here.
|
||||
|
||||
## Network
|
||||
|
||||
|
||||
+34
-32
@@ -14,6 +14,7 @@
|
||||
../common/features/remote-desktop.nix
|
||||
../common/features/docker.nix
|
||||
../common/features/fonts.nix
|
||||
../common/features/kdeconnect.nix
|
||||
../common/features/nix-ld.nix
|
||||
../common/features/appimage.nix
|
||||
../common/features/home
|
||||
@@ -24,11 +25,6 @@
|
||||
# screen and reports no error.
|
||||
networking.hostName = "yolo";
|
||||
|
||||
# Lets the Proxmox host drive clean shutdowns/reboots and report the guest's
|
||||
# IP. The qemu-guest profile in hardware-configuration.nix only sets up the
|
||||
# virtio drivers; the agent itself is a separate service.
|
||||
services.qemuGuest.enable = true;
|
||||
|
||||
# Hung shutdown waiting on systemd-zram-setup@zram0: deactivating zram swap
|
||||
# has to fault every stored page back into RAM first. Redundant here anyway —
|
||||
# this VM has a real 8G swap partition, and the host manages its own memory.
|
||||
@@ -54,35 +50,41 @@
|
||||
environment.systemPackages = [ pkgs.git ];
|
||||
|
||||
# This clone lives under ~/tea, not the ~/projects path global/nix.nix assumes.
|
||||
programs.nh.flake = lib.mkForce "/home/naps62/tea/nixos-config";
|
||||
programs.nh.flake = "/home/naps62/tea/nixos-config";
|
||||
|
||||
# No physical seat: Sunshine is a user service and cannot capture until a
|
||||
# graphical session exists, so a cold boot must reach one unattended. No
|
||||
# hyprlock on start (unlike konishi) — nobody could type the password in.
|
||||
services.displayManager = {
|
||||
autoLogin = {
|
||||
enable = true;
|
||||
user = "naps62";
|
||||
services = {
|
||||
# Lets the Proxmox host drive clean shutdowns/reboots and report the guest's
|
||||
# IP. The qemu-guest profile in hardware-configuration.nix only sets up the
|
||||
# virtio drivers; the agent itself is a separate service.
|
||||
qemuGuest.enable = true;
|
||||
|
||||
# No physical seat: Sunshine is a user service and cannot capture until a
|
||||
# graphical session exists, so a cold boot must reach one unattended. No
|
||||
# hyprlock on start (unlike konishi) — nobody could type the password in.
|
||||
displayManager = {
|
||||
autoLogin = {
|
||||
enable = true;
|
||||
user = "naps62";
|
||||
};
|
||||
defaultSession = "hyprland";
|
||||
};
|
||||
defaultSession = "hyprland";
|
||||
};
|
||||
|
||||
# A Proxmox guest has no emulated sound card, so PipeWire starts with no sink
|
||||
# and Sunshine has nothing to capture — the stream is silent. This sink is the
|
||||
# only one, so it wins the default and Sunshine records its monitor.
|
||||
services.pipewire.extraConfig.pipewire."10-null-sink" = {
|
||||
"context.objects" = [
|
||||
{
|
||||
factory = "adapter";
|
||||
args = {
|
||||
"factory.name" = "support.null-audio-sink";
|
||||
"node.name" = "sunshine-sink";
|
||||
"node.description" = "Sunshine";
|
||||
"media.class" = "Audio/Sink";
|
||||
"audio.position" = "FL,FR";
|
||||
};
|
||||
}
|
||||
];
|
||||
# A Proxmox guest has no emulated sound card, so PipeWire starts with no
|
||||
# sink and Sunshine has nothing to capture — the stream is silent. This sink
|
||||
# is the only one, so it wins the default and Sunshine records its monitor.
|
||||
pipewire.extraConfig.pipewire."10-null-sink" = {
|
||||
"context.objects" = [
|
||||
{
|
||||
factory = "adapter";
|
||||
args = {
|
||||
"factory.name" = "support.null-audio-sink";
|
||||
"node.name" = "sunshine-sink";
|
||||
"node.description" = "Sunshine";
|
||||
"media.class" = "Audio/Sink";
|
||||
"audio.position" = "FL,FR";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
lib,
|
||||
stdenvNoCC,
|
||||
fetchurl,
|
||||
autoPatchelfHook,
|
||||
makeWrapper,
|
||||
stdenv,
|
||||
}:
|
||||
let
|
||||
version = "0.84.2";
|
||||
|
||||
# Prebuilt Bun executables from the release page. Bump version + hashes from
|
||||
# the SHA256SUMS asset:
|
||||
# https://github.com/earendil-works/pi/releases
|
||||
sources = {
|
||||
x86_64-linux = {
|
||||
asset = "pi-linux-x64.tar.gz";
|
||||
hash = "sha256-kG++eH/SJcSsYk/n69Wx1Vpg4PXH71F5XSMVZPnuHBM=";
|
||||
};
|
||||
aarch64-linux = {
|
||||
asset = "pi-linux-arm64.tar.gz";
|
||||
hash = "sha256-0VNy2p5LTF/vn9Fb7XbX9fFyDdOf583g7GLltlrWPvE=";
|
||||
};
|
||||
};
|
||||
|
||||
source =
|
||||
sources.${stdenvNoCC.hostPlatform.system}
|
||||
or (throw "pi: unsupported system ${stdenvNoCC.hostPlatform.system}");
|
||||
in
|
||||
stdenvNoCC.mkDerivation {
|
||||
pname = "pi";
|
||||
inherit version;
|
||||
|
||||
src = fetchurl {
|
||||
url = "https://github.com/earendil-works/pi/releases/download/v${version}/${source.asset}";
|
||||
inherit (source) hash;
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
autoPatchelfHook
|
||||
makeWrapper
|
||||
];
|
||||
|
||||
# libgcc, for the bundled native clipboard module.
|
||||
buildInputs = [ (lib.getLib stdenv.cc.cc) ];
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
# The binary resolves its themes, bundled node_modules and wasm relative to
|
||||
# itself, so the whole tree ships together and only `pi` gets a wrapper.
|
||||
mkdir -p $out/share/pi
|
||||
cp -r . $out/share/pi/
|
||||
chmod +x $out/share/pi/pi
|
||||
|
||||
# Nix owns updates here; `pi update --self` would write into the store.
|
||||
makeWrapper $out/share/pi/pi $out/bin/pi \
|
||||
--set-default PI_SKIP_VERSION_CHECK 1
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
meta = {
|
||||
description = "Pi coding agent CLI";
|
||||
homepage = "https://pi.dev";
|
||||
changelog = "https://github.com/earendil-works/pi/releases/tag/v${version}";
|
||||
license = lib.licenses.mit;
|
||||
sourceProvenance = [ lib.sourceTypes.binaryNativeCode ];
|
||||
platforms = lib.attrNames sources;
|
||||
mainProgram = "pi";
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user