diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9645a59 --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +# nixos-rebuild runs as you and only uses sudo to activate: evaluation needs +# your SSH agent and config, because the ripdj input is fetched over SSH. +# The flake's hosts are named after the hostnames, so `--flake .` finds this one. + +HOSTS = nixbook nixpad tuxcomp + +switch: + nixos-rebuild switch --flake . --sudo --ask-sudo-password + +# build this host's system (./result) without activating it. +build: + nixos-rebuild build --flake . + +# evaluate every host. +check: + @for host in $(HOSTS); do \ + echo "$$host"; \ + nix eval --raw .#nixosConfigurations.$$host.config.system.build.toplevel.drvPath > /dev/null || exit 1; \ + done + +update: + nix flake update + +.PHONY: switch build check update diff --git a/flake.nix b/flake.nix new file mode 100755 index 0000000..b92ed8f --- /dev/null +++ b/flake.nix @@ -0,0 +1,46 @@ +{ + description = "Djai's nixos machines"; + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + nix-flatpak.url = "github:gmodena/nix-flatpak/?ref=latest"; + + home-manager = { + url = "github:nix-community/home-manager"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + nix4nvchad = { + url = "github:nix-community/nix4nvchad"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + sops-nix = { + url = "github:Mic92/sops-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = + { nixpkgs, ... }@inputs: + let + mkHost = + hostname: + nixpkgs.lib.nixosSystem { + specialArgs = { inherit inputs; }; + modules = [ + inputs.home-manager.nixosModules.default + inputs.nix-flatpak.nixosModules.nix-flatpak + inputs.sops-nix.nixosModules.sops + ./hosts/${hostname} + ]; + }; + in + { + # name configurations after hostnames so `nixos-rebuild --flake .` gets the right one. + nixosConfigurations = nixpkgs.lib.genAttrs [ "nixbook" "nixpad" "tuxcomp" ] mkHost; + + formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.nixfmt-tree.override { + settings.formatter.nixfmt.excludes = [ "hosts/*/hardware-configuration.nix" ]; + }; + }; +} diff --git a/hosts/nixbook/default.nix b/hosts/nixbook/default.nix new file mode 100644 index 0000000..6a25d11 --- /dev/null +++ b/hosts/nixbook/default.nix @@ -0,0 +1,47 @@ +{ config, pkgs, ... }: + +{ + imports = + [ + ./hardware-configuration.nix + ../../modules + ]; + + networking.hostName = "nixbook"; + system.stateVersion = "26.05"; + + boot.initrd.luks.devices."luks-082cc474-c00a-46c5-b140-4b7ccc2e6958".device = "/dev/disk/by-uuid/082cc474-c00a-46c5-b140-4b7ccc2e6958"; + + services.power-profiles-daemon.enable = true; + + hardware.bluetooth.powerOnBoot = false; + + users.users.djai.packages = with pkgs; [ + steam + ]; + + # BELOW IS OLD!! remove as we make more modular + # custom certificates + # TODO: move certs to secrets, make this a module not a host based thing + security.pki.certificateFiles = [ + ./certs/npm-caddy-private.crt + ./certs/npm-caddy-public.crt + ./certs/npm-caddy-shared.crt + ]; + + # TODO: move to secrets + # tunnel + networking.wg-quick.interfaces = { + npm = { + address = ["10.8.0.3/32"]; + privateKeyFile = "/root/wireguard-keys/npm"; + peers = [{ + publicKey = "YlQfDLqOET0RCTfbeRTDUicF/RA6dic/ThHLy25w4nQ="; + presharedKeyFile = "/root/wireguard-keys/npm_preshared"; + allowedIPs = ["10.9.11.0/24" "10.8.0.0/24"]; + endpoint = "wg-easy.u.djairo.dev:51820"; + persistentKeepalive = 25; + }]; + }; + }; +} diff --git a/hosts/nixbook/hardware-configuration.nix b/hosts/nixbook/hardware-configuration.nix new file mode 100644 index 0000000..dbf197a --- /dev/null +++ b/hosts/nixbook/hardware-configuration.nix @@ -0,0 +1,35 @@ +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ config, lib, pkgs, modulesPath, ... }: + +{ + imports = + [ (modulesPath + "/installer/scan/not-detected.nix") + ]; + + boot.initrd.availableKernelModules = [ "xhci_pci" "nvme" "usb_storage" "sd_mod" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-intel" ]; + boot.extraModulePackages = [ ]; + + fileSystems."/" = + { device = "/dev/mapper/luks-9902aae8-b296-4d67-8349-eab712cfefa4"; + fsType = "ext4"; + }; + + boot.initrd.luks.devices."luks-9902aae8-b296-4d67-8349-eab712cfefa4".device = "/dev/disk/by-uuid/9902aae8-b296-4d67-8349-eab712cfefa4"; + + fileSystems."/boot" = + { device = "/dev/disk/by-uuid/2589-CE74"; + fsType = "vfat"; + options = [ "fmask=0077" "dmask=0077" ]; + }; + + swapDevices = + [ { device = "/dev/mapper/luks-082cc474-c00a-46c5-b140-4b7ccc2e6958"; } + ]; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; + hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; +} diff --git a/hosts/tuxcomp/default.nix b/hosts/tuxcomp/default.nix new file mode 100644 index 0000000..6526de0 --- /dev/null +++ b/hosts/tuxcomp/default.nix @@ -0,0 +1,21 @@ +{ config, pkgs, ... }: + +{ + imports = + [ + ./hardware-configuration.nix + ../../modules + ./gaming.nix + ]; + # TODO: literally this whole configuration lmao + + # adds a `stretch_aspect` scaler (Valve/gamescope#927) and lets `--rt` use the + # rtprio limit from modules/gaming.nix instead of CAP_SYS_NICE. + nixpkgs.overlays = [ + (_final: prev: { + gamescope = prev.gamescope.overrideAttrs (old: { + patches = (old.patches or [ ]) ++ [ ../../patches/gamescope-stretch-aspect.patch ]; + }); + }) + ]; +} diff --git a/hosts/tuxcomp/gaming.nix b/hosts/tuxcomp/gaming.nix new file mode 100644 index 0000000..060fd85 --- /dev/null +++ b/hosts/tuxcomp/gaming.nix @@ -0,0 +1,73 @@ +{ pkgs, ... }: + +{ + programs.gamemode.enable = true; + users.users.djai.extraGroups = [ "gamemode" ]; + + # capSysNice would make glibc strip LD_PRELOAD from gamescope, breaking steam's overlay. + # '--rt' works through the rtprio limit below instead (see the gamescope patch on tuxcomp). + programs.gamescope = { + enable = true; + capSysNice = false; + }; + + security.pam.loginLimits = [ + { + domain = "@users"; + type = "-"; + item = "rtprio"; + value = "99"; + } + { + domain = "@users"; + type = "-"; + item = "nice"; + value = "-19"; + } + ]; + + # Wine 10+ uses the kernel's ntsync when /dev/ntsync exists; far cheaper + # than esync/fsync under thread contention. + boot.kernelModules = [ "ntsync" ]; + + + users.users.djai.packages = with pkgs; [ + desmume + eden + libretro.citra + prismlauncher + steam + winetricks + ]: + + services.flatpak.packages = [ + "org.vinegarhq.Sober" + ]; + + # TODO: switch to home manager + # home-manager.users.djai = + # { config, ... }: + # { + # home.packages = with pkgs; [ ]; + # + # xdg.desktopEntries.sims4 = { + # name = "The Sims 4"; + # exec = ''xdg-open "PATHTOEXECHERE"''; + # icon = "${config.home.homeDirectory}/.icons/"; + # categories = [ "Game" ]; + # terminal = false; + # }; + # + # # gamescope does fullscreen and scaling; setting content=game sets direct scnaout + # # requires this in hyprland config: render.direct_scanout = 2; + # wayland.windowManager.hyprland.settings.window_rule = [ + # { + # match.class = "gamescope"; + # fullscreen = true; + # content = "game"; + # opaque = true; + # immediate = true; + # } + # ]; + # }; +} diff --git a/modules/apps.nix b/modules/apps.nix new file mode 100644 index 0000000..bb835ae --- /dev/null +++ b/modules/apps.nix @@ -0,0 +1,47 @@ +{ pkgs, ... }: + +{ + + services.flatpak = { + enable = true; + update.onActivation = false; + update.auto = { + enable = true; + onCalendar = "weekly"; + }; + packages = [ + "io.freetubeapp.FreeTube" + ]; + }; + + # TODO: switch to home manager + # home-manager.users.djai.home.packages = with pkgs; [ ]; + + users.users.djai.packages = with pkgs; [ + # chat + element-desktop + signal-desktop + vesktop + + # productivity + libreoffice + zathura + + # media + feh + feishin # TODO: inject my patches / use my version + ffmpeg + gimp + kdePackages.kdenlive + krita + mpv + puddletag + + # tools + fastfetch + gparted + unrar + unzip + zip + ]; +} diff --git a/modules/audio.nix b/modules/audio.nix new file mode 100644 index 0000000..19dadda --- /dev/null +++ b/modules/audio.nix @@ -0,0 +1,39 @@ +{ pkgs, ... }: + +{ + software.pulseaudio.enable = false; + security.rtkit.enable = true; + + services.pipewire = { + enable = true; + alsa.enable = true; + alsa.support32Bit = true; + pulse.enable = true; + jack.enable = true; + + # disable automatic gain control for specific apps + extraConfig.pipewire-pulse."10-block-source-volume.conf"."pulse.rules" = [ + { + matches = [ + { "application.name" = "~Chromium.*"; } + { "application.process.binary" = "Discord"; } + { "application.process.binary" = "vesktop"; } + # TODO: add element to this list + ]; + actions.quirks = [ "block-source-volume" ]; + } + ]; + }; + + programs.nix-ld.libraries = [ pkgs.pipewire ]; + + # home-manager.users.djai.home.packages = with pkgs; [ + # pavucontrol + # qpwgraph + # ]; + + users.users.djai.packages = with pkgs; [ + pavucontrol + qpwgraph + ]; +} diff --git a/modules/base.nix b/modules/base.nix new file mode 100644 index 0000000..ac854db --- /dev/null +++ b/modules/base.nix @@ -0,0 +1,72 @@ +{ + config, + pkgs, + inputs, + ... +}: + +{ + nix.settings.experimental-features = [ + "nix-command" + "flakes" + ]; + + # garbage collection + nix.gc = { + automatic = true; + dates = "weekly"; + options = "--delete-older-than 30d"; + }; + + nix.optimise.automatic = true; + nixpkgs.config.allowUnfree = true; + + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; + boot.kernelPackages = pkgs.linuxPackages_latest; + + users.defaultUserShell = pkgs.fish; + users.users.djai = { + isNormalUser = true; + description = "djai"; + extraGroups = [ + "docker" + "networkmanager" + "video" + "wheel" + ]; + }; + + # TODO: figure out what we want to do w firewall + # networking.firewall.enable = false; + networking.networkmanager.enable = true; + programs.nix-ld.enable = true; + services.upower.enable = true; + virtualisation.docker.enable = true; + + environment.systemPackages = with pkgs; [ + git + wget + vim + ]; + + + # TODO: use home-manager + # home-manager = { + # useGlobalPkgs = true; + # useUserPackages = true; + # extraSpecialArgs = { inherit inputs; }; + # # Move a file home-manager would overwrite aside instead of failing. + # backupFileExtension = "backup"; + # + # users.djai = { + # home.stateVersion = "26.05"; + # programs.home-manager.enable = true; + # + # # TODO: unified ssh-config secret + # home.file.".ssh/config".text = '' + # Include ${config.sops.secrets."ssh-config".path} + # ''; + # }; + # }; +} diff --git a/modules/default.nix b/modules/default.nix new file mode 100644 index 0000000..d6332d2 --- /dev/null +++ b/modules/default.nix @@ -0,0 +1,15 @@ +{ + imports = [ + ../secrets + ./apps.nix + ./audio.nix + ./base.nix + ./desktop + ./dev.nix + ./email.nix + ./web + ./dunst.nix + ./terminal.nix + ./locale.nix + ]; +} diff --git a/modules/desktop/default.nix b/modules/desktop/default.nix new file mode 100644 index 0000000..ab88dc7 --- /dev/null +++ b/modules/desktop/default.nix @@ -0,0 +1,112 @@ +{ pkgs, inputs, ... }: + +{ + services.displayManager.sddm.enable = true; + services.displayManager.autoLogin = { + enable = true; + user = djai; + }; + + services.xserver = { + enable = true; + videoDrivers = [ + nvidia + amdgpu + intel + ]; + xkb = { + layout = "us"; + variant = "euro"; + options = "caps:escape"; + }; + }; + + programs.hyprland.enable = true; + programs.hyprlock.enable = true; + + hardware.graphics = { + enable = true; + enable32Bit = true; + }; + + hardware.bluetooth.enable = true; + services.blueman.enable = true; + + fonts.packages = with pkgs; [ + nerd-fonts.caskaydia-cove + nerd-fonts.fira-code + nerd-fonts.iosevka + nerd-fonts.shure-tech-mono + nerd-fonts.symbols-only + ]; + + # services.gvfs.enable = true; + services.udisks2.enable = true; + services.printing.enable = true; + + users.users.djai.packages = with pkgs; [ + hyprpicker + hyprsunset + kanshi + rofi + waybar + wl-clipboard + wl-clipboard-x11 + ]; + + # TODO: qt theming + qt = { + enable = true; + platformTheme = "gnome"; # have to use gnome as the "kde" setting crashes kde apps iff plasma isn't running + style = "adwaita-dark"; + }; + + # i have no clue what this does. + # GLib aborts any program that loads a GSettings schema it can't find, and Qt's GTK file dialog needs gtk3's. Nothing else puts these on the path. + environment.sessionVariables.GSETTINGS_SCHEMA_DIR = builtins.concatStringsSep ":" [ + "${pkgs.gsettings-desktop-schemas}/share/gsettings-schemas/${pkgs.gsettings-desktop-schemas.name}/glib-2.0/schemas" + "${pkgs.gtk3}/share/gsettings-schemas/${pkgs.gtk3.name}/glib-2.0/schemas" + ]; + + # TODO: other mimetypes? + xdg.mime.defaultApplications = { + "inode/directory" = [ "kitty" ]; + "text/html" = [ "librewolf.desktop" ]; + "x-scheme-handler/http" = [ "liberwolf.desktop" ]; + "x-scheme-handler/https" = [ "librewolf.desktop" ]; + }; + + # TODO: check we like these defaults + xdg.portal = { + enable = true; + config = { + common.default = [ "gtk" ]; + hyprland = { + default = [ + "gtk" + "hyprland" + ]; + "org.freedesktop.impl.portal.FileChooser" = [ "gtk" ]; + "org.freedesktop.impl.portal.OpenURI" = [ "gtk" ]; + }; + }; + # programs.hyprland adds the Hyprland portal. + extraPortals = [ pkgs.xdg-desktop-portal-gtk ]; + }; + + # TODO: start using home manager + # home-manager.users.djai = { + # imports = [ ./hyprland.nix ]; # loogat husseins dots for inspo + # + # home.packages = with pkgs; [ ]; + # + # # TODO: cursor + icon theming + # gtk = { + # enable = true; + # iconTheme = { + # name = "Papirus-Dark"; + # package = pkgs.papirus-icon-theme; + # }; + # }; + # }; +} diff --git a/modules/dev.nix b/modules/dev.nix new file mode 100644 index 0000000..881927e --- /dev/null +++ b/modules/dev.nix @@ -0,0 +1,136 @@ +{ pkgs, inputs, ... }: + +let + system = pkgs.stdenv.hostPlatform.system; +in +{ + programs.java.enable = true; + programs.nix-ld.libraries = with pkgs; [ + stdenv.cc.cc.lib + zlib + ncurses5 + libGL + libGLU + vulkan-loader + libdrm + mesa + libx11 + libxcursor + libxrandr + libxi + libxext + libxfixes + libxcb + wayland + libpulseaudio + alsa-lib + nss + nspr + expat + glib + dbus + systemd + freetype + fontconfig + ]; + + users.users.djai.packages = with pkgs; [ + # dev environment + godot + inputs.nix4nvchad.packages.${system}.nvchad + jq + lazygit + ripgrep + + # toolchains + cargo + gcc + gnumake + openssl + python3 + rustc + uv + + # containers / secrets + age + fluxcd + k9s + kubectl + kubectx + sops + + # hosts / networks + wireguard-tools + lm_sensors + + ]; + + # TODO: switch to home manager + # FIXME: use builtins.readfile to store these config options i think + # home-manager.users.djai.home.packages = with pkgs; []; + + nvchad = inputs.nix4nvchad.packages.${system}.default.override { + extraConfig = '' + local map = vim.keymap.set + local builtin = require('telescope.builtin') + + map("n", ";", ":", { desc = "CMD enter command mode" }) + map("n", "sw", " set wrap! ", {desc = "Toggle text wrapping"}) + map("n", "fm", function() require("conform").format() end, {desc = "Format with conform"}) + map("n", "-", " Oil ", {desc = "Open directory in oil"}) + map("n", "rcu", function() require('crates').upgrade_all_crates() end, {desc = "Upgrade rust crates"}) + map("n", "ca", function() vim.lsp.buf.code_action() end, {desc = "Lsp code action"}) + map("n", "gr", function() vim.lsp.buf.references() end, {desc = "Lsp references"}) + map("n", "gm", "Telescope git_commits", { desc = "telescope git commits" }) + + map('n', 'fi', function() + builtin.live_grep({ + prompt_title = "Files matching TODO", + default_text = "(TODO|todo|FIXME|THOUGHT|REFACTOR|TEST)", + }) + end, {desc = "find comments using telescope"}) + + -- stay in v mode after indenting + map("v", "<", "", ">gv") + ''; + + extraPlugins = '' + return { + { + "neovim/nvim-lspconfig", + config = function() + require "configs.lspconfig" + end, + },{ + "rcarriga/nvim-notify", + event = "VeryLazy", + config = function() + local notify = require "notify" + notify.setup { + background_colour = "#2D3540", + } + vim.notify = notify + end, + },{ + "stevearc/oil.nvim", + opts = {}, + dependencies = { "nvim-tree/nvim-web-devicons"}, + lazy = false + },{ + 'saecki/crates.nvim', + event = { "BufRead Cargo.toml" }, + config = function() + require('crates').setup() + end, + },{ + "jglasovic/venv-lsp.nvim", + config = function() + require("venv-lsp").setup() + end, + lazy = false, + } + } + ''; + }; +} diff --git a/modules/dunst.nix b/modules/dunst.nix new file mode 100644 index 0000000..d3ea6cf --- /dev/null +++ b/modules/dunst.nix @@ -0,0 +1,94 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + services.dunst = { + enable = true; + enableWayland = true; + settings = { + global = { + alignment = "left"; + always_run_script = true; + class = "Dunst"; + corner_radius = 10; + corners = "bottom-left, top-right"; + ellipsize = "middle"; + enable_posix_regex = true; + follow = mouse; + font = "Iosevka Mono Nerd Font Medium 10"; + force_xinerama = false; + force_xwayland = false; + format = "%s\n%b"; + frame_color = "#9399b2"; + frame_width = 0; + hide_duplicate_count = false; + history_length = 20; + horizontal_padding = 15; + icon_position = "left"; + idle_threshold = 120; + ignore_dbusclose = false; + ignore_newline = false; + indicate_hidden = yes; + line_height = 0; + markup = "full"; + max_icon_size = 32; + min_icon_size = 0; + mouse_left_click = "do_action, close_current"; + mouse_middle_click = "do_action, close_current"; + mouse_right_click = "close_all"; + offset = "3x3"; + padding = 15; + progress_bar_frame_width = 1; + progress_bar_height = 10; + progress_bar_max_width = 400; + progress_bar_min_width = 150; + progress_bar = true; + separator_color = frame; + separator_height = 2; + show_age_threshold = 60; + show_indicators = true; + shrink = no; + sort = yes; + stack_duplicates = true; + sticky_history = true; + text_icon_padding = 15; + title = "Dunst"; + vertical_alignment = "center"; + width = 250; + word_wrap =false; + }; + + experimental.per_monitor_dpi = false; + + urgency_low = { + background = "#181825CC"; + foreground = "#CDD6F4"; + timeout = 10; + }; + + urgency_normal = { + background = "#181825CC"; + foreground = "#CDD6F4"; + frame_color = "#b4befe"; + timeout = 10; + }; + + urgency_high = { + background = "#181825CC"; + foreground = "#F38BA8"; + format = "%s\n%b"; + frame_color = "#F38BA8"; + timeout = 60 ; + }; + + fcitx5-filter = { + summary = "Wayland Diagnose"; + skip_display = true; + }; + }; + }; +} diff --git a/modules/email.nix b/modules/email.nix new file mode 100644 index 0000000..19016a6 --- /dev/null +++ b/modules/email.nix @@ -0,0 +1,87 @@ +{ config, pkgs, ... }: + + +{ + programs.thunderbird.enable = true; +} + +# TODO: use thunderbird seeding config + +# Thunderbird with all mail accounts declared. Thunderbird can't read passwords +# from a command, so its credential store (logins.json + key4.db) is kept in +# sops and copied into a fresh profile once. The Microsoft 365 account still +# needs its OAuth consent once per machine. + +# let +# realName = "Djai Hougee"; +# profile = "/home/djai/.thunderbird/main"; +# +# # without this thunderbird picks OAuth2 for gmail.com accounts, but these need app passwords +# passwordAuth = id: { +# "mail.server.server_${id}.authMethod" = 3; +# "mail.smtpserver.smtp_${id}.authMethod" = 3; +# }; +# +# oauthAuth = id: { +# "mail.server.server_${id}.authMethod" = 10; +# "mail.smtpserver.smtp_${id}.authMethod" = 10; +# }; +# +# account = flavor: address: auth: { +# inherit flavor address realName; +# userName = address; +# thunderbird = { +# enable = true; +# settings = auth; +# }; +# }; +# in +# { +# # Only seeds a profile that has no logins.json yet, so the live store (with +# # Thunderbird's own token refreshes) is never overwritten. +# systemd.services.thunderbird-seed-creds = { +# description = "Seed Thunderbird credential store from sops (first run only)"; +# wantedBy = [ "multi-user.target" ]; +# after = [ +# "sops-install-secrets.service" +# "home-manager-djai.service" +# ]; +# serviceConfig = { +# Type = "oneshot"; +# User = "djai"; +# RemainAfterExit = true; +# }; +# script = '' +# set -eu +# secret="${config.sops.secrets."thunderbird-creds".path}" +# if [ ! -d "${profile}" ]; then +# echo "profile ${profile} not present yet; skipping seed" +# exit 0 +# fi +# if [ -e "${profile}/logins.json" ]; then +# echo "logins.json already present; leaving live store untouched" +# exit 0 +# fi +# if [ ! -r "$secret" ]; then +# echo "secret $secret not readable; skipping" +# exit 0 +# fi +# ${pkgs.gnutar}/bin/tar -C "${profile}" -xf "$secret" +# echo "seeded Thunderbird credential store into ${profile}" +# ''; +# }; +# +# home-manager.users.djai = { +# programs.thunderbird = { +# enable = true; +# profiles.main.isDefault = true; +# }; +# +# accounts.email.accounts = { +# djai = account "kolabnow.com" "ljxjah@mykolab.com" passwordAuth // { +# primary = true; +# }; +# uni = account "gmail.com" "d.hougee@student.rug.nl" oauthAuth; +# }; +# }; +# } diff --git a/modules/locale.nix b/modules/locale.nix new file mode 100644 index 0000000..85b9cc0 --- /dev/null +++ b/modules/locale.nix @@ -0,0 +1,20 @@ +{ pkgs, ... }: + +{ + time.timeZone = "Europe/Amsterdam"; + + # TODO: copy locale settings from tuxcomp + i18n.defaultLocale = "en_GB.UTF-8"; + + i18n.extraLocaleSettings = { + LC_ADDRESS = "nl_NL.UTF-8"; + LC_IDENTIFICATION = "nl_NL.UTF-8"; + LC_MEASUREMENT = "nl_NL.UTF-8"; + LC_MONETARY = "nl_NL.UTF-8"; + LC_NAME = "nl_NL.UTF-8"; + LC_NUMERIC = "nl_NL.UTF-8"; + LC_PAPER = "nl_NL.UTF-8"; + LC_TELEPHONE = "nl_NL.UTF-8"; + LC_TIME = "nl_NL.UTF-8"; + }; +} diff --git a/modules/terminal.nix b/modules/terminal.nix new file mode 100644 index 0000000..ee1421d --- /dev/null +++ b/modules/terminal.nix @@ -0,0 +1,130 @@ +{ pkgs, ... }: + +{ + environment.defaultPackages = with pkgs; [ + fbterm + kitty + starship + ]; + + # TODO: switch to home manager + # FIXME: do not embed our entire tmux config into this + programs.fish.enable = true; + programs.tmux = { + enable = true; + + extraConfig = '' + # use as leader + unbind C-b + set-option -g prefix C-a + bind-key C-a send-prefix + + bind ? list-keys + + # window/pane management + bind s split-window -h -c "#{pane_current_path}" + bind C-s select-layout main-horizontal \; swap-pane -t 0 \; select-pane -t 0 + bind S select-layout even-horizontal + bind v split-window -v -c "#{pane_current_path}" + bind C-v select-layout main-vertical \; swap-pane -t 0 \; select-pane -t 0 + bind V select-layout even-vertical + bind c new-window -c "#{pane_current_path}" + # move pane to new tab + bind b break-pane + # swap active pane and first pane + bind a swap-pane -t 0 \; select-pane -t 0 + unbind '"' + unbind % + + # vim-style pane switching with through + bind -n M-h select-pane -L + bind -n M-j select-pane -D + bind -n M-k select-pane -U + bind -n M-l select-pane -R + bind h select-pane -L + bind j select-pane -D + bind k select-pane -U + bind l select-pane -R + # Resize using caps + bind -r H resize-pane -L 5 + bind -r J resize-pane -D 5 + bind -r K resize-pane -U 5 + bind -r L resize-pane -R 5 + + # vim-style copy mode + set-window-option -g mode-keys vi + bind-key -T copy-mode-vi 'v' send-keys -X begin-selection + bind-key -T copy-mode-vi 'y' send-keys -X copy-selection-and-cancel + bind-key p paste-buffer + + # window traversal + bind -n M-n next-window + bind tab next-window + + + # reload config with r + bind r source-file ~/.config/tmux/tmux.conf + # dont rename windows automatically + # set-option -g allow-rename off + + # bind m send-keys -R M-k C-a '[kV' C-m M-j 'Pd/[\.ACFGV]' C-m 'Ax' + bind m source-file ~/.config/tmux/copy.tmux + + + # ----- UI/UX ----- + + # don't do anything when a 'bell' rings + set -g visual-activity off + set -g visual-bell off + set -g visual-silence off + setw -g monitor-activity off + set -g bell-action none + + # copy mode + setw -g mode-style 'fg=black bg=red bold' + + # panes + set -g pane-border-style 'fg=black' + set -g pane-active-border-style 'fg=white' + + # status + set -g status-position bottom + set -g status-justify left + set -g status-style 'bg=black fg=white' + + set -g status-left "" + set -g status-right "" + + # window status + setw -g window-status-current-style 'bg=blue fg=black' + setw -g window-status-current-format '◤ #W ◢' + + setw -g window-status-style 'fg=white bg=black' + setw -g window-status-format '#[fg=black]◤ #[fg=white]#W #[fg=black]◢' # todo: styling + setw -g window-status-bell-style 'fg=yellow bg=red bold' + + # messages + set -g message-style 'fg=black bg=blue' + ''; + + plugins = with pkgs.tmuxPlugins; [ + sensible + resurrect + ]; + }; + + # home-manager.users.djai = { + # programs.fish.enable = true; + # programs.tmux.enable = true; + # + # programs.kitty = { + # enable = true; + # settings = { + # shell = "${pkgs.tmux}/bin/tmux"; + # font_family = "Iosevka Nerd Font"; + # }; + # extraConfig = ""; # TODO theming + # }; + # + # }; +} diff --git a/modules/web/default.nix b/modules/web/default.nix new file mode 100644 index 0000000..e4cb655 --- /dev/null +++ b/modules/web/default.nix @@ -0,0 +1,18 @@ +{ pkgs, inputs, ... }: + +{ + # TODO: switch to home manager + # then configure plugins n shi here too + # make a shared section (about:config sections, plugins, profiles) and then have two separate configs in firefox.nix and librewolf.nix + # https://wiki.nixos.org/wiki/Librewolf + # https://wiki.nixos.org/wiki/Firefox/en + imports = [ + ./firefox.nix + ]; + + users.users.djai.packages = with pkgs; [ + librewolf + firefox + ]; +} + diff --git a/modules/web/firefox.nix b/modules/web/firefox.nix new file mode 100644 index 0000000..b271870 --- /dev/null +++ b/modules/web/firefox.nix @@ -0,0 +1,39 @@ +{ lib, pkgs, ... }: + +{ + programs.firefox = { + enable = true; + preferences = { #TODO: port over all changes in about:config from tuxcomp + "browser.resistFingerPrinting" = true; + }; + + policies = { + DisableTelemetry = true; + ExtensionSettings = { + "uBlock0@raymondhill.net" = { + installation_mode = "force_installed"; + install_url = + "https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi"; + }; + + "vimium@vimium.org" = { + installation_mode = "force_installed"; + install_url = + "https://addons.mozilla.org/firefox/downloads/latest/vimium/latest.xpi"; + }; + + "addon@darkreader.org" = { + installation_mode = "force_installed"; + install_url = + "https://addons.mozilla.org/firefox/downloads/latest/darkreader/latest.xpi"; + }; + + "{446900e4-71c2-419f-a6a7-df9c091e268b}" = { + installation_mode = "force_installed"; + install_url = + "https://addons.mozilla.org/firefox/downloads/latest/bitwarden-password-manager/latest.xpi"; + }; + }; + }; + }; +} diff --git a/patches/gamescope-stretch-aspect.patch b/patches/gamescope-stretch-aspect.patch new file mode 100644 index 0000000..5be7377 --- /dev/null +++ b/patches/gamescope-stretch-aspect.patch @@ -0,0 +1,118 @@ +diff --git a/src/Utils/Process.cpp b/src/Utils/Process.cpp +index f5191af..434d7f4 100644 +--- a/src/Utils/Process.cpp ++++ b/src/Utils/Process.cpp +@@ -423,9 +423,7 @@ namespace gamescope::Process + void SetNice( int nNice ) + { + #if defined(__linux__) +- if ( !HasCapSysNice() ) +- return; +- ++ // Try regardless of CAP_SYS_NICE — succeeds if RLIMIT_NICE permits. + errno = 0; + int nOldNice = nice( 0 ); + if ( nOldNice != -1 || errno == 0 ) +@@ -494,9 +492,7 @@ namespace gamescope::Process + bool SetRealtime() + { + #if defined(__linux__) +- if ( !HasCapSysNice() ) +- return false; +- ++ // Try regardless of CAP_SYS_NICE — succeeds if RLIMIT_RTPRIO permits. + g_oOldSchedulerInfo = SchedulerInfo::Get(); + if ( !g_oOldSchedulerInfo ) + return false; +diff --git a/src/main.cpp b/src/main.cpp +index 1397028..bffdbdc 100644 +--- a/src/main.cpp ++++ b/src/main.cpp +@@ -171,7 +171,7 @@ const char usage[] = + " -h, --nested-height game height\n" + " -r, --nested-refresh game refresh rate (frames per second)\n" + " -m, --max-scale maximum scale factor\n" +- " -S, --scaler upscaler type (auto, integer, fit, fill, stretch)\n" ++ " -S, --scaler upscaler type (auto, integer, fit, fill, stretch, stretch_aspect)\n" + " -F, --filter upscaler filter (linear, nearest, fsr, nis, pixel)\n" + " fsr => AMD FidelityFX™ Super Resolution 1.0\n" + " nis => NVIDIA Image Scaling v1.0.3\n" +@@ -392,6 +392,8 @@ static enum GamescopeUpscaleScaler parse_upscaler_scaler(const char *str) + return GamescopeUpscaleScaler::FILL; + } else if (strcmp(str, "stretch") == 0) { + return GamescopeUpscaleScaler::STRETCH; ++ } else if (strcmp(str, "stretch_aspect") == 0) { ++ return GamescopeUpscaleScaler::STRETCH_ASPECT; + } else { + fprintf( stderr, "gamescope: invalid value for --scaler\n" ); + exit(1); +@@ -843,16 +845,12 @@ int main(int argc, char **argv) + } + } + +- if ( gamescope::Process::HasCapSysNice() ) ++ // Try elevating priority unconditionally; the calls succeed when either ++ // CAP_SYS_NICE is set or RLIMIT_NICE/RTPRIO permit it (PAM loginLimits). ++ gamescope::Process::SetNice( -20 ); ++ if ( g_bRt && !gamescope::Process::SetRealtime() && !gamescope::Process::HasCapSysNice() ) + { +- gamescope::Process::SetNice( -20 ); +- +- if ( g_bRt ) +- gamescope::Process::SetRealtime(); +- } +- else +- { +- fprintf( stderr, "No CAP_SYS_NICE, falling back to regular-priority compute and threads.\nPerformance will be affected.\n" ); ++ fprintf( stderr, "Failed to set realtime scheduling. Need CAP_SYS_NICE or RLIMIT_RTPRIO > 0.\n" ); + } + + #if 0 +diff --git a/src/main.hpp b/src/main.hpp +index 2e6fb83..89d6bc9 100644 +--- a/src/main.hpp ++++ b/src/main.hpp +@@ -54,6 +54,7 @@ enum class GamescopeUpscaleScaler : uint32_t + FIT, + FILL, + STRETCH, ++ STRETCH_ASPECT, + }; + + extern GamescopeUpscaleFilter g_upscaleFilter; +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 0518950..2f5db9f 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -1508,6 +1508,31 @@ window_is_fullscreen( steamcompmgr_win_t *w ) + + void calc_scale_factor_scaler(float &out_scale_x, float &out_scale_y, float sourceWidth, float sourceHeight) + { ++ // Stretches source to a sub-rect of the output that matches the -W/-H aspect ratio, ++ // pillar/letterboxing the rest. Useful for e.g. 4:3-stretched on a 21:9 panel. ++ // Uses g_nPreferredOutput{Width,Height} (the user-supplied -W/-H, never mutated) ++ // rather than g_nOutput{Width,Height} (which backends overwrite on resize/fullscreen). ++ extern int g_nPreferredOutputWidth; ++ extern int g_nPreferredOutputHeight; ++ if (g_upscaleScaler == GamescopeUpscaleScaler::STRETCH_ASPECT ++ && g_nPreferredOutputWidth > 0 && g_nPreferredOutputHeight > 0 ++ && currentOutputWidth > 0 && currentOutputHeight > 0) ++ { ++ float targetAspect = (float)g_nPreferredOutputWidth / (float)g_nPreferredOutputHeight; ++ float currentAspect = (float)currentOutputWidth / (float)currentOutputHeight; ++ float effW, effH; ++ if (currentAspect > targetAspect) { ++ effH = (float)currentOutputHeight; ++ effW = effH * targetAspect; ++ } else { ++ effW = (float)currentOutputWidth; ++ effH = effW / targetAspect; ++ } ++ out_scale_x = effW / sourceWidth; ++ out_scale_y = effH / sourceHeight; ++ return; ++ } ++ + float XOutputRatio = currentOutputWidth / (float)g_nNestedWidth; + float YOutputRatio = currentOutputHeight / (float)g_nNestedHeight; + float outputScaleRatio = std::min(XOutputRatio, YOutputRatio);