diff --git a/flake.nix b/flake.nix index 2b34a84..bd88c45 100644 --- a/flake.nix +++ b/flake.nix @@ -22,7 +22,7 @@ inherit inputs; src = ./.; - snowfall.namespace = "hh-config"; + snowfall.namespace = "plusultra"; }; in lib.mkFlake { 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 0fba931..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 @@ -7,351 +7,30 @@ , ... }: { - # Home Manager needs a bit of information about you and the paths it should - # manage. - home.username = "harald"; - home.homeDirectory = "/home/harald/nix"; - home.stateVersion = "23.11"; # Please read the comment before changing. - home.sessionPath = [ - "$HOME/.local/share/JetBrains/Toolbox/scripts" - "$HOME/bin" - ]; - - # The home.packages option allows you to install Nix packages into your - # environment. - home.packages = with pkgs; [ - bashInteractive - cachix - jetbrains-toolbox - delta - git - vim - tmux - (pkgs.nerdfonts.override { fonts = [ "FiraCode" "DroidSansMono" "JetBrainsMono" ]; }) - # # You can also create simple shell scripts directly inside your - # # configuration. For example, this adds a command 'my-hello' to your - # # environment: - # (pkgs.writeShellScriptBin "my-hello" '' - # echo "Hello, ${config.home.username}!" - # '') - ]; - - home.sessionVariables = { - EDITOR = "vim"; + home = { + username = "harald"; + homeDirectory = "/home/${config.home.username}/nix"; + stateVersion = "23.11"; # Please read the comment before changing. + sessionPath = [ "$HOME/bin" ]; }; - home.shellAliases = { - cat = "${pkgs.bat}/bin/bat --decorations never"; - less = ''${pkgs.bat}/bin/bat --decorations never --paging=always --pager "${pkgs.less}/bin/less -RF"''; - man = "${pkgs.bat-extras.batman}/bin/batman"; + plusultra = { + cli-apps = { + bash.enable = true; + fish.enable = true; + neovim.enable = true; + 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; - - programs = { - bash = { - enable = true; - }; - bat = { - enable = true; - config.theme = "ansi"; - extraPackages = with pkgs.bat-extras; [ batdiff batman batgrep batwatch ]; - }; - git = { - enable = true; - userName = "Harald Hoyer"; - userEmail = "harald@hoyer.xyz"; - extraConfig = { - init.defaultBranch = "main"; - "credential \"https://github.com\"" = { - helper = "!gh auth git-credential"; - }; - alias = { - co = "checkout"; - ci = "commit"; - }; - pull.ff = "only"; - core.pager = "delta"; - delta = { - features = "side-by-side line-numbers decorations"; - syntax-theme = "Dracula"; - }; - interactive.diffFilter = "delta --color-only"; - merge.conflictStyle = "diff3"; - diff.colorMoved = "default"; - }; - }; - direnv = { - enable = true; - nix-direnv.enable = true; - }; - fish.enable = true; - fish.interactiveShellInit = '' - function msh --wraps mosh --description 'mosh with tmux' - if not set -q argv[1] - echo 'Usage: msh [user@]host [command]' - else - ${pkgs.mosh}/bin/mosh $argv -- tmux new-session -A -s 0 - end - end - ''; - - fish.plugins = [{ - name = "foreign-env"; - src = pkgs.fetchFromGitHub { - owner = "oh-my-fish"; - repo = "plugin-foreign-env"; - rev = "dddd9213272a0ab848d474d0cbde12ad034e65bc"; - sha256 = "00xqlyl3lffc5l0viin1nyp819wf81fncqyz87jx8ljjdhilmgbs"; - }; - }]; - - fish.shellInit = - '' - # nix - if test -e /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh - fenv source /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh - end - - # home-manager - if test -e $HOME/.nix-profile/etc/profile.d/hm-session-vars.sh - fenv source $HOME/.nix-profile/etc/profile.d/hm-session-vars.sh - end - ''; - - # programs.bash.enable = true; - bash.initExtra = '' - function msh() { mosh "$@" -- bash -c 'if type -f tmux; then tmux new-session -A -s 0; else screen -R; fi;' ; }; - ''; - - # Let Home Manager install and manage itself. - home-manager.enable = true; - starship.enable = true; - starship.settings = - { - container.format = "[\\[$name\\]]($style) "; - git_status = { - ahead = "⇡$\{count}"; - diverged = "⇕⇡$\{ahead_count}⇣$\{behind_count}"; - behind = "⇣$\{count}"; - }; - }; - - neovim-flake = { - enable = true; - # your settings need to go into the settings attrset - settings = { - vim = { - viAlias = false; - vimAlias = false; - debugMode = { - enable = false; - level = 20; - logFile = "/tmp/nvim.log"; - }; - }; - - vim.lsp = { - formatOnSave = true; - lspkind.enable = false; - lightbulb.enable = true; - lspsaga.enable = false; - nvimCodeActionMenu.enable = true; - trouble.enable = true; - lspSignature.enable = true; - lsplines.enable = true; - }; - - vim.debugger = { - nvim-dap = { - enable = true; - ui.enable = true; - }; - }; - - vim.languages = { - enableLSP = true; - enableFormat = true; - enableTreesitter = true; - enableExtraDiagnostics = true; - - nix = { - enable = true; - format.type = "nixpkgs-fmt"; - }; - html.enable = true; - clang = { - enable = true; - lsp.server = "clangd"; - }; - sql.enable = false; - rust = { - enable = true; - crates.enable = true; - }; - java.enable = true; - ts.enable = true; - svelte.enable = true; - go.enable = true; - zig.enable = true; - python.enable = true; - dart.enable = true; - elixir.enable = false; - }; - - vim.visuals = { - enable = true; - nvimWebDevicons.enable = true; - scrollBar.enable = true; - smoothScroll.enable = true; - cellularAutomaton.enable = true; - fidget-nvim.enable = true; - indentBlankline = { - enable = true; - fillChar = null; - eolChar = null; - showCurrContext = true; - }; - cursorline = { - enable = true; - lineTimeout = 0; - }; - }; - - vim.statusline = { - lualine = { - enable = true; - theme = "catppuccin"; - }; - }; - - vim.theme = { - enable = true; - name = "catppuccin"; - style = "mocha"; - transparent = false; - }; - - vim.autopairs.enable = true; - - vim.autocomplete = { - enable = true; - type = "nvim-cmp"; - }; - - vim.filetree = { - nvimTree = { - enable = true; - }; - }; - - vim.tabline = { - nvimBufferline.enable = true; - }; - - vim.treesitter.context.enable = true; - - vim.binds = { - whichKey.enable = true; - cheatsheet.enable = true; - }; - - vim.telescope.enable = true; - - vim.git = { - enable = true; - gitsigns.enable = true; - gitsigns.codeActions = false; # throws an annoying debug message - }; - - vim.minimap = { - minimap-vim.enable = false; - codewindow.enable = true; # lighter, faster, and uses lua for configuration - }; - - vim.dashboard = { - dashboard-nvim.enable = false; - alpha.enable = true; - }; - - vim.notify = { - nvim-notify.enable = true; - }; - - vim.projects = { - project-nvim.enable = true; - }; - - vim.utility = { - ccc.enable = true; - vim-wakatime.enable = false; - icon-picker.enable = true; - surround.enable = true; - diffview-nvim.enable = true; - motion = { - hop.enable = true; - leap.enable = true; - }; - }; - - vim.notes = { - obsidian.enable = false; # FIXME neovim fails to build if obsidian is enabled - orgmode.enable = false; - mind-nvim.enable = true; - todo-comments.enable = true; - }; - - vim.terminal = { - toggleterm = { - enable = true; - lazygit.enable = true; - }; - }; - - vim.ui = { - borders.enable = true; - noice.enable = true; - colorizer.enable = true; - modes-nvim.enable = false; # the theme looks terrible with catppuccin - illuminate.enable = true; - breadcrumbs = { - enable = true; - navbuddy.enable = true; - }; - smartcolumn = { - enable = true; - columnAt.languages = { - # this is a freeform module, it's `buftype = int;` for configuring column position - nix = 110; - ruby = 120; - java = 130; - go = [ 90 130 ]; - }; - }; - }; - - vim.assistant = { - copilot = { - enable = true; - cmp.enable = true; - }; - }; - - vim.session = { - nvim-session-manager.enable = false; - }; - - vim.gestures = { - gesture-nvim.enable = false; - }; - - vim.comments = { - comment-nvim.enable = true; - }; - - vim.spellChecking.languages = [ "en" "de" ]; - }; - }; - }; } diff --git a/lib/audio/default.nix b/lib/audio/default.nix new file mode 100644 index 0000000..ebe8154 --- /dev/null +++ b/lib/audio/default.nix @@ -0,0 +1,65 @@ +{ lib }: + +rec { + ## Renames an alsa device from a given `name` using the new `description`. + ## + #@ { name: String, description: String } -> { matches: List, apply_properties: Attrs } + mkAlsaRename = { name, description }: { + matches = [ + [ + [ "device.name" "matches" name ] + ] + ]; + # actions = { "update-props" = { "node.description" = description; }; }; + apply_properties = { + "device.description" = description; + }; + }; + + ## Create a pipewire audio node. + ## + #@ { name: String, factory: String ? "adapter", ... } -> { factory: String, args: Attrs } + mkAudioNode = args@{ name, factory ? "adapter", ... }: { + inherit factory; + args = (builtins.removeAttrs args [ "name" "description" ]) // { + "node.name" = name; + "node.description" = args.description or args."node.description"; + "factory.name" = args."factory.name" or "support.null-audio-sink"; + }; + }; + + ## Create a virtual pipewire audio node. + ## + #@ { name: String, ... } -> { factory: "adapter", args: Attrs } + mkVirtualAudioNode = args@{ name, ... }: + mkAudioNode (args // { + name = "virtual-${lib.toLower name}-audio"; + description = "${name} (Virtual)"; + "media.class" = args.class or args."media.class" or "Audio/Duplex"; + "object.linger" = args."object.linger" or true; + "audio.position" = args."audio.position" or [ "FL" "FR" ]; + "monitor.channel-volumes" = args."monitor.channel-volumes" or true; + }); + + ## Connect two pipewire audio nodes + ## + #@ { name: String?, from: String, to: String, ... } -> { name: "libpipewire-module-loopback", args: Attrs } + mkBridgeAudioModule = args@{ from, to, ... }: { + name = "libpipewire-module-loopback"; + args = (builtins.removeAttrs args [ "from" "to" "name" ]) // { + "node.name" = + if args ? name then + "${args.name}-bridge" + else + "${lib.toLower from}-to-${lib.toLower to}-bridge"; + "audio.position" = args."audio.position" or [ "FL" "FR" ]; + "capture.props" = { + "node.target" = from; + } // (args."capture.props" or { }); + "playback.props" = { + "node.target" = to; + "monitor.channel-volumes" = true; + } // (args."playback.props" or { }); + }; + }; +} diff --git a/lib/default.nix b/lib/default.nix new file mode 100644 index 0000000..61216de --- /dev/null +++ b/lib/default.nix @@ -0,0 +1,20 @@ +{ lib, inputs, snowfall-inputs }: + +rec { + ## Override a package's metadata + ## + ## ```nix + ## let + ## new-meta = { + ## description = "My new description"; + ## }; + ## in + ## lib.override-meta new-meta pkgs.hello + ## ``` + ## + #@ Attrs -> Package -> Package + override-meta = meta: package: + package.overrideAttrs (attrs: { + meta = (attrs.meta or { }) // meta; + }); +} diff --git a/lib/deploy/default.nix b/lib/deploy/default.nix new file mode 100644 index 0000000..478d7d1 --- /dev/null +++ b/lib/deploy/default.nix @@ -0,0 +1,51 @@ +{ lib, inputs }: + +let + inherit (inputs) deploy-rs; +in +rec { + ## Create deployment configuration for use with deploy-rs. + ## + ## ```nix + ## mkDeploy { + ## inherit self; + ## overrides = { + ## my-host.system.sudo = "doas -u"; + ## }; + ## } + ## ``` + ## + #@ { self: Flake, overrides: Attrs ? {} } -> Attrs + mkDeploy = { self, overrides ? { } }: + let + hosts = self.nixosConfigurations or { }; + names = builtins.attrNames hosts; + nodes = lib.foldl + (result: name: + let + host = hosts.${name}; + user = host.config.plusultra.user.name or null; + inherit (host.pkgs) system; + in + result // { + ${name} = (overrides.${name} or { }) // { + hostname = overrides.${name}.hostname or "${name}"; + profiles = (overrides.${name}.profiles or { }) // { + system = (overrides.${name}.profiles.system or { }) // { + path = deploy-rs.lib.${system}.activate.nixos host; + } // lib.optionalAttrs (user != null) { + user = "root"; + sshUser = user; + } // lib.optionalAttrs + (host.config.plusultra.security.doas.enable or false) + { + sudo = "doas -u"; + }; + }; + }; + }) + { } + names; + in + { inherit nodes; }; +} diff --git a/lib/file/default.nix b/lib/file/default.nix new file mode 100644 index 0000000..97a3078 --- /dev/null +++ b/lib/file/default.nix @@ -0,0 +1,25 @@ +{ lib, ... }: + +rec { + ## Append text to the contents of a file + ## + ## ```nix + ## fileWithText ./some.txt "appended text" + ## ``` + ## + #@ Path -> String -> String + fileWithText = file: text: '' + ${builtins.readFile file} + ${text}''; + + ## Prepend text to the contents of a file + ## + ## ```nix + ## fileWithText' ./some.txt "prepended text" + ## ``` + ## + #@ Path -> String -> String + fileWithText' = file: text: '' + ${text} + ${builtins.readFile file}''; +} diff --git a/lib/module/default.nix b/lib/module/default.nix new file mode 100644 index 0000000..48e53c6 --- /dev/null +++ b/lib/module/default.nix @@ -0,0 +1,62 @@ +{ lib, ... }: + +with lib; rec { + ## Create a NixOS module option. + ## + ## ```nix + ## lib.mkOpt nixpkgs.lib.types.str "My default" "Description of my option." + ## ``` + ## + #@ Type -> Any -> String + mkOpt = type: default: description: + mkOption { inherit type default description; }; + + ## Create a NixOS module option without a description. + ## + ## ```nix + ## lib.mkOpt' nixpkgs.lib.types.str "My default" + ## ``` + ## + #@ Type -> Any -> String + mkOpt' = type: default: mkOpt type default null; + + ## Create a boolean NixOS module option. + ## + ## ```nix + ## lib.mkBoolOpt true "Description of my option." + ## ``` + ## + #@ Type -> Any -> String + mkBoolOpt = mkOpt types.bool; + + ## Create a boolean NixOS module option without a description. + ## + ## ```nix + ## lib.mkBoolOpt true + ## ``` + ## + #@ Type -> Any -> String + mkBoolOpt' = mkOpt' types.bool; + + enabled = { + ## Quickly enable an option. + ## + ## ```nix + ## services.nginx = enabled; + ## ``` + ## + #@ true + enable = true; + }; + + disabled = { + ## Quickly disable an option. + ## + ## ```nix + ## services.nginx = enabled; + ## ``` + ## + #@ false + enable = false; + }; +} diff --git a/lib/network/default.nix b/lib/network/default.nix new file mode 100644 index 0000000..5b1033a --- /dev/null +++ b/lib/network/default.nix @@ -0,0 +1,54 @@ +{ lib, inputs, snowfall-inputs }: + +let + inherit (inputs.nixpkgs.lib) assertMsg last; +in +{ + network = { + # Split an address to get its host name or ip and its port. + # Type: String -> Attrs + # Usage: get-address-parts "bismuth:3000" + # result: { host = "bismuth"; port = "3000"; } + get-address-parts = address: + let + address-parts = builtins.split ":" address; + ip = builtins.head address-parts; + host = if ip == "" then "127.0.0.1" else ip; + port = if builtins.length address-parts != 3 then "" else last address-parts; + in + { inherit host port; }; + + ## Create proxy configuration for NGINX virtual hosts. + ## + ## ```nix + ## services.nginx.virtualHosts."example.com" = lib.network.create-proxy { + ## port = 3000; + ## host = "0.0.0.0"; + ## proxy-web-sockets = true; + ## extra-config = { + ## forceSSL = true; + ## }; + ## } + ## `` + ## + #@ { port: Int ? null, host: String ? "127.0.0.1", proxy-web-sockets: Bool ? false, extra-config: Attrs ? { } } -> Attrs + create-proxy = + { port ? null + , host ? "127.0.0.1" + , proxy-web-sockets ? false + , extra-config ? { } + }: + assert assertMsg (port != "" && port != null) "port cannot be empty"; + assert assertMsg (host != "") "host cannot be empty"; + extra-config // { + locations = (extra-config.locations or { }) // { + "/" = (extra-config.locations."/" or { }) // { + proxyPass = + "http://${host}${if port != null then ":${builtins.toString port}" else ""}"; + + proxyWebsockets = proxy-web-sockets; + }; + }; + }; + }; +} 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/home/cli-apps/bash/default.nix b/modules/home/cli-apps/bash/default.nix new file mode 100644 index 0000000..d6d316e --- /dev/null +++ b/modules/home/cli-apps/bash/default.nix @@ -0,0 +1,27 @@ +{ lib +, config +, pkgs +, ... +}: +let + inherit (lib) mkEnableOption mkIf; + + cfg = config.plusultra.cli-apps.bash; +in +{ + options.plusultra.cli-apps.bash = { + enable = mkEnableOption "BASH shell"; + }; + + config = mkIf cfg.enable { + home.packages = with pkgs; [ + bashInteractive + ]; + programs.bash = { + enable = true; + initExtra = '' + function msh() { mosh "$@" -- bash -c 'if type -f tmux; then tmux new-session -A -s 0; else screen -R; fi;' ; }; + ''; + }; + }; +} diff --git a/modules/home/cli-apps/bat/default.nix b/modules/home/cli-apps/bat/default.nix new file mode 100644 index 0000000..4eadb9c --- /dev/null +++ b/modules/home/cli-apps/bat/default.nix @@ -0,0 +1,22 @@ +{ lib +, config +, pkgs +, ... +}: +with lib; +with lib.plusultra; let + cfg = config.plusultra.cli-apps.bat; +in +{ + options.plusultra.cli-apps.bat = { + enable = mkEnableOption "bat"; + }; + + config = mkIf cfg.enable { + programs.bat = { + enable = true; + config.theme = "ansi"; + extraPackages = with pkgs.bat-extras; [ batdiff batman batgrep batwatch ]; + }; + }; +} diff --git a/modules/home/cli-apps/fish/default.nix b/modules/home/cli-apps/fish/default.nix new file mode 100644 index 0000000..5318b32 --- /dev/null +++ b/modules/home/cli-apps/fish/default.nix @@ -0,0 +1,53 @@ +{ lib +, config +, pkgs +, ... +}: +let + inherit (lib) mkEnableOption mkIf; + + cfg = config.plusultra.cli-apps.fish; +in +{ + options.plusultra.cli-apps.fish = { + enable = mkEnableOption "FISH shell"; + }; + + config = mkIf cfg.enable { + programs.fish = { + enable = true; + interactiveShellInit = '' + function msh --wraps mosh --description 'mosh with tmux' + if not set -q argv[1] + echo 'Usage: msh [user@]host [command]' + else + ${pkgs.mosh}/bin/mosh $argv -- tmux new-session -A -s 0 + end + end + ''; + + plugins = [{ + name = "foreign-env"; + src = pkgs.fetchFromGitHub { + owner = "oh-my-fish"; + repo = "plugin-foreign-env"; + rev = "dddd9213272a0ab848d474d0cbde12ad034e65bc"; + sha256 = "00xqlyl3lffc5l0viin1nyp819wf81fncqyz87jx8ljjdhilmgbs"; + }; + }]; + + shellInit = + '' + # nix + if test -e /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh + fenv source /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh + end + + # home-manager + if test -e $HOME/.nix-profile/etc/profile.d/hm-session-vars.sh + fenv source $HOME/.nix-profile/etc/profile.d/hm-session-vars.sh + end + ''; + }; + }; +} diff --git a/modules/home/cli-apps/git/default.nix b/modules/home/cli-apps/git/default.nix new file mode 100644 index 0000000..f2c2c0b --- /dev/null +++ b/modules/home/cli-apps/git/default.nix @@ -0,0 +1,49 @@ +{ lib +, config +, pkgs +, ... +}: +with lib; +with lib.plusultra; let + cfg = config.plusultra.cli-apps.git; +in +{ + options.plusultra.cli-apps.git = { + enable = mkEnableOption "git"; + }; + + config = mkIf cfg.enable { + home.packages = with pkgs; [ + delta + gh + ]; + programs.git = { + enable = true; + userName = "Harald Hoyer"; + userEmail = "harald@hoyer.xyz"; + extraConfig = { + init.defaultBranch = "main"; + "credential \"https://github.com\"" = { + helper = "!gh auth git-credential"; + }; + alias = { + co = "checkout"; + ci = "commit"; + }; + pull.ff = "only"; + core.pager = "delta"; + delta = { + features = "side-by-side line-numbers decorations"; + syntax-theme = "DarkNeon"; + light = "false"; + line-numbers = "true"; + navigate = "true"; + }; + interactive.diffFilter = "${pkgs.delta}/bin/delta --color-only"; + merge.conflictStyle = "diff3"; + diff.colorMoved = "default"; + }; + }; + + }; +} diff --git a/modules/home/cli-apps/home-manager/default.nix b/modules/home/cli-apps/home-manager/default.nix new file mode 100644 index 0000000..450c53c --- /dev/null +++ b/modules/home/cli-apps/home-manager/default.nix @@ -0,0 +1,33 @@ +{ lib, config, pkgs, ... }: + +let + inherit (lib) mkEnableOption mkIf; + inherit (lib.plusultra) enabled; + + cfg = config.plusultra.cli-apps.home-manager; +in +{ + options.plusultra.cli-apps.home-manager = { + enable = mkEnableOption "home-manager"; + }; + + config = mkIf cfg.enable { + programs.home-manager = enabled; + home.sessionVariables = { + EDITOR = "${pkgs.vim}/bin/vim"; + BATDIFF_USE_DELTA = "true"; + }; + + home.shellAliases = { + cat = "${pkgs.bat}/bin/bat --decorations never"; + less = ''${pkgs.bat}/bin/bat --decorations never --paging=always --pager "${pkgs.less}/bin/less -RF"''; + man = "${pkgs.bat-extras.batman}/bin/batman"; + }; + + home.packages = with pkgs; [ + bat + vim + cachix + ]; + }; +} diff --git a/modules/home/cli-apps/neovim/default.nix b/modules/home/cli-apps/neovim/default.nix new file mode 100644 index 0000000..1b08bcb --- /dev/null +++ b/modules/home/cli-apps/neovim/default.nix @@ -0,0 +1,230 @@ +{ lib, config, pkgs, ... }: + +let + inherit (lib) mkEnableOption mkIf; + + cfg = config.plusultra.cli-apps.neovim; +in +{ + options.plusultra.cli-apps.neovim = { + enable = mkEnableOption "Neovim"; + }; + + config = mkIf cfg.enable { + programs.neovim-flake = { + enable = true; + # your settings need to go into the settings attrset + settings = { + vim = { + viAlias = false; + vimAlias = false; + debugMode = { + enable = false; + level = 20; + logFile = "/tmp/nvim.log"; + }; + }; + + vim.lsp = { + formatOnSave = true; + lspkind.enable = false; + lightbulb.enable = true; + lspsaga.enable = false; + nvimCodeActionMenu.enable = true; + trouble.enable = true; + lspSignature.enable = true; + lsplines.enable = true; + }; + + vim.debugger = { + nvim-dap = { + enable = true; + ui.enable = true; + }; + }; + + vim.languages = { + enableLSP = true; + enableFormat = true; + enableTreesitter = true; + enableExtraDiagnostics = true; + + nix = { + enable = true; + format.type = "nixpkgs-fmt"; + }; + html.enable = true; + clang = { + enable = true; + lsp.server = "clangd"; + }; + sql.enable = false; + rust = { + enable = true; + crates.enable = true; + }; + java.enable = true; + ts.enable = true; + svelte.enable = true; + go.enable = true; + zig.enable = true; + python.enable = true; + dart.enable = true; + elixir.enable = false; + }; + + vim.visuals = { + enable = true; + nvimWebDevicons.enable = true; + scrollBar.enable = true; + smoothScroll.enable = true; + cellularAutomaton.enable = true; + fidget-nvim.enable = true; + indentBlankline = { + enable = true; + fillChar = null; + eolChar = null; + showCurrContext = true; + }; + cursorline = { + enable = true; + lineTimeout = 0; + }; + }; + + vim.statusline = { + lualine = { + enable = true; + theme = "catppuccin"; + }; + }; + + vim.theme = { + enable = true; + name = "catppuccin"; + style = "mocha"; + transparent = false; + }; + + vim.autopairs.enable = true; + + vim.autocomplete = { + enable = true; + type = "nvim-cmp"; + }; + + vim.filetree = { + nvimTree = { + enable = true; + }; + }; + + vim.tabline = { + nvimBufferline.enable = true; + }; + + vim.treesitter.context.enable = true; + + vim.binds = { + whichKey.enable = true; + cheatsheet.enable = true; + }; + + vim.telescope.enable = true; + + vim.git = { + enable = true; + gitsigns.enable = true; + gitsigns.codeActions = false; # throws an annoying debug message + }; + + vim.minimap = { + minimap-vim.enable = false; + codewindow.enable = true; # lighter, faster, and uses lua for configuration + }; + + vim.dashboard = { + dashboard-nvim.enable = false; + alpha.enable = true; + }; + + vim.notify = { + nvim-notify.enable = true; + }; + + vim.projects = { + project-nvim.enable = true; + }; + + vim.utility = { + ccc.enable = true; + vim-wakatime.enable = false; + icon-picker.enable = true; + surround.enable = true; + diffview-nvim.enable = true; + motion = { + hop.enable = true; + leap.enable = true; + }; + }; + + vim.notes = { + obsidian.enable = false; # FIXME neovim fails to build if obsidian is enabled + orgmode.enable = false; + mind-nvim.enable = true; + todo-comments.enable = true; + }; + + vim.terminal = { + toggleterm = { + enable = true; + lazygit.enable = true; + }; + }; + + vim.ui = { + borders.enable = true; + noice.enable = true; + colorizer.enable = true; + modes-nvim.enable = false; # the theme looks terrible with catppuccin + illuminate.enable = true; + breadcrumbs = { + enable = true; + navbuddy.enable = true; + }; + smartcolumn = { + enable = true; + columnAt.languages = { + # this is a freeform module, it's `buftype = int;` for configuring column position + nix = 110; + ruby = 120; + java = 130; + go = [ 90 130 ]; + }; + }; + }; + + vim.assistant = { + copilot = { + enable = true; + cmp.enable = true; + }; + }; + + vim.session = { + nvim-session-manager.enable = false; + }; + + vim.gestures = { + gesture-nvim.enable = false; + }; + + vim.comments = { + comment-nvim.enable = true; + }; + + vim.spellChecking.languages = [ "en" "de" ]; + }; + }; + }; +} diff --git a/modules/home/cli-apps/starship/default.nix b/modules/home/cli-apps/starship/default.nix new file mode 100644 index 0000000..82bc343 --- /dev/null +++ b/modules/home/cli-apps/starship/default.nix @@ -0,0 +1,32 @@ +{ lib +, config +, pkgs +, ... +}: +let + inherit (lib) mkEnableOption mkIf; + + cfg = config.plusultra.cli-apps.starship; +in +{ + options.plusultra.cli-apps.starship = { + enable = mkEnableOption "starship"; + }; + + config = mkIf cfg.enable { + home.packages = with pkgs; [ + (pkgs.nerdfonts.override { fonts = [ "FiraCode" "DroidSansMono" "JetBrainsMono" ]; }) + ]; + programs.starship = { + enable = true; + settings = { + container.format = "[\\[$name\\]]($style) "; + git_status = { + ahead = "⇡$\{count}"; + diverged = "⇕⇡$\{ahead_count}⇣$\{behind_count}"; + behind = "⇣$\{count}"; + }; + }; + }; + }; +} diff --git a/modules/home/cli-apps/tmux/default.nix b/modules/home/cli-apps/tmux/default.nix new file mode 100644 index 0000000..89218d7 --- /dev/null +++ b/modules/home/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 { + home.packages = with pkgs; [ + tmux + ]; + }; +} diff --git a/modules/home/host/default.nix b/modules/home/host/default.nix new file mode 100644 index 0000000..9c22b68 --- /dev/null +++ b/modules/home/host/default.nix @@ -0,0 +1,11 @@ +{ lib, config, pkgs, host ? null, format ? "unknown", ... }: + +let + inherit (lib) types; + inherit (lib.plusultra) mkOpt; +in +{ + options.plusultra.host = { + name = mkOpt (types.nullOr types.str) host "The host name."; + }; +} diff --git a/modules/home/tools/direnv/default.nix b/modules/home/tools/direnv/default.nix new file mode 100644 index 0000000..f9a77c7 --- /dev/null +++ b/modules/home/tools/direnv/default.nix @@ -0,0 +1,18 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.tools.direnv; +in +{ + options.plusultra.tools.direnv = with types; { + enable = mkBoolOpt false "Whether or not to enable direnv."; + }; + + config = mkIf cfg.enable { + programs.direnv = { + enable = true; + nix-direnv = enabled; + }; + }; +} diff --git a/modules/home/tools/git/default.nix b/modules/home/tools/git/default.nix new file mode 100644 index 0000000..4392d50 --- /dev/null +++ b/modules/home/tools/git/default.nix @@ -0,0 +1,40 @@ +{ lib, config, pkgs, ... }: + +let + inherit (lib) types mkEnableOption mkIf; + inherit (lib.plusultra) mkOpt enabled; + + cfg = config.plusultra.tools.git; + user = config.plusultra.user; +in +{ + options.plusultra.tools.git = { + enable = mkEnableOption "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."; + signByDefault = mkOpt types.bool true "Whether to sign commits by default."; + }; + + config = mkIf cfg.enable { + programs.git = { + enable = true; + inherit (cfg) userName userEmail; + lfs = enabled; + signing = { + key = cfg.signingKey; + inherit (cfg) signByDefault; + }; + extraConfig = { + init = { defaultBranch = "main"; }; + pull = { rebase = true; }; + push = { autoSetupRemote = true; }; + core = { whitespace = "trailing-space,space-before-tab"; }; + safe = { + directory = "${user.home}/work/config"; + }; + }; + }; + }; +} diff --git a/modules/home/tools/jetbrains/default.nix b/modules/home/tools/jetbrains/default.nix new file mode 100644 index 0000000..03159d2 --- /dev/null +++ b/modules/home/tools/jetbrains/default.nix @@ -0,0 +1,21 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.tools.jetbrains; +in +{ + options.plusultra.tools.jetbrains = with types; { + enable = mkBoolOpt false "Whether or not to enable jetbrains."; + }; + + config = mkIf cfg.enable { + home.sessionPath = [ + "$HOME/.local/share/JetBrains/Toolbox/scripts" + ]; + home.packages = with pkgs; [ + jetbrains-toolbox + (pkgs.nerdfonts.override { fonts = [ "FiraCode" "DroidSansMono" "JetBrainsMono" ]; }) + ]; + }; +} diff --git a/modules/home/tools/ssh/default.nix b/modules/home/tools/ssh/default.nix new file mode 100644 index 0000000..6d6cd55 --- /dev/null +++ b/modules/home/tools/ssh/default.nix @@ -0,0 +1,20 @@ +{ lib, config, pkgs, ... }: + +let + inherit (lib) types mkEnableOption mkIf; + cfg = config.plusultra.tools.ssh; +in +{ + options.plusultra.tools.ssh = { + enable = mkEnableOption "SSH"; + }; + + config = mkIf cfg.enable { + programs.ssh = { + extraConfig = '' + Host * + HostKeyAlgorithms +ssh-rsa + ''; + }; + }; +} diff --git a/modules/home/user/default.nix b/modules/home/user/default.nix new file mode 100644 index 0000000..2489926 --- /dev/null +++ b/modules/home/user/default.nix @@ -0,0 +1,50 @@ +{ lib, config, pkgs, osConfig ? { }, ... }: + +let + inherit (lib) types mkIf mkDefault mkMerge; + inherit (lib.plusultra) mkOpt; + + cfg = config.plusultra.user; + + is-linux = pkgs.stdenv.isLinux; + is-darwin = pkgs.stdenv.isDarwin; + + home-directory = + if cfg.name == null then + null + else if is-darwin then + "/Users/${cfg.name}" + else + "/home/${cfg.name}"; +in +{ + options.plusultra.user = { + enable = mkOpt types.bool false "Whether to configure the user account."; + name = mkOpt (types.nullOr types.str) config.snowfallorg.user.name "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."; + + home = mkOpt (types.nullOr types.str) home-directory "The user's home directory."; + }; + + config = mkIf cfg.enable (mkMerge [ + { + assertions = [ + { + assertion = cfg.name != null; + message = "plusultra.user.name must be set"; + } + { + assertion = cfg.home != null; + message = "plusultra.user.home must be set"; + } + ]; + + home = { + username = mkDefault cfg.name; + homeDirectory = mkDefault cfg.home; + }; + } + ]); +} 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 new file mode 100644 index 0000000..3f47f81 --- /dev/null +++ b/modules/nixos/home/default.nix @@ -0,0 +1,36 @@ +{ options, config, pkgs, lib, inputs, ... }: + +with lib; +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`."); + configFile = mkOpt attrs { } + (mdDoc "A set of files to be managed by home-manager's `xdg.configFile`."); + extraOptions = mkOpt attrs { } "Options to pass directly to home-manager."; + }; + + config = { + plusultra.home.extraOptions = { + home.stateVersion = config.system.stateVersion; + home.file = mkAliasDefinitions options.plusultra.home.file; + xdg.enable = true; + xdg.configFile = mkAliasDefinitions options.plusultra.home.configFile; + }; + + home-manager = { + useUserPackages = true; + useGlobalPkgs = true; + + users.${config.plusultra.user.name} = + mkAliasDefinitions options.plusultra.home.extraOptions; + }; + }; +} diff --git a/modules/nixos/nix/default.nix b/modules/nixos/nix/default.nix new file mode 100644 index 0000000..ec2a79c --- /dev/null +++ b/modules/nixos/nix/default.nix @@ -0,0 +1,92 @@ +{ options, config, pkgs, lib, inputs, ... }: + +with lib; +with lib.plusultra; +let + cfg = config.plusultra.nix; + + substituters-submodule = types.submodule ({ name, ... }: { + options = with types; { + key = mkOpt (nullOr str) null "The trusted public key for this substituter."; + }; + }); +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."; + + default-substituter = { + url = mkOpt str "https://cache.nixos.org" "The url for the substituter."; + key = mkOpt str "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" "The trusted public key for the substituter."; + }; + + extra-substituters = mkOpt (attrsOf substituters-submodule) { } "Extra substituters to configure."; + }; + + config = mkIf cfg.enable { + assertions = mapAttrsToList + (name: value: { + assertion = value.key != null; + message = "plusultra.nix.extra-substituters.${name}.key must be set"; + }) + cfg.extra-substituters; + + environment.systemPackages = with pkgs; [ + plusultra.nixos-revision + (plusultra.nixos-hosts.override { + hosts = inputs.self.nixosConfigurations; + }) + deploy-rs + nixfmt + nix-index + nix-prefetch-git + nix-output-monitor + flake-checker + ]; + + nix = + let + users = [ "root" config.plusultra.user.name ] ++ + optional config.services.hydra.enable "hydra"; + in + { + package = cfg.package; + + settings = { + experimental-features = "nix-command flakes"; + http-connections = 50; + warn-dirty = false; + log-lines = 50; + sandbox = "relaxed"; + auto-optimise-store = true; + trusted-users = users; + allowed-users = users; + + substituters = + [ cfg.default-substituter.url ] + ++ + (mapAttrsToList (name: value: name) cfg.extra-substituters); + trusted-public-keys = + [ cfg.default-substituter.key ] + ++ + (mapAttrsToList (name: value: value.key) cfg.extra-substituters); + + } // (lib.optionalAttrs config.plusultra.tools.direnv.enable { + keep-outputs = true; + keep-derivations = true; + }); + + gc = { + automatic = true; + dates = "weekly"; + options = "--delete-older-than 30d"; + }; + + # flake-utils-plus + generateRegistryFromInputs = true; + generateNixPathFromInputs = true; + linkInputs = true; + }; + }; +} 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/direnv/default.nix b/modules/nixos/tools/direnv/default.nix new file mode 100644 index 0000000..f09398c --- /dev/null +++ b/modules/nixos/tools/direnv/default.nix @@ -0,0 +1,20 @@ +{ options, config, lib, pkgs, ... }: + +with lib; +with lib.plusultra; +let cfg = config.plusultra.tools.direnv; +in +{ + options.plusultra.tools.direnv = with types; { + enable = mkBoolOpt false "Whether or not to enable direnv."; + }; + + config = mkIf cfg.enable { + plusultra.home.extraOptions = { + programs.direnv = { + enable = true; + nix-direnv = enabled; + }; + }; + }; +} 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 new file mode 100644 index 0000000..076d3ac --- /dev/null +++ b/modules/nixos/user/default.nix @@ -0,0 +1,168 @@ +{ options +, config +, pkgs +, lib +, ... +}: +with lib; +with lib.plusultra; let + cfg = config.plusultra.user; + defaultIconFileName = "profile.png"; + defaultIcon = pkgs.stdenvNoCC.mkDerivation { + name = "default-icon"; + src = ./. + "/${defaultIconFileName}"; + + dontUnpack = true; + + installPhase = '' + cp $src $out + ''; + + passthru = { fileName = defaultIconFileName; }; + }; + propagatedIcon = + pkgs.runCommandNoCC "propagated-icon" + { passthru = { fileName = cfg.icon.fileName; }; } + '' + local target="$out/share/plusultra-icons/user/${cfg.name}" + mkdir -p "$target" + + cp ${cfg.icon} "$target/${cfg.icon.fileName}" + ''; +in +{ + options.plusultra.user = with types; { + 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."; + icon = + mkOpt (nullOr package) defaultIcon + "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."; + extraOptions = + mkOpt attrs { } + (mdDoc "Extra options passed to `users.users.`."); + }; + + 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 = ""; + "Documents/.keep".text = ""; + "Downloads/.keep".text = ""; + "Music/.keep".text = ""; + "Pictures/.keep".text = ""; + "Videos/.keep".text = ""; + "work/.keep".text = ""; + ".face".source = cfg.icon; + "Pictures/${ + cfg.icon.fileName or (builtins.baseNameOf cfg.icon) + }".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; + + home = "/home/${cfg.name}"; + group = "users"; + + 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. + # However, if you add multiple users you'll need to change this + # so each user has their own unique uid (or leave it out for the + # system to select). + uid = 1000; + + extraGroups = [ ] ++ cfg.extraGroups; + } + // cfg.extraOptions; + }; +} diff --git a/modules/nixos/user/profile.png b/modules/nixos/user/profile.png new file mode 100644 index 0000000..5aee9a3 Binary files /dev/null and b/modules/nixos/user/profile.png differ 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; + }; + }; + }; +}