617 lines
18 KiB
HolyC
617 lines
18 KiB
HolyC
{ config, pkgs, ... }:
|
|
|
|
let
|
|
greetdApodDir = "/var/lib/greetd/apod";
|
|
greetdApodCurrent = "${greetdApodDir}/current";
|
|
swaySession = pkgs.writeTextDir "share/wayland-sessions/sway.desktop" ''
|
|
[Desktop Entry]
|
|
Name=Sway
|
|
Comment=An i3-compatible Wayland compositor
|
|
Exec=${config.programs.sway.package}/bin/sway
|
|
Type=Application
|
|
DesktopNames=sway
|
|
'';
|
|
regreetPasswordPrompt = pkgs.regreet.overrideAttrs (oldAttrs: {
|
|
postPatch = (oldAttrs.postPatch or "") + ''
|
|
substituteInPlace src/gui/model.rs \
|
|
--replace-fail $' } else {\n let username = if let Some(username) = self.get_current_username() {' \
|
|
$' } else if self.sys_util.get_sessions().len() == 1 {\n let (session, sess_info) = self.sys_util.get_sessions().iter().next().expect("one session");\n info!("No session selected; using only available session: {session}");\n (Some(session.to_string()), Some(sess_info.clone()))\n } else {\n let username = if let Some(username) = self.get_current_username() {'
|
|
|
|
substituteInPlace src/gui/component.rs \
|
|
--replace-fail $' // Set the default behaviour of pressing the Return key to act like the login button.\n root.set_default_widget(Some(&widgets.ui.login_button));\n\n AsyncComponentParts { model, widgets }' \
|
|
$' // Set the default behaviour of pressing the Return key to act like the login button.\n root.set_default_widget(Some(&widgets.ui.login_button));\n\n // Immediately start authentication so the password entry appears and receives focus.\n sender.input(Self::Input::Login {\n input: String::new(),\n info: UserSessInfo::extract(\n &widgets.ui.usernames_box,\n &widgets.ui.username_entry,\n &widgets.ui.sessions_box,\n &widgets.ui.session_entry,\n ),\n });\n\n AsyncComponentParts { model, widgets }'
|
|
'';
|
|
});
|
|
regreetState = pkgs.writeText "regreet-state.toml" ''
|
|
last_user = "jet"
|
|
|
|
[user_to_last_sess]
|
|
jet = "Sway"
|
|
'';
|
|
fetchGreetdApod = pkgs.writeShellApplication {
|
|
name = "greetd-apod-wallpaper";
|
|
runtimeInputs = [
|
|
pkgs.coreutils
|
|
pkgs.curl
|
|
pkgs.jq
|
|
];
|
|
text = ''
|
|
set -euo pipefail
|
|
|
|
state_dir="${greetdApodDir}"
|
|
current_link="${greetdApodCurrent}"
|
|
user_current="/home/jet/.local/state/nasa-apod/current"
|
|
mkdir -p "$state_dir"
|
|
chmod 0755 "$state_dir"
|
|
|
|
install_current() {
|
|
local source="$1"
|
|
local target="$2"
|
|
|
|
if [ -s "$source" ]; then
|
|
cp --dereference --force "$source" "$target"
|
|
chmod 0644 "$target"
|
|
ln -sfn "$target" "$current_link"
|
|
fi
|
|
}
|
|
|
|
if [ ! -e "$current_link" ] && [ -e "$user_current" ]; then
|
|
install_current "$user_current" "$state_dir/bootstrap"
|
|
fi
|
|
|
|
read_api_key_file() {
|
|
local key_file="$1"
|
|
|
|
if [ -r "$key_file" ]; then
|
|
while IFS= read -r line; do
|
|
case "$line" in
|
|
NASA_API_KEY=*)
|
|
api_key="''${line#NASA_API_KEY=}"
|
|
;;
|
|
esac
|
|
done < "$key_file"
|
|
fi
|
|
}
|
|
|
|
api_key="''${NASA_API_KEY:-}"
|
|
if [ -z "$api_key" ]; then
|
|
read_api_key_file "''${NASA_API_KEY_FILE:-/home/jet/.config/nasa-api.env}"
|
|
fi
|
|
if [ -z "$api_key" ]; then
|
|
read_api_key_file /etc/nasa-api.env
|
|
fi
|
|
if [ -z "$api_key" ]; then
|
|
exit 0
|
|
fi
|
|
|
|
today="$(date +%F)"
|
|
for cached in "$state_dir/apod-$today".*; do
|
|
if [ -s "$cached" ]; then
|
|
ln -sfn "$cached" "$current_link"
|
|
exit 0
|
|
fi
|
|
done
|
|
|
|
api_curl_args=(
|
|
--fail
|
|
--silent
|
|
--show-error
|
|
--location
|
|
--connect-timeout 5
|
|
--max-time 20
|
|
)
|
|
|
|
image_curl_args=(
|
|
--fail
|
|
--silent
|
|
--show-error
|
|
--location
|
|
--retry 2
|
|
--retry-delay 5
|
|
--retry-max-time 120
|
|
--connect-timeout 10
|
|
--max-time 60
|
|
)
|
|
|
|
api_request="$(mktemp)"
|
|
trap 'rm -f "$api_request"' EXIT
|
|
{
|
|
printf '%s\n' 'url = "https://api.nasa.gov/planetary/apod"'
|
|
printf '%s\n' 'get'
|
|
printf 'data-urlencode = "api_key=%s"\n' "$api_key"
|
|
printf '%s\n' 'data-urlencode = "thumbs=True"'
|
|
} > "$api_request"
|
|
chmod 0600 "$api_request"
|
|
|
|
json="$(curl "''${api_curl_args[@]}" --config "$api_request" || true)"
|
|
if [ -z "$json" ]; then
|
|
exit 0
|
|
fi
|
|
|
|
media_type="$(printf '%s' "$json" | jq -r '.media_type // empty')"
|
|
case "$media_type" in
|
|
image)
|
|
image_url="$(printf '%s' "$json" | jq -r '.hdurl // .url // empty')"
|
|
;;
|
|
video)
|
|
image_url="$(printf '%s' "$json" | jq -r '.thumbnail_url // empty')"
|
|
;;
|
|
*)
|
|
exit 0
|
|
;;
|
|
esac
|
|
if [ -z "$image_url" ]; then
|
|
exit 0
|
|
fi
|
|
|
|
ext="''${image_url##*.}"
|
|
ext="''${ext%%\?*}"
|
|
case "$ext" in
|
|
jpg|jpeg|png|webp) ;;
|
|
*) ext="jpg" ;;
|
|
esac
|
|
|
|
date_stamp="$(printf '%s' "$json" | jq -r '.date // empty')"
|
|
if [ -z "$date_stamp" ]; then
|
|
date_stamp="$(date +%F)"
|
|
fi
|
|
|
|
target="$state_dir/apod-$date_stamp.$ext"
|
|
tmp="$target.tmp"
|
|
|
|
if [ ! -s "$target" ]; then
|
|
if curl "''${image_curl_args[@]}" "$image_url" -o "$tmp" && [ -s "$tmp" ]; then
|
|
mv "$tmp" "$target"
|
|
chmod 0644 "$target"
|
|
else
|
|
rm -f "$tmp"
|
|
fi
|
|
fi
|
|
|
|
if [ -e "$target" ]; then
|
|
ln -sfn "$target" "$current_link"
|
|
fi
|
|
'';
|
|
};
|
|
in
|
|
|
|
{
|
|
boot.loader.systemd-boot.enable = true;
|
|
boot.loader.systemd-boot.configurationLimit = 3;
|
|
boot.loader.efi.canTouchEfiVariables = true;
|
|
boot.loader.timeout = 1;
|
|
boot.loader.systemd-boot.consoleMode = "max";
|
|
boot.binfmt.emulatedSystems = [ "aarch64-linux" ];
|
|
|
|
# Ensure current wireless firmware is available.
|
|
hardware.enableRedistributableFirmware = true;
|
|
|
|
hardware.bluetooth = {
|
|
enable = true;
|
|
powerOnBoot = true;
|
|
settings = {
|
|
General = {
|
|
Experimental = true; # Show battery charge of Bluetooth devices
|
|
};
|
|
};
|
|
};
|
|
services.blueman.enable = true;
|
|
|
|
networking.networkmanager.enable = true;
|
|
networking.networkmanager.settings = {
|
|
connection = {
|
|
"wifi.powersave" = 2;
|
|
};
|
|
device = {
|
|
"wifi.scan-rand-mac-address" = false;
|
|
};
|
|
};
|
|
|
|
services.resolved.enable = true;
|
|
|
|
networking.firewall.enable = true;
|
|
# Required for Tailscale
|
|
networking.firewall.checkReversePath = "loose";
|
|
networking.firewall.interfaces."tailscale0".allowedTCPPorts = [ 4096 ];
|
|
|
|
services.tailscale = {
|
|
enable = true;
|
|
};
|
|
|
|
systemd.services.tailscale-set-operator = {
|
|
description = "Set Tailscale operator user";
|
|
after = [ "tailscaled.service" ];
|
|
requires = [ "tailscaled.service" ];
|
|
wantedBy = [ "multi-user.target" ];
|
|
serviceConfig.Type = "oneshot";
|
|
path = [ pkgs.tailscale ];
|
|
script = ''
|
|
tailscale set --operator=jet
|
|
'';
|
|
};
|
|
|
|
systemd.services.opencode-tailnet = {
|
|
description = "Expose OpenCode on the tailnet";
|
|
after = [
|
|
"network-online.target"
|
|
"tailscaled.service"
|
|
"tailscale-set-operator.service"
|
|
];
|
|
wants = [ "network-online.target" ];
|
|
requires = [
|
|
"tailscaled.service"
|
|
"tailscale-set-operator.service"
|
|
];
|
|
wantedBy = [ "multi-user.target" ];
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
User = "jet";
|
|
Restart = "always";
|
|
RestartSec = 5;
|
|
ExecStartPre = [
|
|
"${pkgs.tailscale}/bin/tailscale serve --bg 4096"
|
|
];
|
|
ExecStart = "/etc/profiles/per-user/jet/bin/opencode serve --hostname 127.0.0.1 --port 4096";
|
|
WorkingDirectory = config.users.users.jet.home;
|
|
};
|
|
};
|
|
|
|
time.timeZone = "America/Los_Angeles";
|
|
i18n.defaultLocale = "en_US.UTF-8";
|
|
|
|
# Framework Laptop 13 AMD AI 300 Series specific configurations
|
|
# Enable AMD GPU support and power management
|
|
hardware.graphics = {
|
|
enable = true;
|
|
enable32Bit = true;
|
|
};
|
|
|
|
# Enable keyd for key remapping
|
|
services.keyd = {
|
|
enable = true;
|
|
keyboards = {
|
|
default = {
|
|
ids = [ "*" ]; # Apply to all keyboards
|
|
settings = {
|
|
main = {
|
|
capslock = "esc";
|
|
esc = "capslock";
|
|
leftalt = "leftcontrol";
|
|
leftcontrol = "leftalt";
|
|
mute = "mute"; # ← Key 1: mute
|
|
volumedown = "playpause"; # ← Key 2: play/pause
|
|
volumeup = "volumedown"; # ← Key 3: vol down
|
|
previoussong = "volumeup"; # ← Key 4: vol up
|
|
playpause = "command(touch /tmp/keyd-f5-test)"; # ← Key 5: lock screen (testing)
|
|
nextsong = "noop"; # ← Key 6: disabled
|
|
brightnessdown = "noop"; # ← Key 7: disabled
|
|
brightnessup = "noop"; # ← Key 8: disabled
|
|
# Key 9: display toggle (leftmeta+p) - disabled below
|
|
rfkill = "brightnessdown"; # ← Key 10: brightness down
|
|
sysrq = "brightnessup"; # ← Key 11: brightness up
|
|
media = "sysrq"; # ← Key 12: screenshot
|
|
};
|
|
};
|
|
};
|
|
frameworkRadio = {
|
|
ids = [ "32ac:0006" ];
|
|
settings = {
|
|
main = {
|
|
brightnessdown = "noop"; # ← Key 7: disabled
|
|
brightnessup = "noop"; # ← Key 8: disabled
|
|
rfkill = "brightnessdown"; # ← Key 10: brightness down
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
# Prevent trackpad interference with keyd
|
|
environment.etc."libinput/local-overrides.quirks".text = ''
|
|
[Serial Keyboards]
|
|
MatchUdevType=keyboard
|
|
MatchName=keyd virtual keyboard
|
|
AttrKeyboardIntegration=internal
|
|
'';
|
|
|
|
# Codex currently probes the conventional FHS bubblewrap path.
|
|
systemd.tmpfiles.rules = [
|
|
"L+ /usr/bin/bwrap - - - - ${pkgs.bubblewrap}/bin/bwrap"
|
|
"d ${greetdApodDir} 0755 root root -"
|
|
];
|
|
|
|
# Set Ghostty as default terminal
|
|
xdg.terminal-exec = {
|
|
enable = true;
|
|
settings = {
|
|
default = [ "com.mitchellh.ghostty.desktop" ];
|
|
};
|
|
};
|
|
|
|
services.flatpak.enable = true;
|
|
|
|
virtualisation.docker.enable = true;
|
|
|
|
programs.sway = {
|
|
enable = true;
|
|
wrapperFeatures.gtk = true;
|
|
};
|
|
|
|
services.greetd = {
|
|
enable = true;
|
|
settings.default_session = {
|
|
command = "env GTK_USE_PORTAL=0 GDK_DEBUG=no-portals SESSION_DIRS=/run/current-system/sw/share/wayland-sessions XDG_DATA_DIRS=/run/current-system/sw/share ${pkgs.dbus}/bin/dbus-run-session ${pkgs.cage}/bin/cage -s -d -- ${config.programs.regreet.package}/bin/regreet";
|
|
user = "greeter";
|
|
};
|
|
};
|
|
|
|
programs.regreet = {
|
|
enable = true;
|
|
package = regreetPasswordPrompt;
|
|
font = {
|
|
package = pkgs.atkinson-hyperlegible-next;
|
|
name = "Atkinson Hyperlegible Next";
|
|
size = 16;
|
|
};
|
|
settings = {
|
|
background = {
|
|
path = greetdApodCurrent;
|
|
fit = "Cover";
|
|
};
|
|
GTK.application_prefer_dark_theme = true;
|
|
appearance.greeting_msg = "Welcome back";
|
|
widget.clock = {
|
|
format = "%a %b %d %I:%M %p";
|
|
resolution = "1s";
|
|
};
|
|
};
|
|
};
|
|
|
|
services.accounts-daemon.enable = true;
|
|
|
|
age = {
|
|
identityPaths = [ "/home/jet/.ssh/id_ed25519" ];
|
|
secrets.nasa-api-env = {
|
|
file = ./secrets/nasa-api.env.age;
|
|
owner = "jet";
|
|
group = "users";
|
|
mode = "0400";
|
|
};
|
|
};
|
|
|
|
system.activationScripts.regreetDefaultSession.text = ''
|
|
${pkgs.coreutils}/bin/install -D -m 0644 ${regreetState} /var/lib/regreet/state.toml
|
|
chown greeter:greeter /var/lib/regreet/state.toml
|
|
'';
|
|
|
|
fonts = {
|
|
packages = [
|
|
pkgs.atkinson-hyperlegible-next
|
|
pkgs.nerd-fonts.commit-mono
|
|
pkgs.nerd-fonts.symbols-only
|
|
pkgs.noto-fonts
|
|
pkgs.noto-fonts-cjk-sans
|
|
pkgs.noto-fonts-cjk-serif
|
|
pkgs.noto-fonts-color-emoji
|
|
pkgs.symbola
|
|
pkgs.unifont
|
|
pkgs.unifont_upper
|
|
];
|
|
|
|
fontconfig = {
|
|
allowBitmaps = false;
|
|
useEmbeddedBitmaps = false;
|
|
|
|
defaultFonts = {
|
|
sansSerif = [
|
|
"Atkinson Hyperlegible Next"
|
|
"Noto Sans"
|
|
"Noto Sans CJK JP"
|
|
"Noto Sans CJK SC"
|
|
"Noto Sans CJK TC"
|
|
"Noto Sans CJK HK"
|
|
"Noto Sans CJK KR"
|
|
"Symbols Nerd Font"
|
|
"Noto Color Emoji"
|
|
"Symbola"
|
|
"Unifont"
|
|
];
|
|
serif = [
|
|
"Noto Serif"
|
|
"Noto Serif CJK JP"
|
|
"Noto Serif CJK SC"
|
|
"Noto Serif CJK TC"
|
|
"Noto Serif CJK KR"
|
|
"Noto Color Emoji"
|
|
"Symbola"
|
|
"Unifont"
|
|
];
|
|
monospace = [
|
|
"CommitMono Nerd Font"
|
|
"Noto Sans Mono"
|
|
"Noto Sans Mono CJK JP"
|
|
"Symbols Nerd Font Mono"
|
|
"Noto Color Emoji"
|
|
"Unifont"
|
|
];
|
|
emoji = [ "Noto Color Emoji" ];
|
|
};
|
|
};
|
|
};
|
|
|
|
systemd.services.greetd-apod-wallpaper = {
|
|
description = "Fetch NASA APOD wallpaper for greetd";
|
|
after = [ "network-online.target" ];
|
|
wants = [ "network-online.target" ];
|
|
restartIfChanged = false;
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
ExecStart = "${fetchGreetdApod}/bin/greetd-apod-wallpaper";
|
|
EnvironmentFile = "-${config.age.secrets.nasa-api-env.path}";
|
|
TimeoutStartSec = "3min";
|
|
};
|
|
};
|
|
|
|
systemd.timers.greetd-apod-wallpaper = {
|
|
wantedBy = [ "timers.target" ];
|
|
timerConfig = {
|
|
OnActiveSec = "2m";
|
|
OnUnitActiveSec = "1h";
|
|
Persistent = false;
|
|
Unit = "greetd-apod-wallpaper.service";
|
|
};
|
|
};
|
|
|
|
xdg.portal = {
|
|
enable = true;
|
|
wlr.enable = true;
|
|
config.common.default = [
|
|
"wlr"
|
|
"gtk"
|
|
];
|
|
extraPortals = with pkgs; [ xdg-desktop-portal-gtk ];
|
|
};
|
|
|
|
programs.dconf.enable = true;
|
|
services.gvfs.enable = true;
|
|
services.udisks2.enable = true;
|
|
security.polkit.enable = true;
|
|
security.pam.services.swaylock = { };
|
|
|
|
services.printing.enable = true;
|
|
|
|
services.pulseaudio.enable = false;
|
|
security.rtkit.enable = true;
|
|
services.pipewire = {
|
|
enable = true;
|
|
alsa.enable = true;
|
|
alsa.support32Bit = true;
|
|
pulse.enable = true;
|
|
};
|
|
users.users.jet = {
|
|
isNormalUser = true;
|
|
description = "Jet";
|
|
extraGroups = [
|
|
"networkmanager"
|
|
"wheel"
|
|
"video"
|
|
"render"
|
|
"docker"
|
|
];
|
|
};
|
|
|
|
nixpkgs.config.allowUnfree = true;
|
|
nix.settings = {
|
|
experimental-features = [
|
|
"nix-command"
|
|
"flakes"
|
|
];
|
|
trusted-users = [
|
|
"root"
|
|
"jet"
|
|
];
|
|
max-jobs = "auto";
|
|
cores = 0;
|
|
build-users-group = "nixbld";
|
|
};
|
|
nix.gc = {
|
|
automatic = true;
|
|
dates = "daily";
|
|
options = "--delete-older-than 7d";
|
|
};
|
|
nix.optimise.automatic = true;
|
|
|
|
# Framework-specific services
|
|
# Enable fwupd for BIOS updates (distributed through LVFS)
|
|
services.fwupd.enable = true;
|
|
|
|
# Enable periodic TRIM for NVMe/SSD health
|
|
services.fstrim.enable = true;
|
|
services.irqbalance.enable = true;
|
|
services.earlyoom.enable = true;
|
|
|
|
# Power management for laptop
|
|
services.logind = {
|
|
settings = {
|
|
Login = {
|
|
HandleLidSwitch = "suspend";
|
|
HandleLidSwitchExternalPower = "suspend";
|
|
HandleLidSwitchDocked = "ignore";
|
|
HandlePowerKey = "suspend";
|
|
};
|
|
};
|
|
};
|
|
|
|
# Enable auto-cpufreq for intelligent power management (replaces TLP)
|
|
services.auto-cpufreq.enable = true;
|
|
services.auto-cpufreq.settings = {
|
|
battery = {
|
|
governor = "powersave";
|
|
turbo = "never";
|
|
};
|
|
charger = {
|
|
governor = "performance";
|
|
turbo = "auto";
|
|
};
|
|
};
|
|
|
|
# Disable power-profiles-daemon (conflicts with auto-cpufreq)
|
|
services.power-profiles-daemon.enable = false;
|
|
|
|
# Enable power management (governor managed dynamically by auto-cpufreq)
|
|
powerManagement.enable = true;
|
|
|
|
# v4l2loopback for OBS Virtual Camera
|
|
boot.extraModulePackages = with config.boot.kernelPackages; [ v4l2loopback ];
|
|
boot.kernelModules = [ "v4l2loopback" ];
|
|
boot.extraModprobeConfig = ''
|
|
options cfg80211 ieee80211_regdom=US
|
|
options v4l2loopback devices=1 video_nr=1 card_label="OBS Virtual Camera" exclusive_caps=1
|
|
'';
|
|
|
|
# RAM optimizations for 96GB system
|
|
boot.kernel.sysctl = {
|
|
"vm.vfs_cache_pressure" = 50; # Keep more filesystem cache in RAM
|
|
"vm.dirty_ratio" = 15; # Write to disk when 15% of RAM is dirty
|
|
"vm.dirty_background_ratio" = 5; # Start writing when 5% dirty
|
|
"kernel.nmi_watchdog" = 0;
|
|
"net.core.default_qdisc" = "fq";
|
|
"net.ipv4.tcp_congestion_control" = "bbr";
|
|
};
|
|
|
|
environment.systemPackages = with pkgs; [
|
|
bubblewrap
|
|
docker
|
|
docker-compose
|
|
flatpak
|
|
swaySession
|
|
wget
|
|
nh
|
|
];
|
|
environment.pathsToLink = [ "/share/wayland-sessions" ];
|
|
|
|
programs.steam.enable = true;
|
|
programs.nix-index-database.comma.enable = true;
|
|
|
|
# https://wiki.nixos.org/wiki/Appimage
|
|
programs.appimage = {
|
|
enable = true;
|
|
binfmt = true;
|
|
};
|
|
|
|
# GameCube adapter udev rules for Slippi/Dolphin
|
|
# Disable USB autosuspend for Framework's problematic devices (fingerprint reader, USB-C hub)
|
|
services.udev.extraRules = ''
|
|
# GameCube adapter USB device (vendor ID 057e, product ID 0337)
|
|
SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="0337", MODE="0666"
|
|
# GameCube adapter HID device (needed for Dolphin to access controllers)
|
|
KERNEL=="hidraw*", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="0337", MODE="0666", GROUP="input"
|
|
# Disable autosuspend for Framework fingerprint reader
|
|
ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="27a6", ATTR{power/autosuspend}="-1"
|
|
# Disable autosuspend for Framework USB-C hub controllers
|
|
ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="32ac", ATTR{power/autosuspend}="-1"
|
|
'';
|
|
|
|
system.stateVersion = "25.05";
|
|
|
|
}
|