diff --git a/flake.lock b/flake.lock index c4c1037..c98cde5 100644 --- a/flake.lock +++ b/flake.lock @@ -608,11 +608,11 @@ ] }, "locked": { - "lastModified": 1704980875, - "narHash": "sha256-IPZmMjk5f4TBbEpzUFBc3OC1W6OwDNEXk2w/0uVXX1o=", + "lastModified": 1704099619, + "narHash": "sha256-QRVMkdxLmv+aKGjcgeEg31xtJEIsYq4i1Kbyw5EPS6g=", "owner": "nix-community", "repo": "home-manager", - "rev": "5f0ab0eedc6ede69beb8f45561ffefa54edc6e65", + "rev": "7e398b3d76bc1503171b1364c9d4a07ac06f3851", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 8d7e253..bd88c45 100644 --- a/flake.nix +++ b/flake.nix @@ -23,13 +23,6 @@ src = ./.; snowfall.namespace = "plusultra"; - - snowfall = { - meta = { - name = "plusultra"; - title = "Plus Ultra"; - }; - }; }; in lib.mkFlake { @@ -37,26 +30,14 @@ allowUnfree = true; }; - homes.users."harald@nix.fedora.fritz.box".modules = with inputs; [ + # Add modules to all homes. + homes.modules = with inputs; [ neovim-flake.homeManagerModules.default ]; - homes.users."harald@sgx-nixos".modules = with inputs; [ - neovim-flake.homeManagerModules.default - ]; - -# homes.users.modules = with inputs; [ -# neovim-flake.homeManagerModules.default -# ]; - - systems.modules.nixos = with inputs; [ - home-manager.nixosModules.home-manager - ]; - outputs-builder = channels: { formatter = channels.nixpkgs.nixpkgs-fmt; defaultApp = lib.flake-utils-plus.mkApp { drv = channels.nixpkgs.home-manager; }; }; - }; } diff --git a/homes/x86_64-linux/harald@nix.fedora.fritz.box/default.nix b/homes/x86_64-linux/harald@nix.fedora.fritz.box/default.nix index 794c6f9..834fdd1 100644 --- a/homes/x86_64-linux/harald@nix.fedora.fritz.box/default.nix +++ b/homes/x86_64-linux/harald@nix.fedora.fritz.box/default.nix @@ -8,10 +8,10 @@ }: { home = { - username = "harald"; - homeDirectory = "/home/${config.home.username}/nix"; - stateVersion = "23.11"; # Please read the comment before changing. - sessionPath = [ "$HOME/bin" ]; + username = "harald"; + homeDirectory = "/home/${config.home.username}/nix"; + stateVersion = "23.11"; # Please read the comment before changing. + sessionPath = [ "$HOME/bin" ]; }; plusultra = { diff --git a/homes/x86_64-linux/harald@sgx-nixos/default.nix b/homes/x86_64-linux/harald@sgx-nixos/default.nix deleted file mode 100644 index 7504363..0000000 --- a/homes/x86_64-linux/harald@sgx-nixos/default.nix +++ /dev/null @@ -1,36 +0,0 @@ -{ lib -, pkgs -, config -, nixpkgs -, osConfig ? { } -, format ? "unknown" -, ... -}: -{ - home = { - username = "harald"; - homeDirectory = "/home/${config.home.username}"; - stateVersion = "23.11"; # Please read the comment before changing. - sessionPath = [ "$HOME/bin" ]; - }; - - plusultra = { - cli-apps = { - bash.enable = true; - fish.enable = true; - neovim.enable = false; - tmux.enable = true; - bat.enable = true; - git.enable = true; - starship.enable = true; - home-manager.enable = true; - }; - tools = { - direnv.enable = true; - # jetbrains.enable = true; - }; - }; - - fonts.fontconfig.enable = true; -} - diff --git a/modules/darwin/apps/firefox/default.nix b/modules/darwin/apps/firefox/default.nix new file mode 100644 index 0000000..61d3487 --- /dev/null +++ b/modules/darwin/apps/firefox/default.nix @@ -0,0 +1,58 @@ +{ options +, config +, lib +, pkgs +, ... +}: +with lib; +with lib.plusultra; let + cfg = config.plusultra.apps.firefox; + defaultSettings = { + "browser.aboutwelcome.enabled" = false; + "browser.meta_refresh_when_inactive.disabled" = true; + "browser.startup.homepage" = "https://start.duckduckgo.com/?kak=-1&kal=-1&kao=-1&kaq=-1&kt=Hack+Nerd+Font&kae=d&ks=m&k7=2e3440&kj=3b4252&k9=eceff4&kaa=d8dee9&ku=1&k8=d8dee9&kx=81a1c1&k21=3b4252&k18=1&k5=2&kp=-2&k1=-1&kaj=u&kay=b&kk=-1&kax=-1&kap=-1&kau=-1"; + "browser.bookmarks.showMobileBookmarks" = true; + "browser.urlbar.suggest.quicksuggest.sponsored" = false; + "browser.newtabpage.activity-stream.showSponsoredTopSites" = false; + "browser.aboutConfig.showWarning" = false; + "browser.ssb.enabled" = true; + }; +in +{ + options.plusultra.apps.firefox = with types; { + enable = mkBoolOpt false "Whether or not to enable Firefox."; + extraConfig = + mkOpt str "" "Extra configuration for the user profile JS file."; + userChrome = + mkOpt str "" "Extra configuration for the user chrome CSS file."; + settings = mkOpt attrs defaultSettings "Settings to apply to the profile."; + }; + + config = mkIf cfg.enable { + plusultra.desktop.addons.firefox-nordic-theme = enabled; + + plusultra.home = { + extraOptions = { + programs.firefox = { + enable = true; + package = pkgs.firefox.override { + cfg = { + # FIXME: This method of enabling browser pass support is + # deprecated, but the suggested solution requires using + # the NixOS module rather than Home-Manager. Update this + # if/when Home-Manager gets support for: + # `programs.firefox.extraNativeMessagingHosts.packages` + enableBrowserpass = true; + }; + }; + + profiles.${config.plusultra.user.name} = { + inherit (cfg) extraConfig userChrome settings; + id = 0; + name = config.plusultra.user.name; + }; + }; + }; + }; + }; +} diff --git a/modules/darwin/apps/iterm2/default.nix b/modules/darwin/apps/iterm2/default.nix new file mode 100644 index 0000000..5c6178c --- /dev/null +++ b/modules/darwin/apps/iterm2/default.nix @@ -0,0 +1,18 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.iterm2; +in +{ + options.plusultra.apps.iterm2 = with types; { + enable = mkBoolOpt false "Whether or not to enable iTerm2."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + iterm2 + ]; + }; +} diff --git a/modules/darwin/apps/vscode/default.nix b/modules/darwin/apps/vscode/default.nix new file mode 100644 index 0000000..60d0d5b --- /dev/null +++ b/modules/darwin/apps/vscode/default.nix @@ -0,0 +1,14 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.apps.vscode; +in +{ + options.plusultra.apps.vscode = with types; { + enable = mkBoolOpt false "Whether or not to enable vscode."; + }; + + config = + mkIf cfg.enable { environment.systemPackages = with pkgs; [ vscode ]; }; +} diff --git a/modules/darwin/cli-apps/neovim/default.nix b/modules/darwin/cli-apps/neovim/default.nix new file mode 100644 index 0000000..be46ce9 --- /dev/null +++ b/modules/darwin/cli-apps/neovim/default.nix @@ -0,0 +1,47 @@ +inputs @ { options +, config +, lib +, pkgs +, ... +}: +with lib; +with lib.plusultra; let + cfg = config.plusultra.cli-apps.neovim; +in +{ + options.plusultra.cli-apps.neovim = with types; { + enable = mkBoolOpt false "Whether or not to enable neovim."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + # FIXME: As of today (2022-12-09), `page` no longer works with my Neovim + # configuration. Either something in my configuration is breaking it or `page` is busted. + # page + plusultra.neovim + ]; + + environment.variables = { + # PAGER = "page"; + # MANPAGER = + # "page -C -e 'au User PageDisconnect sleep 100m|%y p|enew! |bd! #|pu p|set ft=man'"; + PAGER = "less"; + MANPAGER = "less"; + NPM_CONFIG_PREFIX = "$HOME/.npm-global"; + EDITOR = "nvim"; + }; + + plusultra.home = { + configFile = { + "dashboard-nvim/.keep".text = ""; + }; + + extraOptions = { + # Use Neovim for Git diffs. + programs.zsh.shellAliases.vimdiff = "nvim -d"; + programs.bash.shellAliases.vimdiff = "nvim -d"; + programs.fish.shellAliases.vimdiff = "nvim -d"; + }; + }; + }; +} diff --git a/modules/darwin/desktop/addons/firefox-nordic-theme/default.nix b/modules/darwin/desktop/addons/firefox-nordic-theme/default.nix new file mode 100644 index 0000000..86033fe --- /dev/null +++ b/modules/darwin/desktop/addons/firefox-nordic-theme/default.nix @@ -0,0 +1,23 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.desktop.addons.firefox-nordic-theme; + profileDir = ".mozilla/firefox/${config.plusultra.user.name}"; +in +{ + options.plusultra.desktop.addons.firefox-nordic-theme = with types; { + enable = mkBoolOpt false "Whether to enable the Nordic theme for firefox."; + }; + + config = mkIf cfg.enable { + plusultra.apps.firefox = { + extraConfig = builtins.readFile + "${pkgs.plusultra.firefox-nordic-theme}/configuration/user.js"; + userChrome = '' + @import "${pkgs.plusultra.firefox-nordic-theme}/userChrome.css"; + ''; + }; + }; +} diff --git a/modules/darwin/desktop/addons/skhd/default.nix b/modules/darwin/desktop/addons/skhd/default.nix new file mode 100644 index 0000000..5aac882 --- /dev/null +++ b/modules/darwin/desktop/addons/skhd/default.nix @@ -0,0 +1,109 @@ +{ lib, pkgs, config, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.desktop.addons.skhd; + + mkScript = name: file: pkgs.writeShellApplication { + inherit name; + checkPhase = ""; + text = builtins.readFile file; + }; + + open-iterm2 = mkScript "open-iterm2" ./scripts/open-iterm2.sh; +in +{ + options.plusultra.desktop.addons.skhd = { + enable = mkEnableOption "skhd"; + }; + + config = mkIf cfg.enable { + services.skhd = { + enable = true; + + skhdConfig = '' + # Movement + shift + cmd - h : yabai -m window --focus west + shift + cmd - j : yabai -m window --focus south + shift + cmd - k : yabai -m window --focus north + shift + cmd - l : yabai -m window --focus east + + # Window Movement + lctrl + shift + cmd - h : yabai -m window --swap west + lctrl + shift + cmd - j : yabai -m window --swap south + lctrl + shift + cmd - k : yabai -m window --swap north + lctrl + shift + cmd - l : yabai -m window --swap east + + # Window Resize + lctrl + cmd - h : yabai -m window --resize left:-50:0; \ + yabai -m window --resize right:-50:0 + lctrl + cmd - j : yabai -m window --resize bottom:0:50; \ + yabai -m window --resize top:0:50 + lctrl + cmd - k : yabai -m window --resize top:0:-50; \ + yabai -m window --resize bottom:0:-50 + lctrl + cmd - l : yabai -m window --resize right:50:0; \ + yabai -m window --resize left:50:0 + + lctrl + alt - h : yabai -m window --resize left:-10:0; \ + yabai -m window --resize right:-10:0 + lctrl + alt - j : yabai -m window --resize bottom:0:10; \ + yabai -m window --resize top:0:10 + lctrl + alt - k : yabai -m window --resize top:0:-10; \ + yabai -m window --resize bottom:0:-10 + lctrl + alt - l : yabai -m window --resize right:10:0; \ + yabai -m window --resize left:10:0 + + lctrl + cmd - e : yabai -m space --balance + + # Move Window To Space + lctrl + shift + cmd - m : yabai -m window --space last; yabai -m space --focus last + lctrl + shift + cmd - p : yabai -m window --space prev; yabai -m space --focus prev + lctrl + shift + cmd - n : yabai -m window --space next; yabai -m space --focus next + lctrl + shift + cmd - 1 : yabai -m window --space 1; yabai -m space --focus 1 + lctrl + shift + cmd - 2 : yabai -m window --space 2; yabai -m space --focus 2 + lctrl + shift + cmd - 3 : yabai -m window --space 3; yabai -m space --focus 3 + lctrl + shift + cmd - 4 : yabai -m window --space 4; yabai -m space --focus 4 + + # Focus Space + # shift + cmd - m : yabai -m space --focus last + # shift + cmd - p : yabai -m space --focus prev + # shift + cmd - n : yabai -m space --focus next + shift + cmd - 1 : yabai -m space --focus 1 + shift + cmd - 2 : yabai -m space --focus 2 + shift + cmd - 3 : yabai -m space --focus 3 + shift + cmd - 4 : yabai -m space --focus 4 + + # Insert Direction + lctrl + shift + cmd - v : yabai -m window --insert south + lctrl + shift + cmd - b : yabai -m window --insert east + lctrl + shift + cmd - s : yabai -m window --insert stack + + # Floating Windows + # shift + cmd - space : \ + # yabai -m window --toggle float; \ + # yabai -m window --toggle border + shift + cmd - space : yabai -m window --toggle float + + # Terminal + shift + cmd - return : ${open-iterm2}/bin/open-iterm2 + + # Fullscreen + alt - f : yabai -m window --toggle zoom-fullscreen + shift + alt - f : yabai -m window --toggle native-fullscreen + + # Restart Yabai + shift + lctrl + alt - r : \ + /usr/bin/env osascript <<< \ + "display notification \"Restarting Yabai\" with title \"Yabai\""; \ + launchctl kickstart -k "gui/$UID/org.nixos.yabai" + + # Restart Spacebar + shift + lctrl + alt - s : \ + /usr/bin/env osascript <<< \ + "display notification \"Restarting Spacebar\" with title \"Spacebar\""; \ + launchctl kickstart -k "gui/$UID/org.nixos.spacebar" + ''; + }; + }; +} diff --git a/modules/darwin/desktop/addons/skhd/scripts/open-iterm2.sh b/modules/darwin/desktop/addons/skhd/scripts/open-iterm2.sh new file mode 100644 index 0000000..c3d35a4 --- /dev/null +++ b/modules/darwin/desktop/addons/skhd/scripts/open-iterm2.sh @@ -0,0 +1,14 @@ +# Detects if iTerm2 is running +if ! pgrep -f "iTerm" > /dev/null 2>&1; then + open -a "/Applications/Nix Apps/iTerm2.app" +else + # Create a new window + script='tell application "iTerm2" to create window with default profile' + ! osascript -e "${script}" > /dev/null 2>&1 && { + # Get pids for any app with "iTerm" and kill + while IFS="" read -r pid; do + kill -15 "${pid}" + done < <(pgrep -f "iTerm") + open -a "/Applications/Nix Apps/iTerm2.app" + } +fi diff --git a/modules/darwin/desktop/addons/spacebar/default.nix b/modules/darwin/desktop/addons/spacebar/default.nix new file mode 100644 index 0000000..69393cc --- /dev/null +++ b/modules/darwin/desktop/addons/spacebar/default.nix @@ -0,0 +1,47 @@ +{ lib, pkgs, config, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.desktop.addons.spacebar; +in +{ + options.plusultra.desktop.addons.spacebar = { + enable = mkEnableOption "Spacebar"; + }; + + config = mkIf cfg.enable { + services.spacebar = { + enable = true; + package = pkgs.spacebar; + + config = { + position = "top"; + display = "all"; + height = 32; + title = "on"; + spaces = "on"; + clock = "on"; + power = "off"; + + padding_left = 10; + padding_right = 10; + + spacing_left = 10; + spacing_right = 10; + + foreground_color = "0xffeceff4"; + background_color = "0xff1d2128"; + + text_font = ''"Hack Nerd Font Mono:Regular:14.0"''; + icon_font = ''"Hack Nerd Font Mono:Regular:20.0"''; + + # Shell entries apparently break the whole bar... + # https://github.com/cmacrae/spacebar/issues/104 + # right_shell_icon = ""; + # right_shell_command = ''"whoami"''; + # right_shell = "on"; + }; + }; + }; +} diff --git a/modules/darwin/desktop/yabai/default.nix b/modules/darwin/desktop/yabai/default.nix new file mode 100644 index 0000000..1404d8c --- /dev/null +++ b/modules/darwin/desktop/yabai/default.nix @@ -0,0 +1,77 @@ +{ lib +, pkgs +, config +, ... +}: +let + cfg = config.plusultra.desktop.yabai; + + inherit (lib) types mkEnableOption mkIf; + inherit (lib.plusultra) mkOpt enabled; +in +{ + options.plusultra.desktop.yabai = { + enable = mkEnableOption "Yabai"; + enable-scripting-addition = mkOpt types.bool true "Whether to enable the scripting addition for Yabai. (Requires SIP to be disabled)"; + }; + + config = mkIf cfg.enable { + plusultra.desktop.addons = { + skhd = enabled; + spacebar = enabled; + }; + + services.yabai = { + enable = true; + enableScriptingAddition = cfg.enable-scripting-addition; + + config = { + layout = "bsp"; + + auto_balance = "off"; + + debug_output = "on"; + + top_padding = 8; + right_padding = 8; + left_padding = 8; + bottom_padding = 8; + + window_gap = 6; + window_topmost = "on"; + window_shadow = "float"; + + # As of macOS Sonoma, window borders break Yabai and cause a bunch of lag. + window_border = "off"; + # window_border = "on"; + # window_border_width = 5; + # window_border_radius = 14; + # window_border_blur = "off"; + # window_border_hidpi = "on"; + # insert_feedback_color = "0xffb48ead"; + # normal_window_border_color = "0x002e3440"; + # active_window_border_color = "0xff5e81ac"; + + external_bar = "all:${builtins.toString config.services.spacebar.config.height}:0"; + + # mouse_modifier = "alt"; + mouse_modifier = "cmd"; + mouse_action1 = "move"; + mouse_action2 = "resize"; + }; + + extraConfig = '' + yabai -m rule --add label="Finder" app="^Finder$" title="(Co(py|nnect)|Move|Info|Pref)" manage=off + yabai -m rule --add label="Safari" app="^Safari$" title="^(General|(Tab|Password|Website|Extension)s|AutoFill|Se(arch|curity)|Privacy|Advance)$" manage=off + yabai -m rule --add label="System Preferences" app="^System Preferences$" title=".*" manage=off + yabai -m rule --add label="App Store" app="^App Store$" manage=off + yabai -m rule --add label="Activity Monitor" app="^Activity Monitor$" manage=off + yabai -m rule --add label="Calculator" app="^Calculator$" manage=off + yabai -m rule --add label="Dictionary" app="^Dictionary$" manage=off + yabai -m rule --add label="mpv" app="^mpv$" manage=off + yabai -m rule --add label="Software Update" title="Software Update" manage=off + yabai -m rule --add label="About This Mac" app="System Information" title="About This Mac" manage=off + ''; + }; + }; +} diff --git a/modules/darwin/home/default.nix b/modules/darwin/home/default.nix new file mode 100644 index 0000000..01d7157 --- /dev/null +++ b/modules/darwin/home/default.nix @@ -0,0 +1,40 @@ +{ options, config, pkgs, lib, inputs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.home; +in +{ + # imports = with inputs; [ + # home-manager.darwinModules.home-manager + # ]; + + options.plusultra.home = with types; { + file = mkOpt attrs { } + "A set of files to be managed by home-manager's ."; + configFile = mkOpt attrs { } + "A set of files to be managed by home-manager's ."; + extraOptions = mkOpt attrs { } "Options to pass directly to home-manager."; + homeConfig = mkOpt attrs { } "Final config for home-manager."; + }; + + config = { + plusultra.home.extraOptions = { + home.stateVersion = mkDefault "22.11"; + home.file = mkAliasDefinitions options.plusultra.home.file; + xdg.enable = true; + xdg.configFile = mkAliasDefinitions options.plusultra.home.configFile; + }; + + snowfallorg.user.${config.plusultra.user.name}.home.config = mkAliasDefinitions options.plusultra.home.extraOptions; + + home-manager = { + useUserPackages = true; + useGlobalPkgs = true; + + # users.${config.plusultra.user.name} = args: + # mkAliasDefinitions options.plusultra.home.extraOptions; + }; + }; +} diff --git a/modules/darwin/nix/default.nix b/modules/darwin/nix/default.nix new file mode 100644 index 0000000..995aebc --- /dev/null +++ b/modules/darwin/nix/default.nix @@ -0,0 +1,74 @@ +{ options +, config +, pkgs +, lib +, ... +}: +with lib; +with lib.plusultra; let + cfg = config.plusultra.nix; +in +{ + options.plusultra.nix = with types; { + enable = mkBoolOpt true "Whether or not to manage nix configuration."; + package = mkOpt package pkgs.nixUnstable "Which nix package to use."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + deploy-rs + nixfmt + nix-index + nix-prefetch-git + ]; + + nix = + let + users = [ "root" config.plusultra.user.name ]; + in + { + package = cfg.package; + + settings = { + experimental-features = "nix-command flakes"; + http-connections = 50; + warn-dirty = false; + log-lines = 50; + + # Large builds apparently fail due to an issue with darwin: + # https://github.com/NixOS/nix/issues/4119 + sandbox = false; + + # This appears to break on darwin + # https://github.com/NixOS/nix/issues/7273 + auto-optimise-store = false; + + allow-import-from-derivation = true; + + trusted-users = users; + allowed-users = users; + + # NOTE: This configuration is generated by nix-installer so I'm adding it here in + # case it becomes important. + extra-nix-path = "nixpkgs=flake:nixpkgs"; + build-users-group = "nixbld"; + }; + #// (lib.optionalAttrs config.plusultra.tools.direnv.enable { + # keep-outputs = true; + # keep-derivations = true; + #}); + + gc = { + automatic = true; + interval = { Day = 7; }; + options = "--delete-older-than 30d"; + user = config.plusultra.user.name; + }; + + # flake-utils-plus + generateRegistryFromInputs = true; + generateNixPathFromInputs = true; + linkInputs = true; + }; + }; +} diff --git a/modules/darwin/security/gpg/default.nix b/modules/darwin/security/gpg/default.nix new file mode 100644 index 0000000..80a4961 --- /dev/null +++ b/modules/darwin/security/gpg/default.nix @@ -0,0 +1,84 @@ +{ lib, config, pkgs, inputs, ... }: + +let + inherit (lib) types mkEnableOption mkIf; + inherit (lib.plusultra) mkOpt; + + cfg = config.plusultra.security.gpg; + + gpgConf = "${inputs.gpg-base-conf}/gpg.conf"; + + gpgAgentConf = '' + enable-ssh-support + default-cache-ttl 60 + max-cache-ttl 120 + ''; + + guide = "${inputs.yubikey-guide}/README.md"; + + theme = pkgs.fetchFromGitHub { + owner = "jez"; + repo = "pandoc-markdown-css-theme"; + rev = "019a4829242937761949274916022e9861ed0627"; + sha256 = "1h48yqffpaz437f3c9hfryf23r95rr319lrb3y79kxpxbc9hihxb"; + }; + + guideHTML = pkgs.runCommand "yubikey-guide" { } '' + ${pkgs.pandoc}/bin/pandoc \ + --standalone \ + --metadata title="Yubikey Guide" \ + --from markdown \ + --to html5+smart \ + --toc \ + --template ${theme}/template.html5 \ + --css ${theme}/docs/css/theme.css \ + --css ${theme}/docs/css/skylighting-solarized-theme.css \ + -o $out \ + ${guide} + ''; + + reload-yubikey = pkgs.writeShellScriptBin "reload-yubikey" '' + ${pkgs.gnupg}/bin/gpg-connect-agent "scd serialno" "learn --force" /bye + ''; +in +{ + options.plusultra.security.gpg = { + enable = mkEnableOption "GPG"; + agentTimeout = mkOpt types.int 5 "The amount of time to wait before continuing with shell init."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + gnupg + ]; + + environment.shellInit = '' + export GPG_TTY="$(tty)" + export SSH_AUTH_SOCK=$(${pkgs.gnupg}/bin/gpgconf --list-dirs agent-ssh-socket) + + ${pkgs.coreutils}/bin/timeout ${builtins.toString cfg.agentTimeout} ${pkgs.gnupg}/bin/gpgconf --launch gpg-agent + gpg_agent_timeout_status=$? + + if [ "$gpg_agent_timeout_status" = 124 ]; then + # Command timed out... + echo "GPG Agent timed out..." + echo 'Run "gpgconf --launch gpg-agent" to try and launch it again.' + fi + ''; + + programs.gnupg.agent = { + enable = true; + enableSSHSupport = true; + }; + + plusultra.home.file = { + ".gnupg/.keep".text = ""; + + ".gnupg/yubikey-guide.md".source = guide; + ".gnupg/yubikey-guide.html".source = guideHTML; + + ".gnupg/gpg.conf".source = gpgConf; + ".gnupg/gpg-agent.conf".text = gpgAgentConf; + }; + }; +} diff --git a/modules/darwin/services/nix-daemon/default.nix b/modules/darwin/services/nix-daemon/default.nix new file mode 100644 index 0000000..020ea6d --- /dev/null +++ b/modules/darwin/services/nix-daemon/default.nix @@ -0,0 +1,17 @@ +{ lib, config, ... }: + +let + inherit (lib) types mkIf; + inherit (lib.plusultra) mkOpt enabled; + + cfg = config.plusultra.services.nix-daemon; +in +{ + options.plusultra.services.nix-daemon = { + enable = mkOpt types.bool true "Whether to enable the Nix daemon."; + }; + + config = mkIf cfg.enable { + services.nix-daemon = enabled; + }; +} diff --git a/modules/darwin/suites/common/default.nix b/modules/darwin/suites/common/default.nix new file mode 100644 index 0000000..485971f --- /dev/null +++ b/modules/darwin/suites/common/default.nix @@ -0,0 +1,43 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.suites.common; +in +{ + options.plusultra.suites.common = with types; { + enable = mkBoolOpt false "Whether or not to enable common configuration."; + }; + + config = mkIf cfg.enable { + programs.zsh = enabled; + + plusultra = { + nix = enabled; + + apps = { + iterm2 = enabled; + }; + + cli-apps = { + neovim = enabled; + }; + + tools = { + git = enabled; + flake = enabled; + }; + + system = { + fonts = enabled; + input = enabled; + interface = enabled; + }; + + security = { + gpg = enabled; + }; + }; + }; +} diff --git a/modules/darwin/suites/development/default.nix b/modules/darwin/suites/development/default.nix new file mode 100644 index 0000000..cafd387 --- /dev/null +++ b/modules/darwin/suites/development/default.nix @@ -0,0 +1,35 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.suites.development; +in +{ + options.plusultra.suites.development = with types; { + enable = mkBoolOpt false + "Whether or not to enable common development configuration."; + }; + + config = mkIf cfg.enable { + plusultra = { + apps = { + vscode = enabled; + }; + + tools = { + # at = enabled; + # direnv = enabled; + # go = enabled; + # http = enabled; + # k8s = enabled; + node = enabled; + # titan = enabled; + python = enabled; + java = enabled; + }; + + # virtualisation = { podman = enabled; }; + }; + }; +} diff --git a/modules/darwin/system/fonts/default.nix b/modules/darwin/system/fonts/default.nix new file mode 100644 index 0000000..b1483e0 --- /dev/null +++ b/modules/darwin/system/fonts/default.nix @@ -0,0 +1,32 @@ +{ options, config, pkgs, lib, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.system.fonts; +in +{ + options.plusultra.system.fonts = with types; { + enable = mkBoolOpt false "Whether or not to manage fonts."; + fonts = mkOpt (listOf package) [ ] "Custom font packages to install."; + }; + + config = mkIf cfg.enable { + environment.variables = { + # Enable icons in tooling since we have nerdfonts. + LOG_ICONS = "true"; + }; + + fonts = { + fontDir = enabled; + + fonts = with pkgs; + [ + noto-fonts + noto-fonts-cjk-sans + noto-fonts-cjk-serif + noto-fonts-emoji + (nerdfonts.override { fonts = [ "Hack" ]; }) + ] ++ cfg.fonts; + }; + }; +} diff --git a/modules/darwin/system/input/DefaultKeyBinding.dict b/modules/darwin/system/input/DefaultKeyBinding.dict new file mode 100644 index 0000000..b65cc38 --- /dev/null +++ b/modules/darwin/system/input/DefaultKeyBinding.dict @@ -0,0 +1 @@ +{ "~a" = (); "~b" = (); "~c" = (); "~d" = (); "~e" = (); "~f" = (); "~g" = (); "~h" = (); "~i" = (); "~j" = (); "~k" = (); "~l" = (); "~m" = (); "~n" = (); "~o" = (); "~p" = (); "~q" = (); "~r" = (); "~s" = (); "~t" = (); "~u" = (); "~v" = (); "~w" = (); "~x" = (); "~y" = (); "~z" = (); } diff --git a/modules/darwin/system/input/default.nix b/modules/darwin/system/input/default.nix new file mode 100644 index 0000000..dfcbec5 --- /dev/null +++ b/modules/darwin/system/input/default.nix @@ -0,0 +1,55 @@ +{ options, config, pkgs, lib, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.system.input; +in +{ + options.plusultra.system.input = with types; { + enable = mkEnableOption "macOS input"; + }; + + config = mkIf cfg.enable (mkMerge [ + { + system = { + keyboard = { + enableKeyMapping = true; + remapCapsLockToEscape = true; + }; + + defaults = { + ".GlobalPreferences" = { + "com.apple.mouse.scaling" = "1"; + }; + + NSGlobalDomain = { + AppleKeyboardUIMode = 3; + ApplePressAndHoldEnabled = false; + + KeyRepeat = 2; + InitialKeyRepeat = 15; + + NSAutomaticCapitalizationEnabled = false; + NSAutomaticDashSubstitutionEnabled = false; + NSAutomaticQuoteSubstitutionEnabled = false; + NSAutomaticPeriodSubstitutionEnabled = false; + NSAutomaticSpellingCorrectionEnabled = false; + }; + }; + }; + + snowfallorg.user.${config.plusultra.user.name}.home.config = { + home.activation = { + # Disable special keys when using Option as a modifier. + # https://superuser.com/questions/941286/disable-default-option-key-binding + disableSpecialKeys = lib.home-manager.hm.dag.entryAfter [ "writeBoundary" ] '' + set +e + $DRY_RUN_CMD /usr/bin/sudo mkdir -p $HOME/Library/KeyBindings + $DRY_RUN_CMD /usr/bin/sudo cp '${builtins.toPath ./DefaultKeyBinding.dict}' "$HOME/Library/KeyBindings/DefaultKeyBinding.dict" + set -e + ''; + }; + }; + } + ]); +} diff --git a/modules/darwin/system/interface/default.nix b/modules/darwin/system/interface/default.nix new file mode 100644 index 0000000..4ecfdf5 --- /dev/null +++ b/modules/darwin/system/interface/default.nix @@ -0,0 +1,29 @@ +{ options, config, pkgs, lib, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.system.interface; +in +{ + options.plusultra.system.interface = with types; { + enable = mkEnableOption "macOS interface"; + }; + + config = mkIf cfg.enable { + system.defaults = { + dock.autohide = true; + + finder = { + AppleShowAllExtensions = true; + FXEnableExtensionChangeWarning = false; + }; + + NSGlobalDomain = { + _HIHideMenuBar = true; + AppleShowScrollBars = "Always"; + }; + }; + + plusultra.home.file.".hushlogin".text = ""; + }; +} diff --git a/modules/darwin/tools/flake/default.nix b/modules/darwin/tools/flake/default.nix new file mode 100644 index 0000000..9f50aee --- /dev/null +++ b/modules/darwin/tools/flake/default.nix @@ -0,0 +1,18 @@ +{ lib, config, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.tools.flake; +in +{ + options.plusultra.tools.flake = { + enable = mkEnableOption "Flake"; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + snowfallorg.flake + ]; + }; +} diff --git a/modules/darwin/tools/git/default.nix b/modules/darwin/tools/git/default.nix new file mode 100644 index 0000000..c13ba67 --- /dev/null +++ b/modules/darwin/tools/git/default.nix @@ -0,0 +1,43 @@ +{ options, config, pkgs, lib, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.tools.git; + gpg = config.plusultra.security.gpg; + user = config.plusultra.user; +in +{ + options.plusultra.tools.git = with types; { + enable = mkBoolOpt false "Whether or not to install and configure git."; + userName = mkOpt types.str user.fullName "The name to configure git with."; + userEmail = mkOpt types.str user.email "The email to configure git with."; + signingKey = + mkOpt types.str "9762169A1B35EA68" "The key ID to sign commits with."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ git ]; + + plusultra.home.extraOptions = { + programs.git = { + enable = true; + inherit (cfg) userName userEmail; + lfs = enabled; + signing = { + key = cfg.signingKey; + signByDefault = mkIf gpg.enable true; + }; + extraConfig = { + init = { defaultBranch = "main"; }; + pull = { rebase = true; }; + push = { autoSetupRemote = true; }; + core = { whitespace = "trailing-space,space-before-tab"; }; + safe = { + directory = "${config.users.users.${user.name}.home}/work/config"; + }; + }; + }; + }; + }; +} diff --git a/modules/darwin/tools/java/default.nix b/modules/darwin/tools/java/default.nix new file mode 100644 index 0000000..aa51095 --- /dev/null +++ b/modules/darwin/tools/java/default.nix @@ -0,0 +1,17 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.tools.java; +in +{ + options.plusultra.tools.java = with types; { + enable = mkBoolOpt false "Whether or not to enable Java."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + jdk + ]; + }; +} diff --git a/modules/darwin/tools/node/default.nix b/modules/darwin/tools/node/default.nix new file mode 100644 index 0000000..9a70afd --- /dev/null +++ b/modules/darwin/tools/node/default.nix @@ -0,0 +1,42 @@ +{ options +, config +, pkgs +, lib +, ... +}: +with lib; +with lib.plusultra; let + cfg = config.plusultra.tools.node; +in +{ + options.plusultra.tools.node = with types; { + enable = mkBoolOpt false "Whether or not to install and configure git"; + pkg = mkOpt package pkgs.nodejs "The NodeJS package to use"; + prettier = { + enable = mkBoolOpt true "Whether or not to install Prettier"; + pkg = + mkOpt package pkgs.nodePackages.prettier "The NodeJS package to use"; + }; + yarn = { + enable = mkBoolOpt true "Whether or not to install Yarn"; + pkg = mkOpt package pkgs.nodePackages.yarn "The NodeJS package to use"; + }; + pnpm = { + enable = mkBoolOpt true "Whether or not to install Pnpm"; + pkg = mkOpt package pkgs.nodePackages.pnpm "The NodeJS package to use"; + }; + flyctl = { + enable = mkBoolOpt true "Whether or not to install flyctl"; + pkg = mkOpt package pkgs.flyctl "The flyctl package to use"; + }; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; + [ cfg.pkg ] + ++ (lib.optional cfg.prettier.enable cfg.prettier.pkg) + ++ (lib.optional cfg.yarn.enable cfg.yarn.pkg) + ++ (lib.optional cfg.pnpm.enable cfg.pnpm.pkg) + ++ (lib.optional cfg.flyctl.enable cfg.flyctl.pkg); + }; +} diff --git a/modules/darwin/tools/python/default.nix b/modules/darwin/tools/python/default.nix new file mode 100644 index 0000000..34de5a8 --- /dev/null +++ b/modules/darwin/tools/python/default.nix @@ -0,0 +1,22 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.tools.python; +in +{ + options.plusultra.tools.python = with types; { + enable = mkBoolOpt false "Whether or not to enable Python."; + }; + + config = + mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + (python311.withPackages (ps: + with ps; [ + numpy + ]) + ) + ]; + }; +} diff --git a/modules/darwin/user/default.nix b/modules/darwin/user/default.nix new file mode 100644 index 0000000..ccea8d3 --- /dev/null +++ b/modules/darwin/user/default.nix @@ -0,0 +1,45 @@ +{ lib +, config +, pkgs +, ... +}: +let + inherit (lib) types mkIf mkDefault; + inherit (lib.plusultra) mkOpt; + + cfg = config.plusultra.user; + + is-linux = pkgs.stdenv.isLinux; + is-darwin = pkgs.stdenv.isDarwin; +in +{ + options.plusultra.user = { + name = mkOpt types.str "short" "The user account."; + + fullName = mkOpt types.str "Jake Hamilton" "The full name of the user."; + email = mkOpt types.str "jake.hamilton@hey.com" "The email of the user."; + + uid = mkOpt (types.nullOr types.int) 501 "The uid for the user account."; + }; + + config = { + users.users.${cfg.name} = { + # NOTE: Setting the uid here is required for another + # module to evaluate successfully since it reads + # `users.users.${plusultra.user.name}.uid`. + uid = mkIf (cfg.uid != null) cfg.uid; + }; + + snowfallorg.user.${config.plusultra.user.name}.home.config = { + home = { + file = { + ".profile".text = '' + # The default file limit is far too low and throws an error when rebuilding the system. + # See the original with: ulimit -Sa + ulimit -n 4096 + ''; + }; + }; + }; + }; +} diff --git a/modules/nixos/apps/_1password/default.nix b/modules/nixos/apps/_1password/default.nix new file mode 100644 index 0000000..74eda46 --- /dev/null +++ b/modules/nixos/apps/_1password/default.nix @@ -0,0 +1,23 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps._1password; +in +{ + options.plusultra.apps._1password = with types; { + enable = mkBoolOpt false "Whether or not to enable 1password."; + }; + + config = mkIf cfg.enable { + programs = { + _1password = enabled; + _1password-gui = { + enable = true; + + polkitPolicyOwners = [ config.plusultra.user.name ]; + }; + }; + }; +} diff --git a/modules/nixos/apps/ardour/default.nix b/modules/nixos/apps/ardour/default.nix new file mode 100644 index 0000000..0049768 --- /dev/null +++ b/modules/nixos/apps/ardour/default.nix @@ -0,0 +1,15 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.ardour; +in +{ + options.plusultra.apps.ardour = with types; { + enable = mkBoolOpt false "Whether or not to enable Ardour."; + }; + + config = + mkIf cfg.enable { environment.systemPackages = with pkgs; [ ardour ]; }; +} diff --git a/modules/nixos/apps/blender/default.nix b/modules/nixos/apps/blender/default.nix new file mode 100644 index 0000000..4c8182b --- /dev/null +++ b/modules/nixos/apps/blender/default.nix @@ -0,0 +1,14 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.apps.blender; +in +{ + options.plusultra.apps.blender = with types; { + enable = mkBoolOpt false "Whether or not to enable Blender."; + }; + + config = + mkIf cfg.enable { environment.systemPackages = with pkgs; [ blender ]; }; +} diff --git a/modules/nixos/apps/bottles/default.nix b/modules/nixos/apps/bottles/default.nix new file mode 100644 index 0000000..b445f27 --- /dev/null +++ b/modules/nixos/apps/bottles/default.nix @@ -0,0 +1,14 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.apps.bottles; +in +{ + options.plusultra.apps.bottles = with types; { + enable = mkBoolOpt false "Whether or not to enable Bottles."; + }; + + config = + mkIf cfg.enable { environment.systemPackages = with pkgs; [ bottles ]; }; +} diff --git a/modules/nixos/apps/cadence/default.nix b/modules/nixos/apps/cadence/default.nix new file mode 100644 index 0000000..ae65bd8 --- /dev/null +++ b/modules/nixos/apps/cadence/default.nix @@ -0,0 +1,14 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.apps.cadence; +in +{ + options.plusultra.apps.cadence = with types; { + enable = mkBoolOpt false "Whether or not to enable Cadence."; + }; + + config = + mkIf cfg.enable { environment.systemPackages = with pkgs; [ cadence ]; }; +} diff --git a/modules/nixos/apps/discord/default.nix b/modules/nixos/apps/discord/default.nix new file mode 100644 index 0000000..fb1d5b8 --- /dev/null +++ b/modules/nixos/apps/discord/default.nix @@ -0,0 +1,41 @@ +{ options, config, lib, pkgs, inputs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.discord; + discord = lib.replugged.makeDiscordPlugged { + inherit pkgs; + + # This is currently broken, but could speed up Discord startup in the future. + withOpenAsar = false; + + plugins = { + inherit (inputs) discord-tweaks; + }; + + themes = { + inherit (inputs) discord-nord-theme; + }; + }; +in +{ + options.plusultra.apps.discord = with types; { + enable = mkBoolOpt false "Whether or not to enable Discord."; + canary.enable = mkBoolOpt false "Whether or not to enable Discord Canary."; + chromium.enable = mkBoolOpt false + "Whether or not to enable the Chromium version of Discord."; + firefox.enable = mkBoolOpt false + "Whether or not to enable the Firefox version of Discord."; + native.enable = mkBoolOpt false "Whether or not to enable the native version of Discord."; + }; + + config = mkIf (cfg.enable or cfg.chromium.enable) { + environment.systemPackages = + lib.optional cfg.enable discord + ++ lib.optional cfg.canary.enable pkgs.plusultra.discord + ++ lib.optional cfg.chromium.enable pkgs.plusultra.discord-chromium + ++ lib.optional cfg.firefox.enable pkgs.plusultra.discord-firefox + ++ lib.optional cfg.native.enable pkgs.discord; + }; +} diff --git a/modules/nixos/apps/dolphin/default.nix b/modules/nixos/apps/dolphin/default.nix new file mode 100644 index 0000000..7b69215 --- /dev/null +++ b/modules/nixos/apps/dolphin/default.nix @@ -0,0 +1,18 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.apps.dolphin; +in +{ + options.plusultra.apps.dolphin = with types; { + enable = mkBoolOpt false "Whether or not to enable Dolphin."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ dolphin-emu ]; + + # Enable GameCube controller support. + services.udev.packages = [ pkgs.dolphinEmu ]; + }; +} diff --git a/modules/nixos/apps/doukutsu-rs/default.nix b/modules/nixos/apps/doukutsu-rs/default.nix new file mode 100644 index 0000000..af267d2 --- /dev/null +++ b/modules/nixos/apps/doukutsu-rs/default.nix @@ -0,0 +1,30 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.doukutsu-rs; + desktopItem = pkgs.makeDesktopItem { + name = "doukutsu-rs"; + desktopName = "doukutsu-rs"; + genericName = + "A fully playable re-implementation of Cave Story (Doukutsu Monogatari) engine written in Rust."; + exec = "${pkgs.plusultra.doukutsu-rs}/bin/doukutsu-rs"; + icon = ./icon.png; + type = "Application"; + categories = [ "Game" "AdventureGame" ]; + terminal = false; + }; +in +{ + options.plusultra.apps.doukutsu-rs = with types; { + enable = mkBoolOpt false "Whether or not to enable doukutsu-rs."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs.plusultra; [ + doukutsu-rs + desktopItem + ]; + }; +} diff --git a/modules/nixos/apps/doukutsu-rs/icon.png b/modules/nixos/apps/doukutsu-rs/icon.png new file mode 100644 index 0000000..acf69cc Binary files /dev/null and b/modules/nixos/apps/doukutsu-rs/icon.png differ diff --git a/modules/nixos/apps/element/default.nix b/modules/nixos/apps/element/default.nix new file mode 100644 index 0000000..20c4591 --- /dev/null +++ b/modules/nixos/apps/element/default.nix @@ -0,0 +1,16 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.element; +in +{ + options.plusultra.apps.element = with types; { + enable = mkBoolOpt false "Whether or not to enable Element."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ element-desktop ]; + }; +} diff --git a/modules/nixos/apps/etcher/default.nix b/modules/nixos/apps/etcher/default.nix new file mode 100644 index 0000000..561f7fd --- /dev/null +++ b/modules/nixos/apps/etcher/default.nix @@ -0,0 +1,22 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.etcher; +in +{ + options.plusultra.apps.etcher = with types; { + enable = mkBoolOpt false "Whether or not to enable etcher."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; + [ + # Etcher is currently broken in nixpkgs, temporarily replaced with + # gnome disk utility. + # etcher + gnome.gnome-disk-utility + ]; + }; +} diff --git a/modules/nixos/apps/expressvpn/default.nix b/modules/nixos/apps/expressvpn/default.nix new file mode 100644 index 0000000..5664833 --- /dev/null +++ b/modules/nixos/apps/expressvpn/default.nix @@ -0,0 +1,35 @@ +{ lib, config, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.expressvpn; +in +{ + options.plusultra.apps.expressvpn = { + enable = mkEnableOption "Express VPN"; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + plusultra.expressvpn + ] ++ optionals config.plusultra.desktop.gnome.enable [ + gnomeExtensions.evpn-shell-assistant + ]; + + boot.kernelModules = [ "tun" ]; + + systemd.services.expressvpn = { + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" "network-online.target" ]; + + description = "ExpressVPN Daemon"; + + serviceConfig = { + ExecStart = "${pkgs.plusultra.expressvpn}/bin/expressvpnd"; + Restart = "on-failure"; + RestartSec = 5; + }; + }; + }; +} diff --git a/modules/nixos/apps/firefox/default.nix b/modules/nixos/apps/firefox/default.nix new file mode 100644 index 0000000..4b592e5 --- /dev/null +++ b/modules/nixos/apps/firefox/default.nix @@ -0,0 +1,66 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.firefox; + defaultSettings = { + "browser.aboutwelcome.enabled" = false; + "browser.meta_refresh_when_inactive.disabled" = true; + "browser.startup.homepage" = "https://hamho.me"; + "browser.bookmarks.showMobileBookmarks" = true; + "browser.urlbar.suggest.quicksuggest.sponsored" = false; + "browser.newtabpage.activity-stream.showSponsoredTopSites" = false; + "browser.aboutConfig.showWarning" = false; + "browser.ssb.enabled" = true; + }; +in +{ + options.plusultra.apps.firefox = with types; { + enable = mkBoolOpt false "Whether or not to enable Firefox."; + extraConfig = + mkOpt str "" "Extra configuration for the user profile JS file."; + userChrome = + mkOpt str "" "Extra configuration for the user chrome CSS file."; + settings = mkOpt attrs defaultSettings "Settings to apply to the profile."; + }; + + config = mkIf cfg.enable { + plusultra.desktop.addons.firefox-nordic-theme = enabled; + + services.gnome.gnome-browser-connector.enable = config.plusultra.desktop.gnome.enable; + + plusultra.home = { + file = { + ".mozilla/native-messaging-hosts/com.dannyvankooten.browserpass.json".source = "${pkgs.browserpass}/lib/mozilla/native-messaging-hosts/com.dannyvankooten.browserpass.json"; + + ".mozilla/native-messaging-hosts/org.gnome.chrome_gnome_shell.json".source = mkIf config.plusultra.desktop.gnome.enable "${pkgs.chrome-gnome-shell}/lib/mozilla/native-messaging-hosts/org.gnome.chrome_gnome_shell.json"; + }; + + extraOptions = { + programs.firefox = { + enable = true; + package = pkgs.firefox.override ( + { + cfg = { + enableBrowserpass = true; + enableGnomeExtensions = config.plusultra.desktop.gnome.enable; + }; + + extraNativeMessagingHosts = + optional + config.plusultra.desktop.gnome.enable + pkgs.gnomeExtensions.gsconnect; + } + ); + + profiles.${config.plusultra.user.name} = { + inherit (cfg) extraConfig userChrome settings; + id = 0; + name = config.plusultra.user.name; + }; + }; + }; + }; + }; +} diff --git a/modules/nixos/apps/frappe-books/default.nix b/modules/nixos/apps/frappe-books/default.nix new file mode 100644 index 0000000..9259548 --- /dev/null +++ b/modules/nixos/apps/frappe-books/default.nix @@ -0,0 +1,16 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.frappe-books; +in +{ + options.plusultra.apps.frappe-books = with types; { + enable = mkBoolOpt false "Whether or not to enable FrappeBooks."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ plusultra.frappe-books ]; + }; +} diff --git a/modules/nixos/apps/freetube/default.nix b/modules/nixos/apps/freetube/default.nix new file mode 100644 index 0000000..ff8f613 --- /dev/null +++ b/modules/nixos/apps/freetube/default.nix @@ -0,0 +1,15 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.freetube; +in +{ + options.plusultra.apps.freetube = with types; { + enable = mkBoolOpt false "Whether or not to enable FreeTube."; + }; + + config = + mkIf cfg.enable { environment.systemPackages = with pkgs; [ freetube ]; }; +} diff --git a/modules/nixos/apps/gimp/default.nix b/modules/nixos/apps/gimp/default.nix new file mode 100644 index 0000000..4eb46fb --- /dev/null +++ b/modules/nixos/apps/gimp/default.nix @@ -0,0 +1,15 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.gimp; +in +{ + options.plusultra.apps.gimp = with types; { + enable = mkBoolOpt false "Whether or not to enable Gimp."; + }; + + config = + mkIf cfg.enable { environment.systemPackages = with pkgs; [ gimp ]; }; +} diff --git a/modules/nixos/apps/gparted/default.nix b/modules/nixos/apps/gparted/default.nix new file mode 100644 index 0000000..5ac7eaa --- /dev/null +++ b/modules/nixos/apps/gparted/default.nix @@ -0,0 +1,15 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.gparted; +in +{ + options.plusultra.apps.gparted = with types; { + enable = mkBoolOpt false "Whether or not to enable gparted."; + }; + + config = + mkIf cfg.enable { environment.systemPackages = with pkgs; [ gparted ]; }; +} diff --git a/modules/nixos/apps/hey/default.nix b/modules/nixos/apps/hey/default.nix new file mode 100644 index 0000000..1e391c5 --- /dev/null +++ b/modules/nixos/apps/hey/default.nix @@ -0,0 +1,14 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.hey; +in +{ + options.plusultra.apps.hey = with types; { + enable = mkBoolOpt false "Whether or not to enable HEY."; + }; + + config = mkIf cfg.enable { environment.systemPackages = with pkgs.plusultra; [ hey ]; }; +} diff --git a/modules/nixos/apps/inkscape/default.nix b/modules/nixos/apps/inkscape/default.nix new file mode 100644 index 0000000..49886af --- /dev/null +++ b/modules/nixos/apps/inkscape/default.nix @@ -0,0 +1,15 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.apps.inkscape; +in +{ + options.plusultra.apps.inkscape = with types; { + enable = mkBoolOpt false "Whether or not to enable Inkscape."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ inkscape-with-extensions google-fonts ]; + }; +} diff --git a/modules/nixos/apps/logseq/default.nix b/modules/nixos/apps/logseq/default.nix new file mode 100644 index 0000000..9f0eefa --- /dev/null +++ b/modules/nixos/apps/logseq/default.nix @@ -0,0 +1,14 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.apps.logseq; +in +{ + options.plusultra.apps.logseq = with types; { + enable = mkBoolOpt false "Whether or not to enable logseq."; + }; + + config = + mkIf cfg.enable { environment.systemPackages = with pkgs; [ logseq ]; }; +} diff --git a/modules/nixos/apps/looking-glass-client/client.ini b/modules/nixos/apps/looking-glass-client/client.ini new file mode 100644 index 0000000..01348d1 --- /dev/null +++ b/modules/nixos/apps/looking-glass-client/client.ini @@ -0,0 +1,9 @@ +[input] +escapeKey=56 +rawMouse=yes +mouseSens=6 + +[win] +size=1920x1080 +autoResize=yes +quickSplash=yes diff --git a/modules/nixos/apps/looking-glass-client/default.nix b/modules/nixos/apps/looking-glass-client/default.nix new file mode 100644 index 0000000..a6b329f --- /dev/null +++ b/modules/nixos/apps/looking-glass-client/default.nix @@ -0,0 +1,23 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.looking-glass-client; + user = config.plusultra.user; +in +{ + options.plusultra.apps.looking-glass-client = with types; { + enable = + mkBoolOpt false "Whether or not to enable the Looking Glass client."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ looking-glass-client ]; + + environment.etc."looking-glass-client.ini" = { + user = "+${toString config.users.users.${user.name}.uid}"; + source = ./client.ini; + }; + }; +} diff --git a/modules/nixos/apps/lutris/default.nix b/modules/nixos/apps/lutris/default.nix new file mode 100644 index 0000000..8cd088d --- /dev/null +++ b/modules/nixos/apps/lutris/default.nix @@ -0,0 +1,21 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.lutris; +in +{ + options.plusultra.apps.lutris = with types; { + enable = mkBoolOpt false "Whether or not to enable Lutris."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + lutris + # Needed for some installers like League of Legends + openssl + gnome.zenity + ]; + }; +} diff --git a/modules/nixos/apps/obs/default.nix b/modules/nixos/apps/obs/default.nix new file mode 100644 index 0000000..f0cc035 --- /dev/null +++ b/modules/nixos/apps/obs/default.nix @@ -0,0 +1,25 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.obs; +in +{ + options.plusultra.apps.obs = with types; { + enable = mkBoolOpt false "Whether or not to enable support for OBS."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = [ + (pkgs.wrapOBS { + plugins = with pkgs.obs-studio-plugins; [ + wlrobs + obs-multi-rtmp + obs-move-transition + looking-glass-obs + ]; + }) + ]; + }; +} diff --git a/modules/nixos/apps/pcsx2/default.nix b/modules/nixos/apps/pcsx2/default.nix new file mode 100644 index 0000000..b35d6b9 --- /dev/null +++ b/modules/nixos/apps/pcsx2/default.nix @@ -0,0 +1,15 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.pcsx2; +in +{ + options.plusultra.apps.pcsx2 = with types; { + enable = mkBoolOpt false "Whether or not to enable PCSX2."; + }; + + config = + mkIf cfg.enable { environment.systemPackages = with pkgs; [ pcsx2 ]; }; +} diff --git a/modules/nixos/apps/pitivi/default.nix b/modules/nixos/apps/pitivi/default.nix new file mode 100644 index 0000000..7398ba7 --- /dev/null +++ b/modules/nixos/apps/pitivi/default.nix @@ -0,0 +1,15 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.pitivi; +in +{ + options.plusultra.apps.pitivi = with types; { + enable = mkBoolOpt false "Whether or not to enable Pitivi."; + }; + + config = + mkIf cfg.enable { environment.systemPackages = with pkgs; [ pitivi ]; }; +} diff --git a/modules/nixos/apps/pocketcasts/default.nix b/modules/nixos/apps/pocketcasts/default.nix new file mode 100644 index 0000000..3d2463f --- /dev/null +++ b/modules/nixos/apps/pocketcasts/default.nix @@ -0,0 +1,16 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.pocketcasts; +in +{ + options.plusultra.apps.pocketcasts = with types; { + enable = mkBoolOpt false "Whether or not to enable Pocketcasts."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs.plusultra; [ pocketcasts ]; + }; +} diff --git a/modules/nixos/apps/prismlauncher/default.nix b/modules/nixos/apps/prismlauncher/default.nix new file mode 100644 index 0000000..ae692bd --- /dev/null +++ b/modules/nixos/apps/prismlauncher/default.nix @@ -0,0 +1,15 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.prismlauncher; +in +{ + options.plusultra.apps.prismlauncher = with types; { + enable = mkBoolOpt false "Whether or not to enable Prism Launcher."; + }; + + config = + mkIf cfg.enable { environment.systemPackages = with pkgs; [ prismlauncher ]; }; +} diff --git a/modules/nixos/apps/protontricks/default.nix b/modules/nixos/apps/protontricks/default.nix new file mode 100644 index 0000000..2c2c12d --- /dev/null +++ b/modules/nixos/apps/protontricks/default.nix @@ -0,0 +1,16 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.protontricks; +in +{ + options.plusultra.apps.protontricks = with types; { + enable = mkBoolOpt false "Whether or not to enable Protontricks."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ protontricks ]; + }; +} diff --git a/modules/nixos/apps/r2modman/default.nix b/modules/nixos/apps/r2modman/default.nix new file mode 100644 index 0000000..9dc7190 --- /dev/null +++ b/modules/nixos/apps/r2modman/default.nix @@ -0,0 +1,18 @@ +{ options +, config +, lib +, pkgs +, ... +}: +with lib; +with lib.plusultra; let + cfg = config.plusultra.apps.r2modman; +in +{ + options.plusultra.apps.r2modman = with types; { + enable = mkBoolOpt false "Whether or not to enable r2modman."; + }; + + config = + mkIf cfg.enable { environment.systemPackages = with pkgs; [ r2modman ]; }; +} diff --git a/modules/nixos/apps/rpcs3/default.nix b/modules/nixos/apps/rpcs3/default.nix new file mode 100644 index 0000000..ed28244 --- /dev/null +++ b/modules/nixos/apps/rpcs3/default.nix @@ -0,0 +1,16 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.rpcs3; +in +{ + options.plusultra.apps.rpcs3 = with types; { + enable = mkBoolOpt false "Whether or not to enable rpcs3."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ rpcs3 ]; + }; +} diff --git a/modules/nixos/apps/steam/default.nix b/modules/nixos/apps/steam/default.nix new file mode 100644 index 0000000..3cadadc --- /dev/null +++ b/modules/nixos/apps/steam/default.nix @@ -0,0 +1,30 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.steam; +in +{ + options.plusultra.apps.steam = with types; { + enable = mkBoolOpt false "Whether or not to enable support for Steam."; + }; + + config = mkIf cfg.enable { + programs.steam.enable = true; + programs.steam.remotePlay.openFirewall = true; + + hardware.steam-hardware.enable = true; + + # Enable GameCube controller support. + services.udev.packages = [ pkgs.dolphinEmu ]; + + environment.systemPackages = with pkgs.plusultra; [ + steam + ]; + + environment.sessionVariables = { + STEAM_EXTRA_COMPAT_TOOLS_PATHS = "$HOME/.steam/root/compatibilitytools.d"; + }; + }; +} diff --git a/modules/nixos/apps/steamtinkerlaunch/default.nix b/modules/nixos/apps/steamtinkerlaunch/default.nix new file mode 100644 index 0000000..39b7577 --- /dev/null +++ b/modules/nixos/apps/steamtinkerlaunch/default.nix @@ -0,0 +1,18 @@ +{ lib, pkgs, config, ... }: + +let + cfg = config.plusultra.apps.steamtinkerlaunch; + + inherit (lib) mkIf mkEnableOption; +in +{ + options.plusultra.apps.steamtinkerlaunch = { + enable = mkEnableOption "Steam Tinker Launch"; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + steamtinkerlaunch + ]; + }; +} diff --git a/modules/nixos/apps/twitter/default.nix b/modules/nixos/apps/twitter/default.nix new file mode 100644 index 0000000..9992996 --- /dev/null +++ b/modules/nixos/apps/twitter/default.nix @@ -0,0 +1,15 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.twitter; +in +{ + options.plusultra.apps.twitter = with types; { + enable = mkBoolOpt false "Whether or not to enable Twitter."; + }; + + config = + mkIf cfg.enable { environment.systemPackages = with pkgs.plusultra; [ twitter ]; }; +} diff --git a/modules/nixos/apps/ubports-installer/default.nix b/modules/nixos/apps/ubports-installer/default.nix new file mode 100644 index 0000000..e000d51 --- /dev/null +++ b/modules/nixos/apps/ubports-installer/default.nix @@ -0,0 +1,23 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.ubports-installer; +in +{ + options.plusultra.apps.ubports-installer = with types; { + enable = mkBoolOpt false "Whether or not to enable the UBPorts Installer."; + }; + + config = + mkIf cfg.enable { + environment.systemPackages = with pkgs.plusultra; [ + ubports-installer + ]; + + services.udev.packages = with pkgs.plusultra; [ + ubports-installer-udev-rules + ]; + }; +} diff --git a/modules/nixos/apps/virtualbox/default.nix b/modules/nixos/apps/virtualbox/default.nix new file mode 100644 index 0000000..c04418f --- /dev/null +++ b/modules/nixos/apps/virtualbox/default.nix @@ -0,0 +1,21 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.virtualbox; +in +{ + options.plusultra.apps.virtualbox = with types; { + enable = mkBoolOpt false "Whether or not to enable Virtualbox."; + }; + + config = mkIf cfg.enable { + virtualisation.virtualbox.host = { + enable = true; + enableExtensionPack = true; + }; + + plusultra.user.extraGroups = [ "vboxusers" ]; + }; +} diff --git a/modules/nixos/apps/vlc/default.nix b/modules/nixos/apps/vlc/default.nix new file mode 100644 index 0000000..d7b13de --- /dev/null +++ b/modules/nixos/apps/vlc/default.nix @@ -0,0 +1,14 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.vlc; +in +{ + options.plusultra.apps.vlc = with types; { + enable = mkBoolOpt false "Whether or not to enable vlc."; + }; + + config = mkIf cfg.enable { environment.systemPackages = with pkgs; [ vlc ]; }; +} diff --git a/modules/nixos/apps/vscode/default.nix b/modules/nixos/apps/vscode/default.nix new file mode 100644 index 0000000..60d0d5b --- /dev/null +++ b/modules/nixos/apps/vscode/default.nix @@ -0,0 +1,14 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.apps.vscode; +in +{ + options.plusultra.apps.vscode = with types; { + enable = mkBoolOpt false "Whether or not to enable vscode."; + }; + + config = + mkIf cfg.enable { environment.systemPackages = with pkgs; [ vscode ]; }; +} diff --git a/modules/nixos/apps/winetricks/default.nix b/modules/nixos/apps/winetricks/default.nix new file mode 100644 index 0000000..1775c14 --- /dev/null +++ b/modules/nixos/apps/winetricks/default.nix @@ -0,0 +1,15 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.winetricks; +in +{ + options.plusultra.apps.winetricks = with types; { + enable = mkBoolOpt false "Whether or not to enable Winetricks."; + }; + + config = + mkIf cfg.enable { environment.systemPackages = with pkgs; [ winetricks ]; }; +} diff --git a/modules/nixos/apps/yt-music/default.nix b/modules/nixos/apps/yt-music/default.nix new file mode 100644 index 0000000..cdd794e --- /dev/null +++ b/modules/nixos/apps/yt-music/default.nix @@ -0,0 +1,15 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.yt-music; +in +{ + options.plusultra.apps.yt-music = with types; { + enable = mkBoolOpt false "Whether or not to enable YouTube Music."; + }; + + config = + mkIf cfg.enable { environment.systemPackages = with pkgs.plusultra; [ yt-music ]; }; +} diff --git a/modules/nixos/apps/yubikey/default.nix b/modules/nixos/apps/yubikey/default.nix new file mode 100644 index 0000000..23b6b01 --- /dev/null +++ b/modules/nixos/apps/yubikey/default.nix @@ -0,0 +1,17 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.yubikey; +in +{ + options.plusultra.apps.yubikey = with types; { + enable = mkBoolOpt false "Whether or not to enable Yubikey."; + }; + + config = mkIf cfg.enable { + services.yubikey-agent.enable = true; + environment.systemPackages = with pkgs; [ yubikey-manager-qt ]; + }; +} diff --git a/modules/nixos/apps/yuzu/default.nix b/modules/nixos/apps/yuzu/default.nix new file mode 100644 index 0000000..1b495db --- /dev/null +++ b/modules/nixos/apps/yuzu/default.nix @@ -0,0 +1,16 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.apps.yuzu; +in +{ + options.plusultra.apps.yuzu = with types; { + enable = mkBoolOpt false "Whether or not to enable Yuzu."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ yuzu-mainline ]; + }; +} diff --git a/modules/nixos/archetypes/gaming/default.nix b/modules/nixos/archetypes/gaming/default.nix new file mode 100644 index 0000000..42eb950 --- /dev/null +++ b/modules/nixos/archetypes/gaming/default.nix @@ -0,0 +1,22 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.archetypes.gaming; +in +{ + options.plusultra.archetypes.gaming = with types; { + enable = mkBoolOpt false "Whether or not to enable the gaming archetype."; + }; + + config = mkIf cfg.enable { + plusultra.suites = { + common = enabled; + desktop = enabled; + games = enabled; + social = enabled; + media = enabled; + }; + }; +} diff --git a/modules/nixos/archetypes/server/default.nix b/modules/nixos/archetypes/server/default.nix new file mode 100644 index 0000000..6a79a5d --- /dev/null +++ b/modules/nixos/archetypes/server/default.nix @@ -0,0 +1,26 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.archetypes.server; +in +{ + options.plusultra.archetypes.server = with types; { + enable = + mkBoolOpt false "Whether or not to enable the server archetype."; + }; + + config = mkIf cfg.enable { + plusultra = { + suites = { + common-slim = enabled; + }; + + cli-apps = { + neovim = enabled; + tmux = enabled; + }; + }; + }; +} diff --git a/modules/nixos/archetypes/workstation/default.nix b/modules/nixos/archetypes/workstation/default.nix new file mode 100644 index 0000000..b487c41 --- /dev/null +++ b/modules/nixos/archetypes/workstation/default.nix @@ -0,0 +1,29 @@ +{ options, config, lib, pkgs, ... }: +with lib; +with lib.plusultra; +let cfg = config.plusultra.archetypes.workstation; +in +{ + options.plusultra.archetypes.workstation = with types; { + enable = + mkBoolOpt false "Whether or not to enable the workstation archetype."; + }; + + config = mkIf cfg.enable { + plusultra = { + suites = { + common = enabled; + desktop = enabled; + development = enabled; + art = enabled; + video = enabled; + social = enabled; + media = enabled; + }; + + tools = { + appimage-run = enabled; + }; + }; + }; +} diff --git a/modules/nixos/cache/public/default.nix b/modules/nixos/cache/public/default.nix new file mode 100644 index 0000000..f06e89b --- /dev/null +++ b/modules/nixos/cache/public/default.nix @@ -0,0 +1,18 @@ +{ config, lib, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.cache.public; +in +{ + options.plusultra.cache.public = { + enable = mkEnableOption "Plus Ultra public cache"; + }; + + config = mkIf cfg.enable { + plusultra.nix.extra-substituters = { + "https://attic.ruby.hamho.me/public".key = "public:QUkZTErD8fx9HQ64kuuEUZHO9tXNzws7chV8qy/KLUk="; + }; + }; +} diff --git a/modules/nixos/cli-apps/flake/default.nix b/modules/nixos/cli-apps/flake/default.nix new file mode 100644 index 0000000..28559c6 --- /dev/null +++ b/modules/nixos/cli-apps/flake/default.nix @@ -0,0 +1,18 @@ +inputs@{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.cli-apps.flake; +in +{ + options.plusultra.cli-apps.flake = with types; { + enable = mkBoolOpt false "Whether or not to enable flake."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + snowfallorg.flake + ]; + }; +} diff --git a/modules/nixos/cli-apps/neovim/default.nix b/modules/nixos/cli-apps/neovim/default.nix new file mode 100644 index 0000000..be46ce9 --- /dev/null +++ b/modules/nixos/cli-apps/neovim/default.nix @@ -0,0 +1,47 @@ +inputs @ { options +, config +, lib +, pkgs +, ... +}: +with lib; +with lib.plusultra; let + cfg = config.plusultra.cli-apps.neovim; +in +{ + options.plusultra.cli-apps.neovim = with types; { + enable = mkBoolOpt false "Whether or not to enable neovim."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + # FIXME: As of today (2022-12-09), `page` no longer works with my Neovim + # configuration. Either something in my configuration is breaking it or `page` is busted. + # page + plusultra.neovim + ]; + + environment.variables = { + # PAGER = "page"; + # MANPAGER = + # "page -C -e 'au User PageDisconnect sleep 100m|%y p|enew! |bd! #|pu p|set ft=man'"; + PAGER = "less"; + MANPAGER = "less"; + NPM_CONFIG_PREFIX = "$HOME/.npm-global"; + EDITOR = "nvim"; + }; + + plusultra.home = { + configFile = { + "dashboard-nvim/.keep".text = ""; + }; + + extraOptions = { + # Use Neovim for Git diffs. + programs.zsh.shellAliases.vimdiff = "nvim -d"; + programs.bash.shellAliases.vimdiff = "nvim -d"; + programs.fish.shellAliases.vimdiff = "nvim -d"; + }; + }; + }; +} diff --git a/modules/nixos/cli-apps/prisma/default.nix b/modules/nixos/cli-apps/prisma/default.nix new file mode 100644 index 0000000..2b66371 --- /dev/null +++ b/modules/nixos/cli-apps/prisma/default.nix @@ -0,0 +1,31 @@ +{ lib, pkgs, config, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.cli-apps.prisma; +in +{ + options.plusultra.cli-apps.prisma = with types; { + enable = mkBoolOpt false "Whether or not to install Prisma"; + pkgs = { + npm = mkOpt package pkgs.nodePackages.prisma "The NPM package to install"; + engines = mkOpt package pkgs.prisma-engines + "The package to get prisma engines from"; + }; + }; + + config = mkIf cfg.enable { + environment.systemPackages = [ cfg.pkgs.npm ]; + + plusultra.home.extraOptions = { + programs.zsh.initExtra = '' + export PRISMA_MIGRATION_ENGINE_BINARY="${cfg.pkgs.engines}/bin/migration-engine" + export PRISMA_QUERY_ENGINE_BINARY="${cfg.pkgs.engines}/bin/query-engine" + export PRISMA_QUERY_ENGINE_LIBRARY="${cfg.pkgs.engines}/lib/libquery_engine.node" + export PRISMA_INTROSPECTION_ENGINE_BINARY="${cfg.pkgs.engines}/bin/introspection-engine" + export PRISMA_FMT_BINARY="${cfg.pkgs.engines}/bin/prisma-fmt" + ''; + }; + }; +} diff --git a/modules/nixos/cli-apps/proton/default.nix b/modules/nixos/cli-apps/proton/default.nix new file mode 100644 index 0000000..7e7c6ce --- /dev/null +++ b/modules/nixos/cli-apps/proton/default.nix @@ -0,0 +1,16 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.cli-apps.proton; +in +{ + options.plusultra.cli-apps.proton = with types; { + enable = mkBoolOpt false "Whether or not to enable Proton."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ proton-caller ]; + }; +} diff --git a/modules/nixos/cli-apps/thaw/default.nix b/modules/nixos/cli-apps/thaw/default.nix new file mode 100644 index 0000000..2e0e1e9 --- /dev/null +++ b/modules/nixos/cli-apps/thaw/default.nix @@ -0,0 +1,20 @@ +{ config +, lib +, pkgs +, ... +}: +with lib; +with lib.plusultra; let + cfg = config.plusultra.cli-apps.thaw; +in +{ + options.plusultra.cli-apps.thaw = with types; { + enable = mkBoolOpt false "Whether or not to enable thaw."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + snowfallorg.thaw + ]; + }; +} diff --git a/modules/nixos/cli-apps/tmux/config/continuum.tmux b/modules/nixos/cli-apps/tmux/config/continuum.tmux new file mode 100644 index 0000000..4b178e8 --- /dev/null +++ b/modules/nixos/cli-apps/tmux/config/continuum.tmux @@ -0,0 +1,5 @@ +# Enable saving sessions. +set -g @continuum-restore 'on' + +# Save every 30 minutes. +set -g @continuum-save-interval '30' diff --git a/modules/nixos/cli-apps/tmux/config/extrakto.tmux b/modules/nixos/cli-apps/tmux/config/extrakto.tmux new file mode 100644 index 0000000..e153d22 --- /dev/null +++ b/modules/nixos/cli-apps/tmux/config/extrakto.tmux @@ -0,0 +1,15 @@ +# Create a vertical split to show search & results to keep +# the content visible. +set -g @extrakto_split_direction "v" + +# Override the way that Extrakto copies text. By default +# it was trying to use xclip and would not properly pick +# up on $XDG_SESSION_TYPE being wayland. Instead, use +# Tmux's built-in clipboard functionality. +set -g @extrakto_clip_tool_run "tmux_osc52" + +# FIXME: The current version of Extrakto in +# NixPkgs is out of date and does not support wayland. +# This overrides the clipping tool to ensure that it works +# under wayland. +set -g @extrakto_clip_tool "wl-copy" diff --git a/modules/nixos/cli-apps/tmux/config/fzf.tmux b/modules/nixos/cli-apps/tmux/config/fzf.tmux new file mode 100644 index 0000000..4252bf6 --- /dev/null +++ b/modules/nixos/cli-apps/tmux/config/fzf.tmux @@ -0,0 +1,2 @@ +# Change default keybinding. +TMUX_FZF_LAUNCH_KEY="C-f" diff --git a/modules/nixos/cli-apps/tmux/config/general.tmux b/modules/nixos/cli-apps/tmux/config/general.tmux new file mode 100644 index 0000000..2db919d --- /dev/null +++ b/modules/nixos/cli-apps/tmux/config/general.tmux @@ -0,0 +1,38 @@ +# Lower delay waiting for chord after escape key press. +set -g escape-time 0 + +# Change the prefix from C-b to C-s to make it easier to type. +set -g prefix C-s +unbind C-b +bind C-s send-prefix + +# Start window numbers at 1 rather than 0. +set -g base-index 1 + +# Use h, j, k, l for movement between panes. +bind h select-pane -L +bind j select-pane -D +bind k select-pane -U +bind l select-pane -R + +# Fix colors being wrong in programs like Neovim. +set-option -ga terminal-overrides ",xterm-256color:Tc" + +# Expand the left status to accomodate longer session names. +set-option -g status-left-length 20 + +# One of the plugins binds C-l, make sure we have accces to it. +unbind C-l +bind -n C-l send-keys C-l + +# Don't require a prompt to detach from the current session. +unbind -n M-E +bind -n M-E detach-client + +# Reload tmux configuration from ~/.config/tmux/tmux.conf instead +# of Tilish's default of ~/.tmux.conf. +unbind -n M-C +bind -n M-C source-file "~/.config/tmux/tmux.conf" + +# Use M-z to zoom and unzoom panes. +bind -n M-z resize-pane -Z diff --git a/modules/nixos/cli-apps/tmux/config/navigator.tmux b/modules/nixos/cli-apps/tmux/config/navigator.tmux new file mode 100644 index 0000000..3358729 --- /dev/null +++ b/modules/nixos/cli-apps/tmux/config/navigator.tmux @@ -0,0 +1,21 @@ +# Smart pane switching with awareness of Vim splits. +# See: https://github.com/christoomey/vim-tmux-navigator +is_vim="ps -o state= -o comm= -t '#{pane_tty}' \ + | grep -iqE '^[^TXZ ]+ +(\\S+\\/)?g?(view|n?vim?x?)(diff)?$'" + +bind-key -n 'M-h' if-shell "$is_vim" 'send-keys M-h' 'select-pane -L' +bind-key -n 'M-j' if-shell "$is_vim" 'send-keys M-j' 'select-pane -D' +bind-key -n 'M-k' if-shell "$is_vim" 'send-keys M-k' 'select-pane -U' +bind-key -n 'M-l' if-shell "$is_vim" 'send-keys M-l' 'select-pane -R' +tmux_version='$(tmux -V | sed -En "s/^tmux ([0-9]+(.[0-9]+)?).*/\1/p")' +if-shell -b '[ "$(echo "$tmux_version < 3.0" | bc)" = 1 ]' \ + "bind-key -n 'M-\\' if-shell \"$is_vim\" 'send-keys M-\\' 'select-pane -l'" +if-shell -b '[ "$(echo "$tmux_version >= 3.0" | bc)" = 1 ]' \ + "bind-key -n 'M-\\' if-shell \"$is_vim\" 'send-keys M-\\\\' 'select-pane -l'" + +bind-key -T copy-mode-vi 'M-h' select-pane -L +bind-key -T copy-mode-vi 'M-j' select-pane -D +bind-key -T copy-mode-vi 'M-k' select-pane -U +bind-key -T copy-mode-vi 'M-l' select-pane -R +bind-key -T copy-mode-vi 'M-p' select-pane -l + diff --git a/modules/nixos/cli-apps/tmux/config/nord.tmux b/modules/nixos/cli-apps/tmux/config/nord.tmux new file mode 100644 index 0000000..0905c11 --- /dev/null +++ b/modules/nixos/cli-apps/tmux/config/nord.tmux @@ -0,0 +1,37 @@ + +# Copyright (C) 2017-present Arctic Ice Studio +# Copyright (C) 2017-present Sven Greb +# Copyright (C) 2022-present Jake Hamilton + +# Project: Nord tmux +# Repository: https://github.com/arcticicestudio/nord-tmux +# License: MIT + +#+----------------+ +#+ Plugin Support + +#+----------------+ +#+--- tmux-prefix-highlight ---+ +set -g @prefix_highlight_output_prefix "#[fg=brightcyan]#[bg=black]#[nobold]#[noitalics]#[nounderscore]#[bg=brightcyan]#[fg=black]" +set -g @prefix_highlight_output_suffix "" +set -g @prefix_highlight_copy_mode_attr "fg=brightcyan,bg=black,bold" + +#+--------+ +#+ Status + +#+--------+ +#+--- Bars ---+ +set -g status-left "#[fg=brightblack,bg=black]#[fg=white,bg=brightblack,bold] #S #[fg=brightblack,bg=black,nobold,noitalics,nounderscore]" +set -g status-right "#{prefix_highlight}#[fg=brightblack,bg=black,nobold,noitalics,nounderscore]#[fg=white,bg=brightblack,nobold] #H #[fg=brightblack,bg=black,nobold]" + +#+--- Windows ---+ +set -g window-status-format "#[fg=brightblack,bg=black,nobold,noitalics,nounderscore]#[fg=white,bg=brightblack] #I#[fg=white,bg=brightblack,nobold,noitalics,nounderscore]: #W #[fg=brightblack,bg=black,nobold,noitalics,nounderscore]" +set -g window-status-current-format "#[fg=#5e81ac,bg=black]#[fg=white,bg=#5e81ac,bold,noitalics,nounderscore] #I#[fg=white,bg=#5e81ac,bold,noitalics,nounderscore]: #W #[fg=#5e81ac,bg=black,nobold,noitalics,nounderscore]" +set -g window-status-separator " " + +# Center the window list (yes, this value has to be written as "centre"). +set -g status-justify centre + +#+----------------+ +#+ Windows + +#+----------------+ +#+--- Bars ---+ +set -g pane-active-border-style "bg=default fg=blue" diff --git a/modules/nixos/cli-apps/tmux/config/tilish.tmux b/modules/nixos/cli-apps/tmux/config/tilish.tmux new file mode 100644 index 0000000..e676ac8 --- /dev/null +++ b/modules/nixos/cli-apps/tmux/config/tilish.tmux @@ -0,0 +1,12 @@ +# Integrate Tmux and Neovim movement. +set -g @tilish-navigator "on" + +# Default to splitting once vertically and then splitting horizontally +# after that. +select-layout "main-vertical" +select-layout -E +set -g @tilish-default "main-vertical" + + +bind-key -n "M-q" kill-pane + diff --git a/modules/nixos/cli-apps/tmux/default.nix b/modules/nixos/cli-apps/tmux/default.nix new file mode 100644 index 0000000..6b4be15 --- /dev/null +++ b/modules/nixos/cli-apps/tmux/default.nix @@ -0,0 +1,20 @@ +{ lib +, config +, pkgs +, ... +}: +with lib; +with lib.plusultra; let + cfg = config.plusultra.cli-apps.tmux; +in +{ + options.plusultra.cli-apps.tmux = { + enable = mkEnableOption "Tmux"; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + plusultra.tmux + ]; + }; +} diff --git a/modules/nixos/cli-apps/wine/default.nix b/modules/nixos/cli-apps/wine/default.nix new file mode 100644 index 0000000..ec60403 --- /dev/null +++ b/modules/nixos/cli-apps/wine/default.nix @@ -0,0 +1,20 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.cli-apps.wine; +in +{ + options.plusultra.cli-apps.wine = with types; { + enable = mkBoolOpt false "Whether or not to enable Wine."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + winePackages.unstable + winetricks + wine64Packages.unstable + ]; + }; +} diff --git a/modules/nixos/cli-apps/wshowkeys/default.nix b/modules/nixos/cli-apps/wshowkeys/default.nix new file mode 100644 index 0000000..fea6e14 --- /dev/null +++ b/modules/nixos/cli-apps/wshowkeys/default.nix @@ -0,0 +1,17 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.cli-apps.wshowkeys; +in +{ + options.plusultra.cli-apps.wshowkeys = with types; { + enable = mkBoolOpt false "Whether or not to enable wshowkeys."; + }; + + config = mkIf cfg.enable { + plusultra.user.extraGroups = [ "input" ]; + environment.systemPackages = with pkgs; [ wshowkeys ]; + }; +} diff --git a/modules/nixos/cli-apps/yubikey/default.nix b/modules/nixos/cli-apps/yubikey/default.nix new file mode 100644 index 0000000..0f549fa --- /dev/null +++ b/modules/nixos/cli-apps/yubikey/default.nix @@ -0,0 +1,17 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.cli-apps.yubikey; +in +{ + options.plusultra.cli-apps.yubikey = with types; { + enable = mkBoolOpt false "Whether or not to enable Yubikey."; + }; + + config = mkIf cfg.enable { + services.yubikey-agent.enable = true; + environment.systemPackages = with pkgs; [ yubikey-manager ]; + }; +} diff --git a/modules/nixos/desktop/addons/electron-support/default.nix b/modules/nixos/desktop/addons/electron-support/default.nix new file mode 100644 index 0000000..79410c7 --- /dev/null +++ b/modules/nixos/desktop/addons/electron-support/default.nix @@ -0,0 +1,19 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.desktop.addons.electron-support; +in +{ + options.plusultra.desktop.addons.electron-support = with types; { + enable = mkBoolOpt false + "Whether to enable electron support in the desktop environment."; + }; + + config = mkIf cfg.enable { + plusultra.home.configFile."electron-flags.conf".source = + ./electron-flags.conf; + + environment.sessionVariables = { NIXOS_OZONE_WL = "1"; }; + }; +} diff --git a/modules/nixos/desktop/addons/electron-support/electron-flags.conf b/modules/nixos/desktop/addons/electron-support/electron-flags.conf new file mode 100644 index 0000000..fa9e252 --- /dev/null +++ b/modules/nixos/desktop/addons/electron-support/electron-flags.conf @@ -0,0 +1,2 @@ +--enable-features=UseOzonePlatform +--ozone-platform=wayland \ No newline at end of file diff --git a/modules/nixos/desktop/addons/firefox-nordic-theme/default.nix b/modules/nixos/desktop/addons/firefox-nordic-theme/default.nix new file mode 100644 index 0000000..86033fe --- /dev/null +++ b/modules/nixos/desktop/addons/firefox-nordic-theme/default.nix @@ -0,0 +1,23 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.desktop.addons.firefox-nordic-theme; + profileDir = ".mozilla/firefox/${config.plusultra.user.name}"; +in +{ + options.plusultra.desktop.addons.firefox-nordic-theme = with types; { + enable = mkBoolOpt false "Whether to enable the Nordic theme for firefox."; + }; + + config = mkIf cfg.enable { + plusultra.apps.firefox = { + extraConfig = builtins.readFile + "${pkgs.plusultra.firefox-nordic-theme}/configuration/user.js"; + userChrome = '' + @import "${pkgs.plusultra.firefox-nordic-theme}/userChrome.css"; + ''; + }; + }; +} diff --git a/modules/nixos/desktop/addons/foot/default.nix b/modules/nixos/desktop/addons/foot/default.nix new file mode 100644 index 0000000..5336ad0 --- /dev/null +++ b/modules/nixos/desktop/addons/foot/default.nix @@ -0,0 +1,20 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.desktop.addons.foot; +in +{ + options.plusultra.desktop.addons.foot = with types; { + enable = mkBoolOpt false "Whether to enable the gnome file manager."; + }; + + config = mkIf cfg.enable { + plusultra.desktop.addons.term = { + enable = true; + pkg = pkgs.foot; + }; + + plusultra.home.configFile."foot/foot.ini".source = ./foot.ini; + }; +} diff --git a/modules/nixos/desktop/addons/foot/foot.ini b/modules/nixos/desktop/addons/foot/foot.ini new file mode 100644 index 0000000..e12cc75 --- /dev/null +++ b/modules/nixos/desktop/addons/foot/foot.ini @@ -0,0 +1,50 @@ +########################################### +#░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░# +#░░█▀█░█░░░█░█░█▀▀░░░█░█░█░░░▀█▀░█▀▄░█▀█░░# +#░░█▀▀░█░░░█░█░▀▀█░░░█░█░█░░░░█░░█▀▄░█▀█░░# +#░░▀░░░▀▀▀░▀▀▀░▀▀▀░░░▀▀▀░▀▀▀░░▀░░▀░▀░▀░▀░░# +#░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░# +########################################### + +[main] +font=Hack Nerd Font Mono:size=12 +#,Noto Color Emoji:size=12 +line-height=14 +underline-offset=2 +pad=20x4 center +term=xterm-256color + +[scrollback] +lines=2000 + +[url] +protocols=http,https,ftp,ftps,file,gemini,gopher,mailto + +[cursor] +blink=yes + +[colors] +# Nord +foreground=D8DEE9 +background=2E3440 + +regular0=2E3440 +regular1=BF616A +regular2=A3BE8C +regular3=EBCB8B +regular4=81A1C1 +regular5=B48EAD +regular6=88C0D0 +regular7=E5E9F0 + +bright0=4C566A +bright1=BF616A +bright2=A3BE8C +bright3=EBCB8B +bright4=8FBCBB +bright5=B48EAD +bright6=8FBCBB +bright7=ECEFF4 + +[csd] +size=0 diff --git a/modules/nixos/desktop/addons/gtk/default.nix b/modules/nixos/desktop/addons/gtk/default.nix new file mode 100644 index 0000000..477b2e3 --- /dev/null +++ b/modules/nixos/desktop/addons/gtk/default.nix @@ -0,0 +1,118 @@ +{ options +, config +, lib +, pkgs +, ... +}: +with lib; +with lib.plusultra; let + cfg = config.plusultra.desktop.addons.gtk; + gdmCfg = config.services.xserver.displayManager.gdm; +in +{ + options.plusultra.desktop.addons.gtk = with types; { + enable = mkBoolOpt false "Whether to customize GTK and apply themes."; + theme = { + name = + mkOpt str "Nordic-darker" + "The name of the GTK theme to apply."; + pkg = mkOpt package pkgs.nordic "The package to use for the theme."; + }; + cursor = { + name = + mkOpt str "Bibata-Modern-Ice" + "The name of the cursor theme to apply."; + pkg = mkOpt package pkgs.plusultra.bibata-cursors "The package to use for the cursor theme."; + }; + icon = { + name = + mkOpt str "Papirus" + "The name of the icon theme to apply."; + pkg = mkOpt package pkgs.papirus-icon-theme "The package to use for the icon theme."; + }; + }; + + config = mkIf cfg.enable { + environment.systemPackages = [ + cfg.icon.pkg + cfg.cursor.pkg + ]; + + environment.sessionVariables = { + XCURSOR_THEME = cfg.cursor.name; + }; + + plusultra.home.extraOptions = { + gtk = { + enable = true; + + theme = { + name = cfg.theme.name; + package = cfg.theme.pkg; + }; + + cursorTheme = { + name = cfg.cursor.name; + package = cfg.cursor.pkg; + }; + + iconTheme = { + name = cfg.icon.name; + package = cfg.icon.pkg; + }; + }; + }; + + # NOTE: In order to set the cursor theme in GDM we have to specify it in the + # dconf profile. However, the NixOS module doesn't provide an easy way to do this so the relevant + # parts have been extracted from: + # https://github.com/NixOS/nixpkgs/blob/96e18717904dfedcd884541e5a92bf9ff632cf39/nixos/modules/services/x11/display-managers/gdm.nix + # + # NOTE: The GTK and icon themes don't seem to affect recent GDM versions. I've + # left them here as reference for the future. + programs.dconf.profiles = mkIf gdmCfg.enable { + gdm = + let + customDconf = pkgs.writeTextFile { + name = "gdm-dconf"; + destination = "/dconf/gdm-custom"; + text = '' + ${optionalString (!gdmCfg.autoSuspend) '' + [org/gnome/settings-daemon/plugins/power] + sleep-inactive-ac-type='nothing' + sleep-inactive-battery-type='nothing' + sleep-inactive-ac-timeout=0 + sleep-inactive-battery-timeout=0 + ''} + + [org/gnome/desktop/interface] + gtk-theme='${cfg.theme.name}' + cursor-theme='${cfg.cursor.name}' + icon-theme='${cfg.icon.name}' + ''; + }; + + customDconfDb = pkgs.stdenv.mkDerivation { + name = "gdm-dconf-db"; + buildCommand = '' + ${pkgs.dconf}/bin/dconf compile $out ${customDconf}/dconf + ''; + }; + in + mkForce ( + pkgs.stdenv.mkDerivation { + name = "dconf-gdm-profile"; + buildCommand = '' + # Check that the GDM profile starts with what we expect. + if [ $(head -n 1 ${pkgs.gnome.gdm}/share/dconf/profile/gdm) != "user-db:user" ]; then + echo "GDM dconf profile changed, please update gtk/default.nix" + exit 1 + fi + # Insert our custom DB behind it. + sed '2ifile-db:${customDconfDb}' ${pkgs.gnome.gdm}/share/dconf/profile/gdm > $out + ''; + } + ); + }; + }; +} diff --git a/modules/nixos/desktop/addons/kanshi/config b/modules/nixos/desktop/addons/kanshi/config new file mode 100644 index 0000000..ea1aab6 --- /dev/null +++ b/modules/nixos/desktop/addons/kanshi/config @@ -0,0 +1,3 @@ +profile { + output eDP-1 enable scale 2 +} diff --git a/modules/nixos/desktop/addons/kanshi/default.nix b/modules/nixos/desktop/addons/kanshi/default.nix new file mode 100644 index 0000000..9c1e6a0 --- /dev/null +++ b/modules/nixos/desktop/addons/kanshi/default.nix @@ -0,0 +1,41 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.desktop.addons.kanshi; + user = config.plusultra.user; + home = config.users.users.${user.name}.home; +in +{ + options.plusultra.desktop.addons.kanshi = with types; { + enable = + mkBoolOpt false "Whether to enable Kanshi in the desktop environment."; + }; + + config = mkIf cfg.enable { + plusultra.home.configFile."kanshi/config".source = ./config; + + environment.systemPackages = with pkgs; [ kanshi ]; + + # configuring kanshi + systemd.user.services.kanshi = { + description = "Kanshi output autoconfig "; + wantedBy = [ "graphical-session.target" ]; + partOf = [ "graphical-session.target" ]; + environment = { XDG_CONFIG_HOME = "${home}/.config"; }; + serviceConfig = { + ExecCondition = '' + ${pkgs.bash}/bin/bash -c '[ -n "$WAYLAND_DISPLAY" ]' + ''; + + ExecStart = '' + ${pkgs.kanshi}/bin/kanshi + ''; + + RestartSec = 5; + Restart = "always"; + }; + }; + }; +} diff --git a/modules/nixos/desktop/addons/keyring/default.nix b/modules/nixos/desktop/addons/keyring/default.nix new file mode 100644 index 0000000..4629edd --- /dev/null +++ b/modules/nixos/desktop/addons/keyring/default.nix @@ -0,0 +1,17 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.desktop.addons.keyring; +in +{ + options.plusultra.desktop.addons.keyring = with types; { + enable = mkBoolOpt false "Whether to enable the gnome keyring."; + }; + + config = mkIf cfg.enable { + services.gnome.gnome-keyring.enable = true; + + environment.systemPackages = with pkgs; [ gnome.seahorse ]; + }; +} diff --git a/modules/nixos/desktop/addons/mako/config b/modules/nixos/desktop/addons/mako/config new file mode 100644 index 0000000..84b6aa0 --- /dev/null +++ b/modules/nixos/desktop/addons/mako/config @@ -0,0 +1,18 @@ +########################################### +#░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░# +#░░█▀█░█░░░█░█░█▀▀░░░█░█░█░░░▀█▀░█▀▄░█▀█░░# +#░░█▀▀░█░░░█░█░▀▀█░░░█░█░█░░░░█░░█▀▄░█▀█░░# +#░░▀░░░▀▀▀░▀▀▀░▀▀▀░░░▀▀▀░▀▀▀░░▀░░▀░▀░▀░▀░░# +#░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░# +########################################### + +font=Hack Nerd Font Mono 10 +border-radius=8 +text-color=#2e3440ff +background-color=#eceff4f4 +border-color=#d8dee9ff +border-size=0 +margin=12,12,6 +padding=12,12,12,12 +default-timeout=5000 +max-visible=3 diff --git a/modules/nixos/desktop/addons/mako/default.nix b/modules/nixos/desktop/addons/mako/default.nix new file mode 100644 index 0000000..12e1f15 --- /dev/null +++ b/modules/nixos/desktop/addons/mako/default.nix @@ -0,0 +1,44 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.desktop.addons.mako; +in +{ + options.plusultra.desktop.addons.mako = with types; { + enable = mkBoolOpt false "Whether to enable Mako in Sway."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ mako libnotify ]; + + systemd.user.services.mako = { + description = "Mako notification daemon"; + wantedBy = [ "graphical-session.target" ]; + partOf = [ "graphical-session.target" ]; + after = [ "graphical-session.target" ]; + serviceConfig = { + Type = "dbus"; + BusName = "org.freedesktop.Notifications"; + + ExecCondition = '' + ${pkgs.bash}/bin/bash -c '[ -n "$WAYLAND_DISPLAY" ]' + ''; + + ExecStart = '' + ${pkgs.mako}/bin/mako + ''; + + ExecReload = '' + ${pkgs.mako}/bin/makoctl reload + ''; + + Restart = "on-failure"; + RestartSec = 1; + TimeoutStopSec = 10; + }; + }; + + plusultra.home.configFile."mako/config".source = ./config; + }; +} diff --git a/modules/nixos/desktop/addons/nautilus/default.nix b/modules/nixos/desktop/addons/nautilus/default.nix new file mode 100644 index 0000000..df48a7d --- /dev/null +++ b/modules/nixos/desktop/addons/nautilus/default.nix @@ -0,0 +1,20 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.desktop.addons.nautilus; +in +{ + options.plusultra.desktop.addons.nautilus = with types; { + enable = mkBoolOpt false "Whether to enable the gnome file manager."; + }; + + config = mkIf cfg.enable { + # Enable support for browsing samba shares. + services.gvfs.enable = true; + networking.firewall.extraCommands = + "iptables -t raw -A OUTPUT -p udp -m udp --dport 137 -j CT --helper netbios-ns"; + + environment.systemPackages = with pkgs; [ gnome.nautilus ]; + }; +} diff --git a/modules/nixos/desktop/addons/rofi/config.rasi b/modules/nixos/desktop/addons/rofi/config.rasi new file mode 100644 index 0000000..25fe407 --- /dev/null +++ b/modules/nixos/desktop/addons/rofi/config.rasi @@ -0,0 +1,42 @@ +/* +vim: filetype=css + */ + +configuration { + fullscreen: false; + show-icons: false; + sidebar-mode: false; +} + +* { + // Default bg is transparent. + background-color: transparent; + // Default text is white + text-color: white; + spacing: 30; +} + +#window { + // Default font + font: "Nerd Font Hack 18"; + fullscreen: true; + transparency: "background"; + + background-color: #282a36BA; + + // Add dummy widgets on top and bottom so the sizing + // nicely centers hdum, independent of resolution. + children: [ dummy1, hdum, dummy2 ]; +} + +#hdum { + orientation: horizontal; + // Add dummy widgets on left and right so the sizing + // nicely centers mainbox, independent of resolution. + children: [ dummy3, mainbox, dummy4 ]; +} + +#element selected { + text-color: #caa9fa; +} + diff --git a/modules/nixos/desktop/addons/rofi/default.nix b/modules/nixos/desktop/addons/rofi/default.nix new file mode 100644 index 0000000..f6c65f1 --- /dev/null +++ b/modules/nixos/desktop/addons/rofi/default.nix @@ -0,0 +1,18 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.desktop.addons.rofi; +in +{ + options.plusultra.desktop.addons.rofi = with types; { + enable = + mkBoolOpt false "Whether to enable Rofi in the desktop environment."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ rofi ]; + + plusultra.home.configFile."rofi/config.rasi".source = ./config.rasi; + }; +} diff --git a/modules/nixos/desktop/addons/swappy/config b/modules/nixos/desktop/addons/swappy/config new file mode 100644 index 0000000..632dc00 --- /dev/null +++ b/modules/nixos/desktop/addons/swappy/config @@ -0,0 +1,3 @@ +[Default] +save_dir=$HOME/Pictures/screenshots +save_filename_format=%Y%m%d-%H%M%S.png diff --git a/modules/nixos/desktop/addons/swappy/default.nix b/modules/nixos/desktop/addons/swappy/default.nix new file mode 100644 index 0000000..3bce885 --- /dev/null +++ b/modules/nixos/desktop/addons/swappy/default.nix @@ -0,0 +1,19 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.desktop.addons.swappy; +in +{ + options.plusultra.desktop.addons.swappy = with types; { + enable = + mkBoolOpt false "Whether to enable Swappy in the desktop environment."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ swappy ]; + + plusultra.home.configFile."swappy/config".source = ./config; + plusultra.home.file."Pictures/screenshots/.keep".text = ""; + }; +} diff --git a/modules/nixos/desktop/addons/term/default.nix b/modules/nixos/desktop/addons/term/default.nix new file mode 100644 index 0000000..862a637 --- /dev/null +++ b/modules/nixos/desktop/addons/term/default.nix @@ -0,0 +1,14 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.desktop.addons.term; +in +{ + options.plusultra.desktop.addons.term = with types; { + enable = mkBoolOpt false "Whether to enable the gnome file manager."; + pkg = mkOpt package pkgs.foot "The terminal to install."; + }; + + config = mkIf cfg.enable { environment.systemPackages = [ cfg.pkg ]; }; +} diff --git a/modules/nixos/desktop/addons/wallpapers/default.nix b/modules/nixos/desktop/addons/wallpapers/default.nix new file mode 100644 index 0000000..4fc9d0d --- /dev/null +++ b/modules/nixos/desktop/addons/wallpapers/default.nix @@ -0,0 +1,26 @@ +{ options, config, pkgs, lib, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.desktop.addons.wallpapers; + inherit (pkgs.plusultra) wallpapers; +in +{ + options.plusultra.desktop.addons.wallpapers = with types; { + enable = mkBoolOpt false + "Whether or not to add wallpapers to ~/Pictures/wallpapers."; + }; + + config = { + plusultra.home.file = lib.foldl + (acc: name: + let wallpaper = wallpapers.${name}; + in + acc // { + "Pictures/wallpapers/${wallpaper.fileName}".source = wallpaper; + }) + { } + (wallpapers.names); + }; +} diff --git a/modules/nixos/desktop/addons/waybar/config b/modules/nixos/desktop/addons/waybar/config new file mode 100644 index 0000000..88e2df4 --- /dev/null +++ b/modules/nixos/desktop/addons/waybar/config @@ -0,0 +1,119 @@ +{ + "layer": "top", + "modules-left": [ + "sway/workspaces", + "custom/media", + "sway/mode" + ], + "modules-center": [], + "modules-right": [ + "network", + "pulseaudio", + "cpu", + "battery", + "tray", + "clock#date", + "clock#time" + ], + "sway/mode": { + "format": "{}" + }, + "custom/media": { + "format": "{icon}", + "return-type": "json", + "format-icons": { + "Playing": "", + "Paused": "ﳌ" + }, + "max-length": 70, + "exec": "playerctl -a metadata --format '{\"text\": \"{{playerName}}\", \"tooltip\": \"{{playerName}}: {{markup_escape(title)}}\", \"alt\": \"{{status}}\", \"class\": \"{{status}}\"}' -F", + "on-click": "playerctl play-pause" + }, + "mpd": { + "tooltip": false, + "format": "{stateIcon} {artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S})", + "format-disconnected": "ﳌ", + "format-stopped": "", + "state-icons": { + "playing": "", + "paused": "" + } + }, + "pulseaudio": { + "tooltip": true, + "tooltip-format": "{volume}%", + "scroll-step": 5, + "format": "{icon}", + "format-bluetooth": "", + "format-muted": "婢", + "format-icons": { + "default": [ + "奄", + "奔", + "墳" + ] + }, + "on-click": "gnome-control-center sound" + }, + "network": { + "tooltip": true, + "format-wifi": " ", + "tooltip-format-wifi": "{essid} @ {signalStrength}%", + "format-ethernet": "", + "on-click": "gnome-control-center wifi" + }, + "temperature": { + "tooltip": true, + "tooltip-format": "{temperatureC} 糖", + "critical-threshold": 70, + "format-icons": [ + "", + "", + "" + ], + "format": "{icon}" + }, + "cpu": { + "tooltip": true, + "format": "", + "states": { + "heavy": 70, + "full": 90 + } + }, + "memory": { + "tooltip": true, + "tooltip-format": "{used:0.1f}G/{total:0.1f}G", + "format": "", + "states": { + "heavy": 70, + "full": 90 + } + }, + "battery": { + "bat": "BAT1", + "interval": 60, + "states": { + "warning": 30, + "critical": 15 + }, + "format": "{icon}", + "format-critical": "", + "format-icons": { + "default": ["", "", "", "", "", "", "", "", "", ""], + "charging": ["", "", "", "", "", "", ""], + "not": "", + "plugged": "" + } + }, + "tray": { + "icon-size": 16, + "spacing": 10 + }, + "clock#date": { + "format": "{:%a, %b %d}" + }, + "clock#time": { + "format": "{:%I:%M %p}" + } +} diff --git a/modules/nixos/desktop/addons/waybar/default.nix b/modules/nixos/desktop/addons/waybar/default.nix new file mode 100644 index 0000000..e04a6b8 --- /dev/null +++ b/modules/nixos/desktop/addons/waybar/default.nix @@ -0,0 +1,19 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.desktop.addons.waybar; +in +{ + options.plusultra.desktop.addons.waybar = with types; { + enable = + mkBoolOpt false "Whether to enable Waybar in the desktop environment."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ waybar ]; + + plusultra.home.configFile."waybar/config".source = ./config; + plusultra.home.configFile."waybar/style.css".source = ./style.css; + }; +} diff --git a/modules/nixos/desktop/addons/waybar/style.css b/modules/nixos/desktop/addons/waybar/style.css new file mode 100644 index 0000000..61170cc --- /dev/null +++ b/modules/nixos/desktop/addons/waybar/style.css @@ -0,0 +1,314 @@ +/* + ******************************************** + *░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░* + *░░█▀█░█░░░█░█░█▀▀░░░█░█░█░░░▀█▀░█▀▄░█▀█░░* + *░░█▀▀░█░░░█░█░▀▀█░░░█░█░█░░░░█░░█▀▄░█▀█░░* + *░░▀░░░▀▀▀░▀▀▀░▀▀▀░░░▀▀▀░▀▀▀░░▀░░▀░▀░▀░▀░░* + *░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░* + ******************************************** + */ + +* { + border: none; + border-radius: 0; + font-family: Nerd Font Hack; + font-size: 14px; +} + +window#waybar { + background: transparent; +} + +window#waybar.hidden { + opacity: 0.2; +} + +#window { + margin-top: 8px; + padding-left: 20px; + padding-right: 20px; + border-radius: 8px; + transition: none; + color: transparent; + background: transparent; +} + +window#waybar.termite #window, +window#waybar.Firefox #window, +window#waybar.Navigator #window, +window#waybar.PCSX2 #window { + color: #4d4d4d; + background: #e6e6e6; +} + +tooltip { + border-radius: 12px; + background: #eceff4; +} + +tooltip label { + color: #2e3440; + text-shadow: none; + padding: 0 4px; +} + +#workspaces button, +#mode, +#mpd, +#custom-media, +#network, +#pulseaudio, +#temperature, +#cpu, +#memory, +#tray, +#clock, +#battery { + color: #2e3440; + background: #eceff4; +} + +#workspaces { + margin-top: 8px; + margin-left: 12px; + margin-bottom: 0; + border-radius: 8px; + transition: none; + background: #d8dee9; +} + +#workspaces button { + transition: none; + font-size: 16px; + border-radius: 8px; + padding: 4px 8px; + font-weight: bold; + background: #d8dee9; +} + +#workspaces button.visible { + color: #88c0d0; + background: #eceff4; +} + +#workspaces button.visible:hover { + color: #88c0d0; + background: #eceff4; +} + +#workspaces button:hover { + transition: none; + box-shadow: inherit; + text-shadow: inherit; + color: #81a1c1; +} + +#custom-media { + margin-top: 8px; + margin-left: 8px; + padding-left: 16px; + padding-right: 16px; + margin-bottom: 0; + border-radius: 8px; + transition: none; + font-weight: bold; +} + +#custom-media.Paused { + color: #4c566a; + background: #d8dee9; +} + +#custom-media.Playing { + color: #eceff4; + background: #8fbcbb; +} + +#network { + margin-top: 8px; + margin-left: 8px; + padding-left: 16px; + padding-right: 10px; + margin-bottom: 0; + border-radius: 8px; + transition: none; + font-size: 24px; +} + +#network.disconnected, +#network.disabled { + color: #2e3440; + background: #eceff4; +} + +#network.wifi { + color: #d8dee9; + background: #8fbcbb; +} + +#pulseaudio { + margin-top: 8px; + margin-left: 8px; + padding-left: 16px; + padding-right: 16px; + margin-bottom: 0; + border-radius: 8px; + transition: none; + font-size: 18px; +} + +#pulseaudio.bluetooth { + color: #d8dee9; + background: #81a1c1; +} + +#pulseaudio.muted { + color: #d8dee9; + background: #bf616a; +} + +#battery { + margin-top: 8px; + margin-left: 8px; + padding-left: 16px; + padding-right: 16px; + margin-bottom: 0; + border-radius: 8px; + transition: none; + font-size: 18px; +} + +#battery.warning { + color: #eceff4; + background: #d08770; +} + +#battery.critical { + color: #eceff4; + background: #bf616a; +} + +#temperature { + margin-top: 8px; + margin-left: 8px; + padding-left: 16px; + padding-right: 16px; + margin-bottom: 0; + border-radius: 8px; + transition: none; + font-size: 20px; +} + +#temperature.critical { + color: #d8dee9; + background: #bf616a; + animation: red-flash 1s linear infinite; +} + +@keyframes red-flash { + 0% { + background: #bf616a; + } + + 50% { + background: #d37f87; + } + + 100% { + background: #bf616a; + } +} + +#cpu { + margin-top: 8px; + margin-left: 8px; + padding-left: 16px; + padding-right: 16px; + margin-bottom: 0; + border-radius: 8px; + transition: none; + font-size: 20px; +} + +#cpu.heavy { + color: #d8dee9; + background: #d08770; +} + +#cpu.full { + color: #d8dee9; + background: #bf616a; +} + +#memory { + margin-top: 8px; + margin-left: 8px; + padding-left: 16px; + padding-right: 16px; + margin-bottom: 0; + border-radius: 8px; + transition: none; + font-size: 20px; +} + +#memory.heavy { + color: #d8dee9; + background: #d08770; +} + +#memory.full { + color: #d8dee9; + background: #bf616a; +} + +#tray { + margin-top: 8px; + margin-left: 8px; + padding-left: 16px; + padding-right: 16px; + margin-bottom: 0; + border-radius: 8px; + transition: none; + font-size: 20px; +} + +#mode { + margin-top: 8px; + margin-left: 8px; + padding-left: 16px; + padding-right: 16px; + margin-bottom: 0; + border-radius: 8px; + transition: none; + /* font-size: 20px; */ + color: #eceff4; + background: #d08770; + font-weight: bold; +} + +#clock { + margin-top: 8px; + margin-bottom: 0; + transition: none; + font-weight: bold; +} + +#clock.date { + margin-left: 8px; + border-radius: 8px; + padding-left: 16px; + padding-right: 10px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + background: #e5e9f0; +} + +#clock.time { + padding-left: 10px; + padding-right: 16px; + border-radius: 8px; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + margin-right: 12px; + background: #d8dee9; +} diff --git a/modules/nixos/desktop/addons/wofi/config b/modules/nixos/desktop/addons/wofi/config new file mode 100644 index 0000000..fe03dbe --- /dev/null +++ b/modules/nixos/desktop/addons/wofi/config @@ -0,0 +1,3 @@ +stylesheet=./style.css +term=foot +insensitive=true diff --git a/modules/nixos/desktop/addons/wofi/default.nix b/modules/nixos/desktop/addons/wofi/default.nix new file mode 100644 index 0000000..2b06577 --- /dev/null +++ b/modules/nixos/desktop/addons/wofi/default.nix @@ -0,0 +1,23 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.desktop.addons.wofi; +in +{ + options.plusultra.desktop.addons.wofi = with types; { + enable = + mkBoolOpt false "Whether to enable the Wofi in the desktop environment."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ wofi wofi-emoji ]; + + # config -> .config/wofi/config + # css -> .config/wofi/style.css + # colors -> $XDG_CACHE_HOME/wal/colors + # plusultra.home.configFile."foot/foot.ini".source = ./foot.ini; + plusultra.home.configFile."wofi/config".source = ./config; + plusultra.home.configFile."wofi/style.css".source = ./style.css; + }; +} diff --git a/modules/nixos/desktop/addons/wofi/style.css b/modules/nixos/desktop/addons/wofi/style.css new file mode 100644 index 0000000..d844501 --- /dev/null +++ b/modules/nixos/desktop/addons/wofi/style.css @@ -0,0 +1,127 @@ +window { + font-family: "Hack Nerd Font"; + background: transparent; +} + +#outer-box { + padding: 10px; + border-radius: 8px; + background: #2e3440; +} + +#scroll { + /* The Nordic gtk theme adds an outline to show scroll areas... */ + outline-color: transparent; +} + +#input { + color: #e5e9f0; + caret-color: #e5e9f0; + background: #3b4252; + border-top-color: #3b4252; + border-left-color: #3b4252; + border-right-color: #3b4252; + border-bottom-color: #3b4252; + box-shadow: 0 0 0 1px transparent inset; + outline-color: transparent !important; +} + +#input:focus { + background: #3b4252; + border-color: #3b4252 !important; + box-shadow: 0 0 0 1px transparent inset; + border-top-color: #3b4252 !important; + border-left-color: #3b4252 !important; + border-right-color: #3b4252 !important; + border-bottom-color: #3b4252 !important; + box-shadow: none !important; + outline-color: transparent !important; +} + +#input image.left { + color: #d8dee9; +} + +#input:focus image.left { + color: #e5e9f0; +} + +#input image.right { + color: #d8dee9; +} + +#input:focus image.right { + color: #e5e9f0; +} + +label { + /* We set backgrounds on the block level. */ + background: transparent; +} + +#scroll { + padding-top: 6px; +} + +#entry { + color: #4c566a; + padding: 8px 8px; + border-radius: 4px; + background: transparent; +} + +#entry:selected { + color: #eceff4; + background: #8fbcbb; + font-weight: bold; +} + +expander arrow { + margin-right: 8px; +} + +#entry #selected #text { + color: #eceff4; +} + +expander list { + margin-top: 8px; + /* background: #8fbcbb; */ + background: transparent; + padding-left: 16px; +} + +expander list #entry { + transition: none; + background: transparent; +} + +expander list #entry:hover, +expander list #entry:active { + /* color: #8fbcbb; + background: #e5e9f0; */ +} + +expander list #entry #selected { + background: #8fbcbb; +} + +expander list #entry #selected label { + color: #eceff4; + font-weight: bold; +} + +expander list #entry:hover, +expander list #entry:active { + background: #8fbcbb; +} + +expander list #entry:hover label, +expander list #entry:active label { + color: #eceff4; + font-weight: bold; +} + +expander list label { + color: #d8dee9; +} diff --git a/modules/nixos/desktop/addons/xdg-portal/default.nix b/modules/nixos/desktop/addons/xdg-portal/default.nix new file mode 100644 index 0000000..dc09173 --- /dev/null +++ b/modules/nixos/desktop/addons/xdg-portal/default.nix @@ -0,0 +1,24 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.desktop.addons.xdg-portal; +in +{ + options.plusultra.desktop.addons.xdg-portal = with types; { + enable = mkBoolOpt false "Whether or not to add support for xdg portal."; + }; + + config = mkIf cfg.enable { + xdg = { + portal = { + enable = true; + extraPortals = with pkgs; [ + xdg-desktop-portal-wlr + xdg-desktop-portal-gtk + ]; + gtkUsePortal = true; + }; + }; + }; +} diff --git a/modules/nixos/desktop/gnome/default.nix b/modules/nixos/desktop/gnome/default.nix new file mode 100644 index 0000000..51cd010 --- /dev/null +++ b/modules/nixos/desktop/gnome/default.nix @@ -0,0 +1,333 @@ +{ options +, config +, lib +, pkgs +, ... +}: +with lib; +with lib.plusultra; let + cfg = config.plusultra.desktop.gnome; + gdmHome = config.users.users.gdm.home; + + defaultExtensions = with pkgs.gnomeExtensions; [ + appindicator + aylurs-widgets + dash-to-dock + emoji-selector + gsconnect + gtile + just-perfection + logo-menu + no-overview + remove-app-menu + space-bar + top-bar-organizer + wireless-hid + + # NOTE: These extensions are currently unsupported. They may also + # no longer be required. + + # audio-output-switcher + # big-avatar + # clear-top-bar + ]; + + default-attrs = mapAttrs (key: mkDefault); + nested-default-attrs = mapAttrs (key: default-attrs); +in +{ + options.plusultra.desktop.gnome = with types; { + enable = + mkBoolOpt false "Whether or not to use Gnome as the desktop environment."; + wallpaper = { + light = mkOpt (oneOf [ str package ]) pkgs.plusultra.wallpapers.nord-rainbow-light-nix "The light wallpaper to use."; + dark = mkOpt (oneOf [ str package ]) pkgs.plusultra.wallpapers.nord-rainbow-dark-nix "The dark wallpaper to use."; + }; + color-scheme = mkOpt (enum [ "light" "dark" ]) "dark" "The color scheme to use."; + wayland = mkBoolOpt true "Whether or not to use Wayland."; + suspend = + mkBoolOpt true "Whether or not to suspend the machine after inactivity."; + monitors = mkOpt (nullOr path) null "The monitors.xml file to create."; + extensions = mkOpt (listOf package) [ ] "Extra Gnome extensions to install."; + }; + + config = mkIf cfg.enable { + plusultra.system.xkb.enable = true; + plusultra.desktop.addons = { + gtk = enabled; + wallpapers = enabled; + electron-support = enabled; + foot = enabled; + }; + + environment.systemPackages = with pkgs; + [ + (hiPrio plusultra.xdg-open-with-portal) + wl-clipboard + gnome.gnome-tweaks + gnome.nautilus-python + ] + ++ defaultExtensions + ++ cfg.extensions; + + environment.gnome.excludePackages = with pkgs.gnome; [ + pkgs.gnome-tour + epiphany + geary + gnome-font-viewer + gnome-system-monitor + gnome-maps + ]; + + systemd.tmpfiles.rules = + [ + "d ${gdmHome}/.config 0711 gdm gdm" + ] + ++ ( + # "./monitors.xml" comes from ~/.config/monitors.xml when GNOME + # display information is updated. + lib.optional (cfg.monitors != null) "L+ ${gdmHome}/.config/monitors.xml - - - - ${cfg.monitors}" + ); + + systemd.services.plusultra-user-icon = { + before = [ "display-manager.service" ]; + wantedBy = [ "display-manager.service" ]; + + serviceConfig = { + Type = "simple"; + User = "root"; + Group = "root"; + }; + + script = '' + config_file=/var/lib/AccountsService/users/${config.plusultra.user.name} + icon_file=/run/current-system/sw/share/plusultra-icons/user/${config.plusultra.user.name}/${config.plusultra.user.icon.fileName} + + if ! [ -d "$(dirname "$config_file")"]; then + mkdir -p "$(dirname "$config_file")" + fi + + if ! [ -f "$config_file" ]; then + echo "[User] + Session=gnome + SystemAccount=false + Icon=$icon_file" > "$config_file" + else + icon_config=$(sed -E -n -e "/Icon=.*/p" $config_file) + + if [[ "$icon_config" == "" ]]; then + echo "Icon=$icon_file" >> $config_file + else + sed -E -i -e "s#^Icon=.*$#Icon=$icon_file#" $config_file + fi + fi + ''; + }; + + # Required for app indicators + services.udev.packages = with pkgs; [ gnome3.gnome-settings-daemon ]; + + services.xserver = { + enable = true; + + libinput.enable = true; + displayManager.gdm = { + enable = true; + wayland = cfg.wayland; + autoSuspend = cfg.suspend; + }; + desktopManager.gnome.enable = true; + }; + + plusultra.home.extraOptions = { + dconf.settings = + let + user = config.users.users.${config.plusultra.user.name}; + get-wallpaper = wallpaper: + if lib.isDerivation wallpaper + then builtins.toString wallpaper + else wallpaper; + in + nested-default-attrs { + "org/gnome/shell" = { + disable-user-extensions = false; + enabled-extensions = + (builtins.map (extension: extension.extensionUuid) (cfg.extensions ++ defaultExtensions)) + ++ [ + "native-window-placement@gnome-shell-extensions.gcampax.github.com" + "drive-menu@gnome-shell-extensions.gcampax.github.com" + "user-theme@gnome-shell-extensions.gcampax.github.com" + ]; + favorite-apps = + [ "org.gnome.Nautilus.desktop" ] + ++ optional config.plusultra.apps.firefox.enable "firefox.desktop" + ++ optional config.plusultra.apps.vscode.enable "code.desktop" + ++ optional config.plusultra.desktop.addons.foot.enable "foot.desktop" + ++ optional config.plusultra.apps.logseq.enable "logseq.desktop" + ++ optional config.plusultra.apps.discord.enable "discord.desktop" + ++ optional config.plusultra.apps.element.enable "element-desktop.desktop" + ++ optional config.plusultra.apps.steam.enable "steam.desktop"; + }; + + "org/gnome/desktop/background" = { + picture-uri = get-wallpaper cfg.wallpaper.light; + picture-uri-dark = get-wallpaper cfg.wallpaper.dark; + }; + "org/gnome/desktop/screensaver" = { + picture-uri = get-wallpaper cfg.wallpaper.light; + picture-uri-dark = get-wallpaper cfg.wallpaper.dark; + }; + "org/gnome/desktop/interface" = { + color-scheme = + if cfg.color-scheme == "light" + then "default" + else "prefer-dark"; + enable-hot-corners = false; + }; + "org/gnome/desktop/peripherals/touchpad" = { + disable-while-typing = false; + }; + "org/gnome/desktop/wm/preferences" = { + num-workspaces = 10; + resize-with-right-button = true; + }; + "org/gnome/desktop/wm/keybindings" = { + switch-to-workspace-1 = [ "1" ]; + switch-to-workspace-2 = [ "2" ]; + switch-to-workspace-3 = [ "3" ]; + switch-to-workspace-4 = [ "4" ]; + switch-to-workspace-5 = [ "5" ]; + switch-to-workspace-6 = [ "6" ]; + switch-to-workspace-7 = [ "7" ]; + switch-to-workspace-8 = [ "8" ]; + switch-to-workspace-9 = [ "9" ]; + switch-to-workspace-10 = [ "0" ]; + + move-to-workspace-1 = [ "1" ]; + move-to-workspace-2 = [ "2" ]; + move-to-workspace-3 = [ "3" ]; + move-to-workspace-4 = [ "4" ]; + move-to-workspace-5 = [ "5" ]; + move-to-workspace-6 = [ "6" ]; + move-to-workspace-7 = [ "7" ]; + move-to-workspace-8 = [ "8" ]; + move-to-workspace-9 = [ "9" ]; + move-to-workspace-10 = [ "0" ]; + }; + "org/gnome/shell/keybindings" = { + # Remove the default hotkeys for opening favorited applications. + switch-to-application-1 = [ ]; + switch-to-application-2 = [ ]; + switch-to-application-3 = [ ]; + switch-to-application-4 = [ ]; + switch-to-application-5 = [ ]; + switch-to-application-6 = [ ]; + switch-to-application-7 = [ ]; + switch-to-application-8 = [ ]; + switch-to-application-9 = [ ]; + switch-to-application-10 = [ ]; + }; + "org/gnome/mutter" = { + edge-tiling = false; + dynamic-workspaces = false; + }; + + "org/gnome/shell/extensions/dash-to-dock" = { + autohide = true; + dock-fixed = false; + dock-position = "BOTTOM"; + pressure-threshold = 200.0; + require-pressure-to-show = true; + show-favorites = true; + hot-keys = false; + }; + + "org/gnome/shell/extensions/just-perfection" = { + panel-size = 48; + activities-button = false; + }; + + "org/gnome/shell/extensions/Logo-menu" = { + hide-softwarecentre = true; + + # Use right click to open Activities. + menu-button-icon-click-type = 3; + + # Use the NixOS logo. + menu-button-icon-image = 23; + + menu-button-terminal = + if config.plusultra.desktop.addons.term.enable + then lib.getExe config.plusultra.desktop.addons.term.pkg + else lib.getExe pkgs.gnome.gnome-terminal; + }; + + "org/gnome/shell/extensions/aylurs-widgets" = { + background-clock = false; + battery-bar = false; + dash-board = false; + date-menu-date-format = "%H:%M %B %d"; + date-menu-hide-clocks = true; + date-menu-hide-system-levels = true; + date-menu-hide-user = true; + + # Hide the indincator + date-menu-indicator-position = 2; + + media-player = false; + media-player-prefer = "firefox"; + notification-indicator = false; + power-menu = false; + quick-toggles = false; + workspace-indicator = false; + }; + + "org/gnome/shell/extensions/top-bar-organizer" = { + left-box-order = [ + "menuButton" + "activities" + "dateMenu" + "appMenu" + ]; + + center-box-order = [ + "Space Bar" + ]; + + right-box-order = [ + "keyboard" + "EmojisMenu" + "wireless-hid" + "drive-menu" + "vitalsMenu" + "screenRecording" + "screenSharing" + "dwellClick" + "a11y" + "quickSettings" + ]; + }; + + "org/gnome/shell/extensions/space-bar/shortcuts" = { + enable-activate-workspace-shortcuts = false; + }; + "org/gnome/shell/extensions/space-bar/behavior" = { + show-empty-workspaces = false; + }; + + "org/gnome/shell/extensions/gtile" = { + show-icon = false; + grid-sizes = "8x2,4x2,2x2"; + }; + }; + }; + + programs.kdeconnect = { + enable = true; + package = pkgs.gnomeExtensions.gsconnect; + }; + + # Open firewall for samba connections to work. + networking.firewall.extraCommands = "iptables -t raw -A OUTPUT -p udp -m udp --dport 137 -j CT --helper netbios-ns"; + }; +} diff --git a/modules/nixos/desktop/gnome/monitors.xml b/modules/nixos/desktop/gnome/monitors.xml new file mode 100644 index 0000000..32c4e6b --- /dev/null +++ b/modules/nixos/desktop/gnome/monitors.xml @@ -0,0 +1,31 @@ + + + + 0 + 0 + 1 + yes + + + DP-3 + DEL + Dell U4919DW + D3TXTY2 + + + 5120 + 1440 + 59.976879119873047 + + + + + + HDMI-2 + AOC + 28E850 + 0x00000000 + + + + diff --git a/modules/nixos/desktop/sway/background.png b/modules/nixos/desktop/sway/background.png new file mode 100644 index 0000000..1a5a078 Binary files /dev/null and b/modules/nixos/desktop/sway/background.png differ diff --git a/modules/nixos/desktop/sway/config b/modules/nixos/desktop/sway/config new file mode 100644 index 0000000..d38c225 --- /dev/null +++ b/modules/nixos/desktop/sway/config @@ -0,0 +1,274 @@ +########################################### +#░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░# +#░░█▀█░█░░░█░█░█▀▀░░░█░█░█░░░▀█▀░█▀▄░█▀█░░# +#░░█▀▀░█░░░█░█░▀▀█░░░█░█░█░░░░█░░█▀▄░█▀█░░# +#░░▀░░░▀▀▀░▀▀▀░▀▀▀░░░▀▀▀░▀▀▀░░▀░░▀░▀░▀░▀░░# +#░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░# +########################################### + + +######################################### +#░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░# +#░░█░█░█▀█░█▀▄░▀█▀░█▀█░█▀▄░█░░░█▀▀░█▀▀░░# +#░░▀▄▀░█▀█░█▀▄░░█░░█▀█░█▀▄░█░░░█▀▀░▀▀█░░# +#░░░▀░░▀░▀░▀░▀░▀▀▀░▀░▀░▀▀░░▀▀▀░▀▀▀░▀▀▀░░# +#░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░# +######################################### + +# Modifier key (Mod1 = Alt, Mod4 = Meta) +set $mod Mod4 + +# Movement keys +set $left h +set $down j +set $up k +set $right l + +# Terminal +set $term env @term@ + +# Swaylock +set $swaylock swaylock-fancy -f "Hack-Regular-Nerd-Font-Complete" + +# Menu +set $menu-run wofi --show drun --prompt search + +# Output names +set $laptop-screen eDP-1 +# set $monitor-left HDMI-A-1 +# set $monitor-center HDMI-A-2 + +# Workspace names +set $workspace1 1 +set $workspace2 2 +set $workspace3 3 +set $workspace4 4 +set $workspace5 5 +set $workspace6 6 +set $workspace7 7 +set $workspace8 8 +set $workspace9 9 +set $workspace10 10 + +############################# +#░░░░░░░░░░░░░░░░░░░░░░░░░░░# +#░░█▀█░█░█░▀█▀░█▀█░█░█░▀█▀░░# +#░░█░█░█░█░░█░░█▀▀░█░█░░█░░░# +#░░▀▀▀░▀▀▀░░▀░░▀░░░▀▀▀░░▀░░░# +#░░░░░░░░░░░░░░░░░░░░░░░░░░░# +############################# + +# Backgrounds +# output $monitor-left bg /home/short/Photos/wallpapers/left.jpg fill +# output $monitor-center bg /home/short/Photos/wallpapers/center.jpg fill + +# Positioning +# output $monitor-left resolution 1920x1080 position 0,300 +# output $monitor-center resolution 1920x1080 position 1920,0 + +################################################### +#░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░# +#░░█░█░█▀▀░█░█░░░█▀▄░▀█▀░█▀█░█▀▄░▀█▀░█▀█░█▀▀░█▀▀░░# +#░░█▀▄░█▀▀░░█░░░░█▀▄░░█░░█░█░█░█░░█░░█░█░█░█░▀▀█░░# +#░░▀░▀░▀▀▀░░▀░░░░▀▀░░▀▀▀░▀░▀░▀▀░░▀▀▀░▀░▀░▀▀▀░▀▀▀░░# +#░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░# +################################################### + +# Start a terminal +bindsym $mod+Return exec $term + +# Kill focused window +bindsym $mod+q kill + +# Reload configuration +bindsym $mod+Shift+r reload + +# Exit sway +bindsym $mod+Shift+e exit + +# Lock +bindsym $mod+Control+l exec $swaylock + +# Open run menu +bindsym $mod+d exec $menu-run + +# Drag floating windows while holding down modifier key +floating_modifier $mod normal + +# Sticky windows +bindsym $mod+Shift+s sticky toggle + +# Toggle fullscreen +bindsym $mod+f fullscreen + +# Toggle floating +bindsym $mod+Shift+space floating toggle + +# Toggle floating focus +bindsym $mod+space focus mode_toggle + +# Focus the parent container +bindsym $mod+a focus parent + +# Focus the child container +bindsym $mod+c focus child + +# Split horizontal +bindsym $mod+b splith + +# Split vertical +bindsym $mod+v splitv + +# Move window into scratchpad +bindsym $mod+Shift+minus move scratchpad + +# Get window out of scratchpad +bindsym $mod+minus scratchpad show + +# Change layout style +bindsym $mod+s layout stacking +bindsym $mod+w layout tabbed +bindsym $mod+e layout toggle split +bindsym $mod+Shift+v layout splitv +bindsym $mod+Shift+b layout splith + +# Moving focus +bindsym $mod+$left focus left +bindsym $mod+$down focus down +bindsym $mod+$up focus up +bindsym $mod+$right focus right + +# Moving windows +bindsym $mod+Shift+$left move left +bindsym $mod+Shift+$down move down +bindsym $mod+Shift+$up move up +bindsym $mod+Shift+$right move right + +# Switching workspace +bindsym $mod+1 workspace $workspace1 +bindsym $mod+2 workspace $workspace2 +bindsym $mod+3 workspace $workspace3 +bindsym $mod+4 workspace $workspace4 +bindsym $mod+5 workspace $workspace5 +bindsym $mod+6 workspace $workspace6 +bindsym $mod+7 workspace $workspace7 +bindsym $mod+8 workspace $workspace8 +bindsym $mod+9 workspace $workspace9 +bindsym $mod+0 workspace $workspace10 + +# Moving windows to workspace +bindsym $mod+Shift+1 move container to workspace $workspace1 +bindsym $mod+Shift+2 move container to workspace $workspace2 +bindsym $mod+Shift+3 move container to workspace $workspace3 +bindsym $mod+Shift+4 move container to workspace $workspace4 +bindsym $mod+Shift+5 move container to workspace $workspace5 +bindsym $mod+Shift+6 move container to workspace $workspace6 +bindsym $mod+Shift+7 move container to workspace $workspace7 +bindsym $mod+Shift+8 move container to workspace $workspace8 +bindsym $mod+Shift+9 move container to workspace $workspace9 +bindsym $mod+Shift+0 move container to workspace $workspace10 + +# Brightness +bindsym XF86MonBrightnessUp exec brightnessctl set 5%+ +bindsym XF86MonBrightnessDown exec brightnessctl set 5%- + +# Sound +bindsym XF86AudioMute exec pulsemixer --toggle-mute +bindsym XF86AudioRaiseVolume exec pulsemixer --change-volume +10 +bindsym XF86AudioLowerVolume exec pulsemixer --change-volume -10 + +# Media +bindsym XF86AudioPrev exec playerctl previous +bindsym XF86AudioPlay exec playerctl play-pause +bindsym XF86AudioNext exec playerctl next + +# Print screen +bindsym Print exec grimshot --notify save screen - | swappy -f - +bindsym Shift+Print exec grimshot --notify save area - | swappy -f - +bindsym Control+Print exec grimshot --notify save window - | swappy -f - +bindsym $mod+Print exec grimshot --notify copy screen +bindsym $mod+Shift+Print exec grimshot --notify copy area +bindsym $mod+Control+Print exec grimshot --notify copy window + +# Airplane mode +# bindsym XF86RFKill exec nmcli radio all $(test $(nmcli -g wifi radio all) == "enabled" && "off" || echo "on") + +# Resize mode +mode "resize" { + # Shrink the width + bindsym $left resize shrink width 10px + + # Grow the width + bindsym $right resize grow width 10px + + # Grow the height + bindsym $up resize grow height 10px + + # Shrink the height + bindsym $down resize shrink height 10px + + # Return to default mode + bindsym Return mode "default" + bindsym Escape mode "default" +} + +# Enter resize mode +bindsym $mod+r mode "resize" + +###################### +#░░░░░░░░░░░░░░░░░░░░# +#░▀█▀░█▀█░█▀█░█░█░▀█▀# +#░░█░░█░█░█▀▀░█░█░░█░# +#░▀▀▀░▀░▀░▀░░░▀▀▀░░▀░# +#░░░░░░░░░░░░░░░░░░░░# +###################### + +input type:keyboard { + xkb_options caps:swapescape +} + +input type:touchpad { + natural_scroll enabled + tap enabled + tap_button_map lrm +} + +################# +#░░░░░░░░░░░░░░░# +#░░█▀▄░█▀█░█▀▄░░# +#░░█▀▄░█▀█░█▀▄░░# +#░░▀▀░░▀░▀░▀░▀░░# +#░░░░░░░░░░░░░░░# +################# + +bar { + # Run waybar + swaybar_command waybar +} + +######################### +#░░░░░░░░░░░░░░░░░░░░░░░# +#░░█▀▀░▀█▀░█░█░█░░░█▀▀░░# +#░░▀▀█░░█░░░█░░█░░░█▀▀░░# +#░░▀▀▀░░▀░░░▀░░▀▀▀░▀▀▀░░# +#░░░░░░░░░░░░░░░░░░░░░░░# +######################### + +# Remove title bars +default_border pixel 1 +default_floating_border pixel 1 + +client.focused #8fbcbb #8fbcbb #eceff4 #eceff4 +client.focused_inactive #2E3440 #2E3440 #e5e9f0 #5e81ac +client.unfocused #3b4252 #3b4252 #4c566a #5e81ac +client.urgent #d08770 #d08770 #eceff4 #bf616a + +for_window [app_id="wofi"] border none +# for_window [app_id="firefox"] border none + +# Don't focus moused over windows +focus_follows_mouse no + +# Gaps +gaps outer 4 +gaps inner 5 diff --git a/modules/nixos/desktop/sway/default.nix b/modules/nixos/desktop/sway/default.nix new file mode 100644 index 0000000..d06f9ec --- /dev/null +++ b/modules/nixos/desktop/sway/default.nix @@ -0,0 +1,148 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.desktop.sway; + term = config.plusultra.desktop.addons.term; + substitutedConfig = pkgs.substituteAll { + src = ./config; + term = term.pkg.pname or term.pkg.name; + }; +in +{ + options.plusultra.desktop.sway = with types; { + enable = mkBoolOpt false "Whether or not to enable Sway."; + wallpaper = mkOpt (nullOr package) null "The wallpaper to display."; + extraConfig = + mkOpt str "" "Additional configuration for the Sway config file."; + }; + + config = mkIf cfg.enable { + # Desktop additions + plusultra.desktop.addons = { + gtk = enabled; + foot = enabled; + mako = enabled; + rofi = enabled; + wofi = enabled; + swappy = enabled; + kanshi = enabled; + waybar = enabled; + keyring = enabled; + nautilus = enabled; + xdg-portal = enabled; + electron-support = enabled; + }; + + plusultra.home.configFile."sway/config".text = + fileWithText substitutedConfig '' + ############################# + #░░░░░░░░░░░░░░░░░░░░░░░░░░░# + #░░█▀▀░█░█░█▀▀░▀█▀░█▀▀░█▄█░░# + #░░▀▀█░░█░░▀▀█░░█░░█▀▀░█░█░░# + #░░▀▀▀░░▀░░▀▀▀░░▀░░▀▀▀░▀░▀░░# + #░░░░░░░░░░░░░░░░░░░░░░░░░░░# + ############################# + + # Launch services waiting for the systemd target sway-session.target + exec "systemctl --user import-environment; systemctl --user start sway-session.target" + + # Start a user session dbus (required for things like starting + # applications through wofi). + exec dbus-daemon --session --address=unix:path=$XDG_RUNTIME_DIR/bus + + ${optionalString (cfg.wallpaper != null) '' + output * { + bg ${cfg.wallpaper.gnomeFilePath or cfg.wallpaper} fill + } + ''} + + ${cfg.extraConfig} + ''; + + programs.sway = { + enable = true; + extraPackages = with pkgs; [ + rofi + swaylock + swayidle + xwayland + sway-contrib.grimshot + swaylock-fancy + wl-clipboard + wf-recorder + libinput + playerctl + brightnessctl + glib # for gsettings + gtk3.out # for gtk-launch + gnome.gnome-control-center + ]; + + extraSessionCommands = '' + export SDL_VIDEODRIVER=wayland + export QT_QPA_PLATFORM=wayland + export QT_WAYLAND_DISABLE_WINDOWDECORATION="1" + export _JAVA_AWT_WM_NONREPARENTING=1 + export MOZ_ENABLE_WAYLAND=1 + export XDG_SESSION_TYPE=wayland + export XDG_SESSION_DESKTOP=sway + export XDG_CURRENT_DESKTOP=sway + ''; + }; + + environment.systemPackages = with pkgs; + [ + (pkgs.writeTextFile { + name = "startsway"; + destination = "/bin/startsway"; + executable = true; + text = '' + #! ${pkgs.bash}/bin/bash + + # Import environment variables from the login manager + systemctl --user import-environment + + # Start Sway + exec systemctl --user start sway.service + ''; + }) + ]; + + # configuring sway itself (assmung a display manager starts it) + systemd.user.targets.sway-session = { + description = "Sway compositor session"; + documentation = [ "man:systemd.special(7)" ]; + bindsTo = [ "graphical-session.target" ]; + wants = [ "graphical-session-pre.target" ]; + after = [ "graphical-session-pre.target" ]; + }; + + systemd.user.services.sway = { + description = "Sway - Wayland window manager"; + documentation = [ "man:sway(5)" ]; + bindsTo = [ "graphical-session.target" ]; + wants = [ "graphical-session-pre.target" ]; + after = [ "graphical-session-pre.target" ]; + # We explicitly unset PATH here, as we want it to be set by + # systemctl --user import-environment in startsway + environment.PATH = lib.mkForce null; + serviceConfig = { + Type = "simple"; + ExecStart = '' + ${pkgs.dbus}/bin/dbus-run-session ${pkgs.sway}/bin/sway --debug + ''; + Restart = "on-failure"; + RestartSec = 1; + TimeoutStopSec = 10; + }; + }; + + services.xserver.enable = true; + services.xserver.displayManager.defaultSession = "sway"; + services.xserver.displayManager.gdm.enable = true; + services.xserver.displayManager.gdm.wayland = true; + services.xserver.libinput.enable = true; + }; +} diff --git a/modules/nixos/hardware/audio/default.nix b/modules/nixos/hardware/audio/default.nix new file mode 100644 index 0000000..001e463 --- /dev/null +++ b/modules/nixos/hardware/audio/default.nix @@ -0,0 +1,200 @@ +{ options +, config +, pkgs +, lib +, ... +}: +# FIXME: The transition to wireplumber from media-session has completely +# broken my setup. I'll need to invest some time to figure out how to override Alsa things +# again... +with lib; +with lib.plusultra; let + cfg = config.plusultra.hardware.audio; + + lua-format = { + type = with lib.types; let + valueType = + nullOr + (oneOf [ + bool + int + float + str + path + (attrsOf valueType) + (listOf valueType) + ]) + // { + description = "Lua value"; + }; + in + valueType; + + generate = name: value: + let + toLuaValue = value: + if value == null + then "null" + else if value == true + then "true" + else if value == false + then "false" + else if builtins.isInt value || builtins.isFloat value + then builtins.toString value + else if builtins.isString value + then toLuaString value + else if builtins.isAttrs value + then toLuaTable value + else if builtins.isList value + then toLuaList value + else builtins.abort "Unsupported value used with formats.lua.generate: ${value}"; + + toLuaString = value: "\"${builtins.toString value}\""; + + toLuaTable = value: + let + pairs = + mapAttrsToList + (name: value: "[${toLuaString name}] = ${toLuaValue value}") + value; + content = concatStringsSep ", " pairs; + in + "{ ${content} }"; + + toLuaList = value: + let + parts = builtins.map toLuaValue value; + content = concatStringsSep ", " parts; + in + "{ ${content} }"; + in + toLuaValue value; + }; + + pipewire-config = { + "context.objects" = cfg.nodes ++ [ ]; + "context.modules" = + [ + { + name = "libpipewire-module-rtkit"; + args = { }; + flags = [ "ifexists" "nofail" ]; + } + { name = "libpipewire-module-protocol-native"; } + { name = "libpipewire-module-profiler"; } + # { + # name = "libpipewire-module-metadata"; + # flags = [ "ifexists" "nofail" ]; + # } + { name = "libpipewire-module-spa-device-factory"; } + { name = "libpipewire-module-spa-node-factory"; } + # { + # name = "libpipewire-module-client-node"; + # flags = [ "ifexists" "nofail" ]; + # } + # { + # name = "libpipewire-module-client-device"; + # flags = [ "ifexists" "nofail" ]; + # } + { + name = "libpipewire-module-portal"; + flags = [ "ifexists" "nofail" ]; + } + { + name = "libpipewire-module-access"; + args = { }; + } + { name = "libpipewire-module-adapter"; } + { name = "libpipewire-module-link-factory"; } + { name = "libpipewire-module-session-manager"; } + ] + ++ cfg.modules; + "context.components" = [ + { + name = "libwireplumber-module-lua-scripting"; + type = "module"; + } + { + name = "config.lua"; + type = "config/lua"; + } + ]; + }; + + alsa-config = { + alsa_monitor = cfg.alsa-monitor; + }; +in +{ + options.plusultra.hardware.audio = with types; { + enable = mkBoolOpt false "Whether or not to enable audio support."; + alsa-monitor = mkOpt attrs { } "Alsa configuration."; + nodes = + mkOpt (listOf attrs) [ ] + "Audio nodes to pass to Pipewire as `context.objects`."; + modules = + mkOpt (listOf attrs) [ ] + "Audio modules to pass to Pipewire as `context.modules`."; + extra-packages = mkOpt (listOf package) [ + pkgs.qjackctl + pkgs.easyeffects + ] "Additional packages to install."; + }; + + config = mkIf cfg.enable { + security.rtkit.enable = true; + + services.pipewire = { + enable = true; + alsa.enable = true; + pulse.enable = true; + jack.enable = true; + + wireplumber.enable = true; + }; + + environment.etc = { + # "pipewire/pipewire.conf.d/10-pipewire.conf".source = + # pkgs.writeText "pipewire.conf" (builtins.toJSON pipewire-config); + # "pipewire/pipewire.conf.d/21-alsa.conf".source = + # pkgs.writeText "pipewire.conf" (builtins.toJSON alsa-config); + + # "wireplumber/wireplumber.conf".source = + # pkgs.writeText "pipewire.conf" (builtins.toJSON pipewire-config); + + # "wireplumber/scripts/config.lua.d/alsa.lua".text = '' + # local input = ${lua-format.generate "sample.lua" cfg.alsa-monitor} + + # if input.rules == nil then + # input.rules = {} + # end + + # local rules = input.rules + + # for _, rule in ipairs(input.rules) do + # table.insert(alsa_monitor.rules, rule) + # end + # ''; + }; + + hardware.pulseaudio.enable = mkForce false; + + environment.systemPackages = with pkgs; + [ + pulsemixer + pavucontrol + ] + ++ cfg.extra-packages; + + plusultra.user.extraGroups = [ "audio" ]; + + plusultra.home.extraOptions = { + systemd.user.services.mpris-proxy = { + Unit.Description = "Mpris proxy"; + Unit.After = [ "network.target" "sound.target" ]; + Service.ExecStart = "${pkgs.bluez}/bin/mpris-proxy"; + Install.WantedBy = [ "default.target" ]; + }; + }; + }; +} diff --git a/modules/nixos/hardware/fingerprint/default.nix b/modules/nixos/hardware/fingerprint/default.nix new file mode 100644 index 0000000..5c6dbf8 --- /dev/null +++ b/modules/nixos/hardware/fingerprint/default.nix @@ -0,0 +1,13 @@ +{ options, config, pkgs, lib, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.hardware.fingerprint; +in +{ + options.plusultra.hardware.fingerprint = with types; { + enable = mkBoolOpt false "Whether or not to enable fingerprint support."; + }; + + config = mkIf cfg.enable { services.fprintd.enable = true; }; +} diff --git a/modules/nixos/hardware/networking/default.nix b/modules/nixos/hardware/networking/default.nix new file mode 100644 index 0000000..3cec0a8 --- /dev/null +++ b/modules/nixos/hardware/networking/default.nix @@ -0,0 +1,32 @@ +{ options, config, pkgs, lib, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.hardware.networking; +in +{ + options.plusultra.hardware.networking = with types; { + enable = mkBoolOpt false "Whether or not to enable networking support"; + hosts = mkOpt attrs { } + (mdDoc "An attribute set to merge with `networking.hosts`"); + }; + + config = mkIf cfg.enable { + plusultra.user.extraGroups = [ "networkmanager" ]; + + networking = { + hosts = { + "127.0.0.1" = [ "local.test" ] ++ (cfg.hosts."127.0.0.1" or [ ]); + } // cfg.hosts; + + networkmanager = { + enable = true; + dhcp = "internal"; + }; + }; + + # Fixes an issue that normally causes nixos-rebuild to fail. + # https://github.com/NixOS/nixpkgs/issues/180175 + systemd.services.NetworkManager-wait-online.enable = false; + }; +} diff --git a/modules/nixos/hardware/storage/default.nix b/modules/nixos/hardware/storage/default.nix new file mode 100644 index 0000000..2f07e62 --- /dev/null +++ b/modules/nixos/hardware/storage/default.nix @@ -0,0 +1,16 @@ +{ options, config, pkgs, lib, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.hardware.storage; +in +{ + options.plusultra.hardware.storage = with types; { + enable = mkBoolOpt false + "Whether or not to enable support for extra storage devices."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ ntfs3g fuseiso ]; + }; +} diff --git a/modules/nixos/home/default.nix b/modules/nixos/home/default.nix index 4c53a0e..3f47f81 100644 --- a/modules/nixos/home/default.nix +++ b/modules/nixos/home/default.nix @@ -5,6 +5,10 @@ with lib.plusultra; let cfg = config.plusultra.home; in { + # imports = with inputs; [ + # home-manager.nixosModules.home-manager + # ]; + options.plusultra.home = with types; { file = mkOpt attrs { } (mdDoc "A set of files to be managed by home-manager's `home.file`."); diff --git a/modules/nixos/nix/default.nix b/modules/nixos/nix/default.nix index 812ef27..ec2a79c 100644 --- a/modules/nixos/nix/default.nix +++ b/modules/nixos/nix/default.nix @@ -42,6 +42,7 @@ in nix-index nix-prefetch-git nix-output-monitor + flake-checker ]; nix = diff --git a/modules/nixos/security/acme/default.nix b/modules/nixos/security/acme/default.nix new file mode 100644 index 0000000..dc2b258 --- /dev/null +++ b/modules/nixos/security/acme/default.nix @@ -0,0 +1,31 @@ +{ lib, pkgs, config, virtual, ... }: + +let + inherit (lib) mkIf mkEnableOption optional; + inherit (lib.plusultra) mkOpt; + + cfg = config.plusultra.security.acme; +in +{ + options.plusultra.security.acme = with lib.types; { + enable = mkEnableOption "default ACME configuration"; + email = mkOpt str config.plusultra.user.email "The email to use."; + staging = mkOpt bool virtual "Whether to use the staging server or not."; + }; + + config = mkIf cfg.enable { + security.acme = { + acceptTerms = true; + + defaults = { + inherit (cfg) email; + + group = mkIf config.services.nginx.enable "nginx"; + server = mkIf cfg.staging "https://acme-staging-v02.api.letsencrypt.org/directory"; + + # Reload nginx when certs change. + reloadServices = optional config.services.nginx.enable "nginx.service"; + }; + }; + }; +} diff --git a/modules/nixos/security/doas/default.nix b/modules/nixos/security/doas/default.nix new file mode 100644 index 0000000..a586858 --- /dev/null +++ b/modules/nixos/security/doas/default.nix @@ -0,0 +1,29 @@ +{ options, config, pkgs, lib, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.security.doas; +in +{ + options.plusultra.security.doas = { + enable = mkBoolOpt false "Whether or not to replace sudo with doas."; + }; + + config = mkIf cfg.enable { + # Disable sudo + security.sudo.enable = false; + + # Enable and configure `doas`. + security.doas = { + enable = true; + extraRules = [{ + users = [ config.plusultra.user.name ]; + noPass = true; + keepEnv = true; + }]; + }; + + # Add an alias to the shell for backward-compat and convenience. + environment.shellAliases = { sudo = "doas"; }; + }; +} diff --git a/modules/nixos/security/gpg/default.nix b/modules/nixos/security/gpg/default.nix new file mode 100644 index 0000000..fccfbc1 --- /dev/null +++ b/modules/nixos/security/gpg/default.nix @@ -0,0 +1,118 @@ +{ options +, config +, pkgs +, lib +, inputs +, ... +}: +with lib; +with lib.plusultra; let + cfg = config.plusultra.security.gpg; + + gpgConf = "${inputs.gpg-base-conf}/gpg.conf"; + + gpgAgentConf = '' + enable-ssh-support + default-cache-ttl 60 + max-cache-ttl 120 + pinentry-program ${pkgs.pinentry-gnome}/bin/pinentry-gnome3 + ''; + + guide = "${inputs.yubikey-guide}/README.md"; + + theme = pkgs.fetchFromGitHub { + owner = "jez"; + repo = "pandoc-markdown-css-theme"; + rev = "019a4829242937761949274916022e9861ed0627"; + sha256 = "1h48yqffpaz437f3c9hfryf23r95rr319lrb3y79kxpxbc9hihxb"; + }; + + guideHTML = pkgs.runCommand "yubikey-guide" { } '' + ${pkgs.pandoc}/bin/pandoc \ + --standalone \ + --metadata title="Yubikey Guide" \ + --from markdown \ + --to html5+smart \ + --toc \ + --template ${theme}/template.html5 \ + --css ${theme}/docs/css/theme.css \ + --css ${theme}/docs/css/skylighting-solarized-theme.css \ + -o $out \ + ${guide} + ''; + + guideDesktopItem = pkgs.makeDesktopItem { + name = "yubikey-guide"; + desktopName = "Yubikey Guide"; + genericName = "View Yubikey Guide in a web browser"; + exec = "${pkgs.xdg-utils}/bin/xdg-open ${guideHTML}"; + icon = ./yubico-icon.svg; + categories = [ "System" ]; + }; + + reload-yubikey = pkgs.writeShellScriptBin "reload-yubikey" '' + ${pkgs.gnupg}/bin/gpg-connect-agent "scd serialno" "learn --force" /bye + ''; +in +{ + options.plusultra.security.gpg = with types; { + enable = mkBoolOpt false "Whether or not to enable GPG."; + agentTimeout = mkOpt int 5 "The amount of time to wait before continuing with shell init."; + }; + + config = mkIf cfg.enable { + services.pcscd.enable = true; + services.udev.packages = with pkgs; [ yubikey-personalization ]; + + # NOTE: This should already have been added by programs.gpg, but + # keeping it here for now just in case. + environment.shellInit = '' + export GPG_TTY="$(tty)" + export SSH_AUTH_SOCK=$(${pkgs.gnupg}/bin/gpgconf --list-dirs agent-ssh-socket) + + ${pkgs.coreutils}/bin/timeout ${builtins.toString cfg.agentTimeout} ${pkgs.gnupg}/bin/gpgconf --launch gpg-agent + gpg_agent_timeout_status=$? + + if [ "$gpg_agent_timeout_status" = 124 ]; then + # Command timed out... + echo "GPG Agent timed out..." + echo 'Run "gpgconf --launch gpg-agent" to try and launch it again.' + fi + ''; + + environment.systemPackages = with pkgs; [ + cryptsetup + paperkey + gnupg + pinentry-curses + pinentry-qt + pinentry-gnome + paperkey + guideDesktopItem + reload-yubikey + ]; + + programs = { + ssh.startAgent = false; + + gnupg.agent = { + enable = true; + enableSSHSupport = true; + enableExtraSocket = true; + pinentryFlavor = "gnome3"; + }; + }; + + plusultra = { + home.file = { + ".gnupg/.keep".text = ""; + + ".gnupg/yubikey-guide.md".source = guide; + ".gnupg/yubikey-guide.html".source = guideHTML; + + ".gnupg/gpg.conf".source = gpgConf; + ".gnupg/gpg-agent.conf".text = gpgAgentConf; + }; + }; + }; +} diff --git a/modules/nixos/security/gpg/yubico-icon.svg b/modules/nixos/security/gpg/yubico-icon.svg new file mode 100644 index 0000000..ebfa48c --- /dev/null +++ b/modules/nixos/security/gpg/yubico-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/modules/nixos/security/keyring/default.nix b/modules/nixos/security/keyring/default.nix new file mode 100644 index 0000000..00ea04d --- /dev/null +++ b/modules/nixos/security/keyring/default.nix @@ -0,0 +1,19 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.security.keyring; +in +{ + options.plusultra.security.keyring = with types; { + enable = mkBoolOpt false "Whether to enable gnome keyring."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + gnome.gnome-keyring + gnome.libgnome-keyring + ]; + }; +} diff --git a/modules/nixos/services/attic/default.nix b/modules/nixos/services/attic/default.nix new file mode 100644 index 0000000..a008fe2 --- /dev/null +++ b/modules/nixos/services/attic/default.nix @@ -0,0 +1,97 @@ +{ lib, config, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.services.attic; + + toml-format = pkgs.formats.toml { }; + + raw-server-toml = toml-format.generate "server.toml" cfg.settings; + + server-toml = pkgs.runCommand "checked-server.toml" { config = raw-server-toml; } '' + cat $config + + export ATTIC_SERVER_TOKEN_HS256_SECRET_BASE64="dGVzdCBzZWNyZXQ=" + export ATTIC_SERVER_DATABASE_URL="sqlite://:memory:" + + ${cfg.package}/bin/atticd --mode check-config -f "$config" + + cat < $config > $out + ''; + + is-local-postgres = + let + url = cfg.settings.database.url or ""; + local-db-strings = [ "localhost" "127.0.0.1" "/run/postgresql" ]; + is-local-db-url = any (flip hasInfix url) local-db-strings; + in + config.services.postgresql.enable + && hasPrefix "postgresql://" url + && is-local-db-url; +in +{ + options.plusultra.services.attic = { + enable = mkEnableOption "Attic"; + + package = mkOpt types.package pkgs.attic-server "The attic-server package to use."; + + credentials = mkOpt (types.nullOr types.path) null "The path to an optional EnvironmentFile for the atticd service to use."; + + user = mkOpt types.str "atticd" "The user under which attic runs."; + group = mkOpt types.str "atticd" "The group under which attic runs."; + + settings = mkOpt toml-format.type { } "Settings for the atticd config file."; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = !isStorePath cfg.credentials; + message = "plusultra.services.attic.credentials CANNOT be in the Nix Store."; + } + ]; + + users = { + users = optionalAttrs (cfg.user == "atticd") { + atticd = { + group = cfg.group; + isSystemUser = true; + }; + }; + + groups = optionalAttrs (cfg.group == "atticd") { + atticd = { }; + }; + }; + + plusultra = { + tools.attic = enabled; + + services.attic.settings = { + database.url = mkDefault "sqlite:///var/lib/atticd/server.db?mode=rwc"; + + storage = mkDefault { + type = "local"; + path = "/var/lib/atticd/storage"; + }; + }; + }; + + systemd.services.atticd = { + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ] + ++ optionals is-local-postgres [ "postgresql.service" "nss-lookup.target" ]; + + serviceConfig = { + ExecStart = "${cfg.package}/bin/atticd -f ${server-toml}"; + StateDirectory = "atticd"; + User = cfg.user; + Group = cfg.group; + DynamicUser = true; + } // optionalAttrs (cfg.credentials != null) { + EnvironmentFile = mkDefault cfg.credentials; + }; + }; + }; +} diff --git a/modules/nixos/services/avahi/default.nix b/modules/nixos/services/avahi/default.nix new file mode 100644 index 0000000..175316f --- /dev/null +++ b/modules/nixos/services/avahi/default.nix @@ -0,0 +1,41 @@ +{ lib, config, options, ... }: + +let + cfg = config.plusultra.services.avahi; + + inherit (lib) types mkEnableOption mkIf; +in +{ + options.plusultra.services.avahi = with types; { + enable = mkEnableOption "Avahi"; + }; + + config = mkIf cfg.enable { + services.avahi = { + enable = true; + nssmdns = true; + publish = { + enable = true; + addresses = true; + domain = true; + hinfo = true; + userServices = true; + workstation = true; + }; + + extraServiceFiles = { + smb = '' + + + + %h + + _smb._tcp + 445 + + + ''; + }; + }; + }; +} diff --git a/modules/nixos/services/cowsay-mastodon-poster/default.nix b/modules/nixos/services/cowsay-mastodon-poster/default.nix new file mode 100644 index 0000000..17ec466 --- /dev/null +++ b/modules/nixos/services/cowsay-mastodon-poster/default.nix @@ -0,0 +1,66 @@ +{ lib, pkgs, config, ... }: + +let + inherit (lib) types mkIf; + inherit (lib.plusultra) mkBoolOpt mkOpt; + inherit (pkgs) fortune toot; + inherit (pkgs.snowfallorg) cow2img; + + cfg = config.plusultra.services.cowsay-mastodon-poster; + + script = '' + if [ ! -f ~/.config/toot/config.json ]; then + echo "File ~/.config/toot/config.json does not exist. Run 'toot login_cli' first." + exit 1 + fi + + tmp_dir=$(mktemp -d) + + pushd $tmp_dir > /dev/null + ${cow2img}/bin/cow2img --no-spinner ${if cfg.short then "--message \"$(${fortune}/bin/fortune -s)\"" else ""} + + cow_name=$(cat ./cow/name) + cow_message=$(cat ./cow/message) + + post="$cow_name saying:"$'\n\n'"$cow_message" + + ${toot}/bin/toot post --media ./cow/image.png --description "$post" "#hachybots" + popd > /dev/null + + rm -rf $tmp_dir + ''; +in +{ + options.plusultra.services.cowsay-mastodon-poster = with types; { + enable = mkBoolOpt false "Whether or not to enable cowsay posts."; + short = mkBoolOpt false "Use short fortunes only."; + user = mkOpt str config.plusultra.user.name "The user to run as."; + group = mkOpt str "users" "The group to run as."; + }; + + config = mkIf cfg.enable { + systemd = { + timers.cowsay-mastodon-poster = { + wantedBy = [ "timers.target" ]; + timerConfig = { + # Run once a day at 10am. + OnCalendar = "*-*-* 10:00:00"; + Unit = "cowsay-mastodon-poster.service"; + }; + }; + + services.cowsay-mastodon-poster = { + after = [ "network-online.target" ]; + description = "Post a cowsay image to Mastodon."; + + inherit script; + + serviceConfig = { + Type = "oneshot"; + User = cfg.user; + Group = cfg.group; + }; + }; + }; + }; +} diff --git a/modules/nixos/services/dex/default.nix b/modules/nixos/services/dex/default.nix new file mode 100644 index 0000000..f37253f --- /dev/null +++ b/modules/nixos/services/dex/default.nix @@ -0,0 +1,198 @@ +{ config, lib, pkgs, ... }: + +let + inherit (builtins) map removeAttrs; + inherit (lib) mapAttrs flatten concatMap concatMapStringsSep; + + cfg = config.plusultra.services.dex; + + process-client-settings = client: + if client ? secretFile then + (removeAttrs client [ "secretFile" ]) + // { secret = client.secretFile; } + else + client; + + settings = + mapAttrs + (name: value: + if name == "staticClients" then + map process-client-settings value + else + value + ) + (cfg.settings // { + storage = (cfg.settings.storage or { }) // { + type = cfg.settings.storage.type or "sqlite3"; + config = cfg.settings.storage.config or { + file = "${cfg.stateDir}/dex.db"; + }; + }; + }); + + secret-files = concatMap + (client: + if client ? secretFile then + [ client.secretFile ] + else + [ ] + ) + (settings.staticClients or [ ]); + + format = pkgs.formats.yaml { }; + + configYaml = format.generate "config.yaml" settings; + + replace-config-secrets = pkgs.writeShellScript "replace-config-secrets" + (concatMapStringsSep "\n" + (file: "${pkgs.replace-secret}/bin/replace-secret '${file}' '${file}' ${cfg.stateDir}/config.yaml") + secret-files + ); + +in +{ + options.plusultra.services.dex = { + enable = lib.mkEnableOption "Dex, the OpenID Connect and OAuth 2 identity provider"; + + stateDir = lib.mkOption { + type = lib.types.path; + default = "/var/lib/dex"; + description = "The state directory where config and data are stored."; + }; + + user = lib.mkOption { + type = lib.types.str; + default = "dex"; + description = "The user to run Dex as."; + }; + + group = lib.mkOption { + type = lib.types.str; + default = "dex"; + description = "The group to run Dex as."; + }; + + settings = lib.mkOption { + type = format.type; + default = { }; + example = lib.literalExpression '' + { + # External url + issuer = "http://127.0.0.1:5556/dex"; + storage = { + type = "postgres"; + config.host = "/var/run/postgres"; + }; + web = { + http = "127.0.0.1:5556"; + }; + enablePasswordDB = true; + staticClients = [ + { + id = "oidcclient"; + name = "Client"; + redirectURIs = [ "https://example.com/callback" ]; + + # The content of `secretFile` will be written into to the config as `secret`. + secretFile = "/etc/dex/oidcclient"; + } + ]; + } + ''; + description = lib.mdDoc '' + The available options can be found in + [the example configuration](https://github.com/dexidp/dex/blob/v${pkgs.dex.version}/config.yaml.dist). + ''; + }; + }; + + config = lib.mkIf cfg.enable { + users = { + users = lib.optionalAttrs (cfg.user == "dex") { + dex = { + group = cfg.group; + home = cfg.stateDir; + isSystemUser = true; + }; + }; + + groups = lib.optionalAttrs (cfg.group == "dex") { + dex = { }; + }; + }; + + systemd = { + tmpfiles.rules = [ + "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group}" + ]; + + services = { + dex = { + description = "dex identity provider"; + wantedBy = [ "multi-user.target" ]; + after = [ "networking.target" ]; + + preStart = '' + cp --remove-destination ${configYaml} ${cfg.stateDir}/config.yaml + + chmod 600 ${cfg.stateDir}/config.yaml + + ${replace-config-secrets} + ''; + + serviceConfig = { + ExecStart = "${pkgs.dex-oidc}/bin/dex serve ${cfg.stateDir}/config.yaml"; + WorkingDirectory = cfg.stateDir; + + User = cfg.user; + Group = cfg.group; + + AmbientCapabilities = "CAP_NET_BIND_SERVICE"; + BindReadOnlyPaths = [ + "/nix/store" + "-/etc/resolv.conf" + "-/etc/nsswitch.conf" + "-/etc/hosts" + "-/etc/localtime" + "-/etc/dex" + ]; + BindPaths = [ cfg.stateDir ] ++ lib.optional (settings.storage.type == "postgres") "/var/run/postgresql"; + CapabilityBoundingSet = "CAP_NET_BIND_SERVICE"; + ## ProtectClock= adds DeviceAllow=char-rtc r + #DeviceAllow = ""; + #DynamicUser = true; + #LockPersonality = true; + #MemoryDenyWriteExecute = true; + #NoNewPrivileges = true; + #PrivateDevices = true; + #PrivateMounts = true; + ## Port needs to be exposed to the host network + ##PrivateNetwork = true; + #PrivateTmp = true; + #PrivateUsers = true; + #ProcSubset = "pid"; + #ProtectClock = true; + #ProtectHome = true; + #ProtectHostname = true; + ## Would re-mount paths ignored by temporary root + ##ProtectSystem = "strict"; + #ProtectControlGroups = true; + #ProtectKernelLogs = true; + #ProtectKernelModules = true; + #ProtectKernelTunables = true; + #ProtectProc = "invisible"; + #RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ]; + #RestrictNamespaces = true; + #RestrictRealtime = true; + #RestrictSUIDSGID = true; + #SystemCallArchitectures = "native"; + #SystemCallFilter = [ "@system-service" "~@privileged @resources @setuid @keyring" ]; + #TemporaryFileSystem = "/:ro"; + # Does not work well with the temporary root + #UMask = "0066"; + }; + }; + }; + }; + }; +} diff --git a/modules/nixos/services/dex/default.orig.nix b/modules/nixos/services/dex/default.orig.nix new file mode 100644 index 0000000..3ec64ef --- /dev/null +++ b/modules/nixos/services/dex/default.orig.nix @@ -0,0 +1,121 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.dex; + fixClient = client: if client ? secretFile then ((builtins.removeAttrs client [ "secretFile" ]) // { secret = client.secretFile; }) else client; + filteredSettings = mapAttrs (n: v: if n == "staticClients" then (builtins.map fixClient v) else v) cfg.settings; + secretFiles = flatten (builtins.map (c: if c ? secretFile then [ c.secretFile ] else [ ]) (cfg.settings.staticClients or [ ])); + + settingsFormat = pkgs.formats.yaml { }; + configFile = settingsFormat.generate "config.yaml" filteredSettings; + + startPreScript = pkgs.writeShellScript "dex-start-pre" ('' + '' + (concatStringsSep "\n" (builtins.map + (file: '' + ${pkgs.replace-secret}/bin/replace-secret '${file}' '${file}' /run/dex/config.yaml + '') + secretFiles))); +in +{ + options.services.dex = { + enable = mkEnableOption "the OpenID Connect and OAuth2 identity provider"; + + settings = mkOption { + type = settingsFormat.type; + default = { }; + example = literalExpression '' + { + # External url + issuer = "http://127.0.0.1:5556/dex"; + storage = { + type = "postgres"; + config.host = "/var/run/postgres"; + }; + web = { + http = "127.0.0.1:5556"; + }; + enablePasswordDB = true; + staticClients = [ + { + id = "oidcclient"; + name = "Client"; + redirectURIs = [ "https://example.com/callback" ]; + secretFile = "/etc/dex/oidcclient"; # The content of `secretFile` will be written into to the config as `secret`. + } + ]; + } + ''; + description = '' + The available options can be found in + the example configuration. + ''; + }; + }; + + config = mkIf cfg.enable { + systemd.services.dex = { + description = "dex identity provider"; + wantedBy = [ "multi-user.target" ]; + after = [ "networking.target" ] ++ (optional (cfg.settings.storage.type == "postgres") "postgresql.service"); + + serviceConfig = { + ExecStart = "${pkgs.dex-oidc}/bin/dex serve /run/dex/config.yaml"; + ExecStartPre = [ + "${pkgs.coreutils}/bin/install -m 600 ${configFile} /run/dex/config.yaml" + "+${startPreScript}" + ]; + RuntimeDirectory = "dex"; + + AmbientCapabilities = "CAP_NET_BIND_SERVICE"; + BindReadOnlyPaths = [ + "/nix/store" + "-/etc/resolv.conf" + "-/etc/nsswitch.conf" + "-/etc/hosts" + "-/etc/localtime" + "-/etc/dex" + ]; + BindPaths = optional (cfg.settings.storage.type == "postgres") "/var/run/postgresql"; + CapabilityBoundingSet = "CAP_NET_BIND_SERVICE"; + # ProtectClock= adds DeviceAllow=char-rtc r + DeviceAllow = ""; + DynamicUser = true; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateMounts = true; + # Port needs to be exposed to the host network + #PrivateNetwork = true; + PrivateTmp = true; + PrivateUsers = true; + ProcSubset = "pid"; + ProtectClock = true; + ProtectHome = true; + ProtectHostname = true; + # Would re-mount paths ignored by temporary root + #ProtectSystem = "strict"; + ProtectControlGroups = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ "@system-service" "~@privileged @resources @setuid @keyring" ]; + TemporaryFileSystem = "/:ro"; + # Does not work well with the temporary root + #UMask = "0066"; + }; + }; + }; + + # uses attributes of the linked package + meta.buildDocsInSandbox = false; +} + diff --git a/modules/nixos/services/homer/default.nix b/modules/nixos/services/homer/default.nix new file mode 100644 index 0000000..44ac2e5 --- /dev/null +++ b/modules/nixos/services/homer/default.nix @@ -0,0 +1,79 @@ +{ lib, config, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.services.homer; + + yaml-format = pkgs.formats.yaml { }; + settings-yaml = yaml-format.generate "config.yml" cfg.settings; + + settings-path = + if cfg.settings-path != null then + cfg.settings-path + else + builtins.toString settings-yaml; +in +{ + options.plusultra.services.homer = { + enable = mkEnableOption "Homer"; + + package = mkOpt types.package pkgs.plusultra.homer "The package of Homer assets to use."; + + settings = mkOpt yaml-format.type { } "Configuration for Homer's config.yml file."; + settings-path = mkOpt (types.nullOr types.path) null "A replacement for the generated config.yml file."; + + host = mkOpt (types.nullOr types.str) null "The host to serve Homer on."; + + nginx = { + forceSSL = mkOption { + type = types.bool; + default = false; + description = "Whether or not to force the use of SSL."; + }; + }; + + acme = { + enable = mkOption { + type = types.bool; + default = false; + description = + "Whether or not to automatically fetch and configure SSL certs."; + }; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = cfg.host != null; + message = "plusultra.services.homer.host must be set."; + } + { + assertion = cfg.settings-path != null -> cfg.settings == { }; + message = "plusultra.services.homer.settings and plusultra.services.homer.settings-path are mutually exclusive."; + } + { + assertion = cfg.nginx.forceSSL -> cfg.acme.enable; + message = "plusultra.services.homer.nginx.forceSSL requires setting plusultra.services.homer.acme.enable to true."; + } + ]; + + services.nginx = { + enable = true; + + virtualHosts."${cfg.host}" = { + enableACME = cfg.acme.enable; + forceSSL = cfg.nginx.forceSSL; + + locations."/" = { + root = "${cfg.package}/share/homer"; + }; + + locations."= /assets/config.yml" = { + alias = settings-path; + }; + }; + }; + }; +} diff --git a/modules/nixos/services/infrared/default.nix b/modules/nixos/services/infrared/default.nix new file mode 100644 index 0000000..220d139 --- /dev/null +++ b/modules/nixos/services/infrared/default.nix @@ -0,0 +1,158 @@ +{ config, options, lib, pkgs, ... }: + +let + inherit (builtins) toString; + inherit (lib) types; + + cfg = config.plusultra.services.infrared; + + format = pkgs.formats.json { }; + + serversType = (types.submodule ({ config, ... }: { + options = { + domain = lib.mkOption { + type = types.str; + default = ""; + description = '' + The domain to proxy. Should be fully qualified domain name. + Note: Every string is accepted. So localhost is also valid. + ''; + example = "minecraft.example.com"; + }; + + host = lib.mkOption { + type = types.str; + default = ""; + description = "The host where the Minecraft server is running. Defaults to local host."; + }; + + port = lib.mkOption { + type = types.port; + default = 25566; + description = "The port where the Minecraft server is running."; + }; + + settings = lib.mkOption { + default = { }; + description = '' + Infrared configuration (config.json). Refer to + + for details. + ''; + + type = types.submodule { + freeformType = format.type; + + options = { + domainName = lib.mkOption { + type = types.str; + default = config.domain; + defaultText = lib.literalExpression '' + "" + ''; + description = "The domain to proxy."; + }; + + proxyTo = lib.mkOption { + type = types.str; + default = "${config.host}:${toString config.port}"; + defaultText = ":25565"; + description = "The address that the proxy should send incoming connections to."; + }; + }; + }; + }; + }; + })); +in +{ + options.plusultra.services.infrared = { + enable = lib.mkEnableOption "Infrared"; + + stateDir = lib.mkOption { + type = types.path; + default = "/var/lib/infrared"; + description = "The state directory where configurations are stored."; + }; + + user = lib.mkOption { + type = types.str; + default = "infrared"; + description = "User under which Infrared is ran."; + }; + + group = lib.mkOption { + type = types.str; + default = "infrared"; + description = "Group under which Infrared is ran."; + }; + + openFirewall = lib.mkOption { + type = types.bool; + default = false; + description = "Whether to open the firewall for ports specified by each server's listenTo address."; + }; + + servers = lib.mkOption { + type = types.listOf serversType; + default = [ ]; + description = "The servers to proxy."; + example = lib.literalExpression '' + [ + { + domain = "minecraft.example.com"; + port = 25567; + } + ] + ''; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + ]; + + networking.firewall = lib.mkIf cfg.openFirewall { + allowedUDPPorts = builtins.map (server: server.port) cfg.servers; + allowedTCPPorts = builtins.map (server: server.port) cfg.servers; + }; + + systemd.tmpfiles.rules = + [ "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -" ] ++ + builtins.map + (server: + let + config = format.generate "${server.domain}.json" server.settings; + in + "L+ '${cfg.stateDir}/${server.domain}.json' - - - - ${config}" + ) + cfg.servers; + + systemd.services.infrared = { + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + Type = "simple"; + User = cfg.user; + Group = cfg.group; + WorkingDirectory = cfg.stateDir; + ExecStart = "${pkgs.plusultra.infrared}/bin/infrared -config-path ${cfg.stateDir}"; + }; + }; + + users = { + users = lib.optionalAttrs (cfg.user == "infrared") { + infrared = { + group = cfg.group; + home = "/var/lib/infrared"; + isSystemUser = true; + }; + }; + + groups = lib.optionalAttrs (cfg.group == "infrared") { + infrared = { }; + }; + }; + }; +} diff --git a/modules/nixos/services/minecraft/default.nix b/modules/nixos/services/minecraft/default.nix new file mode 100644 index 0000000..9fbe2a9 --- /dev/null +++ b/modules/nixos/services/minecraft/default.nix @@ -0,0 +1,195 @@ +{ config, options, lib, pkgs, ... }: + +let + inherit (lib) types; + + cfg = config.plusultra.services.minecraft; +in +{ + options.plusultra.services.minecraft = { + enable = lib.mkEnableOption "Minecraft server"; + + eula = lib.mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Whether you agree to + [Mojang's EULA](https://account.mojang.com/documents/minecraft_eula). + This option must be set to `true` to run Minecraft server. + ''; + }; + + infrared = { + enable = lib.mkEnableOption "Infrared"; + }; + + servers = lib.mkOption { + default = { }; + description = "The Minecraft servers to run."; + example = lib.literalExpression '' + { + # A default vanilla server. + vanilla-1 = {}; + + # A vanilla server with a custom port. + vanilla-2 = { + port = 4000; + }; + + # A vanilla server proxied by Infrared (when enabled). + vanilla-3 = { + domain = "minecraft.example.com"; + }; + + # A Forge server. + forge-1 = { + type = "forge"; + }; + + # Use a custom Minecraft server version. + custom-vanilla = { + package = pkgs.minecraft-server_1_12_2; + }; + + # Use a custom Forge server version. + custom-forge = { + type = "forge"; + package = pkgs.minecraft-forge_1_19_2-43_1_25; + }; + } + ''; + + type = types.attrsOf (types.submodule ({ config, name, ... }: { + options = { + type = lib.mkOption { + type = types.enum [ "vanilla" "forge" ]; + default = "vanilla"; + description = "The kind of Minecraft server to create."; + }; + + package = lib.mkOption { + type = types.package; + default = + if config.type == "vanilla" then + pkgs.minecraft-server + else + pkgs.plusultra.minecraft-forge; + defaultText = lib.literalExpression '' + pkgs.minecraft-server + ''; + }; + + dataDir = lib.mkOption { + type = types.path; + default = "/var/lib/minecraft/${name}"; + defaultText = "/var/lib/minecraft/"; + description = "The datrectory where data for the server is stored."; + }; + + port = lib.mkOption { + type = types.port; + default = 25565; + description = "The port for the server to listen on."; + }; + + domain = lib.mkOption { + type = types.str; + default = ""; + description = "The domain to pass to Infrared (if enabled)."; + }; + + jvmOpts = lib.mkOption { + type = types.separatedString " "; + default = "-Xmx2048M -Xms2048M"; + # Example options from https://minecraft.gamepedia.com/Tutorials/Server_startup_script + example = "-Xms4092M -Xmx4092M -XX:+UseG1GC -XX:+CMSIncrementalPacing " + + "-XX:+CMSClassUnloadingEnabled -XX:ParallelGCThreads=2 " + + "-XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=10"; + description = lib.mdDoc "JVM options for the Minecraft server."; + }; + + serverProperties = lib.mkOption { + type = types.attrsOf (types.oneOf [ types.bool types.int types.str ]); + default = { }; + example = lib.literalExpression '' + { + server-port = 43000; + difficulty = 3; + gamemode = 1; + max-players = 5; + motd = "NixOS Minecraft server!"; + white-list = true; + enable-rcon = true; + "rcon.password" = "hunter2"; + } + ''; + description = lib.mdDoc '' + Minecraft server properties for the server.properties file. Only has + an effect when {option}`services.minecraft-server.declarative` + is set to `true`. See + + for documentation on these values. + ''; + }; + + whitelist = lib.mkOption { + type = + let + minecraftUUID = lib.types.strMatching + "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" // { + description = "Minecraft UUID"; + }; + in + lib.types.attrsOf minecraftUUID; + default = { }; + description = lib.mdDoc '' + Whitelisted players, only has an effect when + {option}`services.minecraft-server.declarative` is + `true` and the whitelist is enabled + via {option}`services.minecraft-server.serverProperties` by + setting `white-list` to `true`. + This is a mapping from Minecraft usernames to UUIDs. + You can use to get a + Minecraft UUID for a username. + ''; + example = lib.literalExpression '' + { + username1 = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; + username2 = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"; + }; + ''; + }; + + openFirewall = lib.mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Whether to open ports in the firewall for the server. + ''; + }; + + declarative = lib.mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Whether to use a declarative Minecraft server configuration. + Only if set to `true`, the options + {option}`plusultra.services.minecraft.servers..whitelist` and + {option}`plusultra.services.minecraft.servers..serverProperties` will be + applied. + ''; + }; + + extraInfraredOptions = lib.mkOption { + type = types.attrs; + default = { }; + + description = lib.mdDoc '' + Extra options passed to Infrared (if enabled) when configuring this server. + ''; + }; + }; + })); + }; + }; +} diff --git a/modules/nixos/services/openssh/default.nix b/modules/nixos/services/openssh/default.nix new file mode 100644 index 0000000..ea9b1d3 --- /dev/null +++ b/modules/nixos/services/openssh/default.nix @@ -0,0 +1,109 @@ +{ options +, config +, pkgs +, lib +, host ? "" +, format ? "" +, inputs ? { } +, ... +}: +with lib; +with lib.plusultra; let + cfg = config.plusultra.services.openssh; + + user = config.users.users.${config.plusultra.user.name}; + user-id = builtins.toString user.uid; + + # TODO: This is a hold-over from an earlier Snowfall Lib version which used + # the specialArg `name` to provide the host name. + name = host; + + default-key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCwaaCUq3Ooq1BaHbg5IwVxWj/xmNJY2dDthHKPZefrHXv/ksM/IREgm38J0CdoMpVS0Zp1C/vFrwGfaYZ2lCF5hBVdV3gf+mvj8Yb8Xpm6aM4L5ig+oBMp/3cz1+g/I4aLMJfCKCtdD6Q2o4vtkTpid6X+kL3UGZbX0HFn3pxoDinzOXQnVGSGw+pQhLASvQeVXWTJjVfIWhj9L2NRJau42cBRRlAH9kE3HUbcgLgyPUZ28aGXLLmiQ6CUjiIlce5ee16WNLHQHOzVfPJfF1e1F0HwGMMBe39ey3IEQz6ab1YqlIzjRx9fQ9hQK6Du+Duupby8JmBlbUAxhh8KJFCJB2cXW/K5Et4R8GHMS6MyIoKQwFUXGyrszVfiuNTGZIkPAYx9zlCq9M/J+x1xUZLHymL85WLPyxhlhN4ysM9ILYiyiJ3gYrPIn5FIZrW7MCQX4h8k0bEjWUwH5kF3dZpEvIT2ssyIu12fGzXkYaNQcJEb5D9gT1mNyi2dxQ62NPZ5orfYyIZ7fn22d1P/jegG+7LQeXPiy5NLE6b7MP5Rq2dL8Y9Oi8pOBtoY9BpLh7saSBbNFXTBtH/8OfAQacxDsZD/zTFtCzZjtTK6yiAaXCZTvMIOuoYGZvEk6zWXrjVsU8FlqF+4JOTfePqr/SSUXNJyKnrvQJ1BfHQiYsrckw=="; + + other-hosts = + lib.filterAttrs + (key: host: + key != name && (host.config.plusultra.user.name or null) != null) + ((inputs.self.nixosConfigurations or { }) // (inputs.self.darwinConfigurations or { })); + + other-hosts-config = + lib.concatMapStringsSep + "\n" + ( + name: + let + remote = other-hosts.${name}; + remote-user-name = remote.config.plusultra.user.name; + remote-user-id = builtins.toString remote.config.users.users.${remote-user-name}.uid; + + forward-gpg = + optionalString (config.programs.gnupg.agent.enable && remote.config.programs.gnupg.agent.enable) + '' + RemoteForward /run/user/${remote-user-id}/gnupg/S.gpg-agent /run/user/${user-id}/gnupg/S.gpg-agent.extra + RemoteForward /run/user/${remote-user-id}/gnupg/S.gpg-agent.ssh /run/user/${user-id}/gnupg/S.gpg-agent.ssh + ''; + in + '' + Host ${name} + User ${remote-user-name} + ForwardAgent yes + Port ${builtins.toString cfg.port} + ${forward-gpg} + '' + ) + (builtins.attrNames other-hosts); +in +{ + options.plusultra.services.openssh = with types; { + enable = mkBoolOpt false "Whether or not to configure OpenSSH support."; + authorizedKeys = + mkOpt (listOf str) [ default-key ] "The public keys to apply."; + port = mkOpt port 2222 "The port to listen on (in addition to 22)."; + manage-other-hosts = mkOpt bool true "Whether or not to add other host configurations to SSH config."; + }; + + config = mkIf cfg.enable { + services.openssh = { + enable = true; + + settings = { + PermitRootLogin = + if format == "install-iso" + then "yes" + else "no"; + PasswordAuthentication = false; + }; + + extraConfig = '' + StreamLocalBindUnlink yes + ''; + + ports = [ + 22 + cfg.port + ]; + }; + + programs.ssh.extraConfig = '' + Host * + HostKeyAlgorithms +ssh-rsa + + ${optionalString cfg.manage-other-hosts other-hosts-config} + ''; + + plusultra.user.extraOptions.openssh.authorizedKeys.keys = + cfg.authorizedKeys; + + plusultra.home.extraOptions = { + programs.zsh.shellAliases = + foldl + (aliases: system: + aliases + // { + "ssh-${system}" = "ssh ${system} -t tmux a"; + }) + { } + (builtins.attrNames other-hosts); + }; + }; +} diff --git a/modules/nixos/services/printing/default.nix b/modules/nixos/services/printing/default.nix new file mode 100644 index 0000000..d6884e8 --- /dev/null +++ b/modules/nixos/services/printing/default.nix @@ -0,0 +1,13 @@ +{ options, config, pkgs, lib, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.services.printing; +in +{ + options.plusultra.services.printing = with types; { + enable = mkBoolOpt false "Whether or not to configure printing support."; + }; + + config = mkIf cfg.enable { services.printing.enable = true; }; +} diff --git a/modules/nixos/services/samba/default.nix b/modules/nixos/services/samba/default.nix new file mode 100644 index 0000000..ba5fdec --- /dev/null +++ b/modules/nixos/services/samba/default.nix @@ -0,0 +1,76 @@ +{ lib, config, ... }: + +let + cfg = config.plusultra.services.samba; + + inherit (lib) + types + mkEnableOption + mkIf + mapAttrs + optionalAttrs; + + inherit (lib.plusultra) + mkOpt + mkBoolOpt; + + bool-to-yes-no = value: if value then "yes" else "no"; + + shares-submodule = with types; submodule ({ name, ... }: { + options = { + path = mkOpt str null "The path to serve."; + public = mkBoolOpt false "Whether the share is public."; + browseable = mkBoolOpt true "Whether the share is browseable."; + comment = mkOpt str name "An optional comment."; + read-only = mkBoolOpt false "Whether the share should be read only."; + only-owner-editable = mkBoolOpt false "Whether the share is only writable by the system owner (plusultra.user.name)."; + + extra-config = mkOpt attrs { } "Extra configuration options for the share."; + }; + }); +in +{ + options.plusultra.services.samba = with types; { + enable = mkEnableOption "Samba"; + workgroup = mkOpt str "WORKGROUP" "The workgroup to use."; + browseable = mkBoolOpt true "Whether the shares are browseable."; + + shares = mkOpt (attrsOf shares-submodule) { } "The shares to serve."; + }; + + config = mkIf cfg.enable { + networking.firewall = { + allowedTCPPorts = [ 5357 ]; + allowedUDPPorts = [ 3702 ]; + }; + + services.samba-wsdd = { + enable = true; + discovery = true; + workgroup = "WORKGROUP"; + }; + + services.samba = { + enable = true; + openFirewall = true; + + extraConfig = '' + browseable = ${bool-to-yes-no cfg.browseable} + ''; + + shares = mapAttrs + (name: value: { + inherit (value) path comment; + + public = bool-to-yes-no value.public; + browseable = bool-to-yes-no value.browseable; + "read only" = bool-to-yes-no value.read-only; + } // (optionalAttrs value.only-owner-editable { + "write list" = config.plusultra.user.name; + "read list" = "guest, nobody"; + "create mask" = "0755"; + }) // value.extra-config) + cfg.shares; + }; + }; +} diff --git a/modules/nixos/services/tailscale/default.nix b/modules/nixos/services/tailscale/default.nix new file mode 100644 index 0000000..02ae1a2 --- /dev/null +++ b/modules/nixos/services/tailscale/default.nix @@ -0,0 +1,69 @@ +{ lib, pkgs, config, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.services.tailscale; +in +{ + options.plusultra.services.tailscale = with types; { + enable = mkBoolOpt false "Whether or not to configure Tailscale"; + autoconnect = { + enable = mkBoolOpt false "Whether or not to enable automatic connection to Tailscale"; + key = mkOpt str "" "The authentication key to use"; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = cfg.autoconnect.enable -> cfg.autoconnect.key != ""; + message = "plusultra.services.tailscale.autoconnect.key must be set"; + } + ]; + + environment.systemPackages = with pkgs; [ tailscale ]; + + services.tailscale = enabled; + + networking = { + firewall = { + trustedInterfaces = [ config.services.tailscale.interfaceName ]; + + allowedUDPPorts = [ config.services.tailscale.port ]; + + # Strict reverse path filtering breaks Tailscale exit node use and some subnet routing setups. + checkReversePath = "loose"; + }; + + networkmanager.unmanaged = [ "tailscale0" ]; + }; + + systemd.services.tailscale-autoconnect = mkIf cfg.autoconnect.enable { + description = "Automatic connection to Tailscale"; + + # Make sure tailscale is running before trying to connect to tailscale + after = [ "network-pre.target" "tailscale.service" ]; + wants = [ "network-pre.target" "tailscale.service" ]; + wantedBy = [ "multi-user.target" ]; + + # Set this service as a oneshot job + serviceConfig.Type = "oneshot"; + + # Have the job run this shell script + script = with pkgs; '' + # Wait for tailscaled to settle + sleep 2 + + # Check if we are already authenticated to tailscale + status="$(${tailscale}/bin/tailscale status -json | ${jq}/bin/jq -r .BackendState)" + if [ $status = "Running" ]; then # if so, then do nothing + exit 0 + fi + + # Otherwise authenticate with tailscale + ${tailscale}/bin/tailscale up -authkey "${cfg.autoconnect.key}" + ''; + + }; + }; +} diff --git a/modules/nixos/services/vault-agent/default.nix b/modules/nixos/services/vault-agent/default.nix new file mode 100644 index 0000000..1d68787 --- /dev/null +++ b/modules/nixos/services/vault-agent/default.nix @@ -0,0 +1,174 @@ +{ lib, config, pkgs, inputs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.services.vault-agent; + + # nixos-vault-service places generated files here: + # https://github.com/DeterminateSystems/nixos-vault-service/blob/45e65627dff5dc4bb40d0f2595916f37e78959c1/module/helpers.nix#L4 + secret-files-root = "/tmp/detsys-vault"; + environment-files-root = "/run/keys/environment"; + + create-environment-files-submodule = service-name: types.submodule ({ name, ... }: { + options = { + text = mkOpt (types.nullOr types.str) null "An inline template for Vault to template."; + source = mkOpt (types.nullOr types.path) null "The file with environment variables for Vault to template."; + path = mkOption { + readOnly = true; + type = types.str; + description = "The path to the environment file."; + default = "${environment-files-root}/${service-name}/${name}.EnvFile"; + defaultText = "${environment-files-root}//.EnvFile"; + }; + }; + }); + + secret-files-submodule = types.submodule ({ name, ... }: { + options = { + text = mkOpt (types.nullOr types.str) null "An inline template for Vault to template."; + source = mkOpt (types.nullOr types.path) null "The file for Vault to template."; + permissions = mkOpt types.str "0400" "The octal mode of this file."; + change-action = mkOpt (types.nullOr (types.enum [ "restart" "stop" "none" ])) null "The action to take when secrets change."; + path = mkOption { + readOnly = true; + type = types.str; + description = "The path to the secret file."; + default = "${secret-files-root}/${name}"; + }; + }; + }); + + + services-submodule = + types.submodule + ({ name, config, ... }: { + options = { + enable = mkBoolOpt true "Whether to enable Vault Agent for this service."; + settings = mkOpt types.attrs { } "Vault Agent configuration."; + secrets = { + environment = { + force = mkOpt types.bool false "Whether or not to force the use of Vault Agent's environment files."; + change-action = mkOpt (types.enum [ "restart" "stop" "none" ]) "restart" "The action to take when secrets change."; + templates = mkOpt (types.attrsOf (create-environment-files-submodule name)) { } "Environment variable files for Vault to template."; + template = mkOpt (types.nullOr (types.either types.path types.str)) null "An environment variable template."; + paths = mkOption { + readOnly = true; + type = types.listOf types.str; + description = "Paths to all of the environment files"; + default = + if config.secrets.environment.template != null then + [ "${environment-files-root}/${name}/EnvFile" ] + else + ( + mapAttrsToList + (template-name: value: value.path) + config.secrets.environment.templates + ); + }; + }; + + file = { + change-action = mkOpt (types.enum [ "restart" "stop" "none" ]) "restart" "The action to take when secrets change."; + files = mkOption { + description = "Secret files to template."; + default = { }; + type = types.attrsOf secret-files-submodule; + }; + }; + }; + }; + }); +in +{ + # imports = [ + # inputs.vault-service.nixosModules.nixos-vault-service + # ]; + + options.plusultra.services.vault-agent = { + enable = mkEnableOption "Vault Agent"; + + settings = mkOpt types.attrs { } "Default Vault Agent configuration."; + + services = mkOption { + description = "Services to install Vault Agent into."; + default = { }; + type = types.attrsOf services-submodule; + }; + }; + + config = mkIf cfg.enable { + assertions = flatten (mapAttrsToList + (service-name: service: + (mapAttrsToList + (template-name: template: + { + assertion = (template.source != null && template.text == null) || (template.source == null && template.text != null); + message = "plusultra.services.vault-agent.services.${service-name}.secrets.environment.templates.${template-name} must set either `source` or `text`."; + } + ) + service.secrets.environment.templates) + ++ + (mapAttrsToList + (file-name: file: + { + assertion = (file.source != null && file.text == null) || (file.source == null && file.text != null); + message = "plusultra.services.vault-agent.services.${service-name}.secrets.file.files.${file-name} must set either `source` or `text`."; + } + ) + service.secrets.file.files) + ) + cfg.services); + + systemd.services = mapAttrs + (service-name: value: mkIf value.secrets.environment.force { + serviceConfig.EnvironmentFile = mkForce value.secrets.environment.paths; + }) + cfg.services; + + detsys.vaultAgent = { + defaultAgentConfig = cfg.settings; + + systemd.services = mapAttrs + (service-name: value: { + inherit (value) enable; + + agentConfig = value.settings; + + environment = { + changeAction = value.secrets.environment.change-action; + + templateFiles = mapAttrs + (template-name: value: { + file = + if value.source != null then + value.source + else + pkgs.writeText "${service-name}-${template-name}-env-template" value.text; + }) + value.secrets.environment.templates; + + template = + if (builtins.isPath value.secrets.environment.template) || (builtins.isNull value.secrets.environment.template) then + value.secrets.environment.template + else + pkgs.writeText "${service-name}-env-template" value.secrets.environment.template; + }; + + secretFiles = { + defaultChangeAction = value.secrets.file.change-action; + + files = mapAttrs + (file-name: value: { + changeAction = value.change-action; + template = value.text; + templateFile = value.source; + perms = value.permissions; + }) + value.secrets.file.files; + }; + }) + cfg.services; + }; + }; +} diff --git a/modules/nixos/services/vault/default.nix b/modules/nixos/services/vault/default.nix new file mode 100644 index 0000000..587f0f6 --- /dev/null +++ b/modules/nixos/services/vault/default.nix @@ -0,0 +1,202 @@ +{ lib, config, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.services.vault; + + package = if cfg.ui then pkgs.vault-bin else pkgs.vault; + + has-policies = (builtins.length (builtins.attrNames cfg.policies)) != 0; + + format-policy = name: file: pkgs.runCommandNoCC + "formatted-vault-policy" + { + inherit file; + buildInputs = [ package ]; + } + '' + name="$(basename "$file")" + + cp "$file" "./$name" + + # Ensure that vault can overwrite the file. + chmod +w "./$name" + + # Create this variable here to avoid swallowing vault's exit code. + vault_output= + + set +e + vault_output=$(vault policy fmt "./$name" 2>&1) + vault_status=$? + set -e + + if [ "$vault_status" != 0 ]; then + echo 'Error formatting policy "${name}"' + echo "This is normally caused by a syntax error in the policy file." + echo "$file" + echo "" + echo "Vault Output:" + echo "$vault_output" + exit 1 + fi + + mv "./$name" $out + ''; + + policies = mapAttrs + (name: value: + if builtins.isPath value then + format-policy name value + else + format-policy name (pkgs.writeText "${name}.hcl" value) + ) + cfg.policies; +in +{ + options.plusultra.services.vault = { + enable = mkEnableOption "Vault"; + + ui = mkBoolOpt true "Whether the UI should be enabled."; + + storage = { + backend = mkOpt types.str "file" "The storage backend for Vault."; + }; + + settings = mkOpt types.str "" "Configuration for Vault's config file."; + + mutable-policies = mkBoolOpt false "Whether policies not specified in Nix should be removed."; + + policies = mkOpt (types.attrsOf (types.either types.str types.path)) { } "Policies to install when Vault runs."; + + policy-agent = { + user = mkOpt types.str "vault" "The user to run the Vault Agent as."; + group = mkOpt types.str "vault" "The group to run the Vault Agent as."; + + auth = { + roleIdFilePath = mkOpt types.str "/var/lib/vault/role-id" "The file to read the role-id from."; + secretIdFilePath = mkOpt types.str "/var/lib/vault/secret-id" "The file to read the secret-id from."; + }; + }; + }; + + config = mkIf cfg.enable { + services.vault = { + enable = true; + inherit package; + + extraConfig = '' + ui = ${if cfg.ui then "true" else "false"} + + ${cfg.settings} + ''; + + }; + + systemd.services.vault = { }; + + systemd.services.vault-policies = mkIf (has-policies || !cfg.mutable-policies) { + wantedBy = [ "vault.service" ]; + after = [ "vault.service" ]; + + serviceConfig = { + Type = "oneshot"; + User = cfg.policy-agent.user; + Group = cfg.policy-agent.group; + Restart = "on-failure"; + RestartSec = 30; + RemainAfterExit = "yes"; + }; + + restartTriggers = (mapAttrsToList (name: value: "${name}=${value}") policies); + + path = [ + package + pkgs.curl + pkgs.jq + ]; + + environment = { + VAULT_ADDR = "http://${config.services.vault.address}"; + }; + + script = + let + write-policies-commands = mapAttrsToList + (name: policy: + '' + echo Writing policy '${name}': '${policy}' + vault policy write '${name}' '${policy}' + '' + ) + policies; + write-policies = concatStringsSep "\n" write-policies-commands; + + known-policies = mapAttrsToList (name: value: name) policies; + + remove-unknown-policies = '' + current_policies=$(vault policy list -format=json | jq -r '.[]') + known_policies=(${concatStringsSep " " (builtins.map (policy: "\"${policy}\"") known-policies)}) + + while read current_policy; do + is_known=false + + for known_policy in "''${known_policies[@]}"; do + if [ "$known_policy" = "$current_policy" ]; then + is_known=true + break + fi + done + + if [ "$is_known" = "false" ] && [ "$current_policy" != "default" ] && [ "$current_policy" != "root" ]; then + echo "Removing policy: $current_policy" + vault policy delete "$current_policy" + else + echo "Keeping policy: $current_policy" + fi + done <<< "$current_policies" + ''; + in + '' + if ! [ -f '${cfg.policy-agent.auth.roleIdFilePath}' ]; then + echo 'role-id file not found: ${cfg.policy-agent.auth.roleIdFilePath}' + exit 1 + fi + + if ! [ -f '${cfg.policy-agent.auth.secretIdFilePath}' ]; then + echo 'secret-id file not found: ${cfg.policy-agent.auth.secretIdFilePath}' + exit 1 + fi + + role_id="$(cat '${cfg.policy-agent.auth.roleIdFilePath}')" + secret_id="$(cat '${cfg.policy-agent.auth.secretIdFilePath}')" + + seal_status=$(curl -s "$VAULT_ADDR/v1/sys/seal-status" | jq ".sealed") + + echo "Seal Status: $seal_status" + + if [ seal_status = "true" ]; then + echo "Vault is currently sealed, cannot install policies." + exit 1 + fi + + echo "Getting token..." + + token=$(vault write -field=token auth/approle/login \ + role_id="$role_id" \ + secret_id="$secret_id" \ + ) + + echo "Logging in..." + + export VAULT_TOKEN="$(vault login -method=token -token-only token="$token")" + + echo "Writing policies..." + + ${write-policies} + + ${optionalString (!cfg.mutable-policies) remove-unknown-policies} + ''; + }; + }; +} diff --git a/modules/nixos/services/writefreely/default.nix b/modules/nixos/services/writefreely/default.nix new file mode 100644 index 0000000..32a2424 --- /dev/null +++ b/modules/nixos/services/writefreely/default.nix @@ -0,0 +1,493 @@ +{ config, lib, pkgs, ... }: + +let + inherit (builtins) toString; + inherit (lib) types mkIf mkOption mkDefault; + inherit (lib) optional optionals optionalAttrs optionalString; + + inherit (pkgs) sqlite; + + format = pkgs.formats.ini { + mkKeyValue = key: value: + let + value' = + if builtins.isNull value then + "" + else if builtins.isBool value then + if value == true then "true" else "false" + else + toString value; + in + "${key} = ${value'}"; + }; + + cfg = config.plusultra.services.writefreely; + + isSqlite = cfg.database.type == "sqlite3"; + isMysql = cfg.database.type == "mysql"; + isMysqlLocal = isMysql && cfg.database.createLocally == true; + + hostProtocol = if cfg.acme.enable then "https" else "http"; + + settings = cfg.settings // { + app = cfg.settings.app or { } // { + host = cfg.settings.app.host or "${hostProtocol}://${cfg.host}"; + }; + + database = + if cfg.database.type == "sqlite3" then { + type = "sqlite3"; + filename = cfg.settings.database.filename or "writefreely.db"; + database = cfg.database.name; + } else { + type = "mysql"; + username = cfg.database.user; + password = "#dbpass#"; + database = cfg.database.name; + host = cfg.database.host; + port = cfg.database.port; + tls = cfg.database.tls; + }; + + server = cfg.settings.server or { } // { + bind = cfg.settings.server.bind or "localhost"; + gopher_port = cfg.settings.server.gopher_port or 0; + autocert = !cfg.nginx.enable && cfg.acme.enable; + templates_parent_dir = + cfg.settings.server.templates_parent_dir or cfg.package.src; + static_parent_dir = cfg.settings.server.static_parent_dir or assets; + pages_parent_dir = + cfg.settings.server.pages_parent_dir or cfg.package.src; + keys_parent_dir = cfg.settings.server.keys_parent_dir or cfg.stateDir; + }; + }; + + configFile = format.generate "config.ini" settings; + + assets = pkgs.stdenvNoCC.mkDerivation { + pname = "writefreely-assets"; + + inherit (cfg.package) version src; + + nativeBuildInputs = with pkgs.nodePackages; [ less ]; + + buildPhase = '' + mkdir -p $out + + cp -r static $out/ + ''; + + installPhase = '' + less_dir=$src/less + css_dir=$out/static/css + + lessc $less_dir/app.less $css_dir/write.css + lessc $less_dir/fonts.less $css_dir/fonts.css + lessc $less_dir/icons.less $css_dir/icons.css + lessc $less_dir/prose.less $css_dir/prose.css + ''; + }; + + withConfigFile = text: '' + db_pass=${ + optionalString (cfg.database.passwordFile != null) + "$(head -n1 ${cfg.database.passwordFile})" + } + + cp -f ${configFile} '${cfg.stateDir}/config.ini' + sed -e "s,#dbpass#,$db_pass,g" -i '${cfg.stateDir}/config.ini' + chmod 440 '${cfg.stateDir}/config.ini' + + ${text} + ''; + + withMysql = text: + withConfigFile '' + query () { + local result=$(${config.services.mysql.package}/bin/mysql \ + --user=${cfg.database.user} \ + --password=$db_pass \ + --database=${cfg.database.name} \ + --silent \ + --raw \ + --skip-column-names \ + --execute "$1" \ + ) + + echo $result + } + + ${text} + ''; + + withSqlite = text: + withConfigFile '' + query () { + local result=$(${sqlite}/bin/sqlite3 \ + '${cfg.stateDir}/${settings.database.filename}' + "$1" \ + ) + + echo $result + } + + ${text} + ''; +in +{ + options.plusultra.services.writefreely = { + enable = + lib.mkEnableOption (lib.mdDoc "Writefreely, build a digital writing community"); + + package = lib.mkOption { + type = lib.types.package; + default = pkgs.writefreely; + defaultText = lib.literalExpression "pkgs.writefreely"; + description = lib.mdDoc "Writefreely package to use."; + }; + + stateDir = mkOption { + type = types.path; + default = "/var/lib/writefreely"; + description = lib.mdDoc "The state directory where keys and data are stored."; + }; + + user = mkOption { + type = types.str; + default = "writefreely"; + description = lib.mdDoc "User under which Writefreely is ran."; + }; + + group = mkOption { + type = types.str; + default = "writefreely"; + description = lib.mdDoc "Group under which Writefreely is ran."; + }; + + host = mkOption { + type = types.str; + default = ""; + description = lib.mdDoc "The public host name to serve."; + example = "example.com"; + }; + + settings = mkOption { + default = { }; + description = lib.mdDoc '' + Writefreely configuration (`config.ini`). Refer to + [writefreely.org/docs/latest/admin/config](https://writefreely.org/docs/latest/admin/config) + for details. + ''; + + type = types.submodule { + freeformType = format.type; + + options = { + app = { + theme = mkOption { + type = types.str; + default = "write"; + description = "The theme to apply."; + }; + }; + + server = { + port = mkOption { + type = types.port; + default = if cfg.nginx.enable then 18080 else 80; + defaultText = "80"; + description = "The port WriteFreely should listen on."; + }; + }; + }; + }; + }; + + database = { + type = mkOption { + type = types.enum [ "sqlite3" "mysql" ]; + default = "sqlite3"; + description = "The database provider to use."; + }; + + name = mkOption { + type = types.str; + default = "writefreely"; + description = "The name of the database to store data in."; + }; + + user = mkOption { + type = types.nullOr types.str; + default = if cfg.database.type == "mysql" then "writefreely" else null; + defaultText = "writefreely"; + description = lib.mdDoc "The database user to connect as."; + }; + + passwordFile = mkOption { + type = types.nullOr types.path; + default = null; + description = lib.mdDoc "The file to load the database password from."; + }; + + host = mkOption { + type = types.str; + default = "localhost"; + description = lib.mdDoc "The database host to connect to."; + }; + + port = mkOption { + type = types.port; + default = 3306; + description = lib.mdDoc "The port used when connecting to the database host."; + }; + + tls = mkOption { + type = types.bool; + default = false; + description = + lib.mdDoc "Whether or not TLS should be used for the database connection."; + }; + + migrate = mkOption { + type = types.bool; + default = true; + description = + lib.mdDoc "Whether or not to automatically run migrations on startup."; + }; + + createLocally = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + When `plusultra.services.writefreely.database.type` is set to + `"mysql"`, this option will enable the MySQL service locally. + ''; + }; + }; + + admin = { + name = mkOption { + type = types.nullOr types.str; + description = "The name of the first admin user."; + default = null; + }; + + initialPasswordFile = mkOption { + type = types.path; + description = '' + Path to a file containing the initial password for the admin user. + If not provided, the default password will be set to nixos. + ''; + default = pkgs.writeText "default-admin-pass" "nixos"; + defaultText = "/nix/store/xxx-default-admin-pass"; + }; + }; + + nginx = { + enable = mkOption { + type = types.bool; + default = false; + description = + "Whether or not to enable and configure nginx as a proxy for WriteFreely."; + }; + + forceSSL = mkOption { + type = types.bool; + default = false; + description = "Whether or not to force the use of SSL."; + }; + }; + + acme = { + enable = mkOption { + type = types.bool; + default = false; + description = + "Whether or not to automatically fetch and configure SSL certs."; + }; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = cfg.host != ""; + message = "plusultra.services.writefreely.host must be set"; + } + { + assertion = isMysqlLocal -> cfg.database.passwordFile != null; + message = + "plusultra.services.writefreely.database.passwordFile must be set if plusultra.services.writefreely.database.createLocally is set to true"; + } + { + assertion = isSqlite -> !cfg.database.createLocally; + message = + "plusultra.services.writefreely.database.createLocally has no use when plusultra.services.writefreely.database.type is set to sqlite3"; + } + ]; + + users = { + users = optionalAttrs (cfg.user == "writefreely") { + writefreely = { + group = cfg.group; + home = cfg.stateDir; + isSystemUser = true; + }; + }; + + groups = + optionalAttrs (cfg.group == "writefreely") { writefreely = { }; }; + }; + + systemd.tmpfiles.rules = + [ "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -" ]; + + systemd.services.writefreely = { + after = [ "network.target" ] + ++ optional isSqlite "writefreely-sqlite-init.service" + ++ optional isMysql "writefreely-mysql-init.service" + ++ optional isMysqlLocal "mysql.service"; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + Type = "simple"; + User = cfg.user; + Group = cfg.group; + WorkingDirectory = cfg.stateDir; + Restart = "always"; + RestartSec = 20; + ExecStart = + "${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' serve"; + AmbientCapabilities = + optionalString (settings.server.port < 1024) "cap_net_bind_service"; + }; + + preStart = '' + if ! test -d "${cfg.stateDir}/keys"; then + mkdir -p ${cfg.stateDir}/keys + + # Key files end up with the wrong permissions by default. + # We need to correct them so that Writefreely can read them. + chmod -R 750 "${cfg.stateDir}/keys" + + ${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' keys generate + fi + ''; + }; + + systemd.services.writefreely-sqlite-init = mkIf isSqlite { + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + Type = "oneshot"; + User = cfg.user; + Group = cfg.group; + WorkingDirectory = cfg.stateDir; + ReadOnlyPaths = optional (cfg.admin.initialPasswordFile != null) + cfg.admin.initialPasswordFile; + }; + + script = + let + migrateDatabase = optionalString cfg.database.migrate '' + ${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' db migrate + ''; + + createAdmin = optionalString (cfg.admin.name != null) '' + if [[ $(query "SELECT COUNT(*) FROM users") == 0 ]]; then + admin_pass=$(head -n1 ${cfg.admin.initialPasswordFile}) + + ${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' --create-admin ${cfg.admin.name}:$admin_pass + fi + ''; + in + withSqlite '' + if ! test -f '${settings.database.filename}'; then + ${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' db init + fi + + ${migrateDatabase} + + ${createAdmin} + ''; + }; + + systemd.services.writefreely-mysql-init = mkIf isMysql { + wantedBy = [ "multi-user.target" ]; + after = optional isMysqlLocal "mysql.service"; + + serviceConfig = { + Type = "oneshot"; + User = cfg.user; + Group = cfg.group; + WorkingDirectory = cfg.stateDir; + ReadOnlyPaths = optional isMysqlLocal cfg.database.passwordFile + ++ optional (cfg.admin.initialPasswordFile != null) + cfg.admin.initialPasswordFile; + }; + + script = + let + updateUser = optionalString isMysqlLocal '' + # WriteFreely currently *requires* a password for authentication, so we + # need to update the user in MySQL accordingly. By default MySQL users + # authenticate with auth_socket or unix_socket. + # See: https://github.com/writefreely/writefreely/issues/568 + ${config.services.mysql.package}/bin/mysql --skip-column-names --execute "ALTER USER '${cfg.database.user}'@'localhost' IDENTIFIED VIA unix_socket OR mysql_native_password USING PASSWORD('$db_pass'); FLUSH PRIVILEGES;" + ''; + + migrateDatabase = optionalString cfg.database.migrate '' + ${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' db migrate + ''; + + createAdmin = optionalString (cfg.admin.name != null) '' + if [[ $(query 'SELECT COUNT(*) FROM users') == 0 ]]; then + admin_pass=$(head -n1 ${cfg.admin.initialPasswordFile}) + ${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' --create-admin ${cfg.admin.name}:$admin_pass + fi + ''; + in + withMysql '' + ${updateUser} + + if [[ $(query "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '${cfg.database.name}'") == 0 ]]; then + ${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' db init + fi + + ${migrateDatabase} + + ${createAdmin} + ''; + }; + + services.mysql = mkIf isMysqlLocal { + enable = true; + package = mkDefault pkgs.mariadb; + ensureDatabases = [ cfg.database.name ]; + ensureUsers = [{ + name = cfg.database.user; + ensurePermissions = { + "${cfg.database.name}.*" = "ALL PRIVILEGES"; + # WriteFreely requires the use of passwords, so we need permissions + # to `ALTER` the user to add password support and also to reload + # permissions so they can be used. + "*.*" = "CREATE USER, RELOAD"; + }; + }]; + }; + + services.nginx = lib.mkIf cfg.nginx.enable { + enable = true; + recommendedProxySettings = true; + + virtualHosts."${cfg.host}" = { + enableACME = cfg.acme.enable; + forceSSL = cfg.nginx.forceSSL; + + locations."/" = { + proxyPass = "http://127.0.0.1:${toString settings.server.port}"; + }; + }; + }; + }; +} diff --git a/modules/nixos/suites/art/default.nix b/modules/nixos/suites/art/default.nix new file mode 100644 index 0000000..256341f --- /dev/null +++ b/modules/nixos/suites/art/default.nix @@ -0,0 +1,22 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.suites.art; +in +{ + options.plusultra.suites.art = with types; { + enable = mkBoolOpt false "Whether or not to enable art configuration."; + }; + + config = mkIf cfg.enable { + plusultra = { + apps = { + gimp = enabled; + inkscape = enabled; + blender = enabled; + }; + }; + }; +} diff --git a/modules/nixos/suites/business/default.nix b/modules/nixos/suites/business/default.nix new file mode 100644 index 0000000..aa5a3a4 --- /dev/null +++ b/modules/nixos/suites/business/default.nix @@ -0,0 +1,15 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.suites.business; +in +{ + options.plusultra.suites.business = with types; { + enable = mkBoolOpt false "Whether or not to enable business configuration."; + }; + + config = + mkIf cfg.enable { plusultra = { apps = { frappe-books = enabled; }; }; }; +} diff --git a/modules/nixos/suites/common-slim/default.nix b/modules/nixos/suites/common-slim/default.nix new file mode 100644 index 0000000..466bb13 --- /dev/null +++ b/modules/nixos/suites/common-slim/default.nix @@ -0,0 +1,63 @@ +{ options +, config +, lib +, pkgs +, ... +}: +with lib; +with lib.plusultra; let + cfg = config.plusultra.suites.common-slim; +in +{ + options.plusultra.suites.common-slim = with types; { + enable = mkBoolOpt false "Whether or not to enable common-slim configuration."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = [ + pkgs.plusultra.list-iommu + ]; + + plusultra = { + nix = enabled; + + # TODO: Enable this once Attic is configured again. + # cache.public = enabled; + + cli-apps = { + flake = enabled; + thaw = enabled; + }; + + tools = { + git = enabled; + fup-repl = enabled; + comma = enabled; + bottom = enabled; + direnv = enabled; + }; + + hardware = { + storage = enabled; + networking = enabled; + }; + + services = { + openssh = enabled; + tailscale = enabled; + }; + + security = { + doas = enabled; + }; + + system = { + boot = enabled; + fonts = enabled; + locale = enabled; + time = enabled; + xkb = enabled; + }; + }; + }; +} diff --git a/modules/nixos/suites/common/default.nix b/modules/nixos/suites/common/default.nix new file mode 100644 index 0000000..993e6ca --- /dev/null +++ b/modules/nixos/suites/common/default.nix @@ -0,0 +1,68 @@ +{ options +, config +, lib +, pkgs +, ... +}: +with lib; +with lib.plusultra; let + cfg = config.plusultra.suites.common; +in +{ + options.plusultra.suites.common = with types; { + enable = mkBoolOpt false "Whether or not to enable common configuration."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = [ + pkgs.plusultra.list-iommu + ]; + + plusultra = { + nix = enabled; + + # TODO: Enable this once Attic is configured again. + # cache.public = enabled; + + cli-apps = { + flake = enabled; + thaw = enabled; + }; + + tools = { + git = enabled; + misc = enabled; + fup-repl = enabled; + comma = enabled; + nix-ld = enabled; + bottom = enabled; + }; + + hardware = { + audio = enabled; + storage = enabled; + networking = enabled; + }; + + services = { + printing = enabled; + openssh = enabled; + tailscale = enabled; + }; + + security = { + gpg = enabled; + doas = enabled; + keyring = enabled; + }; + + system = { + boot = enabled; + fonts = enabled; + locale = enabled; + time = enabled; + xkb = enabled; + }; + }; + }; +} diff --git a/modules/nixos/suites/desktop/default.nix b/modules/nixos/suites/desktop/default.nix new file mode 100644 index 0000000..d8720af --- /dev/null +++ b/modules/nixos/suites/desktop/default.nix @@ -0,0 +1,38 @@ +{ options +, config +, lib +, pkgs +, ... +}: +with lib; +with lib.plusultra; let + cfg = config.plusultra.suites.desktop; +in +{ + options.plusultra.suites.desktop = with types; { + enable = + mkBoolOpt false "Whether or not to enable common desktop configuration."; + }; + + config = mkIf cfg.enable { + plusultra = { + desktop = { + gnome = enabled; + + addons = { wallpapers = enabled; }; + }; + + apps = { + _1password = enabled; + firefox = enabled; + vlc = enabled; + logseq = enabled; + hey = enabled; + pocketcasts = enabled; + yt-music = enabled; + twitter = enabled; + gparted = enabled; + }; + }; + }; +} diff --git a/modules/nixos/suites/development/default.nix b/modules/nixos/suites/development/default.nix new file mode 100644 index 0000000..8929984 --- /dev/null +++ b/modules/nixos/suites/development/default.nix @@ -0,0 +1,55 @@ +{ options +, config +, lib +, pkgs +, ... +}: +with lib; +with lib.plusultra; let + cfg = config.plusultra.suites.development; + apps = { + vscode = enabled; + yubikey = enabled; + }; + cli-apps = { + tmux = enabled; + neovim = enabled; + yubikey = enabled; + prisma = enabled; + }; +in +{ + options.plusultra.suites.development = with types; { + enable = + mkBoolOpt false + "Whether or not to enable common development configuration."; + }; + + config = mkIf cfg.enable { + networking.firewall.allowedTCPPorts = [ + 12345 + 3000 + 3001 + 8080 + 8081 + ]; + + plusultra = { + inherit apps cli-apps; + + tools = { + # attic = enabled; + at = enabled; + direnv = enabled; + go = enabled; + http = enabled; + k8s = enabled; + node = enabled; + titan = enabled; + qmk = enabled; + }; + + virtualisation = { podman = enabled; }; + }; + }; +} diff --git a/modules/nixos/suites/emulation/default.nix b/modules/nixos/suites/emulation/default.nix new file mode 100644 index 0000000..7665b16 --- /dev/null +++ b/modules/nixos/suites/emulation/default.nix @@ -0,0 +1,23 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.suites.emulation; +in +{ + options.plusultra.suites.emulation = with types; { + enable = + mkBoolOpt false "Whether or not to enable emulation configuration."; + }; + + config = mkIf cfg.enable { + plusultra = { + apps = { + yuzu = enabled; + pcsx2 = enabled; + dolphin = enabled; + }; + }; + }; +} diff --git a/modules/nixos/suites/games/default.nix b/modules/nixos/suites/games/default.nix new file mode 100644 index 0000000..0e84ba0 --- /dev/null +++ b/modules/nixos/suites/games/default.nix @@ -0,0 +1,28 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.suites.games; + apps = { + steam = enabled; + prismlauncher = enabled; + lutris = enabled; + winetricks = enabled; + protontricks = enabled; + doukutsu-rs = enabled; + bottles = enabled; + }; + cli-apps = { + wine = enabled; + proton = enabled; + }; +in +{ + options.plusultra.suites.games = with types; { + enable = + mkBoolOpt false "Whether or not to enable common games configuration."; + }; + + config = mkIf cfg.enable { plusultra = { inherit apps cli-apps; }; }; +} diff --git a/modules/nixos/suites/media/default.nix b/modules/nixos/suites/media/default.nix new file mode 100644 index 0000000..4b18918 --- /dev/null +++ b/modules/nixos/suites/media/default.nix @@ -0,0 +1,14 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.suites.media; +in +{ + options.plusultra.suites.media = with types; { + enable = mkBoolOpt false "Whether or not to enable media configuration."; + }; + + config = mkIf cfg.enable { plusultra = { apps = { freetube = enabled; }; }; }; +} diff --git a/modules/nixos/suites/music/default.nix b/modules/nixos/suites/music/default.nix new file mode 100644 index 0000000..1715e8b --- /dev/null +++ b/modules/nixos/suites/music/default.nix @@ -0,0 +1,21 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.suites.music; +in +{ + options.plusultra.suites.music = with types; { + enable = mkBoolOpt false "Whether or not to enable music configuration."; + }; + + config = mkIf cfg.enable { + plusultra = { + apps = { + ardour = enabled; + bottles = enabled; + }; + }; + }; +} diff --git a/modules/nixos/suites/social/default.nix b/modules/nixos/suites/social/default.nix new file mode 100644 index 0000000..7923897 --- /dev/null +++ b/modules/nixos/suites/social/default.nix @@ -0,0 +1,27 @@ +{ options +, config +, lib +, pkgs +, ... +}: +with lib; +with lib.plusultra; let + cfg = config.plusultra.suites.social; +in +{ + options.plusultra.suites.social = with types; { + enable = mkBoolOpt false "Whether or not to enable social configuration."; + }; + + config = mkIf cfg.enable { + plusultra = { + apps = { + discord = { + enable = true; + chromium = enabled; + }; + element = enabled; + }; + }; + }; +} diff --git a/modules/nixos/suites/video/default.nix b/modules/nixos/suites/video/default.nix new file mode 100644 index 0000000..080bf10 --- /dev/null +++ b/modules/nixos/suites/video/default.nix @@ -0,0 +1,21 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.suites.video; +in +{ + options.plusultra.suites.video = with types; { + enable = mkBoolOpt false "Whether or not to enable video configuration."; + }; + + config = mkIf cfg.enable { + plusultra = { + apps = { + pitivi = enabled; + obs = enabled; + }; + }; + }; +} diff --git a/modules/nixos/system/boot/default.nix b/modules/nixos/system/boot/default.nix new file mode 100644 index 0000000..587d66a --- /dev/null +++ b/modules/nixos/system/boot/default.nix @@ -0,0 +1,24 @@ +{ options +, config +, pkgs +, lib +, ... +}: +with lib; +with lib.plusultra; let + cfg = config.plusultra.system.boot; +in +{ + options.plusultra.system.boot = with types; { + enable = mkBoolOpt false "Whether or not to enable booting."; + }; + + config = mkIf cfg.enable { + boot.loader.systemd-boot.enable = true; + boot.loader.systemd-boot.configurationLimit = 10; + boot.loader.efi.canTouchEfiVariables = true; + + # https://github.com/NixOS/nixpkgs/blob/c32c39d6f3b1fe6514598fa40ad2cf9ce22c3fb7/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix#L66 + boot.loader.systemd-boot.editor = false; + }; +} diff --git a/modules/nixos/system/env/default.nix b/modules/nixos/system/env/default.nix new file mode 100644 index 0000000..d98732e --- /dev/null +++ b/modules/nixos/system/env/default.nix @@ -0,0 +1,39 @@ +{ options, config, pkgs, lib, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.system.env; +in +{ + options.plusultra.system.env = with types; + mkOption { + type = attrsOf (oneOf [ str path (listOf (either str path)) ]); + apply = mapAttrs (n: v: + if isList v then + concatMapStringsSep ":" (x: toString x) v + else + (toString v)); + default = { }; + description = "A set of environment variables to set."; + }; + + config = { + environment = { + sessionVariables = { + XDG_CACHE_HOME = "$HOME/.cache"; + XDG_CONFIG_HOME = "$HOME/.config"; + XDG_DATA_HOME = "$HOME/.local/share"; + XDG_BIN_HOME = "$HOME/.local/bin"; + # To prevent firefox from creating ~/Desktop. + XDG_DESKTOP_DIR = "$HOME"; + }; + variables = { + # Make some programs "XDG" compliant. + LESSHISTFILE = "$XDG_CACHE_HOME/less.history"; + WGETRC = "$XDG_CONFIG_HOME/wgetrc"; + }; + extraInit = concatStringsSep "\n" + (mapAttrsToList (n: v: ''export ${n}="${v}"'') cfg); + }; + }; +} diff --git a/modules/nixos/system/fonts/default.nix b/modules/nixos/system/fonts/default.nix new file mode 100644 index 0000000..e254d91 --- /dev/null +++ b/modules/nixos/system/fonts/default.nix @@ -0,0 +1,35 @@ +{ options +, config +, pkgs +, lib +, ... +}: +with lib; +with lib.plusultra; let + cfg = config.plusultra.system.fonts; +in +{ + options.plusultra.system.fonts = with types; { + enable = mkBoolOpt false "Whether or not to manage fonts."; + fonts = mkOpt (listOf package) [ ] "Custom font packages to install."; + }; + + config = mkIf cfg.enable { + environment.variables = { + # Enable icons in tooling since we have nerdfonts. + LOG_ICONS = "true"; + }; + + environment.systemPackages = with pkgs; [ font-manager ]; + + fonts.packages = with pkgs; + [ + noto-fonts + noto-fonts-cjk-sans + noto-fonts-cjk-serif + noto-fonts-emoji + (nerdfonts.override { fonts = [ "Hack" ]; }) + ] + ++ cfg.fonts; + }; +} diff --git a/modules/nixos/system/locale/default.nix b/modules/nixos/system/locale/default.nix new file mode 100644 index 0000000..5887920 --- /dev/null +++ b/modules/nixos/system/locale/default.nix @@ -0,0 +1,17 @@ +{ options, config, pkgs, lib, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.system.locale; +in +{ + options.plusultra.system.locale = with types; { + enable = mkBoolOpt false "Whether or not to manage locale settings."; + }; + + config = mkIf cfg.enable { + i18n.defaultLocale = "en_US.UTF-8"; + + console = { keyMap = mkForce "us"; }; + }; +} diff --git a/modules/nixos/system/time/default.nix b/modules/nixos/system/time/default.nix new file mode 100644 index 0000000..027e8ba --- /dev/null +++ b/modules/nixos/system/time/default.nix @@ -0,0 +1,14 @@ +{ options, config, pkgs, lib, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.system.time; +in +{ + options.plusultra.system.time = with types; { + enable = + mkBoolOpt false "Whether or not to configure timezone information."; + }; + + config = mkIf cfg.enable { time.timeZone = "America/Los_Angeles"; }; +} diff --git a/modules/nixos/system/xkb/default.nix b/modules/nixos/system/xkb/default.nix new file mode 100644 index 0000000..a6bef3a --- /dev/null +++ b/modules/nixos/system/xkb/default.nix @@ -0,0 +1,19 @@ +{ options, config, lib, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.system.xkb; +in +{ + options.plusultra.system.xkb = with types; { + enable = mkBoolOpt false "Whether or not to configure xkb."; + }; + + config = mkIf cfg.enable { + console.useXkbConfig = true; + services.xserver = { + layout = "us"; + xkbOptions = "caps:escape"; + }; + }; +} diff --git a/modules/nixos/system/zfs/default.nix b/modules/nixos/system/zfs/default.nix new file mode 100644 index 0000000..4030b30 --- /dev/null +++ b/modules/nixos/system/zfs/default.nix @@ -0,0 +1,47 @@ +{ config, lib, ... }: + +let + cfg = config.plusultra.system.zfs; + + inherit (lib) mkEnableOption mkIf mkDefault; + inherit (lib.plusultra) mkOpt enabled; + inherit (lib.types) listOf str; +in +{ + options.plusultra.system.zfs = { + enable = mkEnableOption "ZFS support"; + + pools = mkOpt (listOf str) [ "rpool" ] "The ZFS pools to manage."; + + auto-snapshot = { + enable = mkEnableOption "ZFS auto snapshotting"; + }; + }; + + config = mkIf cfg.enable { + boot.kernelPackages = config.boot.zfs.package.latestCompatibleLinuxPackages; + + services.zfs = { + autoScrub = { + enable = true; + pools = cfg.pools; + }; + + autoSnapshot = mkIf cfg.auto-snapshot.enable { + enable = true; + flags = "-k -p --utc"; + weekly = mkDefault 3; + daily = mkDefault 3; + hourly = mkDefault 0; + frequent = mkDefault 0; + monthly = mkDefault 2; + }; + }; + + plusultra = { + tools = { + icehouse = enabled; + }; + }; + }; +} diff --git a/modules/nixos/tools/appimage-run/default.nix b/modules/nixos/tools/appimage-run/default.nix new file mode 100644 index 0000000..67a4420 --- /dev/null +++ b/modules/nixos/tools/appimage-run/default.nix @@ -0,0 +1,20 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.tools.appimage-run; +in +{ + options.plusultra.tools.appimage-run = with types; { + enable = mkBoolOpt false "Whether or not to enable appimage-run."; + }; + + config = mkIf cfg.enable { + plusultra.home.configFile."wgetrc".text = ""; + + environment.systemPackages = with pkgs; [ + appimage-run + ]; + }; +} diff --git a/modules/nixos/tools/at/default.nix b/modules/nixos/tools/at/default.nix new file mode 100644 index 0000000..fdf61c3 --- /dev/null +++ b/modules/nixos/tools/at/default.nix @@ -0,0 +1,19 @@ +{ options, config, pkgs, lib, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.tools.at; +in +{ + options.plusultra.tools.at = with types; { + enable = mkBoolOpt false "Whether or not to install at."; + pkg = mkOpt package pkgs.plusultra.at "The package to install as at."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = [ + cfg.pkg + ]; + }; +} diff --git a/modules/nixos/tools/attic/default.nix b/modules/nixos/tools/attic/default.nix new file mode 100644 index 0000000..94efa85 --- /dev/null +++ b/modules/nixos/tools/attic/default.nix @@ -0,0 +1,18 @@ +{ config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.tools.attic; +in +{ + options.plusultra.tools.attic = { + enable = mkEnableOption "Attic"; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + attic + ]; + }; +} diff --git a/modules/nixos/tools/bottom/default.nix b/modules/nixos/tools/bottom/default.nix new file mode 100644 index 0000000..287f641 --- /dev/null +++ b/modules/nixos/tools/bottom/default.nix @@ -0,0 +1,18 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.tools.bottom; +in +{ + options.plusultra.tools.bottom = with types; { + enable = mkBoolOpt false "Whether or not to enable Bottom."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + bottom + ]; + }; +} diff --git a/modules/nixos/tools/comma/default.nix b/modules/nixos/tools/comma/default.nix new file mode 100644 index 0000000..b9cc77c --- /dev/null +++ b/modules/nixos/tools/comma/default.nix @@ -0,0 +1,29 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.tools.comma; +in +{ + options.plusultra.tools.comma = with types; { + enable = mkBoolOpt false "Whether or not to enable comma."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + comma + plusultra.nix-update-index + ]; + + plusultra.home = { + configFile = { + "wgetrc".text = ""; + }; + + extraOptions = { + programs.nix-index.enable = true; + }; + }; + }; +} diff --git a/modules/nixos/tools/fup-repl/default.nix b/modules/nixos/tools/fup-repl/default.nix new file mode 100644 index 0000000..52c01ab --- /dev/null +++ b/modules/nixos/tools/fup-repl/default.nix @@ -0,0 +1,17 @@ +{ lib, pkgs, config, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.tools.fup-repl; + fup-repl = pkgs.writeShellScriptBin "fup-repl" '' + ${pkgs.fup-repl}/bin/repl ''${@} + ''; +in +{ + options.plusultra.tools.fup-repl = with types; { + enable = mkBoolOpt false "Whether to enable fup-repl or not"; + }; + + config = mkIf cfg.enable { environment.systemPackages = [ fup-repl ]; }; +} diff --git a/modules/nixos/tools/git/default.nix b/modules/nixos/tools/git/default.nix new file mode 100644 index 0000000..c13ba67 --- /dev/null +++ b/modules/nixos/tools/git/default.nix @@ -0,0 +1,43 @@ +{ options, config, pkgs, lib, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.tools.git; + gpg = config.plusultra.security.gpg; + user = config.plusultra.user; +in +{ + options.plusultra.tools.git = with types; { + enable = mkBoolOpt false "Whether or not to install and configure git."; + userName = mkOpt types.str user.fullName "The name to configure git with."; + userEmail = mkOpt types.str user.email "The email to configure git with."; + signingKey = + mkOpt types.str "9762169A1B35EA68" "The key ID to sign commits with."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ git ]; + + plusultra.home.extraOptions = { + programs.git = { + enable = true; + inherit (cfg) userName userEmail; + lfs = enabled; + signing = { + key = cfg.signingKey; + signByDefault = mkIf gpg.enable true; + }; + extraConfig = { + init = { defaultBranch = "main"; }; + pull = { rebase = true; }; + push = { autoSetupRemote = true; }; + core = { whitespace = "trailing-space,space-before-tab"; }; + safe = { + directory = "${config.users.users.${user.name}.home}/work/config"; + }; + }; + }; + }; + }; +} diff --git a/modules/nixos/tools/go/default.nix b/modules/nixos/tools/go/default.nix new file mode 100644 index 0000000..923c027 --- /dev/null +++ b/modules/nixos/tools/go/default.nix @@ -0,0 +1,21 @@ +{ options, config, pkgs, lib, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.tools.go; +in +{ + options.plusultra.tools.go = with types; { + enable = mkBoolOpt false "Whether or not to enable Go support."; + }; + + config = mkIf cfg.enable { + environment = { + systemPackages = with pkgs; [ go gopls ]; + sessionVariables = { + GOPATH = "$HOME/work/go"; + }; + }; + }; +} diff --git a/modules/nixos/tools/http/default.nix b/modules/nixos/tools/http/default.nix new file mode 100644 index 0000000..0094c31 --- /dev/null +++ b/modules/nixos/tools/http/default.nix @@ -0,0 +1,14 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.tools.http; +in +{ + options.plusultra.tools.http = with types; { + enable = mkBoolOpt false "Whether or not to enable common http utilities."; + }; + + config = + mkIf cfg.enable { environment.systemPackages = with pkgs; [ wget curl ]; }; +} diff --git a/modules/nixos/tools/icehouse/default.nix b/modules/nixos/tools/icehouse/default.nix new file mode 100644 index 0000000..dc58825 --- /dev/null +++ b/modules/nixos/tools/icehouse/default.nix @@ -0,0 +1,16 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.plusultra.tools.icehouse; + + inherit (lib) mkEnableOption mkIf; +in +{ + options.plusultra.tools.icehouse = { + enable = mkEnableOption "Icehouse"; + }; + + config = mkIf cfg.enable { + environment.systemPackages = [ pkgs.snowfallorg.icehouse ]; + }; +} diff --git a/modules/nixos/tools/k8s/default.nix b/modules/nixos/tools/k8s/default.nix new file mode 100644 index 0000000..b1cc8bf --- /dev/null +++ b/modules/nixos/tools/k8s/default.nix @@ -0,0 +1,31 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.tools.k8s; +in +{ + options.plusultra.tools.k8s = with types; { + enable = + mkBoolOpt false "Whether or not to enable common Kubernetes utilities."; + }; + + config = mkIf cfg.enable { + programs.zsh.shellAliases = { + k = "kubecolor"; + kubectl = "kubecolor"; + kc = "kubectx"; + kn = "kubens"; + ks = "kubeseal"; + }; + + environment.systemPackages = with pkgs; [ + kubectl + kubectx + kubeseal + kubecolor + kubernetes-helm + helmfile + ]; + }; +} diff --git a/modules/nixos/tools/misc/default.nix b/modules/nixos/tools/misc/default.nix new file mode 100644 index 0000000..379c1ec --- /dev/null +++ b/modules/nixos/tools/misc/default.nix @@ -0,0 +1,25 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.tools.misc; +in +{ + options.plusultra.tools.misc = with types; { + enable = mkBoolOpt false "Whether or not to enable common utilities."; + }; + + config = mkIf cfg.enable { + plusultra.home.configFile."wgetrc".text = ""; + + environment.systemPackages = with pkgs; [ + fzf + killall + unzip + file + jq + clac + wget + ]; + }; +} diff --git a/modules/nixos/tools/nix-ld/default.nix b/modules/nixos/tools/nix-ld/default.nix new file mode 100644 index 0000000..c84c284 --- /dev/null +++ b/modules/nixos/tools/nix-ld/default.nix @@ -0,0 +1,15 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.tools.nix-ld; +in +{ + options.plusultra.tools.nix-ld = with types; { + enable = mkBoolOpt false "Whether or not to enable nix-ld."; + }; + + config = mkIf cfg.enable { + programs.nix-ld.enable = true; + }; +} diff --git a/modules/nixos/tools/node/default.nix b/modules/nixos/tools/node/default.nix new file mode 100644 index 0000000..9a70afd --- /dev/null +++ b/modules/nixos/tools/node/default.nix @@ -0,0 +1,42 @@ +{ options +, config +, pkgs +, lib +, ... +}: +with lib; +with lib.plusultra; let + cfg = config.plusultra.tools.node; +in +{ + options.plusultra.tools.node = with types; { + enable = mkBoolOpt false "Whether or not to install and configure git"; + pkg = mkOpt package pkgs.nodejs "The NodeJS package to use"; + prettier = { + enable = mkBoolOpt true "Whether or not to install Prettier"; + pkg = + mkOpt package pkgs.nodePackages.prettier "The NodeJS package to use"; + }; + yarn = { + enable = mkBoolOpt true "Whether or not to install Yarn"; + pkg = mkOpt package pkgs.nodePackages.yarn "The NodeJS package to use"; + }; + pnpm = { + enable = mkBoolOpt true "Whether or not to install Pnpm"; + pkg = mkOpt package pkgs.nodePackages.pnpm "The NodeJS package to use"; + }; + flyctl = { + enable = mkBoolOpt true "Whether or not to install flyctl"; + pkg = mkOpt package pkgs.flyctl "The flyctl package to use"; + }; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; + [ cfg.pkg ] + ++ (lib.optional cfg.prettier.enable cfg.prettier.pkg) + ++ (lib.optional cfg.yarn.enable cfg.yarn.pkg) + ++ (lib.optional cfg.pnpm.enable cfg.pnpm.pkg) + ++ (lib.optional cfg.flyctl.enable cfg.flyctl.pkg); + }; +} diff --git a/modules/nixos/tools/qmk/default.nix b/modules/nixos/tools/qmk/default.nix new file mode 100644 index 0000000..1f2d45e --- /dev/null +++ b/modules/nixos/tools/qmk/default.nix @@ -0,0 +1,21 @@ +{ options, config, pkgs, lib, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.tools.qmk; +in +{ + options.plusultra.tools.qmk = with types; { + enable = mkBoolOpt false "Whether or not to enable QMK"; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + qmk + ]; + + services.udev.packages = with pkgs; [ + qmk-udev-rules + ]; + }; +} diff --git a/modules/nixos/tools/titan/default.nix b/modules/nixos/tools/titan/default.nix new file mode 100644 index 0000000..9442da2 --- /dev/null +++ b/modules/nixos/tools/titan/default.nix @@ -0,0 +1,25 @@ +{ options, config, pkgs, lib, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.tools.titan; +in +{ + options.plusultra.tools.titan = with types; { + enable = mkBoolOpt false "Whether or not to install Titan."; + pkg = mkOpt package pkgs.plusultra.titan "The package to install as Titan."; + }; + + config = mkIf cfg.enable { + plusultra.tools = { + # Titan depends on Node and Git + node = enabled; + git = enabled; + }; + + environment.systemPackages = [ + cfg.pkg + ]; + }; +} diff --git a/modules/nixos/user/default.nix b/modules/nixos/user/default.nix index 7b12b82..076d3ac 100644 --- a/modules/nixos/user/default.nix +++ b/modules/nixos/user/default.nix @@ -32,9 +32,9 @@ with lib.plusultra; let in { options.plusultra.user = with types; { - name = mkOpt str "harald" "The name to use for the user account."; - fullName = mkOpt str "Harald Hoyer" "The full name of the user."; - email = mkOpt str "harald@hoyer.xyz" "The email of the user."; + name = mkOpt str "short" "The name to use for the user account."; + fullName = mkOpt str "Jake Hamilton" "The full name of the user."; + email = mkOpt str "jake.hamilton@hey.com" "The email of the user."; initialPassword = mkOpt str "password" "The initial password to use when the user is first created."; @@ -43,12 +43,6 @@ in "The profile picture to use for the user."; prompt-init = mkBoolOpt true "Whether or not to show an initial message when opening a new shell."; extraGroups = mkOpt (listOf str) [ ] "Groups for the user to be assigned."; - sshKeys = mkOpt (listOf str) [ - "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIDsb/Tr69YN5MQLweWPuJaRGm+h2kOyxfD6sqKEDTIwoAAAABHNzaDo= harald@fedora.fritz.box" - "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBACLgT81iB1iWWVuXq6PdQ5GAAGhaZhSKnveQCvcNnAOZ5WKH80bZShKHyAYzrzbp8IGwLWJcZQ7TqRK+qZdfagAAAAEc3NoOg== harald@hoyer.xyz" - "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBAYbUTKpy4QR3s944/hjJ1UK05asFEs/SmWeUbtS0cdA660sT4xHnRfals73FicOoz+uIucJCwn/SCM804j+wtM=" - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMNsmP15vH8BVKo7bdvIiiEjiQboPGcRPqJK0+bH4jKD harald@lenovo.fritz.box" - ] "ssh keys"; extraOptions = mkOpt attrs { } (mdDoc "Extra options passed to `users.users.`."); @@ -56,8 +50,19 @@ in config = { environment.systemPackages = with pkgs; [ + cowsay + fortune + lolcat + plusultra.cowsay-plus + propagatedIcon ]; + programs.zsh = { + enable = true; + autosuggestions.enable = true; + histFile = "$XDG_CACHE_HOME/zsh.history"; + }; + plusultra.home = { file = { "Desktop/.keep".text = ""; @@ -73,19 +78,81 @@ in }".source = cfg.icon; }; + + extraOptions = { + home.shellAliases = { + lc = "${pkgs.colorls}/bin/colorls --sd"; + lcg = "lc --gs"; + lcl = "lc -1"; + lclg = "lc -1 --gs"; + lcu = "${pkgs.colorls}/bin/colorls -U"; + lclu = "${pkgs.colorls}/bin/colorls -U -1"; + }; + + programs = { + starship = { + enable = true; + settings = { + character = { + success_symbol = "[➜](bold green)"; + error_symbol = "[✗](bold red) "; + vicmd_symbol = "[](bold blue) "; + }; + }; + }; + + zsh = { + enable = true; + enableCompletion = true; + enableAutosuggestions = true; + syntaxHighlighting.enable = true; + + initExtra = + '' + # Fix an issue with tmux. + export KEYTIMEOUT=1 + + # Use vim bindings. + set -o vi + + # Improved vim bindings. + source ${pkgs.zsh-vi-mode}/share/zsh-vi-mode/zsh-vi-mode.plugin.zsh + '' + + optionalString cfg.prompt-init '' + ${pkgs.toilet}/bin/toilet -f future "Plus Ultra" --gay + ''; + + shellAliases = { + say = "${pkgs.toilet}/bin/toilet -f pagga"; + }; + + plugins = [ + { + name = "zsh-nix-shell"; + file = "nix-shell.plugin.zsh"; + src = pkgs.fetchFromGitHub { + owner = "chisui"; + repo = "zsh-nix-shell"; + rev = "v0.4.0"; + sha256 = "037wz9fqmx0ngcwl9az55fgkipb745rymznxnssr3rx9irb6apzg"; + }; + } + ]; + }; + }; + }; }; users.users.${cfg.name} = { isNormalUser = true; - # inherit (cfg) name initialPassword; + inherit (cfg) name initialPassword; - openssh.authorizedKeys.keys = cfg.sshKeys; home = "/home/${cfg.name}"; group = "users"; - shell = pkgs.fish; + shell = pkgs.zsh; # Arbitrary user ID to use for the user. Since I only # have a single user on my machines this won't ever collide. diff --git a/modules/nixos/virtualisation/kvm/default.nix b/modules/nixos/virtualisation/kvm/default.nix new file mode 100644 index 0000000..896434d --- /dev/null +++ b/modules/nixos/virtualisation/kvm/default.nix @@ -0,0 +1,108 @@ +{ config +, lib +, pkgs +, ... +}: +with lib; +with lib.plusultra; let + cfg = config.plusultra.virtualisation.kvm; + user = config.plusultra.user; +in +{ + options.plusultra.virtualisation.kvm = with types; { + enable = mkBoolOpt false "Whether or not to enable KVM virtualisation."; + vfioIds = + mkOpt (listOf str) [ ] + "The hardware IDs to pass through to a virtual machine."; + platform = + mkOpt (enum [ "amd" "intel" ]) "amd" + "Which CPU platform the machine is using."; + # Use `machinectl` and then `machinectl status ` to + # get the unit "*.scope" of the virtual machine. + machineUnits = + mkOpt (listOf str) [ ] + "The systemd *.scope units to wait for before starting Scream."; + }; + + config = mkIf cfg.enable { + boot = { + kernelModules = [ + "kvm-${cfg.platform}" + "vfio_virqfd" + "vfio_pci" + "vfio_iommu_type1" + "vfio" + ]; + kernelParams = [ + "${cfg.platform}_iommu=on" + "${cfg.platform}_iommu=pt" + "kvm.ignore_msrs=1" + # "vfio-pci.ids=${concatStringsSep "," cfg.vfioIds}" + ]; + extraModprobeConfig = + optionalString (length cfg.vfioIds > 0) + '' + softdep amdgpu pre: vfio vfio-pci + options vfio-pci ids=${concatStringsSep "," cfg.vfioIds} + ''; + }; + + systemd.tmpfiles.rules = [ + "f /dev/shm/looking-glass 0660 ${user.name} qemu-libvirtd -" + "f /dev/shm/scream 0660 ${user.name} qemu-libvirtd -" + ]; + + environment.systemPackages = with pkgs; [ + virt-manager + ]; + + virtualisation = { + libvirtd = { + enable = true; + extraConfig = '' + user="${user.name}" + ''; + + onBoot = "ignore"; + onShutdown = "shutdown"; + + qemu = { + package = pkgs.qemu_kvm; + ovmf = enabled; + swtpm = enabled; + verbatimConfig = '' + namespaces = [] + user = "+${builtins.toString config.users.users.${user.name}.uid}" + ''; + }; + }; + }; + + plusultra = { + user = { extraGroups = [ "qemu-libvirtd" "libvirtd" "disk" ]; }; + + apps = { looking-glass-client = enabled; }; + + home = { + extraOptions = { + systemd.user.services.scream = { + Unit.Description = "Scream"; + Unit.After = + [ + "libvirtd.service" + "pipewire-pulse.service" + "pipewire.service" + "sound.target" + ] + ++ cfg.machineUnits; + Service.ExecStart = "${pkgs.scream}/bin/scream -n scream -o pulse -m /dev/shm/scream"; + Service.Restart = "always"; + Service.StartLimitIntervalSec = "5"; + Service.StartLimitBurst = "1"; + Install.RequiredBy = cfg.machineUnits; + }; + }; + }; + }; + }; +} diff --git a/modules/nixos/virtualisation/podman/default.nix b/modules/nixos/virtualisation/podman/default.nix new file mode 100644 index 0000000..80e7d2b --- /dev/null +++ b/modules/nixos/virtualisation/podman/default.nix @@ -0,0 +1,32 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.virtualisation.podman; +in +{ + options.plusultra.virtualisation.podman = with types; { + enable = mkBoolOpt false "Whether or not to enable Podman."; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ podman-compose ]; + + plusultra.home.extraOptions = { + home.shellAliases = { "docker-compose" = "podman-compose"; }; + }; + + # NixOS 22.05 moved NixOS Containers to a new state directory and the old + # directory is taken over by OCI Containers (eg. podman). For systems with + # system.stateVersion < 22.05, it is not possible to have both enabled. + # This option disables NixOS Containers, leaving OCI Containers available. + boot.enableContainers = false; + + virtualisation = { + podman = { + enable = cfg.enable; + dockerCompat = true; + }; + }; + }; +} diff --git a/packages/nixos-hosts/default.nix b/packages/nixos-hosts/default.nix deleted file mode 100644 index a070435..0000000 --- a/packages/nixos-hosts/default.nix +++ /dev/null @@ -1,49 +0,0 @@ -{ lib -, writeText -, writeShellApplication -, substituteAll -, gum -, inputs -, hosts ? { } -, ... -}: - -let - inherit (lib) mapAttrsToList concatStringsSep; - inherit (lib.plusultra) override-meta; - - substitute = args: builtins.readFile (substituteAll args); - - formatted-hosts = mapAttrsToList - (name: host: "${name},${host.pkgs.system}") - hosts; - - hosts-csv = writeText "hosts.csv" '' - Name,System - ${concatStringsSep "\n" formatted-hosts} - ''; - - nixos-hosts = writeShellApplication { - name = "nixos-hosts"; - - text = substitute { - src = ./nixos-hosts.sh; - - help = ./help; - hosts = if hosts == { } then "" else hosts-csv; - }; - - checkPhase = ""; - - runtimeInputs = [ - gum - ]; - }; - - new-meta = with lib; { - description = "A helper to list all of the NixOS hosts available from your flake."; - license = licenses.asl20; - maintainers = with maintainers; [ jakehamilton ]; - }; -in -override-meta new-meta nixos-hosts diff --git a/packages/nixos-hosts/help/nixos-hosts.sh b/packages/nixos-hosts/help/nixos-hosts.sh deleted file mode 100644 index eef512d..0000000 --- a/packages/nixos-hosts/help/nixos-hosts.sh +++ /dev/null @@ -1,16 +0,0 @@ -echo -e " -${text_bold}${text_fg_blue}nixos-hosts${text_reset} - -${text_bold}DESCRIPTION${text_reset} - - Show NixOS hosts from your flake. - -${text_bold}USAGE${text_reset} - - ${text_dim}\$${text_reset} ${text_bold}nixos-hosts${text_reset} [options] - -${text_bold}OPTIONS${text_reset} - - --help, -h Show this help message - --debug Show debug messages -" diff --git a/packages/nixos-hosts/nixos-hosts.sh b/packages/nixos-hosts/nixos-hosts.sh deleted file mode 100644 index 6532660..0000000 --- a/packages/nixos-hosts/nixos-hosts.sh +++ /dev/null @@ -1,324 +0,0 @@ -#!/usr/bin/env bash - -#==============================# -# Global # -#==============================# - -DEBUG=${DEBUG:-"false"} - -#==============================# -# Injected # -#==============================# - -hosts="@hosts@" -help_root="@help@" - -#==============================# -# Logging # -#==============================# - -text_reset="\e[m" -text_bold="\e[1m" -text_dim="\e[2m" -text_italic="\e[3m" -text_underline="\e[4m" -text_blink="\e[5m" -text_highlight="\e[7m" -text_hidden="\e[8m" -text_strike="\e[9m" - -text_fg_red="\e[38;5;1m" -text_fg_green="\e[38;5;2m" -text_fg_yellow="\e[38;5;3m" -text_fg_blue="\e[38;5;4m" -text_fg_magenta="\e[38;5;5m" -text_fg_cyan="\e[38;5;6m" -text_fg_white="\e[38;5;7m" -text_fg_dim="\e[38;5;8m" - -text_bg_red="\e[48;5;1m" -text_bg_green="\e[48;5;2m" -text_bg_yellow="\e[48;5;3m" -text_bg_blue="\e[48;5;4m" -text_bg_magenta="\e[48;5;5m" -text_bg_cyan="\e[48;5;6m" -text_bg_white="\e[48;5;7m" -text_bg_dim="\e[48;5;8m" - -# Usage: log_info -log_info() { - echo -e "${text_fg_blue}info${text_reset} $1" -} - -# Usage: log_todo -log_todo() { - echo -e "${text_bg_magenta}${text_fg_white}todo${text_reset} $1" -} - -# Usage: log_debug -log_debug() { - if [[ $DEBUG == true ]]; then - echo -e "${text_fg_dim}debug${text_reset} $1" - fi -} - -# Usage: log_warn -log_warn() { - echo -e "${text_fg_yellow}warn${text_reset} $1" -} - -# Usage: log_error -log_error() { - echo -e "${text_fg_red}error${text_reset} $1" -} - -# Usage: log_fatal [exit-code] -log_fatal() { - echo -e "${text_fg_white}${text_bg_red}fatal${text_reset} $1" - - if [ -z ${2:-} ]; then - exit 1 - else - exit $2 - fi -} - -# Usage: clear_previous_line [number] -clear_line() { - echo -e "\e[${1:-"1"}A\e[2K" -} - -# Usage: -# rewrite_line -# rewrite_line -rewrite_line() { - if [[ $# == 1 ]]; then - echo -e "\e[1A\e[2K${1}" - else - echo -e "\e[${1}A\e[2K${2}" - fi -} - -#==============================# -# Options # -#==============================# -positional_args=() - -opt_help=false -opt_pick=false -opt_list=false - -# Usage: missing_value