diff --git a/hosts/nb/configuration.nix b/hosts/nb/configuration.nix index 57ba254..da643ef 100644 --- a/hosts/nb/configuration.nix +++ b/hosts/nb/configuration.nix @@ -5,9 +5,10 @@ { config, lib, pkgs, ... }: let impermanence = builtins.fetchTarball "https://github.com/nix-community/impermanence/archive/master.tar.gz"; -in { +in +{ nixpkgs.config.allowUnfree = true; - nixpkgs.config.allowBroken = true; + nixpkgs.config.allowBroken = true; security.pki.certificates = [ "/home/dominik/.local/share/mkcert/rootCA.pem" ]; @@ -30,6 +31,7 @@ in { ./modules/ollama.nix ./modules/qdrant.nix ./modules/battery-brightness.nix + ./modules/charge-control.nix ./modules/suspend-fixes.nix ./cachix.nix @@ -71,9 +73,9 @@ in { programs.zsh = { enable = true; ohMyZsh = { - enable = true; # Enable Oh My Zsh - theme = "steeef"; # Set theme - plugins = [ "git" ]; # Add plugins + enable = true; # Enable Oh My Zsh + theme = "steeef"; # Set theme + plugins = [ "git" ]; # Add plugins }; interactiveShellInit = '' # Bind Shift+Return to insert newline (foot terminal sends \e[27;2;13~) @@ -90,10 +92,10 @@ in { # Disable irqbalance to save battery (not critical for laptop workloads) services.irqbalance.enable = false; - swapDevices = [ { + swapDevices = [{ device = "/swap/swapfile"; - size = 96 * 1024; # Size is in megabytes (96GB for full hibernation with 92GB RAM) - } ]; + size = 96 * 1024; # Size is in megabytes (96GB for full hibernation with 92GB RAM) + }]; # Memory tuning for 92GB RAM boot.kernel.sysctl = { @@ -102,8 +104,8 @@ in { "vm.dirty_background_ratio" = 5; "vm.vfs_cache_pressure" = 50; # Battery optimization - increase dirty writeback time to batch writes - "vm.dirty_writeback_centisecs" = 3000; # 30 seconds (default: 500 = 5s) - "vm.dirty_expire_centisecs" = 3000; # 30 seconds (default: 3000) + "vm.dirty_writeback_centisecs" = 3000; # 30 seconds (default: 500 = 5s) + "vm.dirty_expire_centisecs" = 3000; # 30 seconds (default: 3000) # Enable laptop mode for disk power management (2 = balanced, less aggressive than 5) "vm.laptop_mode" = 2; }; @@ -131,7 +133,7 @@ in { }; hardware.bluetooth.enable = true; - hardware.bluetooth.powerOnBoot = false; # Save battery - enable manually when needed + hardware.bluetooth.powerOnBoot = false; # Save battery - enable manually when needed hardware.bluetooth.settings = { General = { ControllerMode = "bredr"; }; }; @@ -171,12 +173,12 @@ in { sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ]; sops.defaultSopsFile = ./secrets.yaml; - sops.secrets.epicenter_vpn_ca = {}; - sops.secrets.epicenter_vpn_cert = {}; - sops.secrets.epicenter_vpn_key = {}; - sops.secrets.wg_private_key = {}; - sops.secrets.wg_preshared_key = {}; - sops.secrets.wg-cloonar-key = {}; + sops.secrets.epicenter_vpn_ca = { }; + sops.secrets.epicenter_vpn_cert = { }; + sops.secrets.epicenter_vpn_key = { }; + sops.secrets.wg_private_key = { }; + sops.secrets.wg_preshared_key = { }; + sops.secrets.wg-cloonar-key = { }; virtualisation.docker.enable = true; services.flatpak.enable = true; @@ -189,7 +191,7 @@ in { }; networking.hostName = "nb-01"; # Define your hostname. - networking.networkmanager.enable = true; # Easiest to use and most distros use this by default. + networking.networkmanager.enable = true; # Easiest to use and most distros use this by default. networking.extraHosts = '' 77.119.230.30 vpn.cloonar.com 23.88.38.1 api.ebs.amz.at @@ -206,7 +208,7 @@ in { extraGroups = [ "wheel" "disk" "video" "audio" "mysql" "docker" "vboxusers" "networkmanager" "onepassword" "onepassword-cli" "dialout" ]; # Enable ‘sudo’ for the user. }; - users.groups.dominik = {}; + users.groups.dominik = { }; environment.systemPackages = with pkgs; [ alsa-utils @@ -219,7 +221,7 @@ in { TERMINAL_COMMAND = "alacritty"; MOZ_ENABLE_WAYLAND = "1"; }; - + services.blueman.enable = true; security.rtkit.enable = true; @@ -241,21 +243,21 @@ in { wants = [ "graphical-session.target" ]; after = [ "graphical-session.target" ]; serviceConfig = { - Type = "simple"; - ExecStart = "${pkgs.polkit_gnome}/libexec/polkit-gnome-authentication-agent-1"; - Restart = "on-failure"; - RestartSec = 1; - TimeoutStopSec = 10; - }; + Type = "simple"; + ExecStart = "${pkgs.polkit_gnome}/libexec/polkit-gnome-authentication-agent-1"; + Restart = "on-failure"; + RestartSec = 1; + TimeoutStopSec = 10; + }; }; }; # pgp services.pcscd.enable = true; programs.gnupg.agent = { - enable = true; - enableSSHSupport = true; - pinentryPackage = pkgs.pinentry-curses; + enable = true; + enableSSHSupport = true; + pinentryPackage = pkgs.pinentry-curses; }; nix = { diff --git a/hosts/nb/modules/charge-control.nix b/hosts/nb/modules/charge-control.nix new file mode 100644 index 0000000..f9045f3 --- /dev/null +++ b/hosts/nb/modules/charge-control.nix @@ -0,0 +1,91 @@ +{ pkgs, ... }: +let + charge-limit-apply = pkgs.writeShellScriptBin "charge-limit-apply" '' + if [ "$1" != "80" ] && [ "$1" != "100" ]; then + echo "Usage: charge-limit-apply <80|100>" >&2 + exit 1 + fi + ${pkgs.fw-ectool}/bin/ectool fwchargelimit "$1" + echo "$1" > /run/fw-charge-limit + chmod 644 /run/fw-charge-limit + ''; + + charge-limit-menu = pkgs.writeShellScriptBin "charge-limit-menu" '' + choice=$(printf "80%%\n100%%" | ${pkgs.wofi}/bin/wofi --dmenu --prompt "Charge limit") + case "$choice" in + "80%") /run/wrappers/bin/sudo ${charge-limit-apply}/bin/charge-limit-apply 80 ;; + "100%") /run/wrappers/bin/sudo ${charge-limit-apply}/bin/charge-limit-apply 100 ;; + esac + ''; + + waybar-battery = pkgs.writeShellScriptBin "waybar-battery" '' + charging_icons=("󰢜" "󰂆" "󰂇" "󰂈" "󰢝" "󰂉" "󰢞" "󰂊" "󰂋" "󰂅") + default_icons=("󰁺" "󰁻" "󰁼" "󰁽" "󰁾" "󰁿" "󰂀" "󰂁" "󰂂" "󰁹") + + bat_path=$(echo /sys/class/power_supply/BAT*) + capacity=$(cat "$bat_path/capacity" 2>/dev/null || echo 0) + status=$(cat "$bat_path/status" 2>/dev/null || echo "Unknown") + limit=$(cat /run/fw-charge-limit 2>/dev/null || echo 80) + + # Select icon based on capacity (10 icons, index 0-9) + idx=$(( capacity / 11 )) + if [ "$idx" -gt 9 ]; then idx=9; fi + + if [ "$status" = "Charging" ]; then + icon="''${charging_icons[$idx]}" + else + icon="''${default_icons[$idx]}" + fi + + # Determine CSS class + class="" + if [ "$capacity" -le 15 ]; then + class="critical" + elif [ "$capacity" -le 30 ]; then + class="warning" + elif [ "$capacity" -ge 95 ]; then + class="good" + fi + + tooltip="Battery: ''${capacity}% [''${status}]\nCharge limit: ''${limit}%" + + ${pkgs.jq}/bin/jq -cn \ + --arg text "$icon ''${capacity}% (''${limit}%)" \ + --arg tooltip "$tooltip" \ + --arg class "$class" \ + '{text: $text, tooltip: $tooltip, class: $class}' + ''; +in +{ + environment.systemPackages = [ + pkgs.fw-ectool + charge-limit-apply + charge-limit-menu + waybar-battery + ]; + + security.sudo.extraRules = [ + { + users = [ "dominik" ]; + commands = [ + { + command = "${charge-limit-apply}/bin/charge-limit-apply"; + options = [ "NOPASSWD" ]; + } + ]; + } + ]; + + systemd.services.fw-charge-limit = { + description = "Set Framework charge limit on boot"; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${pkgs.writeShellScript "fw-charge-limit-init" '' + ${pkgs.fw-ectool}/bin/ectool fwchargelimit 80 + echo 80 > /run/fw-charge-limit + chmod 644 /run/fw-charge-limit + ''}"; + }; + }; +} diff --git a/hosts/nb/modules/desktop/default.nix b/hosts/nb/modules/desktop/default.nix index 4661440..f91fef5 100644 --- a/hosts/nb/modules/desktop/default.nix +++ b/hosts/nb/modules/desktop/default.nix @@ -13,9 +13,10 @@ let sessionsFontSize = 24; usersFontSize = 32; }; - variants = ["qt5"]; + variants = [ "qt5" ]; }; -in { +in +{ imports = [ ../sway/sway.nix ../sway/launcher-cleanup.nix @@ -115,7 +116,7 @@ in { programs.light.enable = true; hardware.bluetooth.enable = true; - hardware.bluetooth.powerOnBoot = false; # Save battery - enable manually when needed + hardware.bluetooth.powerOnBoot = false; # Save battery - enable manually when needed hardware.bluetooth.settings = { General = { ControllerMode = "bredr"; }; }; @@ -162,7 +163,7 @@ in { USB_EXCLUDE_PHONE = 0; USB_EXCLUDE_PRINTER = 1; USB_EXCLUDE_WWAN = 0; - USB_EXCLUDE_INPUT = 1; # Exclude keyboard/touchpad to prevent suspend issues + USB_EXCLUDE_INPUT = 1; # Exclude keyboard/touchpad to prevent suspend issues # Audio power saving SOUND_POWER_SAVE_ON_AC = 0; @@ -173,9 +174,6 @@ in { DISK_IDLE_SECS_ON_AC = 0; DISK_IDLE_SECS_ON_BAT = 2; - # Battery charge thresholds (Framework 13 recommendation) - START_CHARGE_THRESH_BAT0 = 60; - STOP_CHARGE_THRESH_BAT0 = 80; }; boot.plymouth = { diff --git a/hosts/nb/modules/sway/waybar.conf b/hosts/nb/modules/sway/waybar.conf index 4f370a4..ea22302 100644 --- a/hosts/nb/modules/sway/waybar.conf +++ b/hosts/nb/modules/sway/waybar.conf @@ -33,7 +33,7 @@ "cpu", "custom/left-arrow-light", "custom/left-arrow-dark", - "battery", + "custom/battery", "custom/left-arrow-light", "custom/left-arrow-dark", "disk", @@ -109,39 +109,11 @@ "interval": 5, "format": " {usage:2}%" }, - "battery": { - "states": { - "good": 95, - "warning": 30, - "critical": 15 - }, - "format": "{icon} {capacity}%", - "format-icons": { - "charging": [ - "󰢜", - "󰂆", - "󰂇", - "󰂈", - "󰢝", - "󰂉", - "󰢞", - "󰂊", - "󰂋", - "󰂅" - ], - "default": [ - "󰁺", - "󰁻", - "󰁼", - "󰁽", - "󰁾", - "󰁿", - "󰂀", - "󰂁", - "󰂂", - "󰁹" - ] - }, + "custom/battery": { + "exec": "waybar-battery", + "return-type": "json", + "interval": 10, + "on-click": "charge-limit-menu" }, "disk": { "interval": 5, diff --git a/hosts/nb/modules/sway/waybar.css b/hosts/nb/modules/sway/waybar.css index a72a625..490ae18 100644 --- a/hosts/nb/modules/sway/waybar.css +++ b/hosts/nb/modules/sway/waybar.css @@ -28,7 +28,7 @@ window#waybar { #pulseaudio, #memory, #cpu, -#battery, +#custom-battery, #disk, #tray { background: #252525; @@ -62,7 +62,7 @@ window#waybar { #cpu { color: #bd93f9; } -#battery { +#custom-battery { color: #f1fa8c; } #disk { @@ -77,7 +77,7 @@ window#waybar { #pulseaudio, #memory, #cpu, -#battery, +#custom-battery, #disk { padding: 0 10px; padding-left: 5px;