diff --git a/configuration.nix b/configuration.nix index 5ebad2b..268b369 100644 --- a/configuration.nix +++ b/configuration.nix @@ -35,70 +35,6 @@ 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 local preferences"; - after = [ "tailscaled.service" ]; - requires = [ "tailscaled.service" ]; - wantedBy = [ "multi-user.target" ]; - serviceConfig.Type = "oneshot"; - serviceConfig.RemainAfterExit = true; - path = [ pkgs.tailscale ]; - script = '' - tailscale set --operator=jet - tailscale set --exit-node-allow-lan-access=true - ''; - }; - - 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" ]; - path = [ - pkgs.tailscale - pkgs.coreutils - pkgs.gnugrep - ]; - preStart = '' - for attempt in {1..60}; do - if tailscale status --json --peers=false | grep -q '"BackendState": *"Running"'; then - tailscale serve --bg 4096 - exit 0 - fi - - sleep 1 - done - - echo "Timed out waiting for Tailscale to reach Running state" - exit 1 - ''; - serviceConfig = { - Type = "simple"; - User = "jet"; - Environment = [ "OPENCODE_DB=opencode.db" ]; - Restart = "always"; - RestartSec = 5; - TimeoutStartSec = 75; - ExecStart = "/etc/profiles/per-user/jet/bin/o 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"; @@ -389,12 +325,9 @@ # Enable base suspend/resume hooks. powerManagement.enable = true; - # v4l2loopback for OBS Virtual Camera - boot.extraModulePackages = with config.boot.kernelPackages; [ v4l2loopback ]; - boot.kernelModules = [ "v4l2loopback" ]; + # Framework AMD laptops perform best with the US regulatory domain set explicitly. 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 @@ -420,7 +353,6 @@ wget ]; - programs.steam.enable = true; programs.nix-index-database.comma.enable = true; programs._1password.enable = true; @@ -435,14 +367,8 @@ 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 devices that have shown resume issues. ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="27c6", ATTR{idProduct}=="609c", ATTR{power/control}="on", ATTR{power/autosuspend}="-1" ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="32ac", ATTR{power/control}="on", ATTR{power/autosuspend}="-1" ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x1022", ATTR{class}=="0x0c0330", ATTR{power/control}="on" diff --git a/flake.nix b/flake.nix index d80ba98..60cbd51 100644 --- a/flake.nix +++ b/flake.nix @@ -105,11 +105,16 @@ sudo -v || exit $? nh os switch --hostname "$(${pkgs.hostname}/bin/hostname)" path:. "$@" ''; + nhb = pkgs.writeShellScriptBin "nhb" '' + sudo -v || exit $? + nh os boot --hostname "$(${pkgs.hostname}/bin/hostname)" path:. "$@" + ''; in pkgs.mkShell { packages = [ pkgs.nh inputs.agenix.packages.x86_64-linux.default + nhb nhs ]; }; diff --git a/home-modules/browser.nix b/home-modules/browser.nix index 5c60737..fbb424a 100644 --- a/home-modules/browser.nix +++ b/home-modules/browser.nix @@ -1,4 +1,9 @@ -{ lib, pkgs, ... }: +{ + hostname, + lib, + pkgs, + ... +}: let firefoxApplicationId = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}"; @@ -63,7 +68,7 @@ let }); in { - home.packages = [ torBrowser ]; + home.packages = lib.optionals (hostname == "framework") [ torBrowser ]; programs.zen-browser = { enable = true; @@ -179,13 +184,21 @@ in force = true; engines = { "Google Web" = { - urls = [ { template = "https://www.google.com/search?q={searchTerms}&udm=14&pws=0&filter=0&nfpr=1&hl=en&gl=US&safe=active"; } ]; + urls = [ + { + template = "https://www.google.com/search?q={searchTerms}&udm=14&pws=0&filter=0&nfpr=1&hl=en&gl=US&safe=active"; + } + ]; definedAliases = [ "@g" ]; }; "Google Basic" = { - urls = [ { template = "https://www.google.com/search?gbv=1&q={searchTerms}&udm=14&pws=0&filter=0&nfpr=1&hl=en&gl=US&safe=active"; } ]; + urls = [ + { + template = "https://www.google.com/search?gbv=1&q={searchTerms}&udm=14&pws=0&filter=0&nfpr=1&hl=en&gl=US&safe=active"; + } + ]; definedAliases = [ "@gb" "@gnj" diff --git a/home-modules/desktop.nix b/home-modules/desktop.nix index b154ed2..6dff423 100644 --- a/home-modules/desktop.nix +++ b/home-modules/desktop.nix @@ -18,7 +18,9 @@ let "-${osConfig.age.secrets."nasa-api-env".path}" else "-%h/.config/nasa-api.env"; - chatDesktopId = if hostname == "framework-work" then "slack.desktop" else "vesktop.desktop"; + isWork = hostname == "framework-work"; + isPersonal = hostname == "framework"; + chatDesktopId = if isWork then "slack.desktop" else "vesktop.desktop"; favoriteApps = [ "zen-beta.desktop" "com.mitchellh.ghostty.desktop" @@ -26,13 +28,13 @@ let "betterbird.desktop" ] ++ ( - if hostname == "framework-work" then - [ ] - else + if isPersonal then [ "signal.desktop" "zulip.desktop" ] + else + [ ] ); autoMoveApplications = [ "zen-beta.desktop:1" @@ -41,32 +43,38 @@ let "betterbird.desktop:4" ] ++ ( - if hostname == "framework-work" then - [ ] - else + if isPersonal then [ "signal.desktop:5" "zulip.desktop:6" ] + else + [ ] ); autostartEntries = [ "${homeLib.zenStartup}/share/applications/zen-startup.desktop" "${homeLib.ghosttyZellijStartup}/share/applications/ghostty-zellij-startup.desktop" ] ++ ( - if hostname == "framework-work" then + if isWork then [ "${pkgs.slack}/share/applications/slack.desktop" "${homeLib.betterbirdStartup}/share/applications/betterbird-startup.desktop" ] - else + else if isPersonal then [ "${homeLib.vesktopStartup}/share/applications/vesktop-startup.desktop" "${homeLib.betterbirdStartup}/share/applications/betterbird-startup.desktop" "${homeLib.signalStartup}/share/applications/signal-startup.desktop" "${homeLib.zulipStartup}/share/applications/zulip-startup.desktop" ] + else + [ ] ); + personalEnabledExtensions = pkgs.lib.optionals isPersonal [ + "tailscale-gnome-qs@tailscale-qs.github.io" + "evil-bit-toggle@jetpham.github.com" + ]; vlcDesktop = "vlc.desktop"; vlcVideoMimeTypes = [ "application/mxf" @@ -121,6 +129,18 @@ let "x-content/video-svcd" "x-content/video-vcd" ]; + personalMimeDefaults = + pkgs.lib.optionalAttrs isPersonal { + "x-content/image-dcf" = "net.damonlynch.RapidPhotoDownloader.desktop"; + } + // pkgs.lib.optionalAttrs isPersonal ( + builtins.listToAttrs ( + map (mimeType: { + name = mimeType; + value = vlcDesktop; + }) vlcVideoMimeTypes + ) + ); in { @@ -146,7 +166,7 @@ in autorun-never = false; autorun-x-content-ignore = [ ]; autorun-x-content-open-folder = [ ]; - autorun-x-content-start-app = [ "x-content/image-dcf" ]; + autorun-x-content-start-app = pkgs.lib.optionals isPersonal [ "x-content/image-dcf" ]; }; "org/gnome/settings-daemon/plugins/power" = { sleep-inactive-ac-type = "nothing"; @@ -183,14 +203,13 @@ in "wifiqrcode@glerro.pm.me" "system-monitor-next@paradoxxx.zero.gmail.com" "clipboard-indicator@tudmotu.com" - "tailscale-gnome-qs@tailscale-qs.github.io" "auto-move-windows@gnome-shell-extensions.gcampax.github.com" "gnome-shell-extension-maximized-by-default@stiggimy.github.com" "no-titlebar-when-maximized@alec.ninja" "opencode-token-usage@jetpham.github.com" - "evil-bit-toggle@jetpham.github.com" "reduced-motion-toggle@jetpham.github.com" - ]; + ] + ++ personalEnabledExtensions; favorite-apps = favoriteApps; }; "org/gnome/shell/extensions/auto-move-windows" = { @@ -305,7 +324,7 @@ in Install.WantedBy = [ "timers.target" ]; }; - xdg.desktopEntries."net.damonlynch.RapidPhotoDownloader" = { + xdg.desktopEntries."net.damonlynch.RapidPhotoDownloader" = pkgs.lib.mkIf isPersonal { name = "Rapid Photo Downloader"; genericName = "Photo Downloader"; comment = "Download, rename, and back up photos and videos from cameras and cards"; @@ -333,7 +352,6 @@ in "x-scheme-handler/unknown" = "zen-beta.desktop"; "x-scheme-handler/mailto" = "betterbird.desktop"; "inode/directory" = "org.gnome.Nautilus.desktop"; - "x-content/image-dcf" = "net.damonlynch.RapidPhotoDownloader.desktop"; "image/x-canon-cr2" = "gimp.desktop"; "application/zip" = "org.gnome.FileRoller.desktop"; "application/x-tar" = "org.gnome.FileRoller.desktop"; @@ -346,11 +364,6 @@ in "application/x-rar" = "org.gnome.FileRoller.desktop"; "application/x-rar-compressed" = "org.gnome.FileRoller.desktop"; } - // builtins.listToAttrs ( - map (mimeType: { - name = mimeType; - value = vlcDesktop; - }) vlcVideoMimeTypes - ); + // personalMimeDefaults; }; } diff --git a/home-modules/opencode.nix b/home-modules/opencode.nix index 0657ead..ea1c8da 100644 --- a/home-modules/opencode.nix +++ b/home-modules/opencode.nix @@ -1,4 +1,9 @@ -{ homeLib, pkgs, ... }: +{ + homeLib, + hostname, + pkgs, + ... +}: let chromeDevtoolsMcpShell = pkgs.runCommand "chrome-devtools-mcp-shell-path" { } '' @@ -41,12 +46,12 @@ in mcp.linear = { type = "remote"; url = "https://mcp.linear.app/mcp"; - enabled = true; + enabled = hostname == "framework-work"; }; mcp.heytea = { type = "remote"; url = "https://mcp.heytea.dev/mcp"; - enabled = true; + enabled = hostname == "framework"; }; mcp.cloudflare-api = { type = "remote"; diff --git a/home-modules/packages.nix b/home-modules/packages.nix index 0f24bd2..02c73f2 100644 --- a/home-modules/packages.nix +++ b/home-modules/packages.nix @@ -2,10 +2,14 @@ inputs, pkgs, homeLib, + hostname, ... }: let + isWork = hostname == "framework-work"; + isPersonal = hostname == "framework"; + evilBitCtl = pkgs.writeShellApplication { name = "evil-bitctl"; runtimeInputs = [ @@ -118,10 +122,8 @@ let runHook postInstall ''; }; -in -{ - home.packages = with pkgs; [ + sharedPackages = with pkgs; [ bat bun claude-code @@ -153,10 +155,8 @@ in typescript-language-server nil - element-desktop file-roller font-manager - foliate (gimp-with-plugins.override { plugins = with gimpPlugins; [ gmic @@ -166,26 +166,11 @@ in google-chrome handbrake inkscape - kdePackages.kdenlive libreoffice - logseq - nufraw-thumbnailer - obs-studio pavucontrol - prismlauncher qpwgraph - signal-desktop - slack - vesktop - vlc - zulip - linphone lmstudio homeLib.betterbird - darktable - digikam - exiftool - rapid-photo-downloader brightnessctl nautilus playerctl @@ -200,12 +185,43 @@ in gnomeExtensions.maximized-by-default-actually-reborn gnomeExtensions.no-titlebar-when-maximized gnomeExtensions.system-monitor-next - gnomeExtensions.tailscale-qs gnomeExtensions.wifi-qrcode - evilBitToggleExtension opencodeTokenUsageExtension reducedMotionToggleExtension nerd-fonts.commit-mono ]; + + workPackages = with pkgs; [ + slack + ]; + + personalPackages = with pkgs; [ + element-desktop + foliate + kdePackages.kdenlive + logseq + nufraw-thumbnailer + obs-studio + prismlauncher + signal-desktop + vesktop + vlc + zulip + linphone + darktable + digikam + exiftool + rapid-photo-downloader + + gnomeExtensions.tailscale-qs + evilBitToggleExtension + ]; +in + +{ + home.packages = + sharedPackages + ++ pkgs.lib.optionals isWork workPackages + ++ pkgs.lib.optionals isPersonal personalPackages; } diff --git a/home-modules/qbittorrent.nix b/home-modules/qbittorrent.nix index 3cc5c0f..f73a445 100644 --- a/home-modules/qbittorrent.nix +++ b/home-modules/qbittorrent.nix @@ -1,6 +1,14 @@ -{ config, pkgs, ... }: +{ + config, + hostname, + lib, + pkgs, + ... +}: let + isPersonal = hostname == "framework"; + configureQbittorrentTailscale = pkgs.writeShellApplication { name = "configure-qbittorrent-tailscale"; runtimeInputs = [ @@ -24,6 +32,7 @@ let postBuild = '' rm -f "$out/bin/qbittorrent" # Enforce qBittorrent's bind settings, then add a systemd interface allowlist. + # RestrictNetworkInterfaces works for transient services, not transient scopes. cat > "$out/bin/qbittorrent" <<'EOF' #!${pkgs.runtimeShell} set -eu @@ -37,7 +46,6 @@ let exec ${pkgs.systemd}/bin/systemd-run \ --user \ - --scope \ --quiet \ --collect \ --property='RestrictNetworkInterfaces=lo tailscale0' \ @@ -57,10 +65,27 @@ let }; in -{ +lib.mkIf isPersonal { home.activation.configureQbittorrentTailscale = config.lib.dag.entryAfter [ "writeBoundary" ] '' ${configureQbittorrentTailscale}/bin/configure-qbittorrent-tailscale ''; + home.file.".local/share/applications/org.qbittorrent.qBittorrent.desktop".text = '' + [Desktop Entry] + Categories=Network;FileTransfer;P2P;Qt; + Exec=${qbittorrentTailscale}/bin/qbittorrent %U + GenericName=BitTorrent client + Comment=Download and share files over BitTorrent + Icon=qbittorrent + MimeType=application/x-bittorrent;x-scheme-handler/magnet; + Name=qBittorrent + Terminal=false + Type=Application + StartupNotify=false + StartupWMClass=qbittorrent + Keywords=bittorrent;torrent;magnet;download;p2p; + SingleMainWindow=true + ''; + home.packages = [ qbittorrentTailscale ]; } diff --git a/home-modules/sway.nix b/home-modules/sway.nix index 8fc4039..94135f5 100644 --- a/home-modules/sway.nix +++ b/home-modules/sway.nix @@ -57,6 +57,8 @@ let "${pkgs.wl-clipboard}/bin/wl-paste --type image --watch ${pkgs.cliphist}/bin/cliphist store" "${pkgs.swayidle}/bin/swayidle -w timeout 300 '${lockCommand}' before-sleep '${lockCommand}'" ]; + isWork = hostname == "framework-work"; + isPersonal = hostname == "framework"; workStartup = [ "${config.programs.zen-browser.package}/bin/zen-beta" "${pkgs.ghostty}/bin/ghostty --fullscreen=true -e ${homeLib.zellijPersistentSession}/bin/zellij-persistent-session" @@ -71,7 +73,13 @@ let "${pkgs.signal-desktop}/bin/signal-desktop --start-fullscreen" "${pkgs.zulip}/bin/zulip --start-fullscreen" ]; - appStartup = if hostname == "framework-work" then workStartup else personalStartup; + appStartup = + if isWork then + workStartup + else if isPersonal then + personalStartup + else + [ ]; in { diff --git a/hosts/framework/default.nix b/hosts/framework/default.nix index 35aa004..ece1388 100644 --- a/hosts/framework/default.nix +++ b/hosts/framework/default.nix @@ -1,4 +1,4 @@ -{ ... }: +{ config, pkgs, ... }: { imports = [ @@ -8,6 +8,85 @@ networking.hostName = "framework"; + # Tailscale and tailnet exposure are personal-laptop only. + networking.firewall.checkReversePath = "loose"; + networking.firewall.interfaces."tailscale0".allowedTCPPorts = [ 4096 ]; + + services.tailscale.enable = true; + + systemd.services.tailscale-set-operator = { + description = "Set Tailscale local preferences"; + after = [ "tailscaled.service" ]; + requires = [ "tailscaled.service" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig.Type = "oneshot"; + serviceConfig.RemainAfterExit = true; + path = [ pkgs.tailscale ]; + script = '' + tailscale set --operator=jet + tailscale set --exit-node-allow-lan-access=true + ''; + }; + + 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" ]; + path = [ + pkgs.tailscale + pkgs.coreutils + pkgs.gnugrep + ]; + preStart = '' + for attempt in {1..60}; do + if tailscale status --json --peers=false | grep -q '"BackendState": *"Running"'; then + tailscale serve --bg 4096 + exit 0 + fi + + sleep 1 + done + + echo "Timed out waiting for Tailscale to reach Running state" + exit 1 + ''; + serviceConfig = { + Type = "simple"; + User = "jet"; + Environment = [ "OPENCODE_DB=opencode.db" ]; + Restart = "always"; + RestartSec = 5; + TimeoutStartSec = 75; + ExecStart = "/etc/profiles/per-user/jet/bin/o serve --hostname 127.0.0.1 --port 4096"; + WorkingDirectory = config.users.users.jet.home; + }; + }; + + programs.steam.enable = true; + + # Personal media/gaming hardware support. + boot.extraModulePackages = with config.boot.kernelPackages; [ v4l2loopback ]; + boot.kernelModules = [ "v4l2loopback" ]; + boot.extraModprobeConfig = '' + options v4l2loopback devices=1 video_nr=1 card_label="OBS Virtual Camera" exclusive_caps=1 + ''; + + 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" + ''; + fileSystems."/tmp" = { device = "tmpfs"; fsType = "tmpfs";