ThinkPad T14 G2 AMD -> P14s G6 AMD

This commit is contained in:
Sergei Poljanski 2026-05-07 17:55:07 +03:00
commit 5fbe4e306f
Signed by: asxpi
GPG key ID: 4F8851660FA4121B
13 changed files with 226 additions and 62 deletions

2
.gitignore vendored
View file

@ -1 +1,3 @@
modules/usb-devices.nix modules/usb-devices.nix
secrets/
.sops.yaml

51
flake.lock generated
View file

@ -1,5 +1,26 @@
{ {
"nodes": { "nodes": {
"anotherim": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1777518264,
"narHash": "sha256-60eHsvcyBcpIbpHNMaMnoc1Nz164Z1+JvMxb78IBSZc=",
"ref": "dev",
"rev": "2d25b219ca4f2b9252b22b5aaa2f73de422e9caa",
"revCount": 1547,
"type": "git",
"url": "https://dev.narayana.im/anotherim/anotherim-desktop.git"
},
"original": {
"ref": "dev",
"type": "git",
"url": "https://dev.narayana.im/anotherim/anotherim-desktop.git"
}
},
"crane": { "crane": {
"locked": { "locked": {
"lastModified": 1765145449, "lastModified": 1765145449,
@ -79,11 +100,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1771008912, "lastModified": 1776877367,
"narHash": "sha256-gf2AmWVTs8lEq7z/3ZAsgnZDhWIckkb+ZnAo5RzSxJg=", "narHash": "sha256-EHq1/OX139R1RvBzOJ0aMRT3xnWyqtHBRUBuO1gFzjI=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "a82ccc39b39b621151d6732718e3e250109076fa", "rev": "0726a0ecb6d4e08f6adced58726b95db924cef57",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -118,8 +139,10 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"anotherim": "anotherim",
"lanzaboote": "lanzaboote", "lanzaboote": "lanzaboote",
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs",
"sops-nix": "sops-nix"
} }
}, },
"rust-overlay": { "rust-overlay": {
@ -142,6 +165,26 @@
"repo": "rust-overlay", "repo": "rust-overlay",
"type": "github" "type": "github"
} }
},
"sops-nix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1777338324,
"narHash": "sha256-bc+ZZCmOTNq86/svGnw0tVpH7vJaLYvGLLKFYP08Q8E=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "8eaee5c45428b28b8c47a83e4c09dccec5f279b5",
"type": "github"
},
"original": {
"owner": "Mic92",
"repo": "sops-nix",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",

View file

@ -7,6 +7,14 @@
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
sops-nix = {
url = "github:Mic92/sops-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
anotherim = {
url = "git+https://dev.narayana.im/anotherim/anotherim-desktop.git?ref=dev";
inputs.nixpkgs.follows = "nixpkgs";
};
}; };
outputs = { self, nixpkgs, ... }@inputs: { outputs = { self, nixpkgs, ... }@inputs: {
@ -15,6 +23,7 @@
specialArgs = { inherit inputs; }; specialArgs = { inherit inputs; };
modules = [ modules = [
inputs.lanzaboote.nixosModules.lanzaboote inputs.lanzaboote.nixosModules.lanzaboote
inputs.sops-nix.nixosModules.sops
./configuration.nix ./configuration.nix
]; ];
}; };

View file

