Compare commits
6
Commits
2ea3058215
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d830eab1e | ||
|
|
9f371b6ab1 | ||
|
|
2666a84516 | ||
|
|
6d699a2cfa | ||
|
|
66714d0675 | ||
|
|
33956f2edf |
No files matched your search
@@ -12,6 +12,7 @@
|
||||
./modules/thinkpad.nix
|
||||
./modules/fingerprint.nix
|
||||
./secrets/secrets.nix
|
||||
./modules/bottles.nix
|
||||
|
||||
(import ../modules/common.nix {
|
||||
inherit lib;
|
||||
@@ -33,7 +34,6 @@
|
||||
|
||||
host.laptop = true;
|
||||
|
||||
programs.gamemode.enable = true;
|
||||
hardware.bluetooth.enable = true;
|
||||
services.printing.enable = true;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{pkgs-stable, ...}: {
|
||||
environment.systemPackages = with pkgs-stable; [
|
||||
bottles
|
||||
];
|
||||
}
|
||||
@@ -152,6 +152,79 @@
|
||||
done
|
||||
'';
|
||||
|
||||
systemctl = lib.getExe' pkgs.systemd "systemctl";
|
||||
busctl = lib.getExe' pkgs.systemd "busctl";
|
||||
timeout = lib.getExe' pkgs.coreutils "timeout";
|
||||
sleep = lib.getExe' pkgs.coreutils "sleep";
|
||||
fprintdList = lib.getExe' pkgs.fprintd "fprintd-list";
|
||||
|
||||
# python-validity initializes the sensor before it registers with
|
||||
# open-fprintd, so a successful ListEnrolledFingers call means the sensor is
|
||||
# usable. This is used as ExecStartPost and by greetd ordering.
|
||||
waitForFprint = pkgs.writeShellScript "wait-for-fprint" ''
|
||||
for ((i = 0; i < 120; i++)); do
|
||||
|
||||
devices="$(${timeout} 1 ${busctl} --system call \
|
||||
net.reactivated.Fprint \
|
||||
/net/reactivated/Fprint/Manager \
|
||||
net.reactivated.Fprint.Manager GetDevices 2>/dev/null || true)"
|
||||
|
||||
if [[ "$devices" =~ ^ao\ [1-9] ]]; then
|
||||
echo "wait-for-fprint: sensor initialized: $devices"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
${sleep} 0.5
|
||||
done
|
||||
|
||||
echo "wait-for-fprint: sensor initialization timeout" >&2
|
||||
exit 1
|
||||
'';
|
||||
|
||||
# The xHCI controller is reset during resume, which invalidates
|
||||
# python-validity's USB handle. Restart it from a systemd-sleep hook: the
|
||||
# hook runs before logind emits PrepareForSleep(false), so hyprlock's
|
||||
# built-in fingerprint auth sees a working sensor as soon as it resumes.
|
||||
fprintResumeHook = pkgs.writeShellScript "fprint-resume" ''
|
||||
if [ "$1" != "post" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Stop the old instance first so its stale D-Bus target cannot be selected
|
||||
# by the readiness checks below. Start without blocking because
|
||||
# python-validity's ExecStartPost waits for fprintd, but open-fprintd is
|
||||
# still suspended until Resume is called below.
|
||||
${timeout} 10 ${systemctl} stop python3-validity.service || true
|
||||
${systemctl} start --no-block python3-validity.service || true
|
||||
|
||||
# Clear open-fprintd's suspended flag. This also unblocks the new
|
||||
# python-validity instance's ExecStartPost.
|
||||
${timeout} 10 ${busctl} call net.reactivated.Fprint /net/reactivated/Fprint/Manager \
|
||||
net.reactivated.Fprint.Manager Resume || true
|
||||
|
||||
# Wait for the new backend to register.
|
||||
for ((i = 0; i < 30; i++)); do
|
||||
if ${timeout} 0.5 ${fprintdList} root >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
${sleep} 0.1
|
||||
done
|
||||
|
||||
# The target now exists, so Resume reinitializes the sensor. Wait for it
|
||||
# to become responsive before logind starts hyprlock's fingerprint auth.
|
||||
${timeout} 10 ${busctl} call net.reactivated.Fprint /net/reactivated/Fprint/Manager \
|
||||
net.reactivated.Fprint.Manager Resume || true
|
||||
|
||||
for ((i = 0; i < 30; i++)); do
|
||||
if ${timeout} 0.5 ${fprintdList} root >/dev/null 2>&1; then
|
||||
exit 0
|
||||
fi
|
||||
${sleep} 0.1
|
||||
done
|
||||
|
||||
echo "fprint-resume: fingerprint backend did not become ready" >&2
|
||||
'';
|
||||
|
||||
sleepTargets = [
|
||||
"suspend.target"
|
||||
"hibernate.target"
|
||||
@@ -175,9 +248,16 @@ in {
|
||||
polkit-1.fprintAuth = true;
|
||||
sudo.fprintAuth = true;
|
||||
login.fprintAuth = true;
|
||||
hyprlock.fprintAuth = true;
|
||||
|
||||
# hyprlock still needs a PAM service for password authentication, but it
|
||||
# must not run pam_fprintd: its own D-Bus fingerprint implementation is
|
||||
# used instead. Two implementations would fight over the device, and the
|
||||
# PAM one breaks when the sensor is reset across suspend.
|
||||
hyprlock = {};
|
||||
};
|
||||
|
||||
environment.etc."systemd/system-sleep/50-fprint-resume".source = fprintResumeHook;
|
||||
|
||||
systemd.services = {
|
||||
open-fprintd = {
|
||||
description = "Open FPrint daemon";
|
||||
@@ -198,6 +278,7 @@ in {
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStart = "${python-validity}/bin/python-validity-service";
|
||||
ExecStartPost = "-${waitForFprint}";
|
||||
Restart = "always";
|
||||
RestartSec = 2;
|
||||
RuntimeDirectory = "python-validity";
|
||||
@@ -215,16 +296,16 @@ in {
|
||||
ExecStart = "${pkgs.open-fprintd}/lib/open-fprintd/suspend.py";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
open-fprintd-resume = {
|
||||
description = "Resume open-fprintd backends";
|
||||
wantedBy = sleepTargets;
|
||||
after = sleepTargets;
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = "${pkgs.open-fprintd}/lib/open-fprintd/resume.py";
|
||||
};
|
||||
};
|
||||
# The sleep hook above resumes open-fprintd before logind wakes the
|
||||
# graphical session. The stock open-fprintd-resume unit runs later and
|
||||
# resets the sensor while hyprlock is already verifying, so it must not be
|
||||
# enabled. Order greetd (and therefore Hyprland/hyprlock) after the
|
||||
# fingerprint backend so the first lock screen can use the sensor.
|
||||
systemd.services.greetd = {
|
||||
after = [ "python3-validity.service" ];
|
||||
wants = [ "python3-validity.service" ];
|
||||
};
|
||||
|
||||
environment.systemPackages = [
|
||||
|
||||
+2
-1
@@ -5,7 +5,8 @@
|
||||
./user/sleepMode.nix
|
||||
./user/ghostty.nix
|
||||
./user/qt.nix
|
||||
./user/zed-idea.nix
|
||||
./user/helix.nix
|
||||
#./user/zed-idea.nix
|
||||
./user/walker.nix
|
||||
./user/btop.nix
|
||||
./user/zsh.nix
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
{ inputs, pkgs, lib, config, ... }: let
|
||||
# glibc >= 2.44 defines bsearch() as a C23 const-generic macro, which clashes
|
||||
# with the copy vendored by some tree-sitter grammars (tree-sitter-perl).
|
||||
# Drop the vendored implementation, same as nixpkgs does for upstream helix.
|
||||
patchedStdenv = pkgs.stdenv // {
|
||||
mkDerivation = args: pkgs.stdenv.mkDerivation (args // {
|
||||
postPatch = (args.postPatch or "") + ''
|
||||
if [ -e src/bsearch.c ]; then
|
||||
rm src/bsearch.c
|
||||
substituteInPlace src/tsp_unicode.h \
|
||||
--replace-fail '#include "bsearch.c"' ""
|
||||
fi
|
||||
'';
|
||||
});
|
||||
};
|
||||
|
||||
helixGrammars = pkgs.callPackage "${inputs.helix-steel}/grammars.nix" {
|
||||
stdenv = patchedStdenv;
|
||||
};
|
||||
|
||||
helixSteel =
|
||||
(
|
||||
pkgs.callPackage "${inputs.helix-steel}/default.nix" {
|
||||
gitRev = inputs.helix-steel.rev;
|
||||
|
||||
# The upstream flake builds the grammars with its own callPackage,
|
||||
# swap in the patched ones.
|
||||
callPackage = path: args:
|
||||
if toString path == toString "${inputs.helix-steel}/grammars.nix" then helixGrammars
|
||||
else pkgs.callPackage path args;
|
||||
}
|
||||
)
|
||||
.overrideAttrs (old: {
|
||||
cargoBuildFlags = (old.cargoBuildFlags or []) ++ [
|
||||
"--features"
|
||||
"steel,git"
|
||||
];
|
||||
});
|
||||
|
||||
comment-headers = pkgs.fetchurl {
|
||||
url = "https://github.com/codingvivi/comment-dividers.hx/raw/refs/heads/main/comment_headers.scm";
|
||||
name = "comment_headers.scm";
|
||||
hash = "sha256-ohgn6DFJl0rdRJuri6Frs/uUlUsPxMvPHDuw4FS3qEE=";
|
||||
};
|
||||
|
||||
patched-comment-headers = pkgs.runCommand "comment_headers.scm" { } ''
|
||||
install -m 0644 ${comment-headers} "$out"
|
||||
|
||||
${pkgs.python3}/bin/python3 - <<'PY'
|
||||
from pathlib import Path
|
||||
import os
|
||||
import sys
|
||||
|
||||
p = Path(os.environ["out"])
|
||||
s = p.read_text()
|
||||
|
||||
start_marker = "(define (get-block-delims)"
|
||||
end_marker = "(define (delete_till_first_nonwhitespace!)"
|
||||
|
||||
try:
|
||||
start = s.index(start_marker)
|
||||
end = s.index(end_marker, start)
|
||||
except ValueError as e:
|
||||
sys.exit(f"failed to find function markers: {e}")
|
||||
|
||||
new_get_block_delims = """(define (get-block-delims)
|
||||
(helix.static.open_above)
|
||||
(helix.static.insert_string "test_string")
|
||||
(helix.static.normal_mode)
|
||||
(helix.static.extend_to_line_bounds)
|
||||
(helix.static.trim_selections)
|
||||
(helix.static.toggle_comments)
|
||||
(let* ([commented-test-string (helix.static.current-highlighted-text!)]
|
||||
[strings-without-whitespace (split-whitespace commented-test-string)])
|
||||
(delete-line-noyank!)
|
||||
(cond
|
||||
[(and (> (length strings-without-whitespace) 1)
|
||||
(equal? (last strings-without-whitespace) "test_string"))
|
||||
(list (first strings-without-whitespace) "")]
|
||||
[(> (length strings-without-whitespace) 2)
|
||||
(list (first strings-without-whitespace) (last strings-without-whitespace))]
|
||||
[else
|
||||
(list "" "")])))
|
||||
|
||||
"""
|
||||
|
||||
s = s[:start] + new_get_block_delims + s[end:]
|
||||
|
||||
old = "(make-line line-length fill-pattern #f padding-pattern)"
|
||||
new = "(make-line line-length \"\" fill-pattern padding-pattern)"
|
||||
|
||||
if old not in s:
|
||||
sys.exit("failed to find insert-divider make-line call")
|
||||
|
||||
s = s.replace(old, new)
|
||||
|
||||
p.write_text(s)
|
||||
PY
|
||||
'';
|
||||
in {
|
||||
home.packages = with pkgs; [
|
||||
steel
|
||||
wakatime-cli
|
||||
];
|
||||
|
||||
# Wakapi is WakaTime-compatible, so the same plugin/client works.
|
||||
sops.templates.".wakatime.cfg" = {
|
||||
path = "${config.home.homeDirectory}/.wakatime.cfg";
|
||||
mode = "0600";
|
||||
content = ''
|
||||
[settings]
|
||||
api_url = https://coding.srcoder.moe/api
|
||||
api_key = ${config.sops.placeholder."tokens/apis/wakapi"}
|
||||
'';
|
||||
};
|
||||
|
||||
home.activation.installWakatimeHx = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
|
||||
export PATH="${pkgs.steel}/bin:$PATH"
|
||||
export STEEL_HOME="${config.xdg.dataHome}/steel"
|
||||
|
||||
if [ ! -f "$STEEL_HOME/cogs/wakatime/wakatime.scm" ]; then
|
||||
forge pkg install --git https://github.com/Xerxes-2/wakatime.hx.git
|
||||
fi
|
||||
'';
|
||||
|
||||
programs.helix = {
|
||||
enable = true;
|
||||
package = helixSteel;
|
||||
defaultEditor = true;
|
||||
|
||||
settings = {
|
||||
# theme = "catppuccin-mocha";
|
||||
editor = {
|
||||
insert-final-newline = false;
|
||||
indent-guides.render = true;
|
||||
|
||||
whitespace.render = {
|
||||
space = "all";
|
||||
tab = "all";
|
||||
nbsp = "none";
|
||||
nnbsp = "none";
|
||||
newline = "none";
|
||||
};
|
||||
|
||||
cursor-shape = {
|
||||
normal = "block";
|
||||
insert = "bar";
|
||||
select = "underline";
|
||||
};
|
||||
};
|
||||
|
||||
keys.normal = {
|
||||
space = {
|
||||
C-c = {
|
||||
h = ":comment-h1";
|
||||
j = ":comment-h2";
|
||||
k = ":comment-h3";
|
||||
d = ":comment-divider";
|
||||
};
|
||||
};
|
||||
|
||||
S-down = ["extend_to_line_bounds" "delete_selection" "paste_after"];
|
||||
S-up = ["extend_to_line_bounds" "delete_selection" "move_line_up" "paste_before"];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
xdg.configFile = {
|
||||
"helix/cogs/comment_headers.scm".source = patched-comment-headers;
|
||||
"helix/helix.scm".text = ''
|
||||
(require (prefix-in div. "cogs/comment_headers.scm"))
|
||||
|
||||
(provide comment-h1 comment-h2 comment-h3 comment-divider)
|
||||
|
||||
(define (comment-h1)
|
||||
(div.insert-h1!))
|
||||
|
||||
(define (comment-h2)
|
||||
(div.insert-h2!))
|
||||
|
||||
(define (comment-h3)
|
||||
(div.insert-h3!))
|
||||
|
||||
(define (comment-divider)
|
||||
(div.insert-divider! 80 "~"))
|
||||
'';
|
||||
"helix/init.scm".text = ''
|
||||
;; Steel init file
|
||||
(require "wakatime/wakatime.scm")
|
||||
'';
|
||||
};
|
||||
}
|
||||
@@ -4,5 +4,6 @@
|
||||
age.keyFile = "${config.home.homeDirectory}/.config/sops/age/keys.txt";
|
||||
|
||||
secrets."tokens/apis/wallhaven" = {};
|
||||
secrets."tokens/apis/wakapi" = {};
|
||||
};
|
||||
}
|
||||
@@ -1,8 +1,50 @@
|
||||
{ pkgs, lib, ... }:
|
||||
{ config, pkgs, lib, ... }:
|
||||
let
|
||||
hyprLua = import ./hyprland/lua.nix { inherit lib; };
|
||||
|
||||
hyprlock = lib.getExe pkgs.hyprlock;
|
||||
hyprctl = lib.getExe' config.wayland.windowManager.hyprland.package "hyprctl";
|
||||
loginctl = lib.getExe' pkgs.systemd "loginctl";
|
||||
pidof = lib.getExe' pkgs.procps "pidof";
|
||||
fprintdList = lib.getExe' pkgs.fprintd "fprintd-list";
|
||||
timeout = lib.getExe' pkgs.coreutils "timeout";
|
||||
sleepCmd = lib.getExe' pkgs.coreutils "sleep";
|
||||
sh = lib.getExe pkgs.bash;
|
||||
|
||||
hyprlockWait = pkgs.writeShellScript "hyprlock-wait-fprint" ''
|
||||
for i in $(seq 1 60); do
|
||||
if ${timeout} 1 ${fprintdList} kirill >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
${sleepCmd} 1
|
||||
done
|
||||
exec ${hyprlock}
|
||||
'';
|
||||
in {
|
||||
wayland.windowManager.hyprland.settings.on = [ (hyprLua.onStart [ "hyprlock" ]) ];
|
||||
wayland.windowManager.hyprland.settings.on = [ (hyprLua.onStart [ "${hyprlockWait}" ]) ];
|
||||
|
||||
# Hypridle listens to logind's PrepareForSleep signal: it locks the session
|
||||
# before suspend and makes sure hyprlock is running (and the display is on)
|
||||
# after resume.
|
||||
services.hypridle = {
|
||||
enable = true;
|
||||
settings = {
|
||||
general = {
|
||||
lock_cmd = "${pidof} hyprlock >/dev/null || ${hyprlockWait}";
|
||||
before_sleep_cmd = "${loginctl} lock-session";
|
||||
after_sleep_cmd = "${pidof} hyprlock >/dev/null || ${hyprlockWait} & ${hyprctl} dispatch 'hl.dsp.dpms({action = \"on\"})'";
|
||||
};
|
||||
|
||||
# Hypridle requires at least one listener, so keep a no-op timer that
|
||||
# practically never fires. Screen locking is driven by sleep/resume only.
|
||||
listener = [
|
||||
{
|
||||
timeout = 604800; # 7 days
|
||||
on-timeout = "";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
programs.hyprlock = let
|
||||
image = pkgs.fetchurl {
|
||||
@@ -14,6 +56,17 @@ in {
|
||||
enable = true;
|
||||
|
||||
settings = {
|
||||
# Hyprlock's built-in fingerprint support watches logind's
|
||||
# PrepareForSleep signal and re-arms itself after resume. PAM-based
|
||||
# fingerprint auth (security.pam.services.hyprlock.fprintAuth) does not
|
||||
# survive the fingerprint device being reset during suspend.
|
||||
auth.fingerprint = {
|
||||
enabled = true;
|
||||
ready_message = "Scan fingerprint to unlock";
|
||||
present_message = "Scanning...";
|
||||
retry_delay = 250;
|
||||
};
|
||||
|
||||
background = {
|
||||
path = lib.mkForce "${image}";
|
||||
blur_passes = 0;
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
tokens:
|
||||
apis:
|
||||
wallhaven: ENC[AES256_GCM,data:pW9QiCB8yM2xSZQG96qCKOGOJbSFFrFxu8s1tHfmzWw=,iv:sezKkABxqVtd3mK8+WxEDPd+GtpyBcUJVLDlj/D68nU=,tag:HWE2QZf66dZGjK21WcxtiA==,type:str]
|
||||
wallhaven: ENC[AES256_GCM,data:yO+thw4xgpMBuNx2LyxguDxAzoKSeDCyAAOcgtTxx74=,iv:j3LGKXnmykTamSuuIFbACT0kRDFazOhNEgWtVoxiWK4=,tag:jLaJrcUmDuxVj1D65NZhyg==,type:str]
|
||||
wakapi: ENC[AES256_GCM,data:B4BOqy6TyZj4qPB9H7Ne+XaW701tX+ho8dMn0EGvqzdVOKq+,iv:/P6KZV9KgIBXslB3yZD5G3XER279iJ6/R2n+ZdTaBLs=,tag:r/SDXjofl8DdDlzP9Xo5mQ==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUVVBNekFNbWNhSEx1d0c0
|
||||
NHRNWW9iR3FSMnZIMkRKQy9pTTIzV2R6VG1BClJ4clI5SzRCTnJhZ25JSEJ3dkFF
|
||||
QVE4VUt3YXBKZnRES3dXSjJxM2RJU0kKLS0tIExKci9iQWJ4VVdyU1dteVZXb21k
|
||||
S1VhaW5rQVkzUWMweHdNaThmblRYWWMKrhP0P4ftPNKfjss5JDa4SESSYXdop/U/
|
||||
uIfI1RPTXbReF124NIIEvJ6AsiQKDDCjpgT0m/1i/U3yIF1UejPiQg==
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1MXcwbkFxZ2R4V0ppOGo3
|
||||
UmFsTTJGekQ0NGVtTFpCRm50ekxJcEZUUW1FCkF5aStVckVNaVJndmptdnhtNjgx
|
||||
Z056TVF5ZVRuMWJZKzk1WlRZTXN3bjAKLS0tIEhHd1JyTkhIdjJuZVZtSFlTRXIy
|
||||
cTRWRkZXUmcrcHRXU2pyTzh1OGhaSkkKiqYLrPm1zS0PfrIeUnZHXvvHcY15PxDS
|
||||
01jUzubGavkuOXb0hJmr6oBkEkSMBW+GLewduqj0SxGF2yxTSkM0kQ==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age1tx6jdr6535xxeygpwkkgvdwc24tulr420cm7gk56xzfp0slzeqks2nwpyg
|
||||
lastmodified: "2026-10-17T13:36:46Z"
|
||||
mac: ENC[AES256_GCM,data:stlsKKQhQL6+utokuXvNZH1dlWfEeUa5nORN5eQnbNcXbo1MrFVz9wVDqVtpgZOc3/v8p4YaHXLhQl8/RpawAwQH03gvcZ5zt1aA+Z7K6yh9ONRSnwz2lUXwCbpaf0xYRIlzv7pSPBJdLfYSN3kaEwVKrItAsPxuHEI/WEh8M3Y=,iv:W1vLWvQ7b635MpbUC7m3ATBPa/MujvAji2/MN9kldq4=,tag:DlVg1LCVvjPOg4nRP3rjAA==,type:str]
|
||||
lastmodified: "2026-10-21T17:46:52Z"
|
||||
mac: ENC[AES256_GCM,data:mLdD78pb0Qf30+l/igb+jXMHxNXTXJPNtsi3cGeEgYLKxnx02OVBuOnncMZvN351yh7h5if9pCqwCJeOISWU8uFZrQdJeDjdmIPlP1eqzBQ7fK4fhEeRzAcn4dTQ5tcmu3uXmWElKcBYhMFEPr3/etyfDcX5ZFwOra8wUqU8nq4=,iv:TydKNjO5stupovtCF/rHMtr1Da2IFafMZiLQxsln1uA=,tag:kXl5eELy9I5APJkAQ8tk3w==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.13.3
|
||||
Reference in new issue
Block a user