@ -1,4 +1,4 @@
# ThinkPad T14 G2(AMD) # ThinkPad P14s Gen 6 AMD (Ryzen AI 7 PRO 350, Strix Point)
# Do not modify this file! It was generated by nixos-generate-config # Do not modify this file! It was generated by nixos-generate-config
# and may be overwritten by future invocations. Please make changes # and may be overwritten by future invocations. Please make changes
@ -10,7 +10,7 @@
[ (modulesPath + "/installer/scan/not-detected.nix") [ (modulesPath + "/installer/scan/not-detected.nix")
]; ];
boot.initrd.availableKernelModules = [ "nvme" "xhci_pci" "usb_storage" "usbhid" "sd_mod" "rtsx_pci_sdmmc" ]; boot.initrd.availableKernelModules = [ "nvme" "xhci_pci" "thunderbolt" ];
boot.initrd.kernelModules = [ ]; boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-amd" ]; boot.kernelModules = [ "kvm-amd" ];
boot.extraModulePackages = [ ]; boot.extraModulePackages = [ ];
@ -40,7 +40,11 @@
options = [ "nofail" ]; options = [ "nofail" ];
}; };
swapDevices = [ ]; # Encrypted swapfile on LUKS root, sized for hibernate (RAM + headroom).
# NixOS auto-creates the file and computes resume offset on activation.
swapDevices = [
{ device = "/var/swapfile"; size = 59392; } # 58 GiB, RAM is 54 GiB
];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;

View file

@ -12,11 +12,12 @@
# Enable networking # Enable networking
networking.networkmanager.enable = true; networking.networkmanager.enable = true;
# Quectel EM120R-GL WWAN Modem Support # Quectel EM160R-GL WWAN Modem Support
# FCC unlock for Quectel EM120R-GL # FCC unlock for Quectel EM160R-GL (USB ID 1eac:100d)
# Reuses the EM120R-GL (1eac:1001) script — same AT command across the family.
networking.modemmanager.fccUnlockScripts = [ networking.modemmanager.fccUnlockScripts = [
{ {
id = "1eac:1001"; id = "1eac:100d";
path = "${pkgs.modemmanager}/share/ModemManager/fcc-unlock.available.d/1eac:1001"; path = "${pkgs.modemmanager}/share/ModemManager/fcc-unlock.available.d/1eac:1001";
} }
]; ];
@ -24,6 +25,15 @@
# Kernel modules for WWAN/MBIM modems # Kernel modules for WWAN/MBIM modems
boot.kernelModules = [ "cdc_mbim" "qmi_wwan" "cdc_wdm" "mhi" ]; boot.kernelModules = [ "cdc_mbim" "qmi_wwan" "cdc_wdm" "mhi" ];
# MediaTek MT7925 Wi-Fi: disable PCIe ASPM.
# Why: under high throughput (>~50 Mbit/s sustained) the PCIe link enters L1
# power-saving mid-flow and the firmware queue stalls, causing TX to decay
# to zero and the driver to deauth (reason=3, locally_generated=1).
# Verified live by reloading mt7925e with disable_aspm=1.
boot.extraModprobeConfig = ''
options mt7925e disable_aspm=1
'';
# udev rules for WWAN devices # udev rules for WWAN devices
services.udev.extraRules = '' services.udev.extraRules = ''
KERNEL=="wwan*mbim*", MODE="0660", GROUP="networkmanager" KERNEL=="wwan*mbim*", MODE="0660", GROUP="networkmanager"
@ -97,8 +107,10 @@
}; };
}; };
# IVPN service # IVPN service — installed but not auto-started at boot.
# Start manually: `sudo systemctl start ivpn-service`
services.ivpn.enable = true; services.ivpn.enable = true;
systemd.services.ivpn-service.wantedBy = lib.mkForce [ ];
# Open ports in the firewall. # Open ports in the firewall.
# networking.firewall.allowedTCPPorts = [ ... ]; # networking.firewall.allowedTCPPorts = [ ... ];

View file

@ -1,5 +1,5 @@
# System packages configuration # System packages configuration
{ config, pkgs, lib, ... }: { config, pkgs, lib, inputs, ... }:
{ {
# Allow unfree packages # Allow unfree packages
@ -64,6 +64,7 @@
# Desktop / GUI Apps # Desktop / GUI Apps
gnome-tweaks gnome-tweaks
obs-studio obs-studio
inputs.anotherim.packages.${pkgs.system}.default
# Development # Development
nodejs_20 nodejs_20
@ -106,6 +107,7 @@
libmbim libmbim
libqmi libqmi
modemmanager modemmanager
lpac # eSIM Local Profile Assistant
# Estonian ID / digital signature # Estonian ID / digital signature
qdigidoc qdigidoc
@ -123,5 +125,8 @@
ivpn-service ivpn-service
ivpn-ui ivpn-ui
xray xray
# GNOME extensions
gnomeExtensions.tlp-profile-switcher
]; ];
} }

View file

@ -1,9 +1,31 @@
# Power management — ThinkPad T14 Gen 2 (AMD) # Power management — ThinkPad P14s Gen 6 (AMD)
{ config, pkgs, lib, ... }: { config, pkgs, lib, ... }:
{ {
# AMD P-State driver (active mode for full EPP control) # AMD P-State driver (active mode for full EPP control)
boot.kernelParams = [ "amd_pstate=active" ]; # resume_offset = first physical block of /var/swapfile (for hibernate from
# an ext4 swapfile on LUKS). Recompute if the swapfile is recreated:
# sudo filefrag -v /var/swapfile | awk 'NR==4{gsub(/\.\./,""); print $4}'
boot.kernelParams = [ "amd_pstate=active" "resume_offset=1769472" ];
# Hibernate / resume from encrypted swapfile on LUKS root.
# P14s G6 AMD has no S3 (Modern Standby only); this enables
# suspend-then-hibernate so long sleeps drain to disk.
boot.resumeDevice = "/dev/mapper/luks-71c97ce7-dd29-4b07-8d2a-8cc3985a65bd";
# systemd-logind: when lid closes / suspend pressed, suspend first;
# if still asleep after the delay, hibernate.
services.logind.settings.Login = {
HandleLidSwitch = "suspend-then-hibernate";
HandleSuspendKey = "suspend-then-hibernate";
IdleAction = "suspend-then-hibernate";
IdleActionSec = "30min";
};
# Switch to hibernate after this long in s2idle.
systemd.sleep.settings.Sleep = {
HibernateDelaySec = "45min";
};
# CPU frequency scaling # CPU frequency scaling
powerManagement.cpuFreqGovernor = "powersave"; powerManagement.cpuFreqGovernor = "powersave";
@ -11,47 +33,93 @@
# Disable power-profiles-daemon (conflicts with TLP) # Disable power-profiles-daemon (conflicts with TLP)
services.power-profiles-daemon.enable = false; services.power-profiles-daemon.enable = false;
# TLP — main power management daemon # TLP — settings owned by the TLP Profile Switcher GNOME extension.
services.tlp = { # The extension does `pkexec cp ~/.tlp/<profile>.conf /etc/tlp.conf`, but
enable = true; # NixOS makes /etc/tlp.conf a read-only symlink into the Nix store. We
settings = { # replace the symlink with a writable copy on every activation, AND if
# CPU # one of the user's ~/.tlp/*.conf matches the previous /etc/tlp.conf
CPU_SCALING_GOVERNOR_ON_AC = "performance"; # (= the user picked it from the panel), restore that one instead of the
CPU_SCALING_GOVERNOR_ON_BAT = "powersave"; # NixOS stub. This way the chosen profile survives nixos-rebuild switch.
CPU_ENERGY_PERF_POLICY_ON_AC = "balance_performance"; services.tlp.enable = true;
CPU_ENERGY_PERF_POLICY_ON_BAT = "balance_power"; system.activationScripts.tlpWritable = lib.stringAfter [ "etc" ] ''
CPU_BOOST_ON_AC = 1; profilesDir=/home/asxpi/.tlp
CPU_BOOST_ON_BAT = 1; target=/etc/tlp.conf
PLATFORM_PROFILE_ON_AC = "balanced"; activeMarker=/var/lib/tlp-active-profile
PLATFORM_PROFILE_ON_BAT = "balanced";
# AMD P-State # If the current file matches one of the user's profiles, remember it
CPU_DRIVER_OPMODE_ON_AC = "active"; # before NixOS replaces the symlink target.
CPU_DRIVER_OPMODE_ON_BAT = "active"; if [ -f "$target" ] && [ ! -L "$target" ] && [ -d "$profilesDir" ]; then
currentSum=$(${pkgs.coreutils}/bin/sha256sum "$target" | ${pkgs.coreutils}/bin/cut -d' ' -f1)
for p in "$profilesDir"/*.conf; do
[ -f "$p" ] || continue
s=$(${pkgs.coreutils}/bin/sha256sum "$p" | ${pkgs.coreutils}/bin/cut -d' ' -f1)
if [ "$s" = "$currentSum" ]; then
${pkgs.coreutils}/bin/basename "$p" .conf > "$activeMarker"
break
fi
done
fi
# WiFi # Replace the read-only symlink with a writable real file.
WIFI_PWR_MGT_ON_AC = "off"; if [ -L "$target" ]; then
WIFI_PWR_MGT_ON_BAT = "on"; cp --remove-destination "$(readlink -f "$target")" "$target"
chmod 0644 "$target"
fi
# PCIe ASPM # If we previously remembered an active profile, restore it.
PCIE_ASPM_ON_AC = "default"; if [ -f "$activeMarker" ]; then
PCIE_ASPM_ON_BAT = "default"; active=$(cat "$activeMarker")
if [ -f "$profilesDir/$active.conf" ]; then
cp "$profilesDir/$active.conf" "$target"
chmod 0644 "$target"
fi
fi
'';
# Runtime PM # Hook the extension's cp: whenever /etc/tlp.conf changes via the panel,
RUNTIME_PM_ON_AC = "on"; # also update the marker. systemd.path watches the file mtime.
RUNTIME_PM_ON_BAT = "auto"; systemd.paths.tlp-active-profile-tracker = {
description = "Track which TLP profile is currently active";
# NVMe wantedBy = [ "multi-user.target" ];
NVME_RUNTIME_PM_ON_AC = "on"; pathConfig = {
NVME_RUNTIME_PM_ON_BAT = "auto"; PathChanged = "/etc/tlp.conf";
Unit = "tlp-active-profile-tracker.service";
# USB autosuspend };
USB_AUTOSUSPEND = 1; };
systemd.services.tlp-active-profile-tracker = {
description = "Update /var/lib/tlp-active-profile to match /etc/tlp.conf";
serviceConfig = {
Type = "oneshot";
ExecStart = pkgs.writeShellScript "tlp-track" ''
profilesDir=/home/asxpi/.tlp
target=/etc/tlp.conf
marker=/var/lib/tlp-active-profile
[ -f "$target" ] || exit 0
[ -d "$profilesDir" ] || exit 0
currentSum=$(${pkgs.coreutils}/bin/sha256sum "$target" | ${pkgs.coreutils}/bin/cut -d' ' -f1)
for p in "$profilesDir"/*.conf; do
[ -f "$p" ] || continue
s=$(${pkgs.coreutils}/bin/sha256sum "$p" | ${pkgs.coreutils}/bin/cut -d' ' -f1)
if [ "$s" = "$currentSum" ]; then
${pkgs.coreutils}/bin/basename "$p" .conf > "$marker"
exit 0
fi
done
'';
}; };
}; };
# Bluetooth off at boot # Bluetooth off at boot
hardware.bluetooth.powerOnBoot = true; hardware.bluetooth.powerOnBoot = true;
hardware.bluetooth.settings = {
General = {
ControllerMode = "dual";
FastConnectable = true;
JustWorksRepairing = "always";
Privacy = "device";
Experimental = true;
};
};
# Battery monitoring # Battery monitoring
services.upower.enable = true; services.upower.enable = true;

View file

@ -0,0 +1,6 @@
{ ... }:
{
# Stub. Real values kept locally via:
# git update-index --skip-worktree modules/private/ssh-hosts.nix
programs.ssh.extraConfig = "";
}

View file

@ -0,0 +1,6 @@
# Stub. Real values kept locally via:
# git update-index --skip-worktree modules/private/xray-routes.nix
{
xrayServer = "0.0.0.0";
routeExcludeAddress = [ ];
}

View file

@ -2,6 +2,8 @@
{ config, pkgs, lib, ... }: { config, pkgs, lib, ... }:
{ {
imports = [ ./private/ssh-hosts.nix ];
# GPG agent # GPG agent
programs.gnupg.agent = { programs.gnupg.agent = {
enable = true; enable = true;
@ -49,6 +51,8 @@
# Enable the OpenSSH daemon. # Enable the OpenSSH daemon.
# services.openssh.enable = true; # services.openssh.enable = true;
# SSH client extraConfig is provided by ./private/ssh-hosts.nix
# Enable fwupd service # Enable fwupd service
services.fwupd.enable = true; services.fwupd.enable = true;

View file

@ -21,7 +21,10 @@
]; ];
# Kernel Hardening # Kernel Hardening
security.protectKernelImage = true; # Disabled to allow hibernate (suspend-then-hibernate on P14s G6 AMD which has no S3).
# Hibernate image lives on LUKS-encrypted root, so the tamper window is the same as
# the LUKS threat boundary. Revisit if TPM-bound LUKS unlock gets set up.
security.protectKernelImage = false;
# Sysctl hardening # Sysctl hardening
boot.kernel.sysctl = { boot.kernel.sysctl = {

View file

@ -12,5 +12,9 @@
configFile = "/etc/wireguard/wg0.conf"; # TODO: Secret Manager. TODO: Add SSH, GPG as well to secret manager configFile = "/etc/wireguard/wg0.conf"; # TODO: Secret Manager. TODO: Add SSH, GPG as well to secret manager
autostart = true; autostart = true;
}; };
wg2 = {
configFile = "/etc/wireguard/wg2.conf";
autostart = true;
};
}; };
} }

View file

@ -2,7 +2,8 @@
{ config, pkgs, lib, ... }: { config, pkgs, lib, ... }:
let let
xrayServer = "172.232.216.157"; private = import ./private/xray-routes.nix;
xrayServer = private.xrayServer;
in in
{ {
# Xray — VLESS+Reality with post-quantum encryption # Xray — VLESS+Reality with post-quantum encryption
@ -32,12 +33,11 @@ in
type = "tun"; type = "tun";
tag = "tun-in"; tag = "tun-in";
interface_name = "tun0"; interface_name = "tun0";
address = [ "198.18.0.1/15" ]; address = [ "198.18.0.1/15" "fdfe:dcba:9876::1/126" ];
auto_route = true; auto_route = true;
strict_route = true; strict_route = true;
route_exclude_address = [ "${xrayServer}/32" ]; route_exclude_address = [ "${xrayServer}/32" ] ++ private.routeExcludeAddress;
stack = "gvisor"; stack = "gvisor";
sniff = true;
}]; }];
outbounds = [ outbounds = [
@ -46,7 +46,7 @@ in
tag = "xray"; tag = "xray";
server = "127.0.0.1"; server = "127.0.0.1";
server_port = 10808; server_port = 10808;
udp_over_tcp = true; udp_over_tcp = false;
} }
{ {
type = "direct"; type = "direct";
@ -67,17 +67,15 @@ in
type = "local"; type = "local";
} }
]; ];
rules = [
{
outbound = [ "any" ];
server = "direct-dns";
}
];
}; };
route = { route = {
auto_detect_interface = true; auto_detect_interface = true;
default_domain_resolver = "doh-proxy";
rules = [ rules = [
{
action = "sniff";
}
{ {
protocol = "dns"; protocol = "dns";
action = "hijack-dns"; action = "hijack-dns